mirror of
https://github.com/EDeev/vkrepost_to_tg.git
synced 2026-10-07 20:49:50 +03:00
Исправлены ошибки пересылки постов
- фоновая рассылка падала на каждом новом посте: NameError: name 'types' is not defined (видно в логе сервера) — бот не пересылал ничего; - пост с одной картинкой не отправлялся: send_media_group принимает от 2 до 10 элементов; больше 10 фото — тоже ошибка; теперь одиночное фото — send_photo, альбом обрезается до 10; - подпись длиннее 1024 символов ломала отправку — длинный текст уходит отдельными сообщениями (с делением по строкам, чтобы не рвать HTML); - если первым вложением была ссылка или опрос, текст поста терялся (подпись ставилась только на вложение с индексом 0); - текст и названия из ВК подставлялись в HTML без экранирования; ссылки оформлялись Markdown-синтаксисом ([..](..)) при разметке HTML; - post_message[0] у одиночного сообщения, logging.danger, /notif без подписок — падения; - у видео без photo_800 разбор поста падал; «Лайков» у видео на самом деле были просмотры; - синхронные вызовы vk_api и time.sleep блокировали бота — теперь в отдельном потоке и asyncio.sleep; ошибка у одного подписчика не мешает остальным. Новое: /logout — удалить сохранённый токен VK; сообщение со ссылкой-токеном удаляется из переписки. Токены бота и VK — из переменных окружения, таблицы SQLite создаются при первом запуске (пустые базы убраны из репозитория), ссылка «АВТОР» — на EDeev.
This commit is contained in:
parent
490ea8b5d6
commit
a38819dee7
10 changed files with 206 additions and 139 deletions
81
code/bot.py
81
code/bot.py
|
|
@ -1,61 +1,60 @@
|
|||
import asyncio, logging, time, random, scripts, vk_scripts
|
||||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
|
||||
from init import *
|
||||
from config import *
|
||||
import scripts
|
||||
import vk_scripts
|
||||
from init import bot, dp, du, db
|
||||
from config import serviceToken
|
||||
from handlers import router
|
||||
|
||||
|
||||
async def check_group(group_id, activ_user, sub_user):
|
||||
tokens = [x[0] for x in activ_user if group_id in x[1]]
|
||||
users = [x[0] for x in sub_user if group_id in x[1]]
|
||||
|
||||
# токен случайного подписчика даёт доступ к закрытым страницам, иначе — сервисный ключ
|
||||
if tokens:
|
||||
vk = vk_scripts.VkParser(db.get_token(random.choice(tokens)))
|
||||
else: vk = vk_scripts.VkParser(serviceToken)
|
||||
|
||||
# vk_api синхронный — в отдельном потоке, чтобы не замирал весь бот
|
||||
output = await asyncio.to_thread(vk.last_post, owner_id=du.get_vk_id(group_id))
|
||||
last_post = db.get_postGroup(group_id)
|
||||
|
||||
if output[3] > last_post:
|
||||
text, audio, media = scripts.pars_post(output)
|
||||
db.update_postGroup(group_id, output[3])
|
||||
|
||||
for sub in users:
|
||||
try:
|
||||
await scripts.send_post(bot, du.get_tg_id(sub), text, audio, media)
|
||||
except Exception:
|
||||
# подписчик мог заблокировать бота — остальным пост всё равно уходит
|
||||
logging.warning("Не удалось отправить пост подписчику %s", sub, exc_info=True)
|
||||
|
||||
|
||||
async def timer(wait_for) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(wait_for)
|
||||
|
||||
notif_group = list(map(lambda x: x[0], db.all_notifGroup()))
|
||||
activ_user = list(map(lambda x: [x[0], list(map(int, x[1].split(';')))], db.all_activUser()))
|
||||
sub_user = list(map(lambda x: [x[0], list(map(int, x[1].split(';')))], db.all_subUser()))
|
||||
notif_group = [x[0] for x in db.all_notifGroup()]
|
||||
activ_user = [[x[0], list(map(int, x[1].split(';')))] for x in db.all_activUser() if x[1]]
|
||||
sub_user = [[x[0], list(map(int, x[1].split(';')))] for x in db.all_subUser() if x[1]]
|
||||
|
||||
for group_id in notif_group:
|
||||
time.sleep(1)
|
||||
|
||||
tokens = [x[0] for x in activ_user if group_id in x[1]]
|
||||
users = [x[0] for x in sub_user if group_id in x[1]]
|
||||
|
||||
if tokens:
|
||||
user_id = tokens[random.randrange(len(tokens))]
|
||||
vk = vk_scripts.VkParser(db.get_token(user_id))
|
||||
else: vk = vk_scripts.VkParser(serviceToken)
|
||||
await asyncio.sleep(1) # пауза между страницами — лимиты VK API
|
||||
|
||||
try:
|
||||
output = vk.last_post(owner_id=du.get_vk_id(group_id))
|
||||
last_post = db.get_postGroup(group_id)
|
||||
|
||||
if output[3] > last_post:
|
||||
text, audio, media = scripts.pars_post(types, output)
|
||||
db.update_postGroup(group_id, output[3])
|
||||
|
||||
for sub in users:
|
||||
tg_id = du.get_tg_id(sub)
|
||||
|
||||
if audio and media:
|
||||
post_message = await bot.send_media_group(chat_id=tg_id, media=media)
|
||||
await bot.send_media_group(chat_id=tg_id, media=audio,
|
||||
reply_to_message_id=post_message[0].message_id)
|
||||
elif audio and media == []:
|
||||
post_message = await bot.send_message(chat_id=tg_id, text=text)
|
||||
await bot.send_media_group(chat_id=tg_id, media=audio,
|
||||
reply_to_message_id=post_message[0].message_id)
|
||||
elif audio == [] and media:
|
||||
await bot.send_media_group(chat_id=tg_id, media=media)
|
||||
else:
|
||||
await bot.send_message(chat_id=tg_id, text=text, disable_web_page_preview=True)
|
||||
|
||||
except Exception as err:
|
||||
await check_group(group_id, activ_user, sub_user)
|
||||
except Exception:
|
||||
logging.error("Ошибка обновления поста:", exc_info=True)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
dp.include_router(router)
|
||||
await bot.delete_webhook(drop_pending_updates=True)
|
||||
await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types(), skipUpdates=True)
|
||||
await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
|
||||
|
||||
|
||||
async def process() -> None:
|
||||
|
|
@ -63,7 +62,5 @@ async def process() -> None:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
try: asyncio.run(process())
|
||||
except KeyboardInterrupt: pass
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import os
|
||||
|
||||
# TOKENS
|
||||
botToken = '**********'
|
||||
serviceToken = "**********"
|
||||
botToken = os.getenv("BOT_TOKEN", "XXXXXXXXXXXXXXXXXXXXXXXX") # @vkportalbot
|
||||
serviceToken = os.getenv("VK_SERVICE_TOKEN", "") # сервисный ключ приложения VK
|
||||
|
||||
# URL
|
||||
loginUrl = "************"
|
||||
loginUrl = os.getenv("VK_LOGIN_URL", "https://oauth.vk.com/authorize?client_id=6121396&scope=215985366"
|
||||
"&redirect_uri=https://oauth.vk.com/blank.html&display=page"
|
||||
"&response_type=token&revoke=1")
|
||||
checkUrl = "https://oauth.vk.com/blank.html#access_token="
|
||||
|
||||
# VARIABILITY
|
||||
|
|
@ -12,3 +16,4 @@ anonymous = {True: 'анонимный опрос', False: 'не анонимн
|
|||
|
||||
# DATA
|
||||
data = {0: 'Б', 1: 'КБ', 2: 'МБ', 3: 'ГБ', 4: 'ТБ'}
|
||||
dura = {0: 'с.', 1: 'мин.'}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiogram import types, F, Router
|
||||
from aiogram.types import Message, CallbackQuery, ContentType
|
||||
from aiogram.filters import Command
|
||||
|
||||
from emoji import emojize
|
||||
|
||||
from init import *
|
||||
from config import *
|
||||
from init import bot, du, db
|
||||
from config import checkUrl, loginUrl, serviceToken
|
||||
from vk_scripts import VkParser
|
||||
from scripts import login, pars_post
|
||||
from scripts import login, pars_post, send_post
|
||||
|
||||
router = Router()
|
||||
|
||||
|
|
@ -18,7 +21,7 @@ async def start(msg: Message) -> None:
|
|||
|
||||
buttons = [[types.InlineKeyboardButton(text="КОМАНДЫ", callback_data="com"),
|
||||
types.InlineKeyboardButton(text="АВТОР", callback_data="auth")]]
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons, row_width=2)
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||
|
||||
await msg.answer(text=f'<b>Portal in VK</b> - это бот для перепоста постов со страниц в социальной сети ВКонтакте. '
|
||||
f'Для начала работы вам нужно всего лишь вызвать команду <b>/add</b> и добавить к ней '
|
||||
|
|
@ -39,7 +42,9 @@ async def function(call: CallbackQuery) -> None:
|
|||
'<b>/list</b> - список страниц от, которых вы получаете уведомления\n'
|
||||
'<b>/notif</b> - отписка или подписка от всех уведомлений\n'
|
||||
'<b>/last_post</b> - получение последнего поста по короткому имени страницы\n'
|
||||
'<b>/add</b> и <b>/del</b> - добавление и удаление странички из списка подписок\n')
|
||||
'<b>/add</b> и <b>/del</b> - добавление и удаление странички из списка подписок\n'
|
||||
'<b>/update</b> - ответом на пост: прислать его заново с актуальными данными\n'
|
||||
'<b>/logout</b> - удалить сохранённый токен VK\n')
|
||||
|
||||
|
||||
@router.callback_query(F.data == "auth")
|
||||
|
|
@ -51,7 +56,7 @@ async def author(call: CallbackQuery) -> None:
|
|||
'возможность ставить через Telegram лайки на посты в самой социальной сети!'
|
||||
|
||||
'\n\nЯ же пишу подобные небольшие проекты, о которых вы можете узнать '
|
||||
'больше на моём <a href="https://github.com/IGlek">GitHub</a>.')
|
||||
'больше на моём <a href="https://github.com/EDeev">GitHub</a>.')
|
||||
|
||||
|
||||
# КОМАНДЫ
|
||||
|
|
@ -60,8 +65,10 @@ async def notification(msg: Message) -> None:
|
|||
user_id = login(msg.chat.id, du, db)
|
||||
status = db.get_status(user_id)
|
||||
|
||||
groups = list(map(int, db.get_user_groups(user_id).split(";")))
|
||||
for group in groups: db.update_countGroup(group, -1 if status else 1)
|
||||
# у пользователя может ещё не быть ни одной подписки
|
||||
groups = db.get_user_groups(user_id)
|
||||
for group in map(int, groups.split(";") if groups else []):
|
||||
db.update_countGroup(group, -1 if status else 1)
|
||||
|
||||
if status: await msg.answer("Получение постов из ВК выключено!")
|
||||
else: await msg.answer("Получение постов из ВК включено!")
|
||||
|
|
@ -81,7 +88,7 @@ async def add_del(msg: Message) -> None:
|
|||
if token: vk = VkParser(token)
|
||||
else: vk = VkParser(serviceToken)
|
||||
|
||||
group_id, typ, last_post = vk.login(domain)
|
||||
group_id, typ, last_post = await asyncio.to_thread(vk.login, domain)
|
||||
|
||||
if not du.group_exists(group_id):
|
||||
du.add_group(group_id)
|
||||
|
|
@ -118,7 +125,7 @@ async def add_del(msg: Message) -> None:
|
|||
if db.get_status(user_id): db.update_countGroup(group_id, -1)
|
||||
|
||||
await msg.answer("Группа успешно удалена из списка уведомлений!")
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
logging.info("Нет доступа к страницы:", exc_info=True)
|
||||
await msg.answer("<b>Произошла ошибка!</b> Проверьте правильность написания <b>короткого адреса</b> "
|
||||
"страницы, <b>имеете ли вы доступ</b> к этой странице и есть ли на ней <b>хотя бы "
|
||||
|
|
@ -142,7 +149,7 @@ async def lst(msg: Message) -> None:
|
|||
group_ids = [du.get_vk_id(id) for id in user_groups]
|
||||
|
||||
vk = VkParser(serviceToken)
|
||||
peoples, groups = vk.info(group_ids)
|
||||
peoples, groups = await asyncio.to_thread(vk.info, group_ids)
|
||||
|
||||
text = "<b><i>* Список страниц, от которых вы получаете уведомления!</i></b>\n"
|
||||
|
||||
|
|
@ -186,10 +193,10 @@ async def like(msg: Message) -> None:
|
|||
[group_id, last_post] = list(map(int, url.split('wall')[-1].split('_')))
|
||||
|
||||
vk = VkParser(token)
|
||||
vk.like(group_id, last_post)
|
||||
await asyncio.to_thread(vk.like, group_id, last_post)
|
||||
|
||||
await msg.answer('Вы поставили лайк 😉')
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
logging.error("Не удалось поставить лайк:", exc_info=True)
|
||||
await msg.answer('По <b>неизвестной причине</b> не удалось поставить лайк!')
|
||||
else:
|
||||
|
|
@ -226,27 +233,16 @@ async def update(msg: Message) -> None:
|
|||
if token: vk = VkParser(token)
|
||||
else: vk = VkParser(serviceToken)
|
||||
|
||||
output = vk.last_post(post=f"{group_id}_{last_post}")
|
||||
text, audio, media = pars_post(types, output)
|
||||
output = await asyncio.to_thread(vk.last_post, post=f"{group_id}_{last_post}")
|
||||
text, audio, media = pars_post(output)
|
||||
|
||||
delete_r = await bot.delete_message(chat_id=user_id, message_id=msg.reply_to_message.message_id)
|
||||
delete = await bot.delete_message(chat_id=user_id, message_id=msg.message_id)
|
||||
await bot.delete_message(chat_id=user_id, message_id=msg.reply_to_message.message_id)
|
||||
await bot.delete_message(chat_id=user_id, message_id=msg.message_id)
|
||||
|
||||
if audio and media:
|
||||
post_message = await bot.send_media_group(chat_id=user_id, media=media)
|
||||
await bot.send_media_group(chat_id=user_id, media=audio,
|
||||
reply_to_message_id=post_message[0].message_id)
|
||||
elif audio and media == []:
|
||||
post_message = await bot.send_message(chat_id=user_id, text=text)
|
||||
await bot.send_media_group(chat_id=user_id, media=audio,
|
||||
reply_to_message_id=post_message[0].message_id)
|
||||
elif audio == [] and media:
|
||||
await bot.send_media_group(chat_id=user_id, media=media)
|
||||
else:
|
||||
await bot.send_message(chat_id=user_id, text=text, disable_web_page_preview=True)
|
||||
await send_post(bot, user_id, text, audio, media)
|
||||
|
||||
except Exception as err:
|
||||
logging.danger("Ошибка обновления поста:", exc_info=True)
|
||||
except Exception:
|
||||
logging.error("Ошибка обновления поста:", exc_info=True)
|
||||
await msg.answer(text='По неизвестной причине не удалось обновить пост!')
|
||||
else: await msg.answer(text='Вы ответили не на тот пост!')
|
||||
else: await msg.answer(text='Вы не отметили пост который хотите обновить!')
|
||||
|
|
@ -265,26 +261,29 @@ async def last_post(msg: Message) -> None:
|
|||
if token: vk = VkParser(token)
|
||||
else: vk = VkParser(serviceToken)
|
||||
|
||||
output = vk.last_post(domain=domain); vk.login(domain)
|
||||
text, audio, media = pars_post(types, output)
|
||||
output = await asyncio.to_thread(vk.last_post, domain=domain)
|
||||
text, audio, media = pars_post(output)
|
||||
|
||||
if audio and media:
|
||||
post_message = await bot.send_media_group(chat_id=user_id, media=media)
|
||||
await bot.send_media_group(chat_id=user_id, media=audio, reply_to_message_id=post_message[0].message_id)
|
||||
elif audio and media == []:
|
||||
post_message = await bot.send_message(chat_id=user_id, text=text)
|
||||
await bot.send_media_group(chat_id=user_id, media=audio, reply_to_message_id=post_message[0].message_id)
|
||||
elif audio == [] and media:
|
||||
await bot.send_media_group(chat_id=user_id, media=media)
|
||||
else:
|
||||
await bot.send_message(chat_id=user_id, text=text, disable_web_page_preview=True)
|
||||
await send_post(bot, user_id, text, audio, media)
|
||||
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
logging.info("Некорректное/недоступное короткое имя:", exc_info=True)
|
||||
await msg.answer("Короткое <b>имя страницы неверное</b> или у вас <b>нет доступа</b> к этой странице!")
|
||||
else: await msg.answer("Команда введена некорректно! Корректный ввод: /last_post <b>example</b>")
|
||||
|
||||
|
||||
@router.message(Command("logout"))
|
||||
async def logout(msg: Message) -> None:
|
||||
user_id = login(msg.chat.id, du, db)
|
||||
|
||||
if db.get_token(user_id):
|
||||
db.update_token(user_id, None)
|
||||
await msg.answer("Токен VK удалён, бот снова работает на сервисном ключе. Полностью отозвать доступ "
|
||||
"можно в настройках VK: <b>Приложения и сайты</b>.")
|
||||
else:
|
||||
await msg.answer("Сохранённого токена нет.")
|
||||
|
||||
|
||||
# ПОЛУЧЕНИЕ ТОКЕНА ПОЛЬЗОВАТЕЛЯ
|
||||
@router.message(F.content_type == ContentType.TEXT)
|
||||
async def url(msg: Message) -> None:
|
||||
|
|
@ -293,11 +292,14 @@ async def url(msg: Message) -> None:
|
|||
if checkUrl in msg.text:
|
||||
try:
|
||||
token = msg.text.split("&")[0].split("=")[-1]
|
||||
info = VkParser(token).check()
|
||||
await asyncio.to_thread(VkParser(token).check)
|
||||
|
||||
db.update_token(user_id, token)
|
||||
await msg.answer("Токен успешно сохранён!")
|
||||
except Exception as err:
|
||||
# ссылка с токеном не должна оставаться в переписке
|
||||
try: await msg.delete()
|
||||
except Exception: pass
|
||||
await msg.answer("Токен успешно сохранён! Удалить его можно командой /logout")
|
||||
except Exception:
|
||||
logging.info("Некорректная ссылка:", exc_info=True)
|
||||
await msg.answer("Ваша <b>ссылка не корректна</b>, попробуйте повторить копирование!")
|
||||
else: await msg.answer("Ваше <b>сообщение не корректно</b>, необходима ссылка, что отобразилась в строке "
|
||||
|
|
|
|||
15
code/init.py
15
code/init.py
|
|
@ -1,22 +1,21 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.enums.parse_mode import ParseMode
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.client.bot import DefaultBotProperties
|
||||
|
||||
|
||||
from config import botToken
|
||||
from sql import Users, Base
|
||||
|
||||
bot = Bot(token=botToken, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||
dp = Dispatcher(storage=MemoryStorage())
|
||||
|
||||
|
||||
from sql import Users, Base
|
||||
|
||||
os.makedirs("../db", exist_ok=True)
|
||||
du = Users('../db/users.db')
|
||||
db = Base('../db/base.db')
|
||||
|
||||
|
||||
import logging, functools
|
||||
|
||||
logging.basicConfig(level=logging.INFO, filename='../info.log',filemode="w",
|
||||
# лог в файл, если задан LOG_FILE (так бот работал на сервере), иначе — в консоль
|
||||
logging.basicConfig(level=logging.INFO, filename=os.getenv("LOG_FILE") or None,
|
||||
format="%(asctime)s %(levelname)s %(message)s")
|
||||
|
|
|
|||
108
code/scripts.py
108
code/scripts.py
|
|
@ -1,5 +1,12 @@
|
|||
from html import escape
|
||||
|
||||
from aiogram import types
|
||||
|
||||
import config
|
||||
|
||||
CAPTION_LIMIT = 1024 # подпись к фото в Telegram
|
||||
TEXT_LIMIT = 4096 # обычное сообщение
|
||||
|
||||
|
||||
def login(user_id, du, db):
|
||||
user_id = int(user_id)
|
||||
|
|
@ -11,7 +18,9 @@ def login(user_id, du, db):
|
|||
return user_id
|
||||
|
||||
|
||||
def pars_post(types, output):
|
||||
def pars_post(output):
|
||||
"""Пост из vk_scripts.get_output → текст в HTML, аудио и медиа для Telegram.
|
||||
Всё, что пришло из ВК, экранируется: «<» или «&» в посте иначе ломают отправку"""
|
||||
media, video, url, audio, poll, link = [], [], [], [], [], []
|
||||
|
||||
for group in output[0]:
|
||||
|
|
@ -20,55 +29,98 @@ def pars_post(types, output):
|
|||
elif group[0] == "video": video.append(group[1])
|
||||
elif group[0] == "doc" and group[1][2] != 4: url.append([group[1][0], group[1][1], group[1][3]])
|
||||
elif group[0] == "audio": audio.append(types.InputMediaAudio(
|
||||
media=group[1][1], caption=f"<b>Название: {group[1][0]} / Автор: {group[1][2]}</b>"))
|
||||
media=group[1][1], caption=f"<b>Название: {escape(group[1][0])} / Автор: {escape(group[1][2])}</b>"))
|
||||
|
||||
if output[1] != "":
|
||||
post = [elem_2 for elem_1 in output[1].split("[") for elem_2 in elem_1.split("]")]
|
||||
# упоминания ВК вида [id1|Имя] превращаются в ссылки
|
||||
post = [elem_2 for elem_1 in escape(output[1], quote=False).split("[") for elem_2 in elem_1.split("]")]
|
||||
for i, elem in enumerate(post):
|
||||
if "|" in elem and i != 0:
|
||||
elem = elem.split("|")
|
||||
post[i] = f"<a href='https://vk.com/{elem[0]}'>{elem[1]}</a>"
|
||||
elem = elem.split("|", 1)
|
||||
post[i] = f"<a href='https://vk.com/{escape(elem[0])}'>{elem[1]}</a>"
|
||||
|
||||
text = f"{output[2]}\n\n{''.join(post)}\n"
|
||||
else: text = f"{output[2]}\n"
|
||||
|
||||
if poll:
|
||||
text += f"\n<b>> Проводиться</b> {config.anonymous[poll[3]]} {config.unvote[poll[2]]}! <b>Вопрос:</b> {poll[0]} <b>[{poll[1]}]</b>\n"
|
||||
text += ''.join([f'<b>{i + 1}. Ответ:</b> {ans[0]} <b>[{ans[1]}]</b>\n' for i, ans in enumerate(poll[4])]) + "\n"
|
||||
text += (f"\n<b>> Проводится</b> {config.anonymous[poll[3]]} {config.unvote[poll[2]]}! "
|
||||
f"<b>Вопрос:</b> {escape(poll[0])} <b>[{poll[1]}]</b>\n")
|
||||
text += ''.join([f'<b>{i + 1}. Ответ:</b> {escape(ans[0])} <b>[{ans[1]}]</b>\n'
|
||||
for i, ans in enumerate(poll[4])]) + "\n"
|
||||
|
||||
if video:
|
||||
for frame in video:
|
||||
text += f"\n<b>Видео</b> <i>« {frame[0]} »</i>\n"
|
||||
text += f"\n<b>Видео</b> <i>« {escape(frame[0])} »</i>\n"
|
||||
|
||||
k, dura = 0, frame[1]
|
||||
while k < 1:
|
||||
if dura > 60: dura = dura // 60; k += 1
|
||||
else: break
|
||||
if dura > 60: dura, k = dura // 60, 1
|
||||
|
||||
text += f"<b>Длительность:</b> {dura} {config.dura[k]}; <b>Лайков:</b> {frame[2]};\n"
|
||||
text += f"<b>Длительность:</b> {dura} {config.dura[k]}; <b>Просмотров:</b> {frame[2]};\n"
|
||||
|
||||
if url:
|
||||
for data in url:
|
||||
k, dt = 0, data[2]
|
||||
while True:
|
||||
if dt > 1024: dt = dt // 1024; k += 1
|
||||
else: break
|
||||
text += f"\n<b>Документ - [</b> <a href='{data[1]}'>{data[0]}</a> ({dt} {config.data[k]}) <b>]</b>"
|
||||
while dt > 1024 and k < len(config.data) - 1:
|
||||
dt, k = dt // 1024, k + 1
|
||||
text += (f"\n<b>Документ - [</b> <a href='{escape(data[1])}'>{escape(data[0])}</a> "
|
||||
f"({dt} {config.data[k]}) <b>]</b>")
|
||||
text += "\n"
|
||||
|
||||
if link: text += ''.join([f"\n<b>Ссылка - [</b> [{s[0]}]({s[1]}) <b>]</b>" for s in link]) + "\n"
|
||||
if link:
|
||||
text += ''.join([f"\n<b>Ссылка - [</b> <a href='{escape(s[1])}'>{escape(s[0])}</a> <b>]</b>"
|
||||
for s in link]) + "\n"
|
||||
if audio: text += '\n<b>« Также присутствуют аудио файлы »</b>'
|
||||
|
||||
for i, group in enumerate(output[0]):
|
||||
for group in output[0]:
|
||||
if group[0] == "photo":
|
||||
if i: media.append(types.InputMediaPhoto(media=group[1]))
|
||||
else: media.append(types.InputMediaPhoto(media=group[1], caption=text))
|
||||
elif group[0] == "video":
|
||||
if i: media.append(types.InputMediaPhoto(media=group[1][3]))
|
||||
else: media.append(types.InputMediaPhoto(media=group[1][3], caption=text))
|
||||
elif group[0] == "doc":
|
||||
if group[1][2] == 4:
|
||||
if i: media.append(types.InputMediaPhoto(media=group[1][1]))
|
||||
else: media.append(types.InputMediaPhoto(media=group[1][1], caption=text))
|
||||
media.append(types.InputMediaPhoto(media=group[1]))
|
||||
elif group[0] == "video" and group[1][3]: # у видео может не быть обложки
|
||||
media.append(types.InputMediaPhoto(media=group[1][3]))
|
||||
elif group[0] == "doc" and group[1][2] == 4:
|
||||
media.append(types.InputMediaPhoto(media=group[1][1]))
|
||||
|
||||
return text, audio, media
|
||||
return text, audio[:10], media[:10] # в одном альбоме Telegram не больше 10 элементов
|
||||
|
||||
|
||||
def split_text(text, limit=TEXT_LIMIT):
|
||||
"""Делит длинный текст по строкам, чтобы не разрывать HTML-теги посередине"""
|
||||
parts, current = [], ""
|
||||
for line in text.split("\n"):
|
||||
while len(line) > limit: # одна строка длиннее лимита — режем как есть
|
||||
if current: parts.append(current); current = ""
|
||||
parts.append(line[:limit]); line = line[limit:]
|
||||
if len(current) + len(line) + 1 > limit:
|
||||
parts.append(current); current = ""
|
||||
current += line + "\n"
|
||||
if current.strip(): parts.append(current)
|
||||
return parts
|
||||
|
||||
|
||||
async def send_post(bot, chat_id, text, audio, media):
|
||||
"""Отправляет пост с учётом ограничений Telegram: альбом — от 2 до 10 элементов, подпись —
|
||||
до 1024 символов, сообщение — до 4096"""
|
||||
first = None
|
||||
|
||||
if media:
|
||||
caption = text if len(text) <= CAPTION_LIMIT else None
|
||||
if len(media) == 1:
|
||||
first = await bot.send_photo(chat_id=chat_id, photo=media[0].media, caption=caption)
|
||||
else:
|
||||
media[0].caption = caption
|
||||
first = (await bot.send_media_group(chat_id=chat_id, media=media))[0]
|
||||
if caption is None:
|
||||
for part in split_text(text):
|
||||
await bot.send_message(chat_id=chat_id, text=part, disable_web_page_preview=True,
|
||||
reply_to_message_id=first.message_id)
|
||||
else:
|
||||
for part in split_text(text):
|
||||
sent = await bot.send_message(chat_id=chat_id, text=part, disable_web_page_preview=True)
|
||||
first = first or sent
|
||||
|
||||
if audio:
|
||||
reply_to = first.message_id if first else None
|
||||
if len(audio) == 1:
|
||||
await bot.send_audio(chat_id=chat_id, audio=audio[0].media, caption=audio[0].caption,
|
||||
reply_to_message_id=reply_to)
|
||||
else:
|
||||
await bot.send_media_group(chat_id=chat_id, media=audio, reply_to_message_id=reply_to)
|
||||
|
|
|
|||
12
code/sql.py
12
code/sql.py
|
|
@ -7,6 +7,11 @@ class Users:
|
|||
self.connection = sqlite3.connect(database)
|
||||
self.cursor = self.connection.cursor()
|
||||
|
||||
# на новой установке (например, в Docker с пустым томом) таблиц ещё нет
|
||||
with self.connection:
|
||||
self.cursor.execute("CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL)")
|
||||
self.cursor.execute("CREATE TABLE IF NOT EXISTS \"group\" (id INTEGER PRIMARY KEY, group_id INTEGER NOT NULL)")
|
||||
|
||||
# КОМАНДЫ USER
|
||||
def user_exists(self, user_id):
|
||||
"""Проверяем, есть ли уже пользователь в базе"""
|
||||
|
|
@ -63,6 +68,12 @@ class Base:
|
|||
self.connection = sqlite3.connect(database)
|
||||
self.cursor = self.connection.cursor()
|
||||
|
||||
with self.connection:
|
||||
self.cursor.execute("CREATE TABLE IF NOT EXISTS user (user_id INTEGER NOT NULL, status BOOLEAN NOT NULL "
|
||||
"DEFAULT (True), count INTEGER NOT NULL DEFAULT (0), groups TEXT, token TEXT)")
|
||||
self.cursor.execute("CREATE TABLE IF NOT EXISTS \"group\" (group_id INTEGER NOT NULL, type BOOLEAN NOT NULL, "
|
||||
"count INTEGER NOT NULL DEFAULT (0), last_post INTEGER NOT NULL)")
|
||||
|
||||
# КОМАНДЫ USER
|
||||
def infoUser_exists(self, user_id):
|
||||
"""Проверяем, есть ли данные уже в базе"""
|
||||
|
|
@ -166,4 +177,3 @@ class Base:
|
|||
def close(self):
|
||||
"""Закрываем соединение с БД"""
|
||||
self.connection.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from html import escape
|
||||
|
||||
import vk_api
|
||||
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ def get_output(post, name, domain, repost=None):
|
|||
[post_id, post] = post
|
||||
owner_id, output = post['owner_id'], []
|
||||
|
||||
attachments = post['attachments']
|
||||
attachments = post.get('attachments', [])
|
||||
types = [[typ['type'], typ] for typ in attachments]
|
||||
|
||||
for typ in types:
|
||||
|
|
@ -78,12 +80,12 @@ def get_output(post, name, domain, repost=None):
|
|||
output.append([typ[0], typ[1]['photo']['sizes'][-1]['url']])
|
||||
elif typ[0] == 'video':
|
||||
video = typ[1]['video']
|
||||
if 'photo_1280' in video:
|
||||
frame = video['photo_1280']
|
||||
else:
|
||||
frame = video['photo_800']
|
||||
# обложка: самая крупная из доступных
|
||||
frame = video.get('photo_1280') or video.get('photo_800') or video.get('photo_320')
|
||||
if frame is None and video.get('image'):
|
||||
frame = video['image'][-1]['url']
|
||||
|
||||
output.append([typ[0], [video['title'], video['duration'], video['views'], frame]])
|
||||
output.append([typ[0], [video['title'], video.get('duration', 0), video.get('views', 0), frame]])
|
||||
elif typ[0] == "doc":
|
||||
output.append([typ[0], [typ[1]['doc']['title'], typ[1]['doc']['url'],
|
||||
typ[1]['doc']['type'], typ[1]['doc']['size']]])
|
||||
|
|
@ -99,11 +101,11 @@ def get_output(post, name, domain, repost=None):
|
|||
if repost:
|
||||
[author_id, author_owner_id, author_name, author_domain, author_comment] = repost
|
||||
|
||||
comment = f"<b>Автор репоста -</b> <a href='https://vk.com/{author_domain}?w=wall{author_owner_id}_{author_id}'>{author_name}</a>"
|
||||
if author_comment != "": comment += f"\n<blockquote>{author_comment}</blockquote>\n"
|
||||
comment += f"\n<b>Автор поста -</b> <a href='https://vk.com/{domain}?w=wall{owner_id}_{post_id}'>{name}</a>"
|
||||
comment = f"<b>Автор репоста -</b> <a href='https://vk.com/{author_domain}?w=wall{author_owner_id}_{author_id}'>{escape(author_name)}</a>"
|
||||
if author_comment != "": comment += f"\n<blockquote>{escape(author_comment)}</blockquote>\n"
|
||||
comment += f"\n<b>Автор поста -</b> <a href='https://vk.com/{domain}?w=wall{owner_id}_{post_id}'>{escape(name)}</a>"
|
||||
|
||||
return [output, f"{post['text']}", comment, author_id]
|
||||
else:
|
||||
return [output, f"{post['text']}",
|
||||
f"<b>Автор поста -</b> <a href='https://vk.com/{domain}?w=wall{owner_id}_{post_id}'>{name}</a>", post_id]
|
||||
f"<b>Автор поста -</b> <a href='https://vk.com/{domain}?w=wall{owner_id}_{post_id}'>{escape(name)}</a>", post_id]
|
||||
0
db/.gitkeep
Normal file
0
db/.gitkeep
Normal file
BIN
db/base.db
BIN
db/base.db
Binary file not shown.
BIN
db/users.db
BIN
db/users.db
Binary file not shown.
Loading…
Add table
Reference in a new issue