Skip to content

Commit

Permalink
feat: add qbittorrent support
Browse files Browse the repository at this point in the history
  • Loading branch information
verdel committed Apr 24, 2024
1 parent 5740c1e commit f044f9d
Show file tree
Hide file tree
Showing 13 changed files with 350 additions and 110 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Transmission Telegram Bot
# Torrent Telegram Bot

## What is this?

With transmission telegram bot, you can manage your Transmission torrent client. You can add, delete and list torrent entry.
With torrent telegram bot, you can manage your Transmission or Qbittorrent torrent client. You can add, delete and list torrent entry.

To get started create `config.yml` file with your favourite text editor. Add your bot token ID, allowed user or chat ID (not username), transmission url, username and password etc.
To get started create `config.yml` file with your favourite text editor. Add your bot token ID, allowed user or chat ID (not username), torrent client url, username and password etc.

## Installation

Expand All @@ -22,12 +22,12 @@ To customize bot, create `config.yml`, then add one or more of the variables. Fo
## Usage

Create telegram bot with **@BotFather**.
Create transmission telegram bot configuration file. And start you bot.
Create torrent telegram bot configuration file. And start you bot.

To start bot use command:

```console
poetry run python -m transmission_telegram_bot.bot -c config.yml
poetry run python -m torrent_telegram_bot.bot -c config.yml
```

Now you can start interacting with your bot.
Expand Down
45 changes: 45 additions & 0 deletions conf/config.qbittorrent.sample.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
telegram:
token: tg-token

allow_chat:
- telegram_id: 111111111
torrent_permission: "all"
allow_category:
- Movies
- Series
notify: all

- telegram_id: 222222222
torrent_permission: "personal"
notify: personal

- telegram_id: 333333333
torrent_permission: "personal"
notify: "none"

proxy:
url: socks5://username:[email protected]:55555

client:
type: qbittorrent
address: qbittorrent.local
port: 8080
user: username
password: password
path:
- category: "Movies"
dir: "/mnt/data/Movies"

- category: "Series"
dir: "/mnt/data/Series"

db:
path: conf/bot.db

schedule:
check_period: 10
max_instances: 1

sentry:
dsn: https://[email protected]/4504037633556480
environment: testing
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ telegram:
proxy:
url: socks5://username:[email protected]:55555

transmission:
client:
type: transmission
address: transmission.local
port: 9091
user: username
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ telegram:
username: {{ TELEGRAM_PROXY_USERNAME }}
password: {{ TELEGRAM_PROXY_PASSWORD }}
{% endif %}
transmission:
address: {{ TRANSMISSION_SERVER }}
port: {{ TRANSMISSION_PORT }}
user: {{ TRANSMISSION_USERNAME }}
password: {{ TRANSMISSION_PASSWORD }}
client:
type: {{ TORRENT_CLIENT_TYPE }}
address: {{ TORRENT_CLIENT_SERVER }}
port: {{ TORRENT_CLIENT_PORT }}
user: {{ TORRENT_CLIENT_USERNAME }}
password: {{ TORRENT_CLIENT_PASSWORD }}
path:
{% for item in TRANSMISSION_PATH | from_json %}
{% for item in TORRENT_CLIENT_PATH | from_json %}
- category: {{ item['category'] }}
dir: '{{ item['dir'] }}'
{% endfor %}
Expand Down
File renamed without changes.
File renamed without changes.
122 changes: 66 additions & 56 deletions transmission_telegram_bot/bot.py → torrent_telegram_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,22 @@
import sys
import traceback
from base64 import b64decode, b64encode
from datetime import datetime
from functools import wraps
from pathlib import Path
from textwrap import dedent
from typing import Coroutine

import sentry_sdk
from emoji import emojize
from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
request,
)
from telegram.ext import (
ApplicationBuilder,
CallbackQueryHandler,
CommandHandler,
MessageHandler,
filters,
)

import transmission_telegram_bot.tools as tools
from transmission_telegram_bot import _version
from transmission_telegram_bot.db import DB
from transmission_telegram_bot.transmission import Transmission
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, request
from telegram.ext import ApplicationBuilder, CallbackQueryHandler, CommandHandler, MessageHandler, filters

import torrent_telegram_bot.tools as tools
from torrent_telegram_bot import _version
from torrent_telegram_bot.db import DB
from torrent_telegram_bot.qbittorrent import Qbittorrent
from torrent_telegram_bot.transmission import Transmission


def restricted(func):
Expand Down Expand Up @@ -72,7 +63,7 @@ async def text_message_action(update, context):


async def get_torrents(update, context, **kwargs):
global transmission
global client
torrents = []
try:
permission = tools.get_torrent_permission(config=cfg, chat_id=update.effective_chat.id)
Expand All @@ -89,10 +80,10 @@ async def get_torrents(update, context, **kwargs):
db_torrents = await db.get_torrent_by_uid(update.effective_chat.id)
if db_torrents:
for db_entry in db_torrents:
torrent = transmission.get_torrent(int(db_entry[1]))
torrent = client.get_torrent(db_entry[1])
torrents.append(torrent)
elif permission == "all":
torrents = transmission.get_torrents()
torrents = client.get_torrents()
return torrents


Expand All @@ -106,23 +97,23 @@ async def download_torrent_action(update, context):
context.user_data.update({"torrent_data": torrent_data})
category = tools.get_torrent_category(config=cfg, chat_id=update.effective_chat.id)

for transmission_path in cfg["transmission"]["path"]:
for client_path in cfg["client"]["path"]:
if category:
if transmission_path["category"] in category:
if client_path["category"] in category:
keyboard.append(
[
InlineKeyboardButton(
transmission_path["category"],
callback_data=f'download:{transmission_path["dir"]}',
client_path["category"],
callback_data=f'download:{client_path["dir"]}',
)
]
)
else:
keyboard.append(
[
InlineKeyboardButton(
transmission_path["category"],
callback_data=f'download:{transmission_path["dir"]}',
client_path["category"],
callback_data=f'download:{client_path["dir"]}',
)
]
)
Expand All @@ -139,7 +130,7 @@ async def download_torrent_action(update, context):

@restricted
async def download_torrent_logic(update, context):
global transmission
global client
callback_data = update.callback_query.data.replace("download:", "")
if callback_data == "cancel":
await context.bot.delete_message(
Expand All @@ -148,7 +139,7 @@ async def download_torrent_logic(update, context):
)
else:
try:
result = transmission.add_torrent(
result = client.add_torrent(
torrent_data=b64decode(context.user_data["torrent_data"]),
download_dir=callback_data,
)
Expand All @@ -174,13 +165,13 @@ async def download_torrent_logic(update, context):
await error_action(update, context)
else:
try:
await db.add_torrent(update.callback_query.message.chat.id, str(result.id))
await db.add_torrent(update.callback_query.message.chat.id, str(result.torrent_id))
except Exception:
await error_action(update, context)

logger.info(
f"User {update.effective_user.first_name} "
f"{update.effective_user.last_name}({update.effective_user.username}) "
f"{update.effective_user.last_name} ({update.effective_user.username}) "
f"added a torrent file {result.name} to the torrent client download queue "
f"with the path {callback_data}"
)
Expand All @@ -204,7 +195,14 @@ async def delete_torrent_action(update, context):
torrents = await get_torrents(update, context)
if len(torrents) > 0:
for torrent in torrents:
keyboard.append([InlineKeyboardButton(torrent.name, callback_data=f"delete:{torrent.id}")])
keyboard.append(
[
InlineKeyboardButton(
torrent.name,
callback_data=f"delete:{torrent.torrent_id}",
)
]
)
keyboard.append([InlineKeyboardButton("Cancel", callback_data="delete:cancel")])
try:
await context.bot.send_message(
Expand All @@ -223,7 +221,7 @@ async def delete_torrent_action(update, context):

@restricted
async def delete_torrent_logic(update, context): # noqa: C901
global transmission
global client
callback_data: str = update.callback_query.data.replace("delete:", "")
if callback_data == "cancel":
await context.bot.delete_message(
Expand All @@ -239,8 +237,10 @@ async def delete_torrent_logic(update, context): # noqa: C901
else:
torrent_name = ""
try:
torrent_name = transmission.get_torrent(int(callback_data)).name
transmission.remove_torrent(torrent_id=int(callback_data))
torrent = client.get_torrent(callback_data)
if torrent is not None:
torrent_name = torrent.name
client.remove_torrent(torrent_id=callback_data)
except Exception:
await error_action(update, context)
if torrent_name != "":
Expand All @@ -267,7 +267,9 @@ async def delete_torrent_logic(update, context): # noqa: C901
)
else:
try:
torrent_name = transmission.get_torrent(int(callback_data)).name
torrent = client.get_torrent(callback_data)
if torrent is not None:
torrent_name = torrent.name
except Exception:
await error_action(update, context)
else:
Expand All @@ -294,7 +296,7 @@ async def list_torrent_action(update, context, **kwargs):
if len(torrents) > 0:
try:
for torrent in torrents:
if not torrent.done_date:
if torrent.done_date == datetime.fromtimestamp(0):
try:
eta = str(torrent.eta)
except ValueError:
Expand All @@ -304,19 +306,19 @@ async def list_torrent_action(update, context, **kwargs):
*{torrent.name}*
Status: {torrent.status}
Procent: {round(torrent.progress, 2)}%
Speed: {tools.humanize_bytes(torrent.rate_download)}/s
Speed: {tools.humanize_bytes(torrent.download_speed)}/s
ETA: {eta}
Peers: {torrent.peers_sending_to_us}
Peers: {torrent.num_seeds}
"""
)
else:
torrent_info = dedent(
f"""
*{torrent.name}*
Status: {torrent.status}
Speed: {tools.humanize_bytes(torrent.rate_upload)}/s
Peers: {torrent.peers_getting_from_us}
Ratio: {torrent.upload_ratio}
Speed: {tools.humanize_bytes(torrent.download_speed)}/s
Peers: {torrent.num_seeds}
Ratio: {torrent.ratio}
"""
)
await context.bot.send_message(
Expand Down Expand Up @@ -404,7 +406,7 @@ async def error_action(update, context):


async def check_torrent_download_status(context): # noqa: C901
global transmission
global client
global db

if isinstance(db, Coroutine):
Expand All @@ -418,17 +420,17 @@ async def check_torrent_download_status(context): # noqa: C901
if torrents:
for torrent in torrents:
try:
task = transmission.get_torrent(int(torrent[1]))
task = client.get_torrent(torrent[1])
except Exception:
await db.remove_torrent_by_id(torrent[1])
else:
try:
if task.done_date:
if task is not None and task.done_date != datetime.fromtimestamp(0):
await db.complete_torrent(torrent[1])
except Exception as exc:
logger.error(f"{type(exc).__name__}({exc})")
else:
if task.done_date:
if task is not None and task.done_date != datetime.fromtimestamp(0):
response = f'Torrent "*{task.name}*" was successfully downloaded'
try:
notify_flag = False
Expand Down Expand Up @@ -464,7 +466,7 @@ async def check_torrent_download_status(context): # noqa: C901

def main():
global cfg
global transmission
global client
global db
global logger

Expand All @@ -476,10 +478,10 @@ def main():
logger = tools.init_log(debug=args.debug)

if not Path(args.config).is_file():
logger.error(f"Transmission telegram bot configuration file {args.config} not found")
logger.error(f"Torrent telegram bot configuration file {args.config} not found")
sys.exit()

logger.info("Starting transmission telegram bot")
logger.info("Starting torrent telegram bot")

try:
cfg = tools.get_config(args.config)
Expand All @@ -496,14 +498,22 @@ def main():
)

try:
transmission = Transmission(
address=cfg["transmission"]["address"],
port=cfg["transmission"]["port"],
user=cfg["transmission"]["user"],
password=cfg["transmission"]["password"],
)
if cfg["client"]["type"] == "transmission":
client = Transmission(
address=cfg["client"]["address"],
port=cfg["client"]["port"],
user=cfg["client"]["user"],
password=cfg["client"]["password"],
)
else:
client = Qbittorrent(
address=cfg["client"]["address"],
port=cfg["client"]["port"],
user=cfg["client"]["user"],
password=cfg["client"]["password"],
)
except Exception as exc:
logger.error(f"Transmission connection error: {exc}")
logger.error(f"Torrent client connection error: {exc}")
sys.exit(1)

try:
Expand Down
Loading

0 comments on commit f044f9d

Please sign in to comment.