Compare commits
No commits in common. "master" and "heroku-old" have entirely different histories.
master
...
heroku-old
@ -1,27 +0,0 @@
|
||||
# Project
|
||||
.idea/
|
||||
.github/
|
||||
pyproject.toml
|
||||
.flake8
|
||||
.*ignore
|
||||
.isort.cfg
|
||||
Makefile
|
||||
readme.md
|
||||
|
||||
# Cache
|
||||
*.py[cod]
|
||||
__pycache__
|
||||
|
||||
# Texts
|
||||
*.mo
|
||||
|
||||
# Environment
|
||||
*.env
|
||||
env_file
|
||||
setenv.py
|
||||
|
||||
# Docker
|
||||
docker-compose*.yml
|
||||
Dockerfile
|
||||
|
||||
.venv
|
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
19
.github/workflows/dockerimage.yml
vendored
19
.github/workflows/dockerimage.yml
vendored
@ -1,19 +0,0 @@
|
||||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build and publish
|
||||
uses: elgohr/Publish-Docker-Github-Action@master
|
||||
with:
|
||||
name: Kylmakalle/tgvkbot:latest
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
snapshot: true
|
152
.gitignore
vendored
152
.gitignore
vendored
@ -1,11 +1,145 @@
|
||||
.idea/
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
data/migrations/*
|
||||
!data/migrations/__init__.py
|
||||
config.py
|
||||
*.pem
|
||||
env_file
|
||||
openssl_config
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask instance folder
|
||||
instance/
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# IPython Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
venv/
|
||||
commands/
|
||||
.venv
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# =========================
|
||||
# Operating System Files
|
||||
# =========================
|
||||
|
||||
# OSX
|
||||
# =========================
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Windows
|
||||
# =========================
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# Bot credentials
|
||||
credentials.py
|
||||
|
||||
# PyCharm project
|
||||
.idea
|
10
Dockerfile
10
Dockerfile
@ -1,10 +0,0 @@
|
||||
FROM python:3.6-slim AS builder
|
||||
RUN apt-get update && apt-get install -y gcc
|
||||
COPY requirements.txt .
|
||||
RUN pip install --user -r requirements.txt
|
||||
|
||||
FROM python:3.6-slim
|
||||
COPY --from=builder /root/.local /root/.local
|
||||
COPY . .
|
||||
ENV PATH=/root/.local/bin:$PATH
|
||||
ENTRYPOINT bash -c "python manage.py makemigrations data && python manage.py migrate data && python telegram.py"
|
20
LICENSE
Normal file
20
LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Sergey
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
80
README.md
80
README.md
@ -1,80 +1,28 @@
|
||||
### Новая версия в разработке!
|
||||
Актуальная ветка разработки находится [здесь](https://github.com/Kylmakalle/tgvkbot/tree/async-dev)
|
||||
|
||||
Подробнее в [чате](https://t.me/joinchat/BZq6jwxeTh04qBzilM5x3g)
|
||||
|
||||
# tgvkbot
|
||||
Общайтесь в VK через Telegram бота.
|
||||
Бот позволяет получать и отправлять сообщения VK находясь в Telegram
|
||||
|
||||
- Бот от разработчика - [@tgvkbot](https://t.me/tgvkbot)
|
||||
https://www.asergey.me/tgvkbot
|
||||
|
||||
- Канал - [@tg_vk](https://t.me/tg_vk)
|
||||
|
||||
- Чат - https://t.me/joinchat/BZq6jwxeTh04qBzilM5x3g
|
||||
[](https://heroku.com/deploy)
|
||||
|
||||
|
||||
# Простая Установка (Ubuntu)
|
||||
```bash
|
||||
git clone https://github.com/Kylmakalle/tgvkbot
|
||||
cd tgvkbot
|
||||
./install.sh
|
||||
|
||||
Токен Telegram бота: 123456789:AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLL
|
||||
VK APP ID (можно оставить пустым):
|
||||
```
|
||||
|
||||
Далее потребуется ввести пароль от `sudo` пользователя и Telegram-token, остальные переменные необязательны.
|
||||
|
||||
_Установщик поставит Docker и docker-compose, настроит переменные окружения и запустит контейнер для обновлений, а затем поднимет бота с его базой данных._
|
||||
|
||||
### Обновление
|
||||
Бот автоматически обновляется через образ на [dockerhub](https://hub.docker.com/r/kylmakalle/tgvkbot/tags?page=1&ordering=last_updated), где на всякий случай фиксируются версии каждого коммита.
|
||||
|
||||
Стандартный установщик поднимает [watchtower](https://containrrr.dev/watchtower), который раз в час проверяет обновления.
|
||||
[Ветка](https://github.com/Kylmakalle/tgvkbot/tree/webhook) для деплоя на локальной машине (поддерживаются вебхуки и лонгполлинг, закомментируйте и раскомментируйте нужные строки)
|
||||
|
||||
|
||||
### Ограничение пользователей
|
||||
Если по каким-то причинам хочется чтобы ботом пользовались исключительно определенные пользователи, то это можно сделать изменив файл конфигурации.
|
||||
Потребуется прописать параметр в таком виде, где числа - Telegram ID пользователей через запятую.
|
||||
Настройка вебхуков по гайду от [@Groosha](https://www.gitbook.com/book/groosha/telegram-bot-lessons)
|
||||
|
||||
`ALLOWED_USER_IDS=12345678,001238091`
|
||||
Бэкенд API для получения музыки через https://asergey.me/vkmusapi/
|
||||
|
||||
ID можно узнать командой `/id` в боте или через других ботов/софт.
|
||||
https://gist.github.com/Kylmakalle/e63902025c527ac3610989530f4fa417
|
||||
|
||||
|
||||
### Кастомизация
|
||||
С недавнего времени бот поднимается с помощью готового docker образа. Если нужно сделать какую-то специфичную правку, то бота можно поднять через отдельный файл командой
|
||||
|
||||
`docker-compose -f docker-compose.local.yml up`
|
||||
|
||||
Все остальные действия (`restart`, `stop`, ...) привязанные к "локальному боту" нужно выполнять с флагом `-f docker-compose.local.yml`
|
||||
|
||||
Не забудьте отключить бота из образа командой `docker-compose down`
|
||||
|
||||
# Установка в Dokku
|
||||
|
||||
Подробнее о деплое через Dokku можно прочитать [здесь](https://dokku.com/docs/deployment/application-deployment/).
|
||||
|
||||
На сервере:
|
||||
|
||||
```bash
|
||||
dokku apps:create tgvkbot
|
||||
dokku postgres:create tgvkbot_db
|
||||
dokku postgres:link tgvkbot_db tgvkbot
|
||||
|
||||
dokku config:set tgvkbot BOT_TOKEN=<tg_token> [VK_APP_ID=<vk_app_id> ALLOWED_USER_IDS=<tg_user_ids,...> MAX_FILE_SIZE=<num> ...]
|
||||
```
|
||||
|
||||
На локальном компьютере/где угодно в папке с репозиторием:
|
||||
|
||||
```bash
|
||||
git remote add dokku dokku@<dokku_host_url>:tgvkbot
|
||||
git push dokku
|
||||
```
|
||||
## Stay Tuned!
|
||||
|
||||
|
||||
# Сервисы музыки (Устаревшие)
|
||||
Ниже прокси для музыки, которые использовали ранее. Сейчас они нерелевантны, но код открыт и в боте есть поддержка кастомных бэкендов музыки.
|
||||
|
||||
API - https://github.com/Kylmakalle/thatmusic-api
|
||||
|
||||
Token Refresher - https://github.com/Kylmakalle/vk-audio-token/tree/refresh-api
|
||||
|
||||
|
||||
# Лицензия
|
||||
MIT
|
||||
_Полноценные комментарии к коду будут чуть позже_
|
||||
|
34
app.json
Normal file
34
app.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "tgvkbot",
|
||||
"description": "Бот позволяет получать и отправлять сообщения VK находясь в Telegram",
|
||||
"repository": "https://github.com/Kylmakalle/tgvkbot",
|
||||
"keywords": ["vk", "bot", "telegram"],
|
||||
"website": "https://asergey.me/tgvkbot/",
|
||||
"buildpacks":[
|
||||
{
|
||||
"url": "heroku/python"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"TELEGRAM_TOKEN": {
|
||||
"description": "Telegram bot API токен от https://t.me/BotFather",
|
||||
"value": "123456789:AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLL"
|
||||
},
|
||||
"VK_APP": {
|
||||
"description": "ID VK приложения из https://vk.com/apps?act=manage",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"addons": [
|
||||
{
|
||||
"plan": "heroku-redis:hobby-dev",
|
||||
"as": "Redis"
|
||||
}
|
||||
],
|
||||
"formation": {
|
||||
"bot": {
|
||||
"quantity": 1,
|
||||
"size": "free"
|
||||
}
|
||||
}
|
||||
}
|
789
bot.py
789
bot.py
@ -1,116 +1,721 @@
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import redis
|
||||
import requests
|
||||
import telebot
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import ujson
|
||||
|
||||
import aiohttp
|
||||
import django.conf
|
||||
import vk
|
||||
import wget
|
||||
from PIL import Image
|
||||
from aiogram import Bot
|
||||
from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.types import ParseMode, MediaGroup, InlineKeyboardMarkup, InlineKeyboardButton, ChatActions
|
||||
from aiogram.utils.exceptions import *
|
||||
from aiogram.utils.parts import safe_split_text, split_text, MAX_MESSAGE_LENGTH
|
||||
from aiogram.utils import context
|
||||
from aiovk import TokenSession, API
|
||||
from aiovk.drivers import HttpDriver
|
||||
from aiovk.exceptions import *
|
||||
from aiovk.mixins import LimitRateDriverMixin
|
||||
from telebot import types
|
||||
|
||||
from config import *
|
||||
from credentials import token, vk_app_id
|
||||
from vk_messages import VkMessage, VkPolling
|
||||
|
||||
django.conf.ENVIRONMENT_VARIABLE = SETTINGS_VAR
|
||||
os.environ.setdefault(SETTINGS_VAR, "settings")
|
||||
# Ensure settings are read
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
vk_threads = {}
|
||||
|
||||
application = get_wsgi_application()
|
||||
vk_dialogs = {}
|
||||
|
||||
from data.models import *
|
||||
VK_API_VERSION = '3.0'
|
||||
|
||||
FILE_URL = 'https://api.telegram.org/file/bot{0}/{1}'
|
||||
|
||||
vk_tokens = redis.from_url(os.environ.get("REDIS_URL"))
|
||||
|
||||
currentchat = {}
|
||||
|
||||
bot = telebot.AsyncTeleBot(token)
|
||||
bot.remove_webhook()
|
||||
|
||||
link = 'https://oauth.vk.com/authorize?client_id={}&' \
|
||||
'display=page&redirect_uri=https://oauth.vk.com/blank.html&scope=friends,messages,offline,docs,photos,video' \
|
||||
'&response_type=token&v={}'.format(vk_app_id, VK_API_VERSION)
|
||||
|
||||
|
||||
class VkSession(TokenSession):
|
||||
API_VERSION = API_VERSION
|
||||
|
||||
|
||||
class RateLimitedDriver(LimitRateDriverMixin, HttpDriver):
|
||||
requests_per_period = 1
|
||||
period = 0.4
|
||||
|
||||
|
||||
DRIVERS = {}
|
||||
|
||||
|
||||
async def get_driver(vk_token=None):
|
||||
if vk_token:
|
||||
if vk_token in DRIVERS:
|
||||
return DRIVERS[vk_token]
|
||||
else:
|
||||
new_driver = RateLimitedDriver()
|
||||
DRIVERS[vk_token] = new_driver
|
||||
return new_driver
|
||||
def get_pages_switcher(markup, page, pages):
|
||||
if page != 0:
|
||||
leftbutton = types.InlineKeyboardButton('◀', callback_data='page{}'.format(page - 1)) # callback
|
||||
else:
|
||||
return RateLimitedDriver()
|
||||
leftbutton = types.InlineKeyboardButton('Поиск 🔍', callback_data='search')
|
||||
if page + 1 < len(pages):
|
||||
rightbutton = types.InlineKeyboardButton('▶', callback_data='page{}'.format(page + 1))
|
||||
else:
|
||||
rightbutton = None
|
||||
|
||||
if rightbutton:
|
||||
markup.row(leftbutton, rightbutton)
|
||||
else:
|
||||
markup.row(leftbutton)
|
||||
|
||||
|
||||
async def get_vk_chat(cid):
|
||||
return VkChat.objects.get_or_create(cid=cid)
|
||||
def replace_shields(text):
|
||||
text = text.replace('<', '<')
|
||||
text = text.replace('>', '>')
|
||||
text = text.replace('&', '&')
|
||||
text = text.replace('©', '©')
|
||||
text = text.replace('®', '®')
|
||||
text = text.replace('«', '«')
|
||||
text = text.replace('»', '«')
|
||||
text = text.replace('°', '°')
|
||||
text = text.replace('™', '™')
|
||||
text = text.replace('±', '±')
|
||||
return text
|
||||
|
||||
|
||||
def get_max_photo(obj, keyword='photo'):
|
||||
maxarr = []
|
||||
max_photo_re = re.compile(f'{keyword}_([0-9]*)')
|
||||
for k, v in obj.items():
|
||||
m = max_photo_re.match(k)
|
||||
if m:
|
||||
maxarr.append(int(m.group(1)))
|
||||
if maxarr:
|
||||
return keyword + '_' + str(max(maxarr))
|
||||
def request_user_dialogs(session, userid):
|
||||
order = []
|
||||
users_ids = []
|
||||
group_ids = []
|
||||
positive_group_ids = []
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
dialogs = api.messages.getDialogs(count=200)
|
||||
for chat in dialogs[1:]:
|
||||
if 'chat_id' in chat:
|
||||
chat['title'] = replace_shields(chat['title'])
|
||||
order.append({'title': chat['title'], 'id': 'group' + str(chat['chat_id'])})
|
||||
elif chat['uid'] > 0:
|
||||
order.append({'title': None, 'id': chat['uid']})
|
||||
users_ids.append(chat['uid'])
|
||||
elif chat['uid'] < 0:
|
||||
order.append({'title': None, 'id': chat['uid']})
|
||||
group_ids.append(chat['uid'])
|
||||
|
||||
for g in group_ids:
|
||||
positive_group_ids.append(str(g)[1:])
|
||||
|
||||
if users_ids:
|
||||
users = api.users.get(user_ids=users_ids, fields=['first_name', 'last_name', 'uid'])
|
||||
else:
|
||||
users = []
|
||||
|
||||
if positive_group_ids:
|
||||
groups = api.groups.getById(group_ids=positive_group_ids, fields=[])
|
||||
else:
|
||||
groups = []
|
||||
|
||||
for output in order:
|
||||
if output['title'] == ' ... ' or not output['title']:
|
||||
if output['id'] > 0:
|
||||
for x in users:
|
||||
if x['uid'] == output['id']:
|
||||
output['title'] = '{} {}'.format(x['first_name'], x['last_name'])
|
||||
break
|
||||
|
||||
async def get_content(url, docname='tgvkbot.document', chrome_headers=True, rewrite_name=False,
|
||||
custom_ext=''):
|
||||
try:
|
||||
with aiohttp.ClientSession(headers=CHROME_HEADERS if chrome_headers else {}) as session:
|
||||
r = await session.request('GET', url)
|
||||
direct_url = str(r.url)
|
||||
tempdir = tempfile.gettempdir()
|
||||
filename_options = {'out': docname} if rewrite_name else {'default': docname}
|
||||
if direct_url != url:
|
||||
r.release()
|
||||
c = await session.request('GET', direct_url)
|
||||
file = wget.detect_filename(direct_url, headers=dict(c.headers), **filename_options)
|
||||
temppath = os.path.join(tempdir, file + custom_ext)
|
||||
with open(temppath, 'wb') as f:
|
||||
f.write(await c.read())
|
||||
else:
|
||||
file = wget.detect_filename(direct_url, headers=dict(r.headers), **filename_options)
|
||||
temppath = os.path.join(tempdir, file + custom_ext)
|
||||
with open(temppath, 'wb') as f:
|
||||
f.write(await r.read())
|
||||
content = open(temppath, 'rb')
|
||||
return {'content': content, 'file_name': file, 'custom_ext': custom_ext, 'temp_path': tempdir}
|
||||
except Exception:
|
||||
return {'url': url, 'docname': docname}
|
||||
for f in groups:
|
||||
if str(f['gid']) == str(output['id'])[1:]:
|
||||
output['title'] = '{}'.format(f['name'])
|
||||
break
|
||||
for button in range(len(order)):
|
||||
order[button] = types.InlineKeyboardButton(order[button]['title'], callback_data=str(order[button]['id']))
|
||||
rows = [order[x:x + 2] for x in range(0, len(order), 2)]
|
||||
pages = [rows[x:x + 4] for x in range(0, len(rows), 4)]
|
||||
vk_dialogs[str(userid)] = pages
|
||||
|
||||
|
||||
bot = Bot(token=BOT_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.loop.set_task_factory(context.task_factory)
|
||||
import traceback
|
||||
|
||||
|
||||
@dp.errors_handler()
|
||||
async def all_errors_handler(dp, update, e):
|
||||
if 'message' in dir(update) and update.message:
|
||||
user = update.message.from_user.full_name
|
||||
user_id = update.message.from_user.id
|
||||
def create_markup(message, user, page, edit=False):
|
||||
markup = types.InlineKeyboardMarkup(row_width=2)
|
||||
for i in vk_dialogs[str(user)][page]:
|
||||
markup.row(*i)
|
||||
get_pages_switcher(markup, page, vk_dialogs[str(user)])
|
||||
if edit:
|
||||
bot.edit_message_text(
|
||||
'<b>Выберите Диалог:</b> <code>{}/{}</code> стр.'.format(page + 1, len(vk_dialogs[str(user)])),
|
||||
message.chat.id, message.message_id,
|
||||
parse_mode='HTML', reply_markup=markup).wait()
|
||||
else:
|
||||
user = update.callback_query.from_user.full_name
|
||||
user_id = update.callback_query.from_user.id
|
||||
bot.send_message(message.from_user.id,
|
||||
'<b>Выберите Диалог:</b> <code>{}/{}</code> стр.'.format(page + 1, len(vk_dialogs[str(user)])),
|
||||
parse_mode='HTML', reply_markup=markup).wait()
|
||||
|
||||
logging.exception(f'The update was: {ujson.dumps(update.to_python(), indent=4)}', exc_info=True)
|
||||
|
||||
def search_users(message, text):
|
||||
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
markup = types.InlineKeyboardMarkup(row_width=1)
|
||||
result = api.messages.searchDialogs(q=text, limit=10, fields=[])
|
||||
for chat in result:
|
||||
if chat['type'] == 'profile':
|
||||
markup.add(types.InlineKeyboardButton('{} {}'.format(chat['first_name'], chat['last_name']),
|
||||
callback_data=str(chat['uid'])))
|
||||
elif chat['type'] == 'chat':
|
||||
markup.add(
|
||||
types.InlineKeyboardButton(replace_shields(chat['title']),
|
||||
callback_data='group' + str(chat['chat_id'])))
|
||||
if markup.keyboard:
|
||||
markup.add(types.InlineKeyboardButton('Поиск 🔍', callback_data='search'))
|
||||
bot.send_message(message.from_user.id, '<b>Результат поиска по</b> <i>{}</i>'.format(text),
|
||||
reply_markup=markup, parse_mode='HTML')
|
||||
else:
|
||||
markup.add(types.InlineKeyboardButton('Поиск 🔍', callback_data='search'))
|
||||
bot.send_message(message.from_user.id, '<b>Ничего не найдено по запросу</b> <i>{}</i>'.format(text),
|
||||
parse_mode='HTML', reply_markup=markup)
|
||||
|
||||
|
||||
@bot.callback_query_handler(func=lambda call: True)
|
||||
def callback_buttons(call):
|
||||
if call.message:
|
||||
if 'page' in call.data:
|
||||
try:
|
||||
create_markup(call.message, call.from_user.id, int(call.data.split('page')[1]), True)
|
||||
except:
|
||||
session = VkMessage(vk_tokens.get(str(call.from_user.id))).session
|
||||
request_user_dialogs(session, call.from_user.id)
|
||||
create_markup(call.message, call.from_user.id, int(call.data.split('page')[1]), True)
|
||||
bot.answer_callback_query(call.id).wait()
|
||||
elif 'search' in call.data:
|
||||
markup = types.ForceReply(selective=False)
|
||||
bot.answer_callback_query(call.id, 'Поиск беседы 🔍').wait()
|
||||
bot.send_message(call.from_user.id, '<b>Поиск беседы</b> 🔍',
|
||||
parse_mode='HTML', reply_markup=markup).wait()
|
||||
elif 'group' in call.data:
|
||||
session = VkMessage(vk_tokens.get(str(call.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
chat = api.messages.getChat(chat_id=call.data.split('group')[1], fields=[])
|
||||
bot.answer_callback_query(call.id,
|
||||
'Вы в беседе {}'.format(replace_shields(chat['title']))).wait()
|
||||
bot.send_message(call.from_user.id,
|
||||
'<i>Вы в беседе {}</i>'.format(chat['title']),
|
||||
parse_mode='HTML').wait()
|
||||
currentchat[str(call.from_user.id)] = {'title': chat['title'], 'id': 'group' + str(chat['chat_id'])}
|
||||
elif call.data.lstrip('-').isdigit():
|
||||
session = VkMessage(vk_tokens.get(str(call.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
if '-' in call.data:
|
||||
user = api.groups.getById(group_id=call.data.lstrip('-'), fields=[])[0]
|
||||
user = {'first_name': user['name'], 'last_name': ''}
|
||||
else:
|
||||
user = api.users.get(user_ids=call.data, fields=[])[0]
|
||||
bot.answer_callback_query(call.id,
|
||||
'Вы в чате с {} {}'.format(user['first_name'], user['last_name'])).wait()
|
||||
bot.send_message(call.from_user.id,
|
||||
'<i>Вы в чате с {} {}</i>'.format(user['first_name'], user['last_name']),
|
||||
parse_mode='HTML').wait()
|
||||
currentchat[str(call.from_user.id)] = {'title': user['first_name'] + ' ' + user['last_name'],
|
||||
'id': call.data}
|
||||
|
||||
|
||||
def create_thread(uid, vk_token):
|
||||
a = VkPolling()
|
||||
longpoller = VkMessage(vk_token)
|
||||
t = threading.Thread(name='vk' + str(uid), target=a.run, args=(longpoller, bot, uid,))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
vk_threads[str(uid)] = a
|
||||
vk_tokens.set(str(uid), vk_token)
|
||||
session = longpoller.session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
api.account.setOffline()
|
||||
|
||||
|
||||
def check_thread(uid):
|
||||
for th in threading.enumerate():
|
||||
if th.getName() == 'vk' + str(uid):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# Creating VkPolling threads and dialogs info after bot's reboot/exception using existing tokens
|
||||
def thread_reviver(uid):
|
||||
tries = 0
|
||||
while check_thread(uid.decode("utf-8")):
|
||||
if tries < 4:
|
||||
try:
|
||||
create_thread(uid.decode("utf-8"), vk_tokens.get(uid))
|
||||
except:
|
||||
time.sleep(10)
|
||||
tries = tries + 1
|
||||
else:
|
||||
mark = types.InlineKeyboardMarkup()
|
||||
login = types.InlineKeyboardButton('ВХОД', url=link)
|
||||
mark.add(login)
|
||||
bot.send_message(uid.decode("utf-8"), '<b>Непредвиденная ошибка, требуется повторный логин ВК!</b>',
|
||||
parse_mode='HTML', reply_markup=mark).wait()
|
||||
break
|
||||
|
||||
|
||||
def thread_supervisor():
|
||||
while True:
|
||||
for uid in vk_tokens.scan_iter():
|
||||
reviver_thread = threading.Thread(name='reviver' + str(uid.decode('utf-8')), target=thread_reviver,
|
||||
args=(uid,))
|
||||
reviver_thread.setDaemon(True)
|
||||
reviver_thread.start()
|
||||
time.sleep(60)
|
||||
|
||||
|
||||
supervisor = threading.Thread(name='supervisor', target=thread_supervisor)
|
||||
supervisor.setDaemon(True)
|
||||
supervisor.start()
|
||||
|
||||
|
||||
def stop_thread(message):
|
||||
for th in threading.enumerate():
|
||||
if th.getName() == 'vk' + str(message.from_user.id):
|
||||
t = vk_threads[str(message.from_user.id)]
|
||||
t.terminate()
|
||||
th.join()
|
||||
vk_tokens.delete(str(message.from_user.id))
|
||||
vk_dialogs.pop(str(message.from_user.id), None)
|
||||
currentchat.pop(str(message.from_user.id), None)
|
||||
|
||||
|
||||
def extract_unique_code(text):
|
||||
# Extracts the unique_code from the sent /start command.
|
||||
try:
|
||||
return text[45:].split('&')[0]
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def verifycode(code):
|
||||
session = vk.Session(access_token=code)
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
return dict(api.account.getProfileInfo(fields=[]))
|
||||
|
||||
|
||||
def info_extractor(info):
|
||||
info = info[-1].url[8:-1].split('.')
|
||||
return info
|
||||
|
||||
|
||||
@bot.message_handler(commands=['chat'])
|
||||
def chat_command(message):
|
||||
if logged(message):
|
||||
if str(message.from_user.id) in currentchat:
|
||||
if 'group' in currentchat[str(message.from_user.id)]['id']:
|
||||
chat = currentchat[str(message.from_user.id)]
|
||||
bot.send_message(message.from_user.id,
|
||||
'<i>Вы в беседе {}</i>'.format(chat['title']),
|
||||
parse_mode='HTML').wait()
|
||||
else:
|
||||
chat = currentchat[str(message.from_user.id)]
|
||||
bot.send_message(message.from_user.id,
|
||||
'<i>Вы в чате с {}</i>'.format(chat['title']),
|
||||
parse_mode='HTML').wait()
|
||||
else:
|
||||
bot.send_message(message.from_user.id,
|
||||
'<i>Вы не находитесь в чате</i>',
|
||||
parse_mode='HTML').wait()
|
||||
|
||||
|
||||
@bot.message_handler(commands=['leave'])
|
||||
def leave_command(message):
|
||||
if logged(message):
|
||||
if str(message.from_user.id) in currentchat:
|
||||
currentchat.pop(str(message.from_user.id), None)
|
||||
bot.send_message(message.from_user.id,
|
||||
'<i>Вы вышли из чата</i>',
|
||||
parse_mode='HTML').wait()
|
||||
else:
|
||||
bot.send_message(message.from_user.id,
|
||||
'<i>Вы не находитесь в чате</i>',
|
||||
parse_mode='HTML').wait()
|
||||
|
||||
|
||||
@bot.message_handler(commands=['dialogs'])
|
||||
def dialogs_command(message):
|
||||
if logged(message):
|
||||
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
|
||||
request_user_dialogs(session, message.from_user.id)
|
||||
create_markup(message, message.from_user.id, 0)
|
||||
|
||||
|
||||
@bot.message_handler(commands=['search'])
|
||||
def search_command(message):
|
||||
if logged(message):
|
||||
markup = types.ForceReply(selective=False)
|
||||
if telebot.util.extract_arguments(message.text):
|
||||
search_users(message, telebot.util.extract_arguments(message.text))
|
||||
else:
|
||||
bot.send_message(message.from_user.id, '<b>Поиск беседы</b> 🔍',
|
||||
parse_mode='HTML', reply_markup=markup).wait()
|
||||
|
||||
|
||||
@bot.message_handler(commands=['stop'])
|
||||
def stop_command(message):
|
||||
if not check_thread(message.from_user.id):
|
||||
stop_thread(message)
|
||||
bot.send_message(message.from_user.id, 'Успешный выход!').wait()
|
||||
else:
|
||||
bot.send_message(message.from_user.id, 'Вход не был выполнен!').wait()
|
||||
|
||||
|
||||
@bot.message_handler(commands=['start'])
|
||||
def start_command(message):
|
||||
if check_thread(message.from_user.id):
|
||||
mark = types.InlineKeyboardMarkup()
|
||||
login = types.InlineKeyboardButton('ВХОД', url=link)
|
||||
mark.add(login)
|
||||
bot.send_message(message.from_user.id,
|
||||
'Привет, этот бот поможет тебе общаться ВКонтакте, войди по кнопке ниже'
|
||||
' и отправь мне то, что получишь в адресной строке.',
|
||||
reply_markup=mark).wait()
|
||||
else:
|
||||
bot.send_message(message.from_user.id, 'Вход уже выполнен!\n/stop для выхода.').wait()
|
||||
|
||||
|
||||
def form_request(message, method, info):
|
||||
if int(info[2]):
|
||||
if message.text and message.text.startswith('!'):
|
||||
if len(message.text) - 1:
|
||||
message.text = message.text[1:]
|
||||
if info[2] != 'None':
|
||||
method(message, info[1], group=True, forward_messages=info[2])
|
||||
else:
|
||||
method(message, info[1], group=True)
|
||||
elif message.caption and message.caption.startswith('!'):
|
||||
if len(message.caption) - 1:
|
||||
message.caption = message.caption[1:]
|
||||
if info[2] != 'None':
|
||||
method(message, info[1], group=True, forward_messages=info[2])
|
||||
else:
|
||||
method(message, info[1], group=True)
|
||||
else:
|
||||
if message.text and message.text.startswith('!'):
|
||||
if len(message.text) - 1:
|
||||
message.text = message.text[1:]
|
||||
if info[1] != 'None':
|
||||
method(message, info[0], group=False, forward_messages=info[1])
|
||||
else:
|
||||
method(message, info[0], group=False)
|
||||
elif message.caption and message.caption.startswith('!'):
|
||||
if len(message.caption) - 1:
|
||||
message.caption = message.caption[1:]
|
||||
if info[1] != 'None':
|
||||
method(message, info[0], group=False, forward_messages=info[1])
|
||||
else:
|
||||
method(message, info[0], group=False)
|
||||
else:
|
||||
method(message, info[0], group=False)
|
||||
|
||||
|
||||
def logged(message):
|
||||
if vk_tokens.get(str(message.from_user.id)):
|
||||
return True
|
||||
else:
|
||||
bot.send_message(message.from_user.id, 'Вход не выполнен! /start для входа').wait()
|
||||
return False
|
||||
|
||||
|
||||
def vk_sender(message, method):
|
||||
if logged(message):
|
||||
if message.reply_to_message:
|
||||
info = info_extractor(message.reply_to_message.entities)
|
||||
if info is not None:
|
||||
form_request(message, method, info)
|
||||
|
||||
elif str(message.from_user.id) in currentchat:
|
||||
info = []
|
||||
if 'group' in currentchat[str(message.from_user.id)]['id']:
|
||||
info.append('0')
|
||||
info.append(currentchat[str(message.from_user.id)]['id'].split('group')[1])
|
||||
info.append('1')
|
||||
else:
|
||||
info.append(currentchat[str(message.from_user.id)]['id'])
|
||||
info.append('0')
|
||||
info.append('0')
|
||||
form_request(message, method, info)
|
||||
|
||||
|
||||
def audio_title_creator(message, performer=None, title=None):
|
||||
if not performer and not title:
|
||||
return 'Аудио_{}'.format(str(message.date)[5:])
|
||||
else:
|
||||
return '{} - {}'.format(performer, title)
|
||||
|
||||
|
||||
def send_text(message, userid, group, forward_messages=None):
|
||||
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
if group:
|
||||
api.messages.send(chat_id=userid, message=message.text, forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(user_id=userid, message=message.text, forward_messages=forward_messages)
|
||||
|
||||
|
||||
def send_doc(message, userid, group, forward_messages=None):
|
||||
filetype = message.content_type
|
||||
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
if filetype == 'document' and 'video' not in message.document.mime_type:
|
||||
file = wget.download(
|
||||
FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path))
|
||||
openedfile = open(file, 'rb')
|
||||
files = {'file': openedfile}
|
||||
fileonserver = ujson.loads(requests.post(api.docs.getUploadServer()['upload_url'],
|
||||
files=files).text)
|
||||
attachment = api.docs.save(file=fileonserver['file'],
|
||||
title=getattr(message, filetype).file_name,
|
||||
tags='')
|
||||
openedfile.close()
|
||||
os.remove(file)
|
||||
|
||||
elif filetype == 'voice':
|
||||
file = wget.download(
|
||||
FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path))
|
||||
openedfile = open(file, 'rb')
|
||||
files = {'file': openedfile}
|
||||
fileonserver = ujson.loads(
|
||||
requests.post(api.docs.getUploadServer(type='audio_message')['upload_url'],
|
||||
files=files).text)
|
||||
attachment = api.docs.save(file=fileonserver['file'], title='Аудиосообщение',
|
||||
tags='')
|
||||
openedfile.close()
|
||||
os.remove(file)
|
||||
|
||||
elif filetype == 'document' and 'video' in message.document.mime_type:
|
||||
vk_sender(message, send_video)
|
||||
return
|
||||
|
||||
else: # filetype == 'audio':
|
||||
file = wget.download(
|
||||
FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path))
|
||||
newfile = file.split('.')[0] + '.aac'
|
||||
os.rename(file, newfile)
|
||||
openedfile = open(newfile, 'rb')
|
||||
files = {'file': openedfile}
|
||||
fileonserver = ujson.loads(requests.post(api.docs.getUploadServer()['upload_url'],
|
||||
files=files).text)
|
||||
attachment = api.docs.save(file=fileonserver['file'],
|
||||
title=audio_title_creator(message, message.audio.performer,
|
||||
message.audio.title), tags='')
|
||||
openedfile.close()
|
||||
os.remove(newfile)
|
||||
|
||||
if group:
|
||||
if message.caption:
|
||||
|
||||
api.messages.send(chat_id=userid, message=message.caption,
|
||||
attachment='doc{}_{}'.format(attachment[0]['owner_id'],
|
||||
attachment[0]['did']),
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(chat_id=userid,
|
||||
attachment='doc{}_{}'.format(attachment[0]['owner_id'],
|
||||
attachment[0]['did']),
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
if message.caption:
|
||||
api.messages.send(user_id=userid, message=message.caption,
|
||||
attachment='doc{}_{}'.format(attachment[0]['owner_id'],
|
||||
attachment[0]['did']),
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(user_id=userid,
|
||||
attachment='doc{}_{}'.format(attachment[0]['owner_id'],
|
||||
attachment[0]['did']),
|
||||
forward_messages=forward_messages)
|
||||
|
||||
|
||||
def send_photo(message, userid, group, forward_messages=None):
|
||||
filetype = message.content_type
|
||||
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
file = wget.download(
|
||||
FILE_URL.format(token, bot.get_file(getattr(message, filetype)[-1].file_id).wait().file_path))
|
||||
openedfile = open(file, 'rb')
|
||||
files = {'file': openedfile}
|
||||
fileonserver = ujson.loads(requests.post(api.photos.getMessagesUploadServer()['upload_url'],
|
||||
files=files).text)
|
||||
attachment = api.photos.saveMessagesPhoto(server=fileonserver['server'], photo=fileonserver['photo'],
|
||||
hash=fileonserver['hash'])
|
||||
if group:
|
||||
if message.caption:
|
||||
api.messages.send(chat_id=userid, message=message.caption, attachment=attachment[0]['id'],
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(chat_id=userid, attachment=attachment[0]['id'],
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
if message.caption:
|
||||
api.messages.send(user_id=userid, message=message.caption, attachment=attachment[0]['id'],
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(user_id=userid, attachment=attachment[0]['id'],
|
||||
forward_messages=forward_messages)
|
||||
openedfile.close()
|
||||
os.remove(file)
|
||||
|
||||
|
||||
def send_sticker(message, userid, group, forward_messages=None):
|
||||
filetype = message.content_type
|
||||
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
file = wget.download(
|
||||
FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path))
|
||||
Image.open(file).save("{}.png".format(file))
|
||||
openedfile = open('{}.png'.format(file), 'rb')
|
||||
files = {'file': openedfile}
|
||||
fileonserver = ujson.loads(requests.post(api.photos.getMessagesUploadServer()['upload_url'],
|
||||
files=files).text)
|
||||
attachment = api.photos.saveMessagesPhoto(server=fileonserver['server'], photo=fileonserver['photo'],
|
||||
hash=fileonserver['hash'])
|
||||
if group:
|
||||
if message.caption:
|
||||
api.messages.send(chat_id=userid, message=message.caption, attachment=attachment[0]['id'],
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(chat_id=userid, attachment=attachment[0]['id'],
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
if message.caption:
|
||||
api.messages.send(user_id=userid, message=message.caption, attachment=attachment[0]['id'],
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(user_id=userid, attachment=attachment[0]['id'],
|
||||
forward_messages=forward_messages)
|
||||
openedfile.close()
|
||||
os.remove('{}.png'.format(file))
|
||||
os.remove(file)
|
||||
|
||||
|
||||
def send_video(message, userid, group, forward_messages=None):
|
||||
filetype = message.content_type
|
||||
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
file = wget.download(
|
||||
FILE_URL.format(token, bot.get_file(getattr(message, filetype).file_id).wait().file_path))
|
||||
openedfile = open(file, 'rb')
|
||||
files = {'video_file': openedfile}
|
||||
|
||||
if group:
|
||||
attachment = api.video.save(is_private=1)
|
||||
fileonserver = ujson.loads(requests.post(attachment['upload_url'],
|
||||
files=files).text)
|
||||
video = 'video{}_{}'.format(attachment['owner_id'], attachment['owner_id']['video_id'])
|
||||
if message.caption:
|
||||
api.messages.send(chat_id=userid, message=message.caption, attachment=video,
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(chat_id=userid, attachment=video, forward_messages=forward_messages)
|
||||
else:
|
||||
attachment = api.video.save(is_private=1)
|
||||
fileonserver = ujson.loads(requests.post(attachment['upload_url'],
|
||||
files=files).text)
|
||||
video = 'video{}_{}'.format(attachment['owner_id'], attachment['vid'])
|
||||
if message.caption:
|
||||
api.messages.send(user_id=userid, message=message.caption, attachment=video,
|
||||
forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(user_id=userid, attachment=video, forward_messages=forward_messages)
|
||||
openedfile.close()
|
||||
os.remove(file)
|
||||
|
||||
|
||||
def send_contact(message, userid, group, forward_messages=None):
|
||||
session = VkMessage(vk_tokens.get(str(message.from_user.id))).session
|
||||
api = vk.API(session, v=VK_API_VERSION)
|
||||
if message.contact.last_name:
|
||||
text = 'Контакт: {} {}'.format(message.contact.first_name, message.contact.last_name)
|
||||
else:
|
||||
text = 'Контакт: {}'.format(message.contact.first_name)
|
||||
if group:
|
||||
api.messages.send(chat_id=userid, message=text, forward_messages=forward_messages)
|
||||
api.messages.send(chat_id=userid, message=message.contact, forward_messages=forward_messages)
|
||||
else:
|
||||
api.messages.send(user_id=userid, message=text, forward_messages=forward_messages)
|
||||
api.messages.send(chat_id=userid, message=message.contact, forward_messages=forward_messages)
|
||||
|
||||
|
||||
@bot.message_handler(content_types=['document', 'voice', 'audio'])
|
||||
def reply_document(message):
|
||||
try:
|
||||
vk_sender(message, send_doc)
|
||||
except:
|
||||
bot.reply_to(message, 'Файл слишком большой, максимально допустимый размер *20мб*!',
|
||||
parse_mode='Markdown').wait()
|
||||
|
||||
|
||||
@bot.message_handler(content_types=['sticker'])
|
||||
def reply_sticker(message):
|
||||
try:
|
||||
vk_sender(message, send_sticker)
|
||||
except Exception:
|
||||
bot.reply_to(message, '*Произошла неизвестная ошибка при отправке*',
|
||||
parse_mode='Markdown').wait() # TODO?: Bugreport system
|
||||
print('Error: {}'.format(traceback.format_exc()))
|
||||
|
||||
|
||||
@bot.message_handler(content_types=['photo'])
|
||||
def reply_photo(message):
|
||||
try:
|
||||
vk_sender(message, send_photo)
|
||||
except:
|
||||
bot.send_message(message.from_user.id, 'Фото слишком большое, максимально допустимый размер *20мб*!',
|
||||
parse_mode='Markdown').wait()
|
||||
|
||||
|
||||
@bot.message_handler(content_types=['video', 'video_note'])
|
||||
def reply_video(message):
|
||||
try:
|
||||
vk_sender(message, send_video)
|
||||
except:
|
||||
bot.reply_to(message, 'Файл слишком большой, максимально допустимый размер *20мб*!',
|
||||
parse_mode='Markdown').wait()
|
||||
|
||||
|
||||
@bot.message_handler(content_types=['contact'])
|
||||
def reply_contact(message):
|
||||
try:
|
||||
vk_sender(message, send_contact)
|
||||
except Exception:
|
||||
bot.reply_to(message, '*Произошла неизвестная ошибка при отправке*',
|
||||
parse_mode='Markdown').wait()
|
||||
print('Error: {}'.format(traceback.format_exc()))
|
||||
|
||||
|
||||
@bot.message_handler(content_types=['text'])
|
||||
def reply_text(message):
|
||||
m = re.search('https://oauth\.vk\.com/blank\.html#access_token=[a-z0-9]*&expires_in=[0-9]*&user_id=[0-9]*',
|
||||
message.text)
|
||||
if m:
|
||||
code = extract_unique_code(m.group(0))
|
||||
if check_thread(message.from_user.id):
|
||||
try:
|
||||
user = verifycode(code)
|
||||
create_thread(message.from_user.id, code)
|
||||
bot.send_message(message.from_user.id,
|
||||
'Вход выполнен в аккаунт {} {}!'.format(user['first_name'], user['last_name'])).wait()
|
||||
|
||||
bot.send_message(message.from_user.id, '[Использование](https://asergey.me/tgvkbot/usage/)',
|
||||
parse_mode='Markdown').wait()
|
||||
except:
|
||||
bot.send_message(message.from_user.id, 'Неверная ссылка, попробуй ещё раз!').wait()
|
||||
else:
|
||||
bot.send_message(message.from_user.id, 'Вход уже выполнен!\n/stop для выхода.').wait()
|
||||
|
||||
elif message.reply_to_message and message.reply_to_message.text == 'Поиск беседы 🔍':
|
||||
search_users(message, message.text)
|
||||
|
||||
else:
|
||||
try:
|
||||
vk_sender(message, send_text)
|
||||
except Exception:
|
||||
bot.reply_to(message, 'Произошла неизвестная ошибка при отправке',
|
||||
parse_mode='Markdown').wait()
|
||||
|
||||
|
||||
bot.polling(none_stop=True)
|
||||
"""class WebhookServer(object):
|
||||
# index равнозначно /, т.к. отсутствию части после ip-адреса (грубо говоря)
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
length = int(cherrypy.request.headers['content-length'])
|
||||
json_string = cherrypy.request.body.read(length).decode("utf-8")
|
||||
update = telebot.types.Update.de_json(json_string)
|
||||
bot.process_new_updates([update])
|
||||
return ''
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(format='%(levelname)-8s [%(asctime)s] %(message)s', level=logging.WARNING, filename='vk.log')
|
||||
bot.remove_webhook()
|
||||
bot.set_webhook('https://{}/{}/'.format(bot_url, token))
|
||||
cherrypy.config.update(
|
||||
{'server.socket_host': '127.0.0.1', 'server.socket_port': local_port, 'engine.autoreload.on': False,
|
||||
'log.screen': False})
|
||||
cherrypy.quickstart(WebhookServer(), '/', {'/': {}})"""
|
||||
|
42
config.py
42
config.py
@ -1,42 +0,0 @@
|
||||
import os
|
||||
|
||||
ALLOWED_USER_IDS = os.environ.get('ALLOWED_USER_IDS', '')
|
||||
|
||||
DATABASE_USER = os.environ.get('POSTGRES_USER', 'postgres')
|
||||
DATABASE_PASSWORD = os.environ.get('POSTGRES_PASSWORD', 'postgres')
|
||||
DATABASE_HOST = os.environ.get('DATABASE_HOST', 'db')
|
||||
DATABASE_PORT = os.environ.get('DATABASE_PORT', '5432')
|
||||
DATABASE_NAME = os.environ.get('POSTGRES_DB', 'tgvkbot')
|
||||
|
||||
DATABASE_URL = os.environ.get('DATABASE_URL', '')
|
||||
|
||||
VK_APP_ID = os.environ.get('VK_APP_ID', '2685278') # Kate mobile
|
||||
|
||||
AUDIO_URL = os.environ.get('AUDIO_URL', '')
|
||||
AUDIO_ACCESS_URL = os.environ.get('AUDIO_ACCESS_URL',
|
||||
'')
|
||||
TOKEN_REFRESH_URL = os.environ.get('TOKEN_REFRESH_URL', '')
|
||||
AUDIO_SEARCH_URL = os.environ.get('AUDIO_SEARCH_URL', '')
|
||||
AUDIO_PROXY_URL = os.environ.get('AUDIO_PROXY_URL', '')
|
||||
AUDIO_HEADERS = {
|
||||
'user-agent': 'KateMobileAndroid/52.1 lite-445 (Android 4.4.2; SDK 19; x86; unknown Android SDK built for x86; en)'}
|
||||
|
||||
CHROME_HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'}
|
||||
BOT_TOKEN = os.environ.get('BOT_TOKEN')
|
||||
|
||||
SETTINGS_VAR = os.environ.get('SETTINGS_VAR', 'DJANGO_TGVKBOT_SETTINGS_MODULE')
|
||||
|
||||
MAX_FILE_SIZE = os.environ.get('MAX_FILE_SIZE', 52428800)
|
||||
|
||||
API_VERSION = os.environ.get('API_VERSION', '5.124')
|
||||
AUDIO_API_VERSION = os.environ.get('API_VERSION', '5.78')
|
||||
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY', '!jh4wm=%s%l&jv7-lru6hg)mq2pk&rd@i*s0*c!v!zv01cf9iw')
|
||||
|
||||
SENTRY_URL = os.environ.get('SENTRY_URL', None)
|
||||
|
||||
if SENTRY_URL:
|
||||
import sentry_sdk
|
||||
|
||||
sentry_sdk.init(SENTRY_URL)
|
3
credentials.py
Normal file
3
credentials.py
Normal file
@ -0,0 +1,3 @@
|
||||
import os
|
||||
token = os.environ['TELEGRAM_TOKEN']
|
||||
vk_app_id = os.environ['VK_APP']
|
105
data/models.py
105
data/models.py
@ -1,105 +0,0 @@
|
||||
import asyncio
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
|
||||
class AsyncManager(models.Manager):
|
||||
""" A model manager which uses the AsyncQuerySet. """
|
||||
|
||||
async def get_query_set(self):
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, AsyncQuerySet(self.model, using=self._db))
|
||||
|
||||
|
||||
class AsyncQuerySet(QuerySet):
|
||||
""" A queryset which allows DB operations to be pre-triggered so that they run in the
|
||||
background while the application can continue doing other processing.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AsyncQuerySet, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class TgUser(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
# id пользователя на сервере Telegram
|
||||
uid = models.BigIntegerField(unique=True)
|
||||
|
||||
# имя
|
||||
first_name = models.CharField(max_length=256)
|
||||
|
||||
# фамилия
|
||||
last_name = models.CharField(
|
||||
max_length=256,
|
||||
null=True,
|
||||
default=None,
|
||||
)
|
||||
|
||||
# username
|
||||
username = models.CharField(
|
||||
max_length=256,
|
||||
null=True,
|
||||
default=None,
|
||||
)
|
||||
|
||||
BLOCKED = -1
|
||||
BASE = 0
|
||||
|
||||
STATUSES = (
|
||||
(BLOCKED, 'Заблокирован'),
|
||||
(BASE, 'Базовый'),
|
||||
)
|
||||
|
||||
status = models.IntegerField(
|
||||
choices=STATUSES,
|
||||
default=BASE
|
||||
)
|
||||
|
||||
|
||||
class VkUser(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
token = models.TextField(unique=True)
|
||||
is_polling = models.BooleanField(default=False)
|
||||
owner = models.ForeignKey(TgUser, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class VkChat(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
cid = models.BigIntegerField(unique=True)
|
||||
|
||||
|
||||
class TgChat(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
cid = models.BigIntegerField(unique=True)
|
||||
|
||||
|
||||
class Forward(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
owner = models.ForeignKey(TgUser, on_delete=models.CASCADE)
|
||||
tgchat = models.ForeignKey(TgChat, on_delete=models.CASCADE)
|
||||
vkchat = models.ForeignKey(VkChat, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
vk_chat = models.BigIntegerField()
|
||||
vk_id = models.BigIntegerField(null=True)
|
||||
tg_chat = models.BigIntegerField()
|
||||
tg_id = models.BigIntegerField()
|
||||
|
||||
|
||||
class MessageMarkup(models.Model):
|
||||
objects = AsyncManager()
|
||||
|
||||
message_id = models.BigIntegerField()
|
||||
|
||||
chat_id = models.BigIntegerField()
|
||||
|
||||
buttons = models.TextField(null=True, blank=True)
|
@ -1,32 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
|
||||
services:
|
||||
bot_local:
|
||||
build: .
|
||||
volumes:
|
||||
- .:/src
|
||||
container_name: tgvkbot_local
|
||||
logging:
|
||||
options:
|
||||
max-size: "10M"
|
||||
max-file: "10"
|
||||
restart: always
|
||||
env_file:
|
||||
- env_file
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: 'true'
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
image: postgres:9-alpine
|
||||
container_name: tgvkbot_db
|
||||
volumes:
|
||||
- "dbdata:/var/lib/postgresql/data"
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: 'tgvkbot'
|
||||
POSTGRES_PASSWORD: 'postgres'
|
||||
|
||||
volumes:
|
||||
dbdata:
|
@ -1,30 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
|
||||
services:
|
||||
bot:
|
||||
image: kylmakalle/tgvkbot:latest
|
||||
container_name: tgvkbot
|
||||
logging:
|
||||
options:
|
||||
max-size: "10M"
|
||||
max-file: "10"
|
||||
restart: always
|
||||
env_file:
|
||||
- env_file
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: 'true'
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
image: postgres:9-alpine
|
||||
container_name: tgvkbot_db
|
||||
volumes:
|
||||
- "dbdata:/var/lib/postgresql/data"
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: 'tgvkbot'
|
||||
POSTGRES_PASSWORD: 'postgres'
|
||||
|
||||
volumes:
|
||||
dbdata:
|
59
install.sh
59
install.sh
@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
ASKED_FOR_SUDO=""
|
||||
|
||||
if [ ! $(which docker) ]; then
|
||||
echo "🔑 Пароль sudo потребуется для установки Docker"
|
||||
sudo true
|
||||
ASKED_FOR_SUDO="1"
|
||||
|
||||
echo "🔨 Устанавливаем docker..."
|
||||
curl -fsSL https://get.docker.com/ | sh
|
||||
user="$(id -un 2>/dev/null || true)"
|
||||
sudo groupadd docker
|
||||
sudo usermod -aG docker $user
|
||||
else
|
||||
echo "👌 Docker уже установлен"
|
||||
fi
|
||||
|
||||
if [ ! $(which docker-compose) ]; then
|
||||
if [ ! $ASKED_FOR_SUDO ]; then
|
||||
echo "🔑 Пароль sudo потребуется для установки docker-compose"
|
||||
sudo true
|
||||
ASKED_FOR_SUDO="1"
|
||||
fi
|
||||
echo "🔨 Устанавливаем docker-compose..."
|
||||
# Install docker-compose
|
||||
COMPOSE_VERSION=$(git ls-remote https://github.com/docker/compose | grep refs/tags | grep -oE "[0-9]+\.[0-9][0-9]+\.[0-9]+$" | sort --version-sort | tail -n 1)
|
||||
sudo sh -c "curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose"
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
sudo sh -c "curl -L https://raw.githubusercontent.com/docker/compose/${COMPOSE_VERSION}/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose"
|
||||
else
|
||||
echo "👌 Docker-compose уже установлен"
|
||||
fi
|
||||
|
||||
# Нужно убедиться, что бот встанет и переменные окружения настроятся
|
||||
set -e
|
||||
|
||||
echo "⚙️ Настраиваем переменные окружения..."
|
||||
python3 setenv.py
|
||||
|
||||
if [ ! "$(docker ps -a | grep watchtower)" ]; then
|
||||
echo "🔄 Поднимаем систему обновлений watchtower..."
|
||||
docker run -d \
|
||||
--name watchtower \
|
||||
--restart always \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-e TZ='Europe/Moscow' \
|
||||
-e WATCHTOWER_CLEANUP='true' \
|
||||
-e WATCHTOWER_INCLUDE_STOPPED='true' \
|
||||
-e WATCHTOWER_MONITOR_ONLY='false' \
|
||||
-e WATCHTOWER_LABEL_ENABLE='true' \
|
||||
containrrr/watchtower:latest
|
||||
else
|
||||
echo "👌 Апдейтер watchtower уже запущен"
|
||||
fi
|
||||
|
||||
echo "🚀 Запускаем бота..."
|
||||
docker-compose up -d
|
||||
|
||||
echo "✅ Готово"
|
24
manage.py
24
manage.py
@ -1,24 +0,0 @@
|
||||
import sys
|
||||
from config import *
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault(SETTINGS_VAR, "settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError:
|
||||
# The above import may fail for some other reason. Ensure that the
|
||||
# issue is really that Django is missing to avoid masking other
|
||||
# exceptions on Python 2.
|
||||
try:
|
||||
import django
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
)
|
||||
raise
|
||||
import django.conf
|
||||
|
||||
django.conf.ENVIRONMENT_VARIABLE = SETTINGS_VAR
|
||||
execute_from_command_line(sys.argv)
|
@ -1,16 +1,6 @@
|
||||
aiogram==1.2.2
|
||||
aiohttp==2.3.10
|
||||
aiovk==1.3.0
|
||||
async-timeout==2.0.1
|
||||
attrs==17.4.0
|
||||
Django==2.0.4
|
||||
multidict==4.1.0
|
||||
Pillow==5.1.0
|
||||
psycopg2-binary==2.7.4
|
||||
pytz==2018.3
|
||||
sentry-sdk==0.20.3
|
||||
ujson==1.35
|
||||
urllib3==1.25.3
|
||||
wget==3.2
|
||||
yarl==1.1.1
|
||||
dj-database-url==0.5.0
|
||||
pyTelegramBotAPI
|
||||
vk
|
||||
redis
|
||||
Pillow
|
||||
ujson
|
||||
wget
|
||||
|
1
runtime.txt
Normal file
1
runtime.txt
Normal file
@ -0,0 +1 @@
|
||||
python-3.6.1
|
77
setenv.py
77
setenv.py
@ -1,77 +0,0 @@
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import urlopen, Request
|
||||
|
||||
from config import API_VERSION, VK_APP_ID
|
||||
|
||||
ENV_FILE_TEMPLATE = """
|
||||
POSTGRES_DB=tgvkbot
|
||||
POSTGRES_PASSWORD=postgres
|
||||
BOT_TOKEN=%(tg_token)s
|
||||
VK_APP_ID=%(vk_app_id)s
|
||||
ALLOWED_USER_IDS=%(allowed_user_ids)s
|
||||
"""
|
||||
|
||||
ENV_FILE = 'env_file'
|
||||
|
||||
def check_token(token):
|
||||
response = urlopen("https://api.telegram.org/bot{token}/{method}".format(token=token, method='getMe'))
|
||||
if response.code == 200:
|
||||
return True
|
||||
else:
|
||||
raise HTTPError
|
||||
|
||||
|
||||
def get_auth_page(app_id):
|
||||
AUTH_URL = 'https://oauth.vk.com/authorize'
|
||||
params = {'client_id': app_id,
|
||||
'redirect_uri': 'https://oauth.vk.com/blank.html',
|
||||
'display': 'mobile',
|
||||
'response_type': 'token',
|
||||
'v': API_VERSION
|
||||
}
|
||||
post_args = urlencode(params).encode('UTF-8')
|
||||
request = Request(AUTH_URL, post_args)
|
||||
response = urlopen(request)
|
||||
if response.code == 200:
|
||||
return True
|
||||
else:
|
||||
raise HTTPError
|
||||
|
||||
|
||||
def set_env():
|
||||
while True:
|
||||
tg_token = input('Токен Telegram бота: ')
|
||||
tg_token = tg_token.strip()
|
||||
try:
|
||||
print('⏳ Проверяем токен...')
|
||||
check_token(tg_token)
|
||||
break
|
||||
except HTTPError:
|
||||
print('❌ Токен бота неверный или нерабочий, попробуйте снова!')
|
||||
|
||||
while True:
|
||||
vk_app_id = input('VK APP ID (можно оставить пустым): ')
|
||||
vk_app_id = vk_app_id.strip()
|
||||
if vk_app_id:
|
||||
try:
|
||||
get_auth_page(vk_app_id)
|
||||
break
|
||||
except HTTPError:
|
||||
print('❌ VK APP ID неверный, попробуйте снова!')
|
||||
else:
|
||||
print('ℹ️ Будет использован VK APP ID {} от Kate Mobile'.format(VK_APP_ID))
|
||||
break
|
||||
|
||||
with open(ENV_FILE, 'w') as env_file:
|
||||
env_file.write(
|
||||
ENV_FILE_TEMPLATE % {'tg_token': tg_token, 'vk_app_id': vk_app_id or VK_APP_ID, 'allowed_user_ids': ''})
|
||||
|
||||
print('✅ Переменные успешно установлены в {}'.format(ENV_FILE))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
set_env()
|
||||
except KeyboardInterrupt:
|
||||
print('\n⚠️ Настройка переменных окружуения была прервана!')
|
24
settings.py
24
settings.py
@ -1,24 +0,0 @@
|
||||
from config import *
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
DATABASES = {}
|
||||
|
||||
if DATABASE_URL:
|
||||
import dj_database_url
|
||||
# Reads string from DATABASE_URL env by default
|
||||
DATABASES['default'] = dj_database_url.config()
|
||||
else:
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': DATABASE_NAME,
|
||||
'USER': DATABASE_USER,
|
||||
'PASSWORD': DATABASE_PASSWORD,
|
||||
'HOST': DATABASE_HOST,
|
||||
'PORT': DATABASE_PORT
|
||||
}
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'data',
|
||||
)
|
976
telegram.py
976
telegram.py
@ -1,976 +0,0 @@
|
||||
from aiogram import types
|
||||
from aiogram.bot.api import FILE_URL
|
||||
from aiogram.utils import executor, json
|
||||
from aiohttp.client_exceptions import ContentTypeError
|
||||
|
||||
from bot import *
|
||||
from config import *
|
||||
from vk_messages import vk_polling_tasks, vk_polling
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
oauth_link = re.compile(
|
||||
'https://(oauth|api)\.vk\.com/blank\.html#access_token=([a-z0-9]*)&expires_in=[0-9]*&user_id=[0-9]*')
|
||||
|
||||
|
||||
async def get_pages_switcher(markup, page, pages):
|
||||
if page != 0:
|
||||
leftbutton = InlineKeyboardButton('◀', callback_data='page{}'.format(page - 1)) # callback
|
||||
else:
|
||||
leftbutton = InlineKeyboardButton('Поиск 🔍', callback_data='search')
|
||||
if page + 1 < len(pages):
|
||||
rightbutton = InlineKeyboardButton('▶', callback_data='page{}'.format(page + 1))
|
||||
else:
|
||||
rightbutton = None
|
||||
|
||||
if rightbutton:
|
||||
markup.row(leftbutton, rightbutton)
|
||||
else:
|
||||
markup.row(leftbutton)
|
||||
|
||||
|
||||
async def logged(uid, reply_to_message_id=None, to_chat=None):
|
||||
vk_user = VkUser.objects.filter(owner__uid=uid).first()
|
||||
if vk_user:
|
||||
return True
|
||||
else:
|
||||
await bot.send_message(to_chat or uid, 'Вход не выполнен! /start для входа',
|
||||
reply_to_message_id=reply_to_message_id)
|
||||
return False
|
||||
|
||||
|
||||
async def update_user_info(from_user: types.User):
|
||||
return TgUser.objects.update_or_create(uid=from_user.id,
|
||||
defaults={'first_name': from_user.first_name,
|
||||
'last_name': from_user.last_name,
|
||||
'username': from_user.username
|
||||
})
|
||||
|
||||
|
||||
async def update_chat_info(from_chat: types.Chat):
|
||||
if from_chat.type == 'private':
|
||||
return None, False
|
||||
return TgChat.objects.update_or_create(cid=from_chat.id)
|
||||
|
||||
|
||||
async def is_forwarding(text):
|
||||
if not text:
|
||||
return False, None
|
||||
if text == '!':
|
||||
return True, None
|
||||
if text.startswith('!'):
|
||||
return True, text[1:]
|
||||
return False, text
|
||||
|
||||
|
||||
async def is_bot_in_iterator(msg: types.Message):
|
||||
iterator = msg.new_chat_members or [msg.left_chat_member] or []
|
||||
me = await bot.me
|
||||
for i in iterator:
|
||||
if me.id == i.id:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
import secrets
|
||||
|
||||
|
||||
def generate_random_id():
|
||||
return secrets.randbelow(2_147_483_647)
|
||||
|
||||
|
||||
async def vk_sender(token, tg_message, **kwargs):
|
||||
session = VkSession(access_token=token, driver=await get_driver(token))
|
||||
kwargs['random_id'] = generate_random_id()
|
||||
try:
|
||||
api = API(session)
|
||||
vk_msg_id = await api('messages.send', **kwargs)
|
||||
except ContentTypeError:
|
||||
kwargs['v'] = session.API_VERSION
|
||||
kwargs['access_token'] = session.access_token
|
||||
try:
|
||||
url, html = await session.driver.post_text(url=session.REQUEST_URL + 'messages.send', data=kwargs)
|
||||
response = json.loads(html)
|
||||
vk_msg_id = response['response']
|
||||
except:
|
||||
log.exception(msg='Error in vk sender', exc_info=True)
|
||||
return None
|
||||
except VkAuthError:
|
||||
vk_user = VkUser.objects.filter(token=token).first()
|
||||
if vk_user:
|
||||
vk_user.delete()
|
||||
await bot.send_message(tg_message.chat.id, 'Вход не выполнен! /start для входа',
|
||||
reply_to_message_id=tg_message.message_id)
|
||||
return
|
||||
except VkAPIError:
|
||||
await asyncio.sleep(5)
|
||||
if kwargs.get('retries', 0) > 4:
|
||||
log.exception(msg='Error in vk sender', exc_info=True)
|
||||
return None
|
||||
else:
|
||||
kwargs['retries'] = kwargs.get('retries', 0) + 1
|
||||
await vk_sender(token, tg_message, **kwargs)
|
||||
except Exception:
|
||||
log.exception(msg='Error in vk sender', exc_info=True)
|
||||
return None
|
||||
Message.objects.create(
|
||||
vk_chat=kwargs['peer_id'],
|
||||
vk_id=vk_msg_id,
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id
|
||||
)
|
||||
return vk_msg_id
|
||||
|
||||
|
||||
async def generate_send_options(msg, forward=None, forward_messages_exists=False, message=None):
|
||||
message_options = dict()
|
||||
if forward:
|
||||
if msg.reply_to_message is not None:
|
||||
message_in_db = Message.objects.filter(tg_chat=msg.chat.id,
|
||||
tg_id=msg.reply_to_message.message_id).first()
|
||||
if message_in_db and message_in_db.vk_id:
|
||||
message_options['forward_messages'] = message_in_db.vk_id
|
||||
message_options['peer_id'] = forward.vkchat.cid
|
||||
|
||||
elif msg.reply_to_message is not None:
|
||||
message_in_db = Message.objects.filter(tg_chat=msg.chat.id, tg_id=msg.reply_to_message.message_id).first()
|
||||
if not message_in_db:
|
||||
await msg.reply('Не знаю в какой чат ответить, нет информации в базе данных.')
|
||||
return message_options
|
||||
if forward_messages_exists and message_in_db.vk_id:
|
||||
message_options['forward_messages'] = message_in_db.vk_id
|
||||
message_options['peer_id'] = message_in_db.vk_chat
|
||||
else:
|
||||
await msg.reply('Не понимаю что делать. Нужна помощь? Используй команду /help')
|
||||
return message_options
|
||||
|
||||
if message:
|
||||
message_options['message'] = message
|
||||
return message_options
|
||||
|
||||
|
||||
async def send_vk_action(token, peer_id, action='typing'):
|
||||
vksession = VkSession(access_token=token, driver=await get_driver(token))
|
||||
api = API(vksession)
|
||||
return # await api('messages.setActivity', peer_id=peer_id, activity=action)
|
||||
|
||||
|
||||
async def upload_attachment(msg, vk_user, file_id, peer_id, attachment_type, upload_field, upload_method,
|
||||
on_server_field='file', save_method='', upload_type=None, default_name='tgvkbot.document',
|
||||
title='tgvkbot.document', rewrite_name=False, custom_ext=''):
|
||||
try:
|
||||
file_info = await bot.get_file(file_id)
|
||||
path = file_info['file_path']
|
||||
if msg.content_type == 'audio':
|
||||
if not custom_ext and '.' in path and path.split('.')[-1] == 'mp3':
|
||||
custom_ext = '.aac'
|
||||
except NetworkError:
|
||||
await msg.reply('Файл слишком большой, максимально допустимый размер <b>20мб!</b>', parse_mode=ParseMode.HTML)
|
||||
return
|
||||
|
||||
url = FILE_URL.format(token=bot._BaseBot__token, path=path)
|
||||
await send_vk_action(vk_user.token, peer_id)
|
||||
content = await get_content(url, default_name, chrome_headers=False, rewrite_name=rewrite_name,
|
||||
custom_ext=custom_ext)
|
||||
filename = (content.get('file_name', '') + content.get('custom_ext', '')) or None
|
||||
if 'content' in content:
|
||||
vksession = VkSession(access_token=vk_user.token, driver=await get_driver(vk_user.token))
|
||||
api = API(vksession)
|
||||
upload_options = {}
|
||||
if attachment_type != 'photo' and upload_type:
|
||||
upload_options['type'] = upload_type
|
||||
if msg.content_type == 'sticker':
|
||||
webp = Image.open(content['content']).convert('RGBA')
|
||||
png = io.BytesIO()
|
||||
webp.save(png, format='png')
|
||||
content['content'] = png.getvalue()
|
||||
if attachment_type == 'video':
|
||||
upload_options['is_private'] = 1
|
||||
upload_server = await api(upload_method, **upload_options)
|
||||
with aiohttp.ClientSession() as session:
|
||||
data = aiohttp.FormData()
|
||||
field_data = {}
|
||||
if filename:
|
||||
field_data['filename'] = filename
|
||||
data.add_field(upload_field, content['content'], content_type='multipart/form-data', **field_data)
|
||||
async with session.post(upload_server['upload_url'], data=data) as upload:
|
||||
file_on_server = json.loads(await upload.text())
|
||||
if msg.content_type != 'sticker':
|
||||
content['content'].close()
|
||||
try:
|
||||
os.remove(os.path.join(content['temp_path'], content['file_name'] + content['custom_ext']))
|
||||
except:
|
||||
pass
|
||||
if attachment_type == 'photo':
|
||||
save_options = {'server': file_on_server['server'], on_server_field: file_on_server[on_server_field],
|
||||
'hash': file_on_server['hash']}
|
||||
elif attachment_type == 'video':
|
||||
return f'{attachment_type}{upload_server["owner_id"]}_{upload_server["video_id"]}_{upload_server["access_key"]}'
|
||||
else:
|
||||
if 'file' not in file_on_server:
|
||||
await msg.reply('<b>Ошибка</b> Не удалось загрузить файл. Файл не должен быть исполняемым.',
|
||||
parse_mode=ParseMode.HTML)
|
||||
return
|
||||
save_options = dict({'file': file_on_server['file']})
|
||||
save_options['title'] = title
|
||||
attachment = await api(save_method, **save_options)
|
||||
if 'type' not in attachment:
|
||||
attachment = attachment[0]
|
||||
else:
|
||||
attachment = attachment[attachment['type']]
|
||||
return f'{attachment_type}{attachment["owner_id"]}_{attachment["id"]}'
|
||||
|
||||
|
||||
async def get_dialogs(token, exclude=None):
|
||||
if not exclude:
|
||||
exclude = []
|
||||
session = VkSession(access_token=token, driver=await get_driver(token))
|
||||
api = API(session)
|
||||
dialogs = await api('messages.getDialogs', count=200)
|
||||
order = []
|
||||
users_ids = []
|
||||
group_ids = []
|
||||
for chat in dialogs.get('items'):
|
||||
chat = chat.get('message', '')
|
||||
if chat:
|
||||
if 'chat_id' in chat:
|
||||
if 2000000000 + chat['chat_id'] not in exclude:
|
||||
chat['title'] = chat['title']
|
||||
order.append({'title': chat['title'], 'id': 2000000000 + chat['chat_id']})
|
||||
elif chat['user_id'] > 0:
|
||||
if chat['user_id'] not in exclude:
|
||||
order.append({'title': 'Диалог ' + str(chat['user_id']), 'id': chat['user_id']})
|
||||
users_ids.append(chat['user_id'])
|
||||
elif chat['user_id'] < 0:
|
||||
if chat['user_id'] not in exclude:
|
||||
order.append({'title': 'Диалог ' + str(chat['user_id']), 'id': chat['user_id']})
|
||||
group_ids.append(chat['user_id'])
|
||||
|
||||
if users_ids:
|
||||
users = await api('users.get', user_ids=', '.join(str(x) for x in users_ids))
|
||||
else:
|
||||
users = []
|
||||
|
||||
if group_ids:
|
||||
groups = await api('groups.getById', group_ids=', '.join(str(abs(x)) for x in group_ids))
|
||||
else:
|
||||
groups = []
|
||||
|
||||
for output in order:
|
||||
if output['id'] > 0:
|
||||
u = next((i for i in users if i['id'] == output['id']), None)
|
||||
if u:
|
||||
output['title'] = f'{u["first_name"]} {u["last_name"]}'
|
||||
else:
|
||||
g = next((i for i in groups if -i['id'] == output['id']), None)
|
||||
if g:
|
||||
output['title'] = g["name"]
|
||||
|
||||
for button in range(len(order)):
|
||||
order[button] = InlineKeyboardButton(order[button]['title'], callback_data=f'chat{order[button]["id"]}')
|
||||
|
||||
rows = [order[x:x + 2] for x in range(0, len(order), 2)]
|
||||
pages = [rows[x:x + 4] for x in range(0, len(rows), 4)]
|
||||
|
||||
return pages
|
||||
|
||||
|
||||
async def search_dialogs(msg: types.Message, user=None):
|
||||
if not user:
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
vkuser = VkUser.objects.filter(owner=user).first()
|
||||
vksession = VkSession(access_token=vkuser.token, driver=await get_driver(vkuser.token))
|
||||
api = API(vksession)
|
||||
markup = InlineKeyboardMarkup(row_width=1)
|
||||
await bot.send_chat_action(msg.chat.id, 'typing')
|
||||
result = await api('messages.searchDialogs', q=msg.text, limit=10)
|
||||
for chat in result:
|
||||
title = None
|
||||
data = None
|
||||
if chat['type'] == 'profile':
|
||||
title = f'{chat["first_name"]} {chat["last_name"]}'
|
||||
data = f'chat{chat["id"]}'
|
||||
elif chat['type'] == 'chat':
|
||||
title = chat['title']
|
||||
data = f'chat{2000000000 + chat["id"]}'
|
||||
elif chat['type'] == 'page':
|
||||
title = await chat['name']
|
||||
data = f'chat{-chat["id"]}'
|
||||
if title and data:
|
||||
markup.add(InlineKeyboardButton(text=title, callback_data=data))
|
||||
markup.add(InlineKeyboardButton('Поиск 🔍', callback_data='search'))
|
||||
if markup.inline_keyboard:
|
||||
text = f'<b>Результат поиска по</b> <i>{msg.text}</i>'
|
||||
else:
|
||||
text = f'<b>Результат поиска по</b> <i>{msg.text}</i>'
|
||||
await bot.send_message(msg.chat.id, text, reply_markup=markup, parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
async def refresh_token(vkuser):
|
||||
try:
|
||||
with aiohttp.ClientSession() as session:
|
||||
r = await session.request('GET', TOKEN_REFRESH_URL, params={'token': vkuser.token})
|
||||
data = await r.json()
|
||||
if data['ok']:
|
||||
vkuser.token = data['token']
|
||||
vkuser.save()
|
||||
session.close()
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('logged'))
|
||||
async def check_logged(call: types.CallbackQuery):
|
||||
vkuser = VkUser.objects.filter(owner__uid=call.from_user.id).count()
|
||||
if vkuser:
|
||||
await handle_join(call.message, edit=True, chat_id=call.message.chat.id, message_id=call.message.message_id,
|
||||
exclude=True)
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('page'))
|
||||
async def page_switcher(call: types.CallbackQuery):
|
||||
# user, created = await update_user_info(call.from_user)
|
||||
# tgchat, tgchat_created = await update_chat_info(call.message.chat)
|
||||
page = int(call.data.split('page')[-1])
|
||||
message_markup = MessageMarkup.objects.filter(
|
||||
chat_id=call.message.chat.id,
|
||||
message_id=call.message.message_id,
|
||||
).first()
|
||||
if message_markup:
|
||||
pages = json.loads(message_markup.buttons)
|
||||
markup = InlineKeyboardMarkup()
|
||||
for row in pages[page]:
|
||||
markup.row(*[InlineKeyboardButton(**button) for button in row])
|
||||
await get_pages_switcher(markup, page, pages)
|
||||
await bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=markup)
|
||||
await bot.answer_callback_query(call.id)
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Нет данных в Базе Данных', show_alert=True)
|
||||
|
||||
|
||||
async def get_dialog_info(api, vk_chat_id, name_case='nom'):
|
||||
title = ''
|
||||
photo = ''
|
||||
dialog_type = ''
|
||||
if vk_chat_id >= 2000000000:
|
||||
dialog_info = await api('messages.getChat', chat_id=vk_chat_id - 2000000000)
|
||||
title = dialog_info['title']
|
||||
max_photo = get_max_photo(dialog_info)
|
||||
if max_photo:
|
||||
photo = dialog_info[max_photo]
|
||||
else:
|
||||
photo = None
|
||||
dialog_type = 'chat'
|
||||
elif vk_chat_id > 0:
|
||||
dialog_info = await api('users.get', user_ids=vk_chat_id, fields='photo_max', name_case=name_case)
|
||||
first_name = dialog_info[0]['first_name']
|
||||
last_name = dialog_info[0]['last_name'] or ''
|
||||
title = first_name + ' ' + last_name
|
||||
photo = dialog_info[0]['photo_max']
|
||||
dialog_type = 'user'
|
||||
elif vk_chat_id < 0:
|
||||
dialog_info = await api('groups.getById', group_ids=abs(vk_chat_id))
|
||||
title = dialog_info[0]['name']
|
||||
max_photo = get_max_photo(dialog_info[0])
|
||||
if max_photo:
|
||||
photo = dialog_info[0][max_photo]
|
||||
else:
|
||||
photo = None
|
||||
dialog_type = 'group'
|
||||
|
||||
return {'title': title, 'photo': photo, 'type': dialog_type}
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('ping'))
|
||||
async def ping_button(call: types.CallbackQuery):
|
||||
tg_chat_id = int(call.data.split('ping')[-1])
|
||||
try:
|
||||
await bot.send_message(tg_chat_id, f'<a href="tg://user?id={call.from_user.id}">Ping!</a>',
|
||||
parse_mode=ParseMode.HTML)
|
||||
await bot.answer_callback_query(call.id, 'Ping!')
|
||||
except BadRequest:
|
||||
await bot.answer_callback_query(call.id, 'Нет доступа к чату, бот кикнут или чат удалён!', show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query_handler(
|
||||
func=lambda call: call and call.message and call.data and call.data.startswith('deleteforward'))
|
||||
async def delete_forward(call: types.CallbackQuery):
|
||||
forward_id = int(call.data.split('deleteforward')[-1])
|
||||
forward_in_db = Forward.objects.filter(id=forward_id).first()
|
||||
if forward_in_db:
|
||||
forward_in_db.delete()
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
|
||||
message_markup = MessageMarkup.objects.filter(
|
||||
message_id=call.message.message_id,
|
||||
chat_id=call.message.chat.id
|
||||
).first()
|
||||
|
||||
buttons = json.loads(message_markup.buttons)
|
||||
|
||||
for row in buttons:
|
||||
if row[1]['callback_data'] == call.data:
|
||||
buttons.remove(row)
|
||||
else:
|
||||
markup.row(*[InlineKeyboardButton(**button) for button in row])
|
||||
if message_markup:
|
||||
if buttons:
|
||||
message_markup.buttons = json.dumps(buttons)
|
||||
message_markup.save()
|
||||
await bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=markup)
|
||||
else:
|
||||
await bot.edit_message_text(
|
||||
'У Вас нет связанных чатов. Чтобы привязать чат, добавьте бота в группу, а если бот уже добавлен - используйте команду /dialogs',
|
||||
call.message.chat.id, call.message.message_id)
|
||||
await bot.answer_callback_query(call.id, 'Успешно удалено!')
|
||||
else:
|
||||
await bot.edit_message_text('<b>Что-то пошло не так, нет информации в Базе Данных</b>',
|
||||
message_id=call.message.message_id, chat_id=call.message.chat.id, reply_markup=None)
|
||||
await bot.answer_callback_query(call.id, 'Ошибка!')
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('setinfo'))
|
||||
async def set_info(call: types.CallbackQuery):
|
||||
user, created = await update_user_info(call.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(call.message.chat)
|
||||
vk_chat_id = int(call.data.split('setinfo')[-1])
|
||||
vkuser = VkUser.objects.filter(owner=user).first()
|
||||
if vkuser:
|
||||
ME = await bot.me
|
||||
can_edit = False
|
||||
if not call.message.chat.all_members_are_administrators and (
|
||||
(await bot.get_chat_member(call.message.chat.id, ME.id)).status == 'administrator'):
|
||||
can_edit = True
|
||||
if not can_edit:
|
||||
admins = await bot.get_chat_administrators(call.message.chat.id)
|
||||
for admin in admins:
|
||||
if admin.user.id == ME.id and admin.can_change_info:
|
||||
can_edit = True
|
||||
break
|
||||
if can_edit:
|
||||
vksession = VkSession(access_token=vkuser.token, driver=await get_driver(vkuser.token))
|
||||
api = API(vksession)
|
||||
dialog_info = await get_dialog_info(api, vk_chat_id, name_case='nom')
|
||||
if dialog_info.get('title', ''):
|
||||
await bot.set_chat_title(call.message.chat.id, dialog_info['title'])
|
||||
if dialog_info.get('photo', ''):
|
||||
content = await get_content(dialog_info['photo'])
|
||||
await bot.set_chat_photo(call.message.chat.id, content['content'])
|
||||
content['content'].close()
|
||||
try:
|
||||
os.remove(os.path.join(content['temp_path'], content['file_name'] + content['custom_ext']))
|
||||
except:
|
||||
pass
|
||||
|
||||
if dialog_info['type'] == 'user':
|
||||
dialog_info = await get_dialog_info(api, vk_chat_id, name_case='ins')
|
||||
text = f'Чат успешно привязан к диалогу c <i>{dialog_info["title"]}</i>'
|
||||
elif dialog_info['type'] == 'group':
|
||||
text = f'Чат успешно привязан к диалогу с сообществом <i>{dialog_info["title"]}</i>'
|
||||
else:
|
||||
text = f'Чат успешно привязан к диалогу <i>{dialog_info["title"]}</i>'
|
||||
|
||||
await bot.edit_message_text(text, call.message.chat.id, call.message.message_id, parse_mode=ParseMode.HTML)
|
||||
await bot.answer_callback_query(call.id)
|
||||
else:
|
||||
await bot.answer_callback_query(call.id,
|
||||
'Недостаточно прав для редактирования информации о группе или бот не администратор!',
|
||||
show_alert=True)
|
||||
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data.startswith('chat'))
|
||||
async def choose_chat(call: types.CallbackQuery):
|
||||
user, created = await update_user_info(call.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(call.message.chat)
|
||||
vk_chat_id = int(call.data.split('chat')[-1])
|
||||
vkuser = VkUser.objects.filter(owner=user).first()
|
||||
if vkuser:
|
||||
if call.message.chat.type == 'private':
|
||||
vksession = VkSession(access_token=vkuser.token, driver=await get_driver(vkuser.token))
|
||||
api = API(vksession)
|
||||
dialog_info = await get_dialog_info(api, vk_chat_id, name_case='gen')
|
||||
markup = types.ForceReply(selective=False)
|
||||
if dialog_info['type'] == 'user':
|
||||
text = f'Сообщение для <i>{dialog_info["title"]}</i>'
|
||||
elif dialog_info['type'] == 'group':
|
||||
text = f'Сообщение сообществу <i>{dialog_info["title"]}</i>'
|
||||
else:
|
||||
text = f'Сообщение в диалог <i>{dialog_info["title"]}</i>'
|
||||
|
||||
tg_message = await bot.send_message(call.message.chat.id, text, reply_markup=markup,
|
||||
parse_mode=ParseMode.HTML)
|
||||
Message.objects.create(
|
||||
tg_chat=tg_message.chat.id,
|
||||
tg_id=tg_message.message_id,
|
||||
vk_chat=vk_chat_id
|
||||
)
|
||||
await bot.answer_callback_query(call.id)
|
||||
else:
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
vkchat = (await get_vk_chat(int(vk_chat_id)))[0]
|
||||
if forward:
|
||||
forward.vkchat = vkchat
|
||||
forward.save()
|
||||
else:
|
||||
Forward.objects.create(
|
||||
tgchat=tgchat,
|
||||
vkchat=vkchat,
|
||||
owner=user
|
||||
)
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton('Установить аватар и название', callback_data=f'setinfo{vkchat.cid}'))
|
||||
text = 'Чат успешно привязан. Я могу автоматически изменить название и установить аватар, сделай бота администратором и убедись в наличии прав на редактирование информации группы'
|
||||
if call.message.chat.type == 'group':
|
||||
text += '\n<b>Внимание!</b> Параметр <i>"All Members Are Administrators"</i> должен быть отключён и боту должна быть присвоена админка в отдельном порядке!'
|
||||
try:
|
||||
await bot.edit_message_text(text, call.message.chat.id, call.message.message_id, reply_markup=markup,
|
||||
parse_mode=ParseMode.HTML)
|
||||
except MessageNotModified:
|
||||
pass
|
||||
await bot.answer_callback_query(call.id)
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.callback_query_handler(func=lambda call: call and call.message and call.data and call.data == 'search')
|
||||
async def search_callback(call: types.CallbackQuery):
|
||||
vkuser = VkUser.objects.filter(owner__uid=call.from_user.id).count()
|
||||
if vkuser:
|
||||
markup = types.ForceReply(selective=False)
|
||||
await bot.send_message(call.message.chat.id, '<b>Поиск беседы 🔍</b>', parse_mode=ParseMode.HTML,
|
||||
reply_markup=markup)
|
||||
await bot.answer_callback_query(call.id, 'Поиск беседы 🔍')
|
||||
else:
|
||||
await bot.answer_callback_query(call.id, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(msg: types.Message):
|
||||
if ALLOWED_USER_IDS:
|
||||
if str(msg.from_user.id) not in ALLOWED_USER_IDS.replace(' ','').split(','):
|
||||
await msg.reply('⛔️ Бот недоступен для Вашего аккаунта.\nУзнать Telegram ID - /id')
|
||||
return
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if not tgchat:
|
||||
existing_vkuser = VkUser.objects.filter(owner=user).count()
|
||||
if not existing_vkuser:
|
||||
link = 'https://oauth.vk.com/authorize?client_id={}&' \
|
||||
'display=page&redirect_uri=https://oauth.vk.com/blank.html&scope=friends,messages,offline,docs,photos,video,stories,audio' \
|
||||
'&response_type=token&v={}'.format(VK_APP_ID, API_VERSION)
|
||||
mark = InlineKeyboardMarkup()
|
||||
login = InlineKeyboardButton('ВХОД', url=link)
|
||||
mark.add(login)
|
||||
await msg.reply('Привет, этот бот поможет тебе общаться ВКонтакте, войди по кнопке ниже'
|
||||
' и отправь мне то, что получишь в адресной строке.',
|
||||
reply_markup=mark)
|
||||
else:
|
||||
await msg.reply('Вход уже выполнен!\n/stop для выхода.')
|
||||
else:
|
||||
markup = InlineKeyboardMarkup()
|
||||
me = await bot.me
|
||||
markup.add(InlineKeyboardButton('Перейти в бота', url=f'https://t.me/{me.username}?start=login'))
|
||||
await msg.reply('Залогиниться можно только через личный чат с ботом', reply_markup=markup)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['stop'])
|
||||
async def stop_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
|
||||
existing_vkuser = VkUser.objects.filter(owner=user).first()
|
||||
if not existing_vkuser:
|
||||
await msg.reply('Вход не выполнен! Используй команду /start для входа')
|
||||
else:
|
||||
polling = next((task for task in TASKS if task['token'] == existing_vkuser.token), None)
|
||||
if polling:
|
||||
polling['task'].cancel()
|
||||
driver = DRIVERS.get(existing_vkuser.token, '')
|
||||
if driver:
|
||||
driver.close()
|
||||
del DRIVERS[existing_vkuser.token]
|
||||
existing_vkuser.delete()
|
||||
await msg.reply('Успешный выход!')
|
||||
|
||||
|
||||
@dp.message_handler(commands=['dialogs', 'd'])
|
||||
async def dialogs_command(msg: types.Message):
|
||||
if msg.chat.type == 'private':
|
||||
await handle_join(msg, text='Выберите диалог для быстрого ответа')
|
||||
else:
|
||||
await handle_join(msg, exclude=True)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['read', 'r'])
|
||||
async def read_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
if msg.chat.type == 'private':
|
||||
if msg.reply_to_message:
|
||||
message_in_db = Message.objects.filter(tg_chat=msg.chat.id,
|
||||
tg_id=msg.reply_to_message.message_id).first()
|
||||
if message_in_db:
|
||||
vksession = VkSession(access_token=vk_user.token, driver=await get_driver(vk_user.token))
|
||||
api = API(vksession)
|
||||
await api('messages.markAsRead', peer_id=message_in_db.vk_chat)
|
||||
await bot.send_message(msg.chat.id, '<i>Диалог прочитан</i>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('Не знаю какой чат прочесть, нет информации в базе данных')
|
||||
else:
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
if forward:
|
||||
vksession = VkSession(access_token=vk_user.token, driver=await get_driver(vk_user.token))
|
||||
api = API(vksession)
|
||||
await api('messages.markAsRead', peer_id=forward.vkchat.cid)
|
||||
await bot.send_message(msg.chat.id, '<i>Диалог прочитан</i>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('Этот чат не привязан к диалогу ВКонтакте, для привязки используй команду /dialogs')
|
||||
|
||||
|
||||
@dp.message_handler(commands=['search', 's'])
|
||||
async def search_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
vkuser = VkUser.objects.filter(owner=user).count()
|
||||
if vkuser:
|
||||
markup = types.ForceReply(selective=False)
|
||||
await bot.send_message(msg.chat.id, '<b>Поиск беседы 🔍</b>', parse_mode=ParseMode.HTML,
|
||||
reply_markup=markup)
|
||||
else:
|
||||
await bot.answer_callback_query(msg, 'Вход не выполнен! Сперва нужно выполнить вход в ВК через бота',
|
||||
show_alert=True)
|
||||
|
||||
|
||||
@dp.message_handler(commands=['chat', 'chats'])
|
||||
async def chat_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
forwards = Forward.objects.filter(owner=user)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
if forwards:
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
vksession = VkSession(access_token=vk_user.token, driver=await get_driver(vk_user.token))
|
||||
api = API(vksession)
|
||||
markup = InlineKeyboardMarkup()
|
||||
for forward in forwards:
|
||||
chat = await get_dialog_info(api, forward.vkchat.cid)
|
||||
markup.row(*[InlineKeyboardButton(chat['title'], callback_data=f'ping{forward.tgchat.cid}'),
|
||||
InlineKeyboardButton('❌', callback_data=f'deleteforward{forward.pk}')])
|
||||
msg_with_markup = await bot.send_message(msg.chat.id,
|
||||
'Список привязанных диалогов\nНажав на имя диалога, бот пинганёт Вас в соответствующем чате Telegram.\nНажав на "❌", привязка чата будет удалена и все сообщение из диалога ВКонтакте будут попадать напрямую к боту',
|
||||
reply_markup=markup)
|
||||
for row in markup.inline_keyboard:
|
||||
for button in range(len(row)):
|
||||
row[button] = row[button].to_python()
|
||||
MessageMarkup.objects.create(
|
||||
message_id=msg_with_markup.message_id,
|
||||
chat_id=msg_with_markup.chat.id,
|
||||
buttons=json.dumps(markup.inline_keyboard)
|
||||
)
|
||||
else:
|
||||
await bot.send_message(msg.chat.id,
|
||||
'У Вас нет связанных чатов. Чтобы привязать чат, добавьте бота в группу, а если бот уже добавлен - используйте команду /dialogs')
|
||||
|
||||
|
||||
@dp.message_handler(commands=['help'])
|
||||
async def help_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
HELP_MESSAGE = '/start - Логин в Вконтакте\n' \
|
||||
'/dialogs /d - Список диалогов\n' \
|
||||
'/read /r - Прочесть диалог ВКонтакте\n' \
|
||||
'/search /s - Поиск по диалогам\n' \
|
||||
'/chat - Список связанных чатов с диалогами ВКонтакте, привязать чат к диалогу можно добавив бота в группу\n' \
|
||||
'/stop - Выход из ВКонтакте\n' \
|
||||
'/help - Помощь' \
|
||||
'/id - Узнать Telegram ID'
|
||||
|
||||
await bot.send_message(msg.chat.id, HELP_MESSAGE, parse_mode=ParseMode.HTML)
|
||||
|
||||
@dp.message_handler(commands=['id'])
|
||||
async def id_command(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
await bot.send_message(msg.chat.id, 'Ваш ID: <code>{}</code>'.format(msg.from_user.id), parse_mode=ParseMode.HTML)
|
||||
|
||||
@dp.message_handler(content_types=['text'])
|
||||
async def handle_text(msg: types.Message):
|
||||
user, created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if msg.chat.type == 'private':
|
||||
m = oauth_link.search(msg.text)
|
||||
if m:
|
||||
if ALLOWED_USER_IDS:
|
||||
if str(msg.from_user.id) not in ALLOWED_USER_IDS.replace(' ', '').split(','):
|
||||
await msg.reply('⛔️ Бот недоступен для Вашего аккаунта.\nУзнать Telegram ID - /id')
|
||||
return
|
||||
await bot.send_chat_action(msg.from_user.id, ChatActions.TYPING)
|
||||
token = m.group(2)
|
||||
if not VkUser.objects.filter(token=token).exists():
|
||||
try:
|
||||
session = VkSession(access_token=token, driver=await get_driver(token))
|
||||
api = API(session)
|
||||
vkuserinfo = await api('account.getProfileInfo', name_case='gen')
|
||||
vkuser, vkuser_created = VkUser.objects.update_or_create(
|
||||
defaults={'token': token, 'is_polling': True}, owner=user)
|
||||
existing_polling = next((task for task in TASKS if task['token'] == vkuser.token), None)
|
||||
if existing_polling:
|
||||
existing_polling['task'].cancel()
|
||||
driver = DRIVERS.get(vkuser.token, '')
|
||||
if driver:
|
||||
driver.close()
|
||||
del DRIVERS[vkuser.token]
|
||||
refreshed_token = await refresh_token(vkuser)
|
||||
TASKS.append({'token': vkuser.token, 'task': asyncio.ensure_future(vk_polling(vkuser))})
|
||||
logged_in = await msg.reply(
|
||||
'Вход выполнен в аккаунт {} {}!\n[Использование](https://akentev.com/tgvkbot/usage/)'.format(
|
||||
vkuserinfo['first_name'], vkuserinfo.get('last_name', '')), parse_mode='Markdown')
|
||||
if refreshed_token:
|
||||
await logged_in.reply('*Вам доступна музыка 🎵*', parse_mode='Markdown')
|
||||
except VkAuthError:
|
||||
await msg.reply('Неверная ссылка, попробуйте ещё раз!')
|
||||
else:
|
||||
await msg.reply('Вход уже выполнен!\n/stop для выхода.')
|
||||
return
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
if msg.reply_to_message and msg.reply_to_message.text == 'Поиск беседы 🔍':
|
||||
if msg.chat.type == 'private' or not Message.objects.filter(tg_id=msg.reply_to_message.message_id,
|
||||
tg_chat=msg.reply_to_message.chat.id).exists():
|
||||
await search_dialogs(msg, user)
|
||||
return
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
forward_messages_exists, message = await is_forwarding(msg.text)
|
||||
message_options = await generate_send_options(msg, forward, forward_messages_exists, message)
|
||||
if message_options != {}:
|
||||
vk_message = await vk_sender(vk_user.token, msg, **message_options)
|
||||
if not vk_message:
|
||||
await msg.reply('<b>Произошла ошибка при отправке</b>', parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['contact'])
|
||||
async def handle_contact(msg: types.Message):
|
||||
new_text = msg.contact.first_name
|
||||
if msg.contact.last_name:
|
||||
new_text += ' ' + msg.contact.last_name
|
||||
new_text += '\n'
|
||||
new_text += msg.contact.phone_number
|
||||
msg.text = new_text
|
||||
await handle_text(msg)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['photo', 'sticker'])
|
||||
async def handle_photo(msg: types.Message):
|
||||
user, user_created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
forward_messages_exists, message = await is_forwarding(msg.caption)
|
||||
message_options = await generate_send_options(msg, forward, forward_messages_exists, message)
|
||||
if msg.content_type == 'photo':
|
||||
file_id = msg.photo[-1].file_id
|
||||
elif msg.content_type == 'sticker':
|
||||
if msg.sticker.to_python()['is_animated']:
|
||||
file_id = msg.sticker.thumb.file_id
|
||||
else:
|
||||
file_id = msg.sticker.file_id
|
||||
if message_options:
|
||||
message_options['attachment'] = await upload_attachment(msg, vk_user, file_id, message_options['peer_id'],
|
||||
attachment_type='photo',
|
||||
upload_field='photo',
|
||||
upload_method='photos.getMessagesUploadServer',
|
||||
on_server_field='photo',
|
||||
save_method='photos.saveMessagesPhoto')
|
||||
if message_options['attachment']:
|
||||
vk_message = await vk_sender(vk_user.token, msg, **message_options)
|
||||
if not vk_message:
|
||||
await msg.reply('<b>Произошла ошибка при отправке</b>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('<b>Ошибка при загрузке файла. Сообщение не отправлено!</b>', parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['document', 'voice', 'audio'])
|
||||
async def handle_documents(msg: types.Message):
|
||||
user, user_created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
if tgchat:
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
else:
|
||||
forward = None
|
||||
forward_messages_exists, message = await is_forwarding(msg.caption)
|
||||
message_options = await generate_send_options(msg, forward, forward_messages_exists, message)
|
||||
file_id = getattr(msg, msg.content_type).file_id
|
||||
if message_options:
|
||||
upload_attachment_options = {
|
||||
'attachment_type': 'doc',
|
||||
'upload_field': 'file',
|
||||
'upload_method': 'docs.getUploadServer',
|
||||
'on_server_field': 'file',
|
||||
'save_method': 'docs.save',
|
||||
}
|
||||
if hasattr(getattr(msg, msg.content_type), 'file_name') and getattr(msg, msg.content_type).file_name:
|
||||
upload_attachment_options['title'] = getattr(msg, msg.content_type).file_name
|
||||
|
||||
if msg.content_type == 'voice':
|
||||
upload_attachment_options['upload_type'] = 'audio_message'
|
||||
|
||||
# https://vk.com/wall-1_395554
|
||||
# if msg.content_type == 'sticker':
|
||||
# if msg.sticker.to_python()['is_animated']:
|
||||
# file_id = msg.sticker.thumb.file_id
|
||||
# upload_attachment_options['upload_type'] = 'graffiti'
|
||||
# upload_attachment_options['rewrite_name'] = True
|
||||
# upload_attachment_options['default_name'] = 'graffiti.png'
|
||||
|
||||
if msg.content_type == 'audio':
|
||||
audioname = ''
|
||||
if msg.audio.performer and msg.audio.title:
|
||||
audioname += msg.audio.performer + ' - ' + msg.audio.title
|
||||
elif msg.audio.performer:
|
||||
audioname += msg.audio.performer
|
||||
elif msg.audio.title:
|
||||
audioname += msg.audio.title
|
||||
else:
|
||||
audioname = f'tgvkbot_audio_{file_id}'
|
||||
upload_attachment_options['title'] = audioname
|
||||
|
||||
message_options['attachment'] = await upload_attachment(msg, vk_user, file_id, message_options['peer_id'],
|
||||
**upload_attachment_options)
|
||||
if message_options['attachment']:
|
||||
vk_message = await vk_sender(vk_user.token, msg, **message_options)
|
||||
if not vk_message:
|
||||
await msg.reply('<b>Произошла ошибка при отправке</b>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('<b>Ошибка при загрузке файла. Сообщение не отправлено!</b>', parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['video', 'video_note'])
|
||||
async def handle_videos(msg: types.Message):
|
||||
user, user_created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
if await logged(msg.from_user.id, msg.message_id, msg.chat.id):
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
if tgchat:
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
else:
|
||||
forward = None
|
||||
forward_messages_exists, message = await is_forwarding(msg.caption)
|
||||
message_options = await generate_send_options(msg, forward, forward_messages_exists, message)
|
||||
file_id = getattr(msg, msg.content_type).file_id
|
||||
if message_options:
|
||||
upload_attachment_options = {
|
||||
'attachment_type': 'video',
|
||||
'upload_field': 'video_file',
|
||||
'upload_method': 'video.save',
|
||||
}
|
||||
if hasattr(getattr(msg, msg.content_type), 'file_name') and getattr(msg, msg.content_type).file_name:
|
||||
upload_attachment_options['title'] = getattr(msg, msg.content_type).file_name
|
||||
|
||||
message_options['attachment'] = await upload_attachment(msg, vk_user, file_id, message_options['peer_id'],
|
||||
**upload_attachment_options)
|
||||
if message_options['attachment']:
|
||||
vk_message = await vk_sender(vk_user.token, msg, **message_options)
|
||||
if not vk_message:
|
||||
await msg.reply('<b>Произошла ошибка при отправке</b>', parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
await msg.reply('<b>Ошибка при загрузке файла. Сообщение не отправлено!</b>', parse_mode=ParseMode.HTML)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=['new_chat_members'], func=is_bot_in_iterator)
|
||||
async def handle_join(msg: types.Message, edit=False, chat_id=None, message_id=None, text='', exclude=False):
|
||||
user, user_created = await update_user_info(msg.from_user)
|
||||
tgchat, tgchat_created = await update_chat_info(msg.chat)
|
||||
forward = Forward.objects.filter(tgchat=tgchat).first()
|
||||
try:
|
||||
await bot.send_chat_action(msg.chat.id, 'typing')
|
||||
except:
|
||||
return
|
||||
vk_user = VkUser.objects.filter(owner=user).first()
|
||||
pages = None
|
||||
reply_to_message_id = None
|
||||
markup = None
|
||||
if vk_user:
|
||||
if forward:
|
||||
text = text or '<i>Этот чат уже привязан к диалогу ВКонтакте, Вы можете выбрать новый диалог</i>'
|
||||
else:
|
||||
text = text or '<i>Выберите диалог ВКонтакте к которому будет привязан этот чат</i>'
|
||||
markup = InlineKeyboardMarkup()
|
||||
excluded_ids = []
|
||||
if exclude:
|
||||
excluded_ids = [forward.vkchat.cid for forward in Forward.objects.filter(owner=user)]
|
||||
pages = await get_dialogs(vk_user.token, excluded_ids)
|
||||
if pages:
|
||||
for buttons_row in pages[0]:
|
||||
markup.row(*buttons_row)
|
||||
await get_pages_switcher(markup, 0, pages)
|
||||
else:
|
||||
me = await bot.me
|
||||
if msg.chat.type == 'private':
|
||||
text = 'Вход не выполнен! /start для входа'
|
||||
reply_to_message_id = msg.message_id
|
||||
else:
|
||||
text = '<i>Вход не выполнен! Сперва нужно выполнить вход в ВК через бота</i>'
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton('ВХОД', url=f'https://t.me/{me.username}?start=login'),
|
||||
InlineKeyboardButton('✅ Я залогинился', callback_data=f'logged-{msg.from_user.id}'))
|
||||
if edit:
|
||||
msg_with_markup = await bot.edit_message_text(text=text, chat_id=chat_id, message_id=message_id,
|
||||
reply_markup=markup, parse_mode=ParseMode.HTML)
|
||||
else:
|
||||
msg_with_markup = await bot.send_message(msg.chat.id, text=text, reply_markup=markup, parse_mode=ParseMode.HTML,
|
||||
reply_to_message_id=reply_to_message_id)
|
||||
if pages:
|
||||
for page in pages:
|
||||
for row in page:
|
||||
for button in range(len(row)):
|
||||
row[button] = row[button].to_python()
|
||||
|
||||
MessageMarkup.objects.create(
|
||||
message_id=msg_with_markup.message_id,
|
||||
chat_id=msg_with_markup.chat.id,
|
||||
buttons=json.dumps(pages)
|
||||
)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=types.ContentType.ANY, func=lambda msg: msg.group_chat_created is True)
|
||||
async def handle_new_group(msg: types.Message):
|
||||
await handle_join(msg)
|
||||
|
||||
|
||||
@dp.message_handler(content_types=types.ContentType.ANY, func=lambda message: message.migrate_to_chat_id)
|
||||
async def handle_chat_migration(msg: types.Message):
|
||||
# Юзеру сначала нужно выбрать чат для привязки, а уже ПОТОМ делать миграцию. Иначе старый chatid останется на иналйнкнопках
|
||||
forwards = Forward.objects.filter(tgchat__cid=msg.chat.id)
|
||||
for forward in forwards:
|
||||
forward.tgchat.cid = msg.migrate_to_chat_id
|
||||
forward.tgchat.save()
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton('Установить аватар и название', callback_data=f'setinfo{forward.vkchat.cid}'))
|
||||
await bot.send_message(msg.migrate_to_chat_id,
|
||||
text='Отлично! Теперь можно установить аватар и название. Если кнопка выше не работает, то воспользуйтесь этой.',
|
||||
reply_markup=markup)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
TASKS = vk_polling_tasks()
|
||||
asyncio.gather(*[task['task'] for task in TASKS])
|
||||
|
||||
executor.start_polling(dp)
|
1496
vk_messages.py
1496
vk_messages.py
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user