Bump version to 0.2.1. See README
This commit is contained in:
parent
09e6e40744
commit
cda10fde05
@ -4,4 +4,4 @@ RUN mkdir -p /opt/vk-mastodon-bridge/data
|
|||||||
ADD . /opt/vk-mastodon-bridge
|
ADD . /opt/vk-mastodon-bridge
|
||||||
WORKDIR /opt/vk-mastodon-bridge
|
WORKDIR /opt/vk-mastodon-bridge
|
||||||
RUN pip install --upgrade pip && pip install --requirement requirements.txt
|
RUN pip install --upgrade pip && pip install --requirement requirements.txt
|
||||||
CMD source .env; python3 vk-mastodon-bridge.py
|
CMD source .env; python3 vk_mastodon_bridge.py
|
||||||
|
35
README.md
35
README.md
@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
## Известные проблемы/TODO
|
## Известные проблемы/TODO
|
||||||
|
|
||||||
- Скрипт пропускает часть постов. Пока не выяснил почему.
|
|
||||||
- Добавить логирование.
|
|
||||||
- Не учитывается длина поста. Если исходный пост не будет укладываться в лимит символов на инстансе Mastodon'а, то неизвестно что произойдёт. Решение: надо обрезать текст поста в функции `build_post()`.
|
- Не учитывается длина поста. Если исходный пост не будет укладываться в лимит символов на инстансе Mastodon'а, то неизвестно что произойдёт. Решение: надо обрезать текст поста в функции `build_post()`.
|
||||||
- Никак не обрабатываются вложения типов отличных от фото (`photo`) и фотоальбома (`album`).
|
- Никак не обрабатываются вложения типов отличных от фото (`photo`) и фотоальбома (`album`).
|
||||||
|
- Указывать в списке вложений в посте только те вложения, которые не были загружены в Mastodon. Пока формируется полный список вложений.
|
||||||
|
- Использовать конфиг вместо переменных окружения?
|
||||||
|
|
||||||
## Настройки и запуск
|
## Настройки и запуск
|
||||||
|
|
||||||
@ -39,15 +39,16 @@
|
|||||||
|
|
||||||
Нужно задать все переменные окружения. Удобный способ — в файле `.env` и экспортировать в шэлл.
|
Нужно задать все переменные окружения. Удобный способ — в файле `.env` и экспортировать в шэлл.
|
||||||
|
|
||||||
| Переменная окружения | Пример | Описание |
|
| Переменная окружения | Умолчание | Пример | Описание |
|
||||||
| ----------------------------- | --------------------------------- | ------------------------------------------------------------- |
|
| ----------------------------- | --------- |---------------------------------- | ---------------------------------------------- |
|
||||||
| `MASTODON_API_URL` | https://mastodon.social/api/v1 | URL адрес API Mastodon. |
|
| `MASTODON_API_URL` | нет | https://mastodon.social/api/v1 | URL адрес API Mastodon. |
|
||||||
| `MASTODON_API_ACCESS_TOKEN` | | Ключ API Mastodon. |
|
| `MASTODON_API_ACCESS_TOKEN` | нет | | Ключ API Mastodon. |
|
||||||
| `VK_API_URL` | https://api.vk.com/method | URL API VK. |
|
| `VK_API_URL` | нет | https://api.vk.com/method | URL API VK. |
|
||||||
| `VK_API_VERSION` | 5.131 | Версия VK API. |
|
| `VK_API_VERSION` | нет | 5.131 | Версия VK API. |
|
||||||
| `VK_API_ACCESS_TOKEN` | | Ключ API VK. |
|
| `VK_API_ACCESS_TOKEN` | нет | | Ключ API VK. |
|
||||||
| `VK_GROUP_DOMAIN` | apiclub | slug адрес группы/паблика VK. |
|
| `VK_GROUP_DOMAIN` | нет | apiclub | slug адрес группы/паблика VK. |
|
||||||
| `POLLING_TIME` | 300 | Количество секунд между получением постов. Умолчание: 300. |
|
| `POLLING_TIME` | 300 | 300 | Задержка получением постов из VK в секундах. |
|
||||||
|
| `REQUEST_DELAY` | 1 | 1 | Задержка загрузки медиа в Mastodon в секундах. |
|
||||||
|
|
||||||
### Запуск без Docker
|
### Запуск без Docker
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ python vk-mastodon-bridge.py
|
|||||||
Сборка образа:
|
Сборка образа:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo docker build -t vk-mastodon-bridge:0.2.0 .
|
sudo docker build -t vk-mastodon-bridge:0.2.1 .
|
||||||
```
|
```
|
||||||
|
|
||||||
Запуск контейнера:
|
Запуск контейнера:
|
||||||
@ -73,11 +74,19 @@ sudo docker build -t vk-mastodon-bridge:0.2.0 .
|
|||||||
sudo docker run --detach \
|
sudo docker run --detach \
|
||||||
--name vk-mastodon-bridge \
|
--name vk-mastodon-bridge \
|
||||||
--volume /opt/vk-mastodon-bridge/data:/opt/vk-mastodon-bridge/data \
|
--volume /opt/vk-mastodon-bridge/data:/opt/vk-mastodon-bridge/data \
|
||||||
vk-mastodon-bridge:0.2.0
|
vk-mastodon-bridge:0.2.1
|
||||||
```
|
```
|
||||||
|
|
||||||
## История изменений
|
## История изменений
|
||||||
|
|
||||||
|
### 0.2.1
|
||||||
|
|
||||||
|
- Исправлена публиация постов. Mastodon API мог отвечать кодом 429 из-за слишком частых загрузок медиа. Решено через добавление задержки `REQUEST_DELAY`.
|
||||||
|
- Исправлена ошибка при посте статуса с менее чем 4-я вложениями.
|
||||||
|
- Файл `./data/last_post_id` теперь содержит JSON и называется `./data/post_id.json`. ID хранится в единственном поле `post_id`.
|
||||||
|
- Имя модуля теперь не содержит дефисов.
|
||||||
|
- Добавлено логирование. Лог пишется в STDOUT.
|
||||||
|
|
||||||
### 0.2.0
|
### 0.2.0
|
||||||
|
|
||||||
- Реализована загрузка вложений в Mastodon (до 4-х штук). Только изображения.
|
- Реализована загрузка вложений в Mastodon (до 4-х штук). Только изображения.
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
__version__ = '0.2.0'
|
__version__ = '0.2.1'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
from urllib.parse import urlparse
|
import urllib.parse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@ -16,6 +19,13 @@ VK_API_VERSION = os.environ['VK_API_VERSION']
|
|||||||
VK_API_ACCESS_TOKEN = os.environ['VK_API_ACCESS_TOKEN']
|
VK_API_ACCESS_TOKEN = os.environ['VK_API_ACCESS_TOKEN']
|
||||||
VK_GROUP_DOMAIN = os.environ['VK_GROUP_DOMAIN']
|
VK_GROUP_DOMAIN = os.environ['VK_GROUP_DOMAIN']
|
||||||
|
|
||||||
|
# Set up logger
|
||||||
|
logger = logging.getLogger('vk_mastodon_bridge')
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
handler = logging.StreamHandler(stream=sys.stdout)
|
||||||
|
handler.setFormatter(logging.Formatter(fmt = '[%(asctime)s: %(levelname)s] %(message)s'))
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
def get_vk_group_last_post():
|
def get_vk_group_last_post():
|
||||||
"""Return dict with VK group last post data."""
|
"""Return dict with VK group last post data."""
|
||||||
return json.loads(requests.get(VK_API_URL + '/wall.get' \
|
return json.loads(requests.get(VK_API_URL + '/wall.get' \
|
||||||
@ -81,7 +91,7 @@ def get_vk_post_attachments(post_data: dict) -> list:
|
|||||||
|
|
||||||
def download_file(url: str) -> str:
|
def download_file(url: str) -> str:
|
||||||
"""Save file to /tmp. Return file path"""
|
"""Save file to /tmp. Return file path"""
|
||||||
filename = '/tmp/' + os.path.basename(urlparse(url).path)
|
filename = '/tmp/' + os.path.basename(urllib.parse.urlparse(url).path)
|
||||||
response = requests.get(url, stream=True)
|
response = requests.get(url, stream=True)
|
||||||
with open(filename, 'wb') as out_file:
|
with open(filename, 'wb') as out_file:
|
||||||
shutil.copyfileobj(response.raw, out_file)
|
shutil.copyfileobj(response.raw, out_file)
|
||||||
@ -93,6 +103,14 @@ def post_media(file: str) -> str:
|
|||||||
headers = {'Authorization': 'Bearer ' + MASTODON_API_ACCESS_TOKEN}
|
headers = {'Authorization': 'Bearer ' + MASTODON_API_ACCESS_TOKEN}
|
||||||
files = {'file': open(file,'rb')}
|
files = {'file': open(file,'rb')}
|
||||||
response = requests.post(MASTODON_API_URL + '/media', files=files, headers=headers)
|
response = requests.post(MASTODON_API_URL + '/media', files=files, headers=headers)
|
||||||
|
logger.info('Post media on Mastodon. Response: ' \
|
||||||
|
+ str(response.status_code) + ' ' + str(response.text))
|
||||||
|
# Sleep some seconds to prevent HTTP 429 response.
|
||||||
|
try:
|
||||||
|
request_delay = int(os.environ['REQUEST_DELAY'])
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
request_delay = 1
|
||||||
|
time.sleep(request_delay)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def publish_toot(post_text: str, media_ids: list):
|
def publish_toot(post_text: str, media_ids: list):
|
||||||
@ -102,6 +120,9 @@ def publish_toot(post_text: str, media_ids: list):
|
|||||||
headers = {'Authorization': 'Bearer ' + MASTODON_API_ACCESS_TOKEN}
|
headers = {'Authorization': 'Bearer ' + MASTODON_API_ACCESS_TOKEN}
|
||||||
params = {'status': post_text, 'media_ids[]': media_ids}
|
params = {'status': post_text, 'media_ids[]': media_ids}
|
||||||
response = requests.post(MASTODON_API_URL + '/statuses', data=params, headers=headers)
|
response = requests.post(MASTODON_API_URL + '/statuses', data=params, headers=headers)
|
||||||
|
post_url = json.loads(response.text)['url']
|
||||||
|
logger.info('Publish status. Response: ' \
|
||||||
|
+ str(response.status_code) + ' Status: ' + str(post_url))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def post_toot(post_data: dict) -> str:
|
def post_toot(post_data: dict) -> str:
|
||||||
@ -115,17 +136,21 @@ def post_toot(post_data: dict) -> str:
|
|||||||
# Upload only first 4 photos and get media_ids
|
# Upload only first 4 photos and get media_ids
|
||||||
i = 0
|
i = 0
|
||||||
media_ids = []
|
media_ids = []
|
||||||
while i < 4:
|
logger.info('Attachments: %s' % str(attachments))
|
||||||
|
|
||||||
|
attachments_count = len(attachments)
|
||||||
|
if attachments_count > 4:
|
||||||
|
attachments_count = 4
|
||||||
|
|
||||||
|
while i < attachments_count:
|
||||||
if list(attachments[i].keys())[0] == 'photo':
|
if list(attachments[i].keys())[0] == 'photo':
|
||||||
photo_url = attachments[i]['photo']
|
photo_url = attachments[i]['photo']
|
||||||
print('Download image:', photo_url) # Log
|
logger.info('Download image: %s' % photo_url)
|
||||||
tmpfile = download_file(photo_url) # Download file from VK
|
tmpfile = download_file(photo_url) # Download file from VK
|
||||||
print('Saved as:', tmpfile) # Log
|
logger.info('Image saved locally as: %s' % tmpfile)
|
||||||
print('Upload to Mastodon:', tmpfile) # Log
|
|
||||||
media_id = json.loads(post_media(tmpfile).text)['id'] # Upload file to Mastodon
|
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
|
media_ids.append(media_id) # Save uploaded media IDs
|
||||||
print('Remove local file:', tmpfile) # Log
|
logger.info('Remove local file: %s' % tmpfile)
|
||||||
os.remove(tmpfile) # Remove local file
|
os.remove(tmpfile) # Remove local file
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
@ -144,31 +169,35 @@ def post_toot(post_data: dict) -> str:
|
|||||||
|
|
||||||
def touch_lock_file(file: str, post_id: int):
|
def touch_lock_file(file: str, post_id: int):
|
||||||
with open(file, 'w') as lock:
|
with open(file, 'w') as lock:
|
||||||
lock.write(str(post_id))
|
data = json.dumps({'post_id': str(post_id)})
|
||||||
|
lock.write(data)
|
||||||
|
|
||||||
def read_lock_file(file: str):
|
def read_lock_file(file: str):
|
||||||
with open(file, 'r') as lock:
|
with open(file, 'r') as lock:
|
||||||
return int(lock.read())
|
data = json.loads(lock.read())
|
||||||
|
return data['post_id']
|
||||||
|
|
||||||
def poll():
|
def poll():
|
||||||
lock_file = './data/last_post_id'
|
logger.info('Start polling %s' % datetime.datetime.now().isoformat())
|
||||||
|
lock_file = './data/post_id.json'
|
||||||
if os.path.exists(lock_file):
|
if os.path.exists(lock_file):
|
||||||
prev_post_id = read_lock_file(lock_file)
|
prev_post_id = read_lock_file(lock_file)
|
||||||
print('Read last post ID from file:', lock_file)
|
logger.info('Read last post ID from file: %s' % lock_file)
|
||||||
|
logger.info('Last post ID: %s' % prev_post_id)
|
||||||
else:
|
else:
|
||||||
prev_post_id = 0
|
prev_post_id = 0
|
||||||
print('Last post ID is 0')
|
logger.info('Last post ID: 0')
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
post_data = get_vk_group_last_post() # raw data
|
post_data = get_vk_group_last_post() # raw data
|
||||||
post_id = post_data['response']['items'][0]['id']
|
post_id = post_data['response']['items'][0]['id']
|
||||||
|
|
||||||
# Don't post duplicates
|
# Don't post duplicates
|
||||||
if post_id == prev_post_id:
|
if int(post_id) == int(prev_post_id):
|
||||||
print('Skip posting') # Log
|
logger.info('Post with VK ID %s already posted, skipping' % post_id)
|
||||||
else:
|
else:
|
||||||
# Toot!
|
# Toot!
|
||||||
print('===> Toot! VK post ID:', post_id) # Log
|
logger.info('Toot! VK post ID: %s' % post_id)
|
||||||
post_toot(post_data)
|
post_toot(post_data)
|
||||||
|
|
||||||
touch_lock_file(lock_file, post_id)
|
touch_lock_file(lock_file, post_id)
|
Reference in New Issue
Block a user