From f340364cb6a95b9c8d9e1ec744c9f831f641abd7 Mon Sep 17 00:00:00 2001 From: VitaliyS Date: Sun, 13 Jun 2021 04:34:26 +0300 Subject: [PATCH 1/2] Add toloka.to tracker --- monitorrent/plugins/trackers/toloka.py | 166 +++++++++++++++++++++++++ src/content/images/toloka.to.png | Bin 0 -> 11501 bytes 2 files changed, 166 insertions(+) create mode 100755 monitorrent/plugins/trackers/toloka.py create mode 100755 src/content/images/toloka.to.png diff --git a/monitorrent/plugins/trackers/toloka.py b/monitorrent/plugins/trackers/toloka.py new file mode 100755 index 00000000..11f7cec6 --- /dev/null +++ b/monitorrent/plugins/trackers/toloka.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import re +import six +from requests import Session +import requests +from sqlalchemy import Column, Integer, String, ForeignKey +from monitorrent.db import Base, DBSession +from monitorrent.plugins import Topic +from monitorrent.plugin_managers import register_plugin +from monitorrent.utils.soup import get_soup +from monitorrent.plugins.trackers import TrackerPluginBase, WithCredentialsMixin, ExecuteWithHashChangeMixin, LoginResult + +PLUGIN_NAME = 'toloka.to' + +class TolokaCredentials(Base): + __tablename__ = "toloka_credentials" + + username = Column(String, primary_key=True) + password = Column(String, primary_key=True) + cookies_data = Column(String, nullable=True) + +class TolokaTopic(Topic): + __tablename__ = "toloka_topics" + + id = Column(Integer, ForeignKey('topics.id'), primary_key=True) + hash = Column(String, nullable=True) + + __mapper_args__ = { + 'polymorphic_identity': PLUGIN_NAME + } + +class TolokaLoginFailedException(Exception): + def __init__(self, code, message): + self.code = code + self.message = message + +class TolokaTracker(object): + tracker_settings = None + login_url = "https://toloka.to/login.php" + profile_page = "https://toloka.to/privmsg.php?folder=inbox" + _regex = re.compile(six.text_type(r'^https?://toloka.to/[a-z](\d+)$')) + + def __init__(self, cookies_data=None): + self.cookies_data = cookies_data + + def setup(self, cookies_data): + self.cookies_data = cookies_data + + def can_parse_url(self, url): + return self._regex.match(url) is not None + + def parse_url(self, url): + match = self._regex.match(url) + if match is None: + return None + cookies = self.get_cookies() + r = requests.get(url, cookies=cookies, allow_redirects=False, **self.tracker_settings.get_requests_kwargs()) + soup = get_soup(r.text) + if soup.h1 is None: + # toloka doesn't return 404 for not existing topic + # it return regular page with text 'Такої теми чи такого повідомлення не існує' + # and we can check it by not existing heading of the requested topic + return None + title = soup.h1.text.strip() + + return {'original_name': title} + + def login(self, username, password): + s = Session() + data = {"username": username, "password": password, 'login': 1} + if self.tracker_settings: + login_result = s.post(self.login_url, data, **self.tracker_settings.get_requests_kwargs()) + else: + login_result = s.post(self.login_url, data) + + if login_result.url.startswith(self.login_url): + raise TolokaLoginFailedException(1, "Invalid login or password") + else: + cookies_data = s.cookies.get('toloka_sid') + if not cookies_data: + raise TolokaLoginFailedException(2, "Failed to retrieve cookie") + + self.cookies_data = cookies_data + + def verify(self): + cookies = self.get_cookies() + if not cookies: + return False + profile_page_result = requests.get(self.profile_page, cookies=cookies, + **self.tracker_settings.get_requests_kwargs()) + return profile_page_result.url == self.profile_page + + def get_cookies(self): + if not self.cookies_data: + return False + return {'toloka_sid': self.cookies_data} + + def get_download_url(self, url): + cookies = self.get_cookies() + page = requests.get(url, cookies=cookies, **self.tracker_settings.get_requests_kwargs()) + page_soup = get_soup(page.content) + download = page_soup.find("a", {"class": "piwik_download"}) + return 'https://toloka.to/%s' % download.attrs['href'] + +class TolokaPlugin(WithCredentialsMixin, ExecuteWithHashChangeMixin, TrackerPluginBase): + tracker = TolokaTracker() + topic_class = TolokaTopic + credentials_class = TolokaCredentials + topic_form = [{ + 'type': 'row', + 'content': [{ + 'type': 'text', + 'model': 'display_name', + 'label': 'Name', + 'flex': 100 + }] + }] + + def login(self): + with DBSession() as db: + cred = db.query(self.credentials_class).first() + if not cred: + return LoginResult.CredentialsNotSpecified + username = cred.username + password = cred.password + if not username or not password: + return LoginResult.CredentialsNotSpecified + try: + self.tracker.login(username, password) + with DBSession() as db: + cred = db.query(self.credentials_class).first() + cred.cookies_data = self.tracker.cookies_data + return LoginResult.Ok + except TolokaLoginFailedException as e: + if e.code == 1: + return LoginResult.IncorrentLoginPassword + return LoginResult.Unknown + except Exception as e: + return LoginResult.Unknown + + def verify(self): + with DBSession() as db: + cred = db.query(self.credentials_class).first() + if not cred: + return False + username = cred.username + password = cred.password + if not username or not password or not cred.cookies_data: + return False + self.tracker.setup(cred.cookies_data) + return self.tracker.verify() + + def can_parse_url(self, url): + return self.tracker.can_parse_url(url) + + def parse_url(self, url): + return self.tracker.parse_url(url) + + def _prepare_request(self, topic): + headers = {'referer': topic.url, 'host': "toloka.to"} + cookies = self.tracker.get_cookies() + request = requests.Request('POST', self.tracker.get_download_url(topic.url), headers=headers, cookies=cookies) + return request.prepare() + +register_plugin('tracker', PLUGIN_NAME, TolokaPlugin()) diff --git a/src/content/images/toloka.to.png b/src/content/images/toloka.to.png new file mode 100755 index 0000000000000000000000000000000000000000..e4980b9040b6d10ab214be13d6435ca0c6e83abb GIT binary patch literal 11501 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 literal 0 HcmV?d00001 From e265348a2d59c0eccf0edfbe7a184b7bd3a5e7a6 Mon Sep 17 00:00:00 2001 From: VitaliyS Date: Sun, 13 Jun 2021 04:51:36 +0300 Subject: [PATCH 2/2] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0de6ef0b..70b22d65 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ This app can watch for torrent updates - support [kinozal.tv](http://kinozal.tv) topic tracking - support [hdclub.org](http://hdclub.org) topic tracking - support [anilibria.tv](https://www.anilibria.tv) topic tracking +- support [toloka.to](https://toloka.to) topic tracking ### Supported torrent clients: - support download torrent files to specified folder (downloader plugin)