Compare commits

...

No commits in common. "master" and "heroku-old" have entirely different histories.

25 changed files with 1274 additions and 2766 deletions

View File

@ -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
View 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

View File

@ -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
View File

@ -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

View File

@ -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
View 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.

1
Procfile Normal file
View File

@ -0,0 +1 @@
bot: python3 bot.py

View File

@ -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
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](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
View 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
View File

@ -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('&lt;', '<')
text = text.replace('&gt;', '>')
text = text.replace('&amp;', '&')
text = text.replace('&copy;', '©')
text = text.replace('&reg;', '®')
text = text.replace('&laquo;', '«')
text = text.replace('&raquo;', '«')
text = text.replace('&deg;', '°')
text = text.replace('&trade;', '')
text = text.replace('&plusmn;', '±')
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(), '/', {'/': {}})"""

View File

@ -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
View File

@ -0,0 +1,3 @@
import os
token = os.environ['TELEGRAM_TOKEN']
vk_app_id = os.environ['VK_APP']

View File

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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 "✅ Готово"

View File

@ -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)

View File

@ -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
View File

@ -0,0 +1 @@
python-3.6.1

View File

@ -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⚠️ Настройка переменных окружуения была прервана!')

View File

@ -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',
)

View File

@ -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)

File diff suppressed because it is too large Load Diff