From 6303bb2e653c81b2e3a64dda724748894c8fa8da Mon Sep 17 00:00:00 2001 From: ge Date: Tue, 7 Jun 2022 18:17:58 +0300 Subject: [PATCH] feat: Add media uploads --- README.rst | 15 +++++---- vk-mastodon-bridge.py | 77 +++++++++++++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 23 deletions(-) diff --git a/README.rst b/README.rst index 19afa87..de9772c 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,6 @@ TODO - Не учитывается длина поста. Если исходный пост не будет укладываться в лимит символов на инстансе Mastodon'а, то неизвестно что произойдёт. Решение: надо обрезать текст поста в функции ``build_post()``. - Никак не обрабатываются вложения типов отличных от фото (`photo`) и фотоальбома (`album`). -- Надо первые 4-е картинки добавлять как вложения в пост в мастодоне. Для остальных вложений достаточно ссылки. Настройки и запуск ================== @@ -51,18 +50,17 @@ API VK Нужно задать все переменные окружения. Удобный способ — в файле ``.env`` и экспортировать в шэлл. -========================= =============================== ======================================== +========================= =============================== ========================================================= Переменная окружения Пример Описание -========================= =============================== ======================================== +========================= =============================== ========================================================= MASTODON_API_URL https://mastodon.social/api/v1 URL адрес API Mastodon. MASTODON_API_ACCESS_TOKEN Ключ API Mastodon. VK_API_URL https://api.vk.com/method URL API VK. VK_API_VERSION 5.131 Версия VK API. VK_API_ACCESS_TOKEN Ключ API VK. VK_GROUP_DOMAIN apiclub slug адрес группы/паблика VK. -POLLING_TIME 300 Количество секунд между получением - постов. По умолчанию 300. -========================= =============================== ======================================== +POLLING_TIME 300 Количество секунд между получением постов. Умолчание: 300. +========================= =============================== ========================================================== Запуск ------ @@ -85,6 +83,11 @@ Docker История изменений ================= +0.2.0 +----- + +- Реализована загрузка вложений в Mastodon (до 4-х штук). Только изображения. + 0.1.0 ----- diff --git a/vk-mastodon-bridge.py b/vk-mastodon-bridge.py index 1f83b7c..330bb59 100644 --- a/vk-mastodon-bridge.py +++ b/vk-mastodon-bridge.py @@ -1,8 +1,11 @@ -__version__ = '0.1.0' +__version__ = '0.2.0' import os import json import time +import shutil +from urllib.parse import urlparse + import requests @@ -36,7 +39,7 @@ def get_vk_post_url(post_data: dict) -> str: def get_vk_post_attachments(post_data: dict) -> list: """Process attachments. See attachments at https://dev.vk.com/method/wall.post - Return list with following structure:: + Return list of dicts with following structure:: [{'photo': 'url'}, {'album': 'url'}] """ @@ -76,27 +79,68 @@ def get_vk_post_attachments(post_data: dict) -> list: pass return attachments -def build_post(post_data: dict) -> str: +def download_file(url: str) -> str: + """Save file to /tmp. Return file path""" + filename = '/tmp/' + os.path.basename(urlparse(url).path) + response = requests.get(url, stream=True) + with open(filename, 'wb') as out_file: + shutil.copyfileobj(response.raw, out_file) + del response + return filename + +def post_media(file: str) -> str: + """Upload media file to Mastodon.""" + headers = {'Authorization': 'Bearer ' + MASTODON_API_ACCESS_TOKEN} + files = {'file': open(file,'rb')} + response = requests.post(MASTODON_API_URL + '/media', files=files, headers=headers) + return response + +def publish_toot(post_text: str, media_ids: list): + """Post toot on Mastodon. + See: https://docs.joinmastodon.org/methods/statuses/ + """ + headers = {'Authorization': 'Bearer ' + MASTODON_API_ACCESS_TOKEN} + params = {'status': post_text, 'media_ids[]': media_ids} + response = requests.post(MASTODON_API_URL + '/statuses', data=params, headers=headers) + return response + +def post_toot(post_data: dict) -> str: + """Upload media files, generate status text for Mastodon post and publish. + """ post_text = get_vk_post_text(post_data) vk_post_url = get_vk_post_url(post_data) attachments = get_vk_post_attachments(post_data) + # Upload attachments! + # Upload only first 4 photos and get media_ids + i = 0 + media_ids = [] + while i < 4: + if list(attachments[i].keys())[0] == 'photo': + photo_url = attachments[i]['photo'] + print('Download image:', photo_url) # Log + tmpfile = download_file(photo_url) # Download file from VK + print('Saved as:', tmpfile) # Log + print('Upload to Mastodon:', tmpfile) # Log + media_id = json.loads(post_media(tmpfile).text)['id'] # Upload file to Mastodon + print('Uploaded. Media ID:', media_id) # Log + media_ids.append(media_id) # Save uploaded media IDs + print('Remove local file:', tmpfile) # Log + os.remove(tmpfile) # Remove local file + i += 1 + # Build attachments list post_attachments = '' for attachment in attachments: key = str(list(attachment.keys())[0]) # Example resulting string: 'Album: https://vk.com/album-26788782_284176934' post_attachments = post_attachments + key.title() + ': ' + attachment[key] + '\n' - return post_text + '\n\n' + 'Source: ' + vk_post_url + '\n\nAttachments:\n' + post_attachments -def post_toot(post_text: str): - """Post toot on Mastodon. - See: https://docs.joinmastodon.org/methods/statuses/ - """ - auth = {'Authorization': 'Bearer ' + MASTODON_API_ACCESS_TOKEN} - params = {'status': post_text} - response = requests.post(MASTODON_API_URL + '/statuses', data=params, headers=auth) - return response + # Get status text + text = post_text + '\n\n' + 'Source: ' + vk_post_url + '\n\nAttachments:\n' + post_attachments + + # Post toot! + publish_toot(text, media_ids) def touch_lock_file(file: str, post_id: int): with open(file, 'w') as lock: @@ -107,7 +151,7 @@ def read_lock_file(file: str): return int(lock.read()) def poll(): - lock_file = './.last_post_id.tmp' + lock_file = './data/last_post_id' if os.path.exists(lock_file): prev_post_id = read_lock_file(lock_file) print('Read last post ID from file:', lock_file) @@ -121,12 +165,11 @@ def poll(): # Don't post duplicates if post_id == prev_post_id: - print('Skip posting') + print('Skip posting') # Log else: # Toot! - print('Toot! VK post ID:', post_id) - post = build_post(post_data) - post_toot(post) + print('===> Toot! VK post ID:', post_id) # Log + post_toot(post_data) touch_lock_file(lock_file, post_id) prev_post_id = read_lock_file(lock_file)