diff --git a/requirements.txt b/requirements.txt index 1bd67ec..63ed28b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +aiofiles>=24.1.0 aiohappyeyeballs>=2.3.4 aiohttp>=3.10.0 aiosignal>=1.3.1 @@ -5,4 +6,5 @@ attrs>=23.2.0 frozenlist>=1.4.1 idna>=3.7 multidict>=6.0.5 +python-magic>=0.4.27 yarl>=1.9.4 diff --git a/setup.py b/setup.py index 285bbb3..dff59ec 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,10 @@ setup( author="ORUDO", author_email="root@orudo.ru", description="A simple Telegram bot that will allow you to upload torrent files / magnet links to a remote Torrent server (qBitTorrent, Transmission, etc.)", - install_requires=["aiohttp>=3.10.0"], - packages=["tubot", "tubot.static"], + install_requires=[ + "aiohttp>=3.10.0", + "aiofiles>=24.1.0", + "aiofiles>=24.1.0" + ], + packages=["tubot", "tubot.static", "tubot.torrent"], ) diff --git a/tubot/torrent/abc.py b/tubot/torrent/abc.py new file mode 100644 index 0000000..1eb13f0 --- /dev/null +++ b/tubot/torrent/abc.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +###################################################### +# Abstract methods and interfaces for torrent module # +###################################################### + +from abc import ABC, abstractmethod +from tubot.static.abc import IValidatable +from tubot.torrent.types import TorrentTypes + + +class TorrentObj(IValidatable, ABC): + """ + Abstract class of torrent object + """ + + _ttype: TorrentTypes # Torrent type property + dest: str + content: str + + @property + def torrent_type(self) -> TorrentTypes: + """ + :return: Torrent type + """ + if self._ttype is None: + raise NotImplementedError("Torrent type not implemented") + return self._ttype + + def __init__(self, content: str, destination: str) -> None: + """ + :param content: Torrent content (link, file path) + :param destination: Download directory + """ + self.content = content + self.dest = destination diff --git a/tubot/torrent/torrents.py b/tubot/torrent/torrents.py new file mode 100644 index 0000000..483a9a5 --- /dev/null +++ b/tubot/torrent/torrents.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +################################# +# Torrent types implementations # +################################# + +# Imports +from tubot.torrent.types import TorrentTypes +from tubot.torrent.abc import TorrentObj +from aiofiles import open, ospath +from magic import Magic +from re import match +from urllib.parse import urlparse + + +class TorrentFile(TorrentObj): + """ + .torrent file + """ + + _ttype = TorrentTypes.File + + async def __validate__(self) -> bool: + if await ospath.isfile(self.content): + mime = Magic(mime=True).from_file(self.content) + if mime == "application/x-bittorrent": + return True + return False + + async def getbytes(self) -> bytes: + async with open(self.content, "rb") as dottorrent: + return await dottorrent.read() + + +class TorrentMagnet(TorrentObj): + """ + Torrent magnet link + """ + + _ttype = TorrentTypes.Magnet + + async def __validate__(self) -> bool: + pattern = r"^magnet:\?xt=urn:btih:[a-fA-F0-9]{40}.*$" + if match(pattern, self.content): + return True + return False + + +class TorrentURL(TorrentObj): + """ + Http(s) link to .torrent file + """ + + _ttype = TorrentTypes.URL + + async def __validate__(self) -> bool: + try: + parse = urlparse(self.content) + return all([parse.scheme, parse.netloc]) + except (TypeError, AttributeError): + return False diff --git a/tubot/torrent/types.py b/tubot/torrent/types.py new file mode 100644 index 0000000..9a28f87 --- /dev/null +++ b/tubot/torrent/types.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +############################ +# Types for torrent module # +############################ + +# Imports +from enum import Enum + + +class TorrentTypes(Enum): + """ + Types of torrents + """ + + File = ".torrent file" + Magnet = "torrent magnet link" + URL = "http(s) link to .torrent file"