-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Nikita Meshaninov
committed
Feb 22, 2020
0 parents
commit 6118de5
Showing
12 changed files
with
389 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.env/ | ||
.vscode/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.env/ | ||
.vscode/ | ||
env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
FROM python:3.8-alpine | ||
|
||
ARG TOKEN | ||
ARG ACCESS | ||
ARG TRANSMISSION_URL=localhost | ||
ARG TRANSMISSION_LOGIN | ||
ARG TRANSMISSION_PASSWORD | ||
ARG TRANSMISSION_PORT | ||
ARG TIME_SHEDULE_SEC=300 | ||
ARG SOCKS5_LOGIN | ||
ARG SOCKS5_PASSWORD | ||
ARG SOCKS5_ADDRESS | ||
|
||
ENV TOKEN=${TOKEN} | ||
ENV ACCESS=${ACCESS} | ||
ENV TRANSMISSION_URL=${TRANSMISSION_URL} | ||
ENV TRANSMISSION_LOGIN=${TRANSMISSION_LOGIN} | ||
ENV TRANSMISSION_PASSWORD=${TRANSMISSION_PASSWORD} | ||
ENV TRANSMISSION_PORT=${TRANSMISSION_PORT} | ||
ENV TIME_SHEDULE_SEC=${TIME_SHEDULE_SEC} | ||
ENV SOCKS5_LOGIN=${SOCKS5_LOGIN} | ||
ENV SOCKS5_PASSWORD=${SOCKS5_PASSWORD} | ||
ENV SOCKS5_ADDRESS=${SOCKS5_ADDRESS} | ||
|
||
WORKDIR /transmission_telegram | ||
|
||
COPY requirements.txt . | ||
RUN pip install --upgrade pip | ||
RUN pip install -r requirements.txt | ||
|
||
COPY . /transmission_telegram/ | ||
|
||
ENTRYPOINT ["python", "bot.py", "&"] | ||
CMD ["python ", "shedule.py", "&"] | ||
|
||
EXPOSE 443 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Telegram-transmission-bot | ||
|
||
Телеграм бот, для личного использования. | ||
Если ему отправить torrent file или magnet ссылку, то через rpc подает запросы на transmission сервер. | ||
Также можно управлять процессом раздачи каждого трекера | ||
|
||
При изменении статуса раздачи уведомляет | ||
|
||
Рекомендую использовать вместие с [докер контейнером transmission](https://hub.docker.com/r/linuxserver/transmission) | ||
|
||
### Список задач: | ||
 | ||
### Изменение статуса: | ||
 | ||
### Добавление файла: | ||
 | ||
|
||
**По сути это упрощенный клиент transmission для **телеграмма**** | ||
|
||
## Сборка | ||
```bash | ||
git clone https://github.com/meshchaninov/transmission-telegram-private-bot.git | ||
cd transmission-telegram-private-bot-master | ||
docker build -t transmission-bot . \ | ||
--build-arg TOKEN=<token> \ | ||
--build-arg ACCESS=<access> \ | ||
--build-arg TRANSMISSION_URL=<transmission_url> \ | ||
--build-arg TRANSMISSION_LOGIN=<login> \ | ||
--build-arg TRANSMISSION_PASSWORD=<password> \ | ||
--build-arg TRANSMISSION_PORT=<transmission_port> \ | ||
--build-arg TIME_SHEDULE_SEC=<time_shedule_sec> \ | ||
--build-arg SOCKS5_LOGIN=<socks5_login> \ | ||
--build-arg SOCKS5_PASSWORD=<socks5_password> \ | ||
--build-arg SOCKS5_ADDRESS=<socks5_address> \ | ||
--restart unless-stopped | ||
``` | ||
|
||
Каждое из значений: | ||
- TOKEN – токен из botFather телеграмма | ||
- ACCESS - id пользователей у которых есть доступ к телеграмму (Их может быть несколько Пример: ```ACCESS=1111:2222:3333```) | ||
- TRANSMISSION_URL – адресс где хостится transmission | ||
- TRANSMISSION_LOGIN – логин в клиенте transmission (надеюсь у вас он есть, как и пароль. В опасное время живем:)) | ||
- TRANSMISSION_PASSWORD - пароль в клиенте transmission | ||
- TRANSMISSION_PORT - порт клиента transmission. Обычно 9091 | ||
- TIME_SHEDULE_SEC – период через которое бот будет проаерять изменение статуса у трекера в секундах (Пример: ```TIME_SHEDULE_SEC=300```, уведомлять о изменениях, если они есть, раз в 5 минут) | ||
- SOCKS5_LOGIN - логин socks5 сервера (Если ты из России, то по другому бот и не запустить, обязталеьно нужен прокси) | ||
- SOCKS5_PASSWORD - пароль socks5 сервера | ||
- SOCKS5_ADDRESS - адресс socks5 сервера | ||
|
||
**Опять же повторю, делалось для личного использования. Из-за этого реализация топорна и отсутствует гипкость.** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
from dataclasses import dataclass | ||
from enum import Enum, auto | ||
from typing import Dict, List | ||
|
||
import transmissionrpc as trpc | ||
|
||
|
||
class TorrentStatus(Enum): | ||
STOPPED = auto() | ||
SEEDING = auto() | ||
CHECKING = auto() | ||
DOWNLOADING = auto() | ||
UNKNOWN = auto() | ||
|
||
@dataclass | ||
class Torrent: | ||
name: str | ||
status: TorrentStatus | ||
hashStr: str | ||
|
||
def str_to_torrent_status(status: str) -> TorrentStatus: | ||
if status == 'stopped': | ||
return TorrentStatus.STOPPED | ||
elif status == 'seeding': | ||
return TorrentStatus.SEEDING | ||
elif status == 'checking': | ||
return TorrentStatus.CHECKING | ||
elif status == 'downloading': | ||
return TorrentStatus.DOWNLOADING | ||
else: | ||
return TorrentStatus.UNKNOWN | ||
|
||
def torrent_status_to_emoji(status: TorrentStatus) -> str: | ||
if status == TorrentStatus.CHECKING: | ||
return '🔎' | ||
elif status == TorrentStatus.SEEDING: | ||
return '🔋' | ||
elif status == TorrentStatus.STOPPED: | ||
return '⛔' | ||
elif status == TorrentStatus.DOWNLOADING: | ||
return '⏳' | ||
elif status == TorrentStatus.UNKNOWN: | ||
return '❓' | ||
|
||
class TransmissionConnection: | ||
def __init__(self, address='localhost', login=None, password=None, port=9091): | ||
try: | ||
if login and password: | ||
self._conn: trpc.Client = trpc.Client(address=address, user=login, password=password, port=port) | ||
else: | ||
self._conn: trpc.Client = trpc.Client(address=address, port=port) | ||
self._torrents, self._torrents_dict = self.get_torrents() | ||
except Exception as e: | ||
raise e | ||
|
||
def get_torrents(self) -> (List[Torrent], Dict[str, Torrent]): | ||
l = [Torrent(t.name, str_to_torrent_status(t.status), t.hashString) for t in self._conn.get_torrents()] | ||
d = {le.hashStr: le for le in l} | ||
return l, d | ||
|
||
def _refresh_torrents(self): | ||
self._torrents, self._torrents_dict = self.get_torrents() | ||
|
||
def add_torrent(self, url): | ||
self._conn.add_torrent(url) | ||
|
||
def stop_torrent(self, torrent: Torrent): | ||
self._conn.stop_torrent(torrent.hashStr) | ||
|
||
def start_torrent(self, torrent: Torrent): | ||
self._conn.start_torrent(torrent.hashStr) | ||
|
||
def del_torrent(self, torrent: Torrent, delete_data=False): | ||
self.stop_torrent(torrent) | ||
self._conn.remove_torrent(torrent.hashStr, delete_data=delete_data) | ||
|
||
def __getitem__(self, key): | ||
self._refresh_torrents() | ||
if isinstance(key, int): | ||
return self._torrents[key] | ||
if isinstance(key, str): | ||
return self._torrents_dict[key] | ||
|
||
def __len__(self): | ||
self._refresh_torrents() | ||
return len(self._torrents) | ||
|
||
def __delitem__(self, key): | ||
self._refresh_torrents() | ||
self.del_torrent(key) | ||
|
||
def __iter__(self): | ||
self._refresh_torrents() | ||
return iter(self._torrents) | ||
|
||
def __list__(self): | ||
self._refresh_torrents() | ||
return self._torrents |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import base64 | ||
import os | ||
import time | ||
from enum import Enum | ||
|
||
import telebot | ||
from telebot import types | ||
|
||
from consts import TOKKEN, ACCESS, LIST_BUTTON_TEXT, list_keyboard | ||
from TransmissionConnection import (TorrentStatus, TransmissionConnection, | ||
torrent_status_to_emoji) | ||
|
||
bot = telebot.TeleBot(TOKKEN) | ||
|
||
|
||
tc = TransmissionConnection(os.environ.get('TRANSMISSION_URL', 'localhost'), os.environ.get('TRANSMISSION_LOGIN', None), os.environ.get('TRANSMISSION_PASSWORD', None), os.environ.get('TRANSMISSION_PORT', 9091)) | ||
|
||
class AccessDeniedException(Exception): | ||
pass | ||
|
||
def access(message): | ||
if message.from_user.id not in ACCESS: | ||
raise AccessDeniedException | ||
|
||
def torrent_info(hashStr: str) -> (str, types.InlineKeyboardMarkup): | ||
torrent = tc[hashStr] | ||
keyboard = types.InlineKeyboardMarkup() | ||
if torrent.status == TorrentStatus.STOPPED: | ||
key_start = types.InlineKeyboardButton(text=f'Раздавать', callback_data=f'start_{torrent.hashStr}') | ||
keyboard.add(key_start) | ||
if torrent.status == TorrentStatus.SEEDING: | ||
key_pause = types.InlineKeyboardButton(text=f'Поставить на паузу', callback_data=f'pause_{torrent.hashStr}') | ||
keyboard.add(key_pause) | ||
key_del = types.InlineKeyboardButton(text=f'Удалить {torrent.name}', callback_data=f'del_{torrent.hashStr}') | ||
keyboard.add(key_del) | ||
text = f'{torrent.name}\nstatus: {torrent_status_to_emoji(torrent.status)}' | ||
return text, keyboard | ||
|
||
|
||
def list_torrents(chat_id): | ||
for torrent in tc: | ||
text, keyboard = torrent_info(torrent.hashStr) | ||
bot.send_message(chat_id, text=text, reply_markup=keyboard) | ||
|
||
@bot.message_handler(content_types=['text']) | ||
def get_text_message(message): | ||
try: | ||
access(message) | ||
if message.text == '/help' or message.text == '/start': | ||
bot.send_message(message.from_user.id, 'Перешли мне торрент файл или магнет ссылку и я начну скачивание. Введи /list – для просмотра списка торрентов. Этим ботом могут пользоваться только доверенные лица', reply_markup=list_keyboard) | ||
elif message.text == '/list' or message.text == LIST_BUTTON_TEXT: | ||
list_torrents(message.from_user.id) | ||
elif message.text.startswith('magnet:'): | ||
tc.add_torrent(message.text) | ||
bot.send_message(message.from_user.id, f'{tc[-1].name} добавлен', reply_markup=list_keyboard) | ||
except AccessDeniedException: | ||
bot.send_message(message.from_user.id, 'Этот бот не для тебя') | ||
|
||
|
||
@bot.message_handler(content_types=['document']) | ||
def get_document_message(message): | ||
try: | ||
access(message) | ||
if len(message.document.file_name) < 8 or message.document.file_name[-7:] != 'torrent': | ||
bot.send_message(message.from_user.id, 'Можно мне отправить только torrent файл') | ||
file_info = bot.get_file(message.document.file_id) | ||
new_torrent = bot.download_file(file_info.file_path) | ||
new_torrent = base64.b64encode(bytes(new_torrent)).decode('utf-8') | ||
tc.add_torrent(new_torrent) | ||
bot.send_message(message.from_user.id, f'{tc[-1].name} добавлен', reply_markup=list_keyboard) | ||
except AccessDeniedException: | ||
bot.send_message(message.from_user.id, 'Этот бот не для тебя') | ||
|
||
@bot.callback_query_handler(func=lambda call: True) | ||
def callback_worker(call): | ||
for torrent in tc: | ||
if call.data == f'del_{torrent.hashStr}': | ||
keyboard = types.InlineKeyboardMarkup() | ||
keyboard.add(types.InlineKeyboardButton(text=f'Да', callback_data=f'del_agree_{torrent.hashStr}')) | ||
keyboard.add(types.InlineKeyboardButton(text=f'Нет', callback_data=f'del_disagree_{torrent.hashStr}')) | ||
bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=f"ТЫ УВЕРЕН?", reply_markup=keyboard) | ||
elif call.data == f'del_disagree_{torrent.hashStr}': | ||
info, keyboard = torrent_info(torrent.hashStr) | ||
bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=info, reply_markup=keyboard) | ||
elif call.data == f'del_agree_{torrent.hashStr}': | ||
tc.del_torrent(torrent, delete_data=True) | ||
|
||
|
||
bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=f"{torrent.name} удален") | ||
elif call.data == f'start_{torrent.hashStr}': | ||
tc.start_torrent(torrent) | ||
text, keyboard = torrent_info(torrent.hashStr) | ||
bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=text, reply_markup=keyboard) | ||
elif call.data == f'pause_{torrent.hashStr}': | ||
tc.stop_torrent(torrent) | ||
text, keyboard = torrent_info(torrent.hashStr) | ||
bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=text, reply_markup=keyboard) | ||
elif call.data == 'list': | ||
list_torrents(call.message.chat.id) | ||
|
||
if __name__ == "__main__": | ||
bot.polling(none_stop=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import os | ||
|
||
from telebot import apihelper, types | ||
|
||
TOKKEN = os.environ.get('TOKEN') | ||
ACCESS = set(int(i) for i in os.environ.get('ACCESS', '').split(':')) | ||
|
||
LIST_BUTTON_TEXT = 'Список' | ||
list_keyboard = types.ReplyKeyboardMarkup() | ||
list_keyboard.add(types.KeyboardButton(text=LIST_BUTTON_TEXT)) | ||
|
||
TIME_SHEDULE_SEC = int(os.environ.get('TIME_SHEDULE_SEC', 600)) | ||
|
||
apihelper.proxy = { | ||
'https': f'socks5://{os.environ.get("SOCKS5_LOGIN")}:{os.environ.get("SOCKS5_PASSWORD")}@{os.environ.get("SOCKS5_ADDRESS")}' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
astroid==2.3.3 | ||
certifi==2019.11.28 | ||
chardet==3.0.4 | ||
gunicorn==20.0.4 | ||
idna==2.6 | ||
isort==4.3.21 | ||
lazy-object-proxy==1.4.3 | ||
mccabe==0.6.1 | ||
pylint==2.4.4 | ||
PySocks==1.7.1 | ||
pyTelegramBotAPI==3.6.7 | ||
requests==2.18.4 | ||
schedule==0.6.0 | ||
six==1.14.0 | ||
transmissionrpc==0.11 | ||
urllib3==1.22 | ||
wrapt==1.11.2 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.