jackm / kijiji-manager

App for viewing, posting, reposting, and deleting your Kijiji ads
https://pypi.org/project/kijiji-manager/
MIT License
52 stars 11 forks source link

Function Request: Export running ads as XML #17

Closed OakandRocks closed 2 years ago

OakandRocks commented 3 years ago

Sorry if I'm the only one with the issue, but it'd love to be able to create an XML from ads in case of loss of original XML.

Give moi pointers and I'll try to add that feature to the project(?)

Close this if you think this isn't a necessary upgrade. (I'll delete the post)

adm-gis commented 3 years ago

Yeah, this would be great. I have quite a few ads and need to transfer them over to the app. It would be much more efficient if I could export them and them load them back in through the app.

asyed03 commented 2 years ago

I actually added some code that does this. In the current version, there is no option to repost ads that were already posted on kijiji. This code generates the xml payload from an already existing ad.

changing the repost method:

def repost(ad_id):
    # Get existing ad
    user_dir = os.path.join(current_app.instance_path, 'user', current_user.id)
    ad_file = os.path.join(user_dir, f'{ad_id}.xml')

    if not os.path.isfile(ad_file):
        payload = generate_post_payload(ad_id)
        flash(f'ad file {ad_file} did not exist, generated from ad')
        if not os.path.isfile(ad_file):
            flash(f'Failed to generate ad file')
            return redirect(url_for('main.home'))

    with open(ad_file, 'r', encoding='utf-8') as f:
        xml_payload = f.read()

    # Kijiji changed their image upload API on around 2022-06-27 to use a different image host. Ad payloads that
    # still contain the old image host URLs will be rejected unless the URLs are translated to the new image host.
    # For ad payloads that already use the new image host, this translation should have no effect.
    xml_payload = translate_image_urls(ad_id, xml_payload)

    # Delete existing ad
    kijiji_api.delete_ad(current_user.id, current_user.token, ad_id)
    flash(f'Deleted old ad {ad_id}')

    # Waiting for 3 minutes appears to be enough time for Kijiji to not consider it a duplicate ad
    delay_minutes = 3

    # Delay and then run callback to post ad again
    future_response = executor.submit(delay, delay_minutes * 60, {'payload': xml_payload, 'ad_id': ad_id})
    future_response.add_done_callback(post_ad_again)

    flash(f'Reposting ad in background after {delay_minutes} minute delay... Do not stop the app from running')
    return redirect(url_for('main.home'))

generating the payload:

def generate_post_payload(ad_id):
    data = kijiji_api.get_ad(current_user.id, current_user.token, ad_id)
    ad_orig = data['ad:ad']
    payload = {
        'ad:ad': {
            '@xmlns:ad': 'http://www.ebayclassifiedsgroup.com/schema/ad/v1',
            '@xmlns:cat': 'http://www.ebayclassifiedsgroup.com/schema/category/v1',
            '@xmlns:loc': 'http://www.ebayclassifiedsgroup.com/schema/location/v1',
            '@xmlns:attr': 'http://www.ebayclassifiedsgroup.com/schema/attribute/v1',
            '@xmlns:types': 'http://www.ebayclassifiedsgroup.com/schema/types/v1',
            '@xmlns:pic': 'http://www.ebayclassifiedsgroup.com/schema/picture/v1',
            '@xmlns:vid': 'http://www.ebayclassifiedsgroup.com/schema/video/v1',
            '@xmlns:user': 'http://www.ebayclassifiedsgroup.com/schema/user/v1',
            '@xmlns:feature': 'http://www.ebayclassifiedsgroup.com/schema/feature/v1',
            '@id': '',
            'cat:category': ad_orig['cat:category'],
            'loc:locations': ad_orig['loc:locations'],
            'ad:ad-type': ad_orig['ad:ad-type'],
            'ad:title': ad_orig['ad:title'],
            'ad:description':  ad_orig['ad:description'],
            'ad:price': ad_orig.get('ad:price'),
            'ad:account-id': current_user.id,
            'ad:email': current_user.email,
            'ad:poster-contact-email': current_user.email,
            # 'ad:poster-contact-name': None,  # Not sent by Kijiji app
            'ad:phone': ad_orig['ad:phone'],
            'ad:ad-address': ad_orig['ad:ad-address'],
            'ad:visible-on-map': 'true',  # appears to make no difference if set to 'true' or 'false'
            'attr:attributes': ad_orig['attr:attributes'],
            'pic:pictures': ad_orig['pic:pictures'],
            'vid:videos': None,
            'ad:adSlots': None,
            'ad:listing-tags': None,
        }
    }
    xml_payload = xmltodict.unparse(payload, short_empty_elements=True)

    # Save ad payload
    user_dir = os.path.join(current_app.instance_path, 'user', current_user.id)
    if not os.path.exists(user_dir):
        os.makedirs(user_dir)
    ad_file = os.path.join(user_dir, f'{ad_id}.xml')
    with open(ad_file, 'w', encoding='utf-8') as f:
        f.write(xml_payload)

    return xml_payload
jackm commented 2 years ago

The app now supports reposting ads which were originally created on the main Kijiji site. This feature has been added in v0.2.2.

There is no dedicated button to export an existing ad as an XML payload file, however any ad that is created via this app will automatically have its payload saved to the users instance folder.

If there are existing ads which were not created via this app, simply reposting them at least once will also automatically have their ad payload saved to the users instance folder.