Skip to content

Commit

Permalink
merge(release/1.1.12): qBittorrent API 2.0, custom thumbnails for los…
Browse files Browse the repository at this point in the history
…tfilm.tv, cloudflare bypass for lostfilm.tv
  • Loading branch information
werwolfby committed Mar 15, 2020
2 parents 70c6729 + 66dd5d4 commit 2dad33b
Show file tree
Hide file tree
Showing 25 changed files with 280 additions and 315 deletions.
17 changes: 9 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
os: linux
dist: bionic
language: python
matrix:
jobs:
include:
- python: 3.5
- python: 3.6
- python: 3.7
dist: xenial
sudo: true
- python: 3.8
install:
- pip install -r requirements-dev.txt
- pip install codecov
- pip install -r requirements-dev.txt
- pip install codecov
script: py.test --cov=monitorrent --cov-report html --html=report.html -rw tests
after_success:
- coveralls
- codecov
after_success:
- coveralls
- codecov
notifications:
slack:
secure: DFYN1rpoR5vZxvBSvkC6kITKHbFEvnrVxkMNsjKEFP30yb3p9x4rjlqXWBaBlo/Q/tBI7zlRmALbcoRlmsffB70LY6DG6UIumBG805ML58gzSyA9fhxvkwAJ7Fr71rVcbu1Cg3SqHsKTYw1fdrLhXyB5Brv1VfmigOMI+NKJ0zZjEY7dvSzNLfzOViiBiafpYUXN6Qekx4mR4X7PcZBAMga0zz7kGM9HNTyn1FQl8DhRJ8zRrYn6o/zb5nESLFLs2RKz042zlw9/sDTF6zC51kg00V96UpVr3pD4M6zb/DNO6Z8FWzEPgTnlHKCRgxd3IGxy7dxUFGDvCelY38zO47zibdu9hNjfmhD+To3Imqq82m0gI1wMyg887a8bkonI2hQ1k9H+D3LveziCsmpCdPaO9z9hJ1AxSJ1ktVQhiEnsyWQAbRppOSHG0I9KpiUYKQiIJo/W6izXyJdXuNxa8qqi1OPjAAR5Y4z51KgkhlAMlSr0XyxIdy3vBgiG2G5mLNyZIq27h2iecjUhOyFXqbtaAkB8j3PMQ6EdBA7b2WmOQsLtEe6Ez0Rluo4lNYjyJDQtyyurYS4ez1S/NahhFtal1C6JKG9iN4yW5pK172Urqph8wIthzwcI7fU9xRk5Gy0dybnlgzE1PK5fjOuVlhXE2gTH+stNmH/m5HN1Fj0=
2 changes: 1 addition & 1 deletion MonitorrentInstaller/MonitorrentInstaller/Product.wxs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="Monitorrent" Language="1033" Version="1.1.11.100" Manufacturer="Monitorrent Team" UpgradeCode="dd4cf505-1e44-4311-a8f2-efcf097175a7">
<Product Id="*" Name="Monitorrent" Language="1033" Version="1.1.12.100" Manufacturer="Monitorrent Team" UpgradeCode="dd4cf505-1e44-4311-a8f2-efcf097175a7">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." AllowSameVersionUpgrades="yes"/>
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ x86: https://hub.docker.com/r/werwolfby/alpine-monitorrent/
[![](https://images.microbadger.com/badges/version/werwolfby/alpine-monitorrent.svg)](https://microbadger.com/images/werwolfby/alpine-monitorrent "Get your own version badge on microbadger.com")

### Windows Installer:
https://github.com/werwolfby/monitorrent/releases/download/1.1.11/MonitorrentInstaller-1.1.11.msi
https://github.com/werwolfby/monitorrent/releases/download/1.1.12/MonitorrentInstaller-1.1.12.msi

### Manual Install

Requirements:
- Python 3.x and pip

Download latest build: https://github.com/werwolfby/monitorrent/releases/download/1.1.11/monitorrent-1.1.11.zip
Download latest build: https://github.com/werwolfby/monitorrent/releases/download/1.1.12/monitorrent-1.1.12.zip
Extract into **monitorent** folder
* pip install -r requirements.txt
* python server.py
Expand Down
12 changes: 4 additions & 8 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,12 @@ environment:
# a later point release.
# See: http://www.appveyor.com/docs/installed-software#python

- PYTHON: "C:\\Python37"
PYTHON_VERSION: "3.7.x"
PYTHON_ARCH: "32"

- PYTHON: "C:\\Python36"
PYTHON_VERSION: "3.6.x"
- PYTHON: "C:\\Python38"
PYTHON_VERSION: "3.8.x"
PYTHON_ARCH: "32"

- PYTHON: "C:\\Python35"
PYTHON_VERSION: "3.5.x"
- PYTHON: "C:\\Python37"
PYTHON_VERSION: "3.7.x"
PYTHON_ARCH: "32"

install:
Expand Down
2 changes: 1 addition & 1 deletion monitorrent/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.1.11'
__version__ = '1.1.12'
5 changes: 5 additions & 0 deletions monitorrent/plugin_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ def get_watching_topics(self):
topic['info'] = tracker.get_topic_info(dbtopic)
topic['tracker'] = dbtopic.type
topic['status'] = dbtopic.status.__str__()

if hasattr(tracker, 'get_thumbnail_url'):
dbtopic = db.query(tracker.topic_class).filter(Topic.id == dbtopic.id).first()
topic['thumbnail_url'] = tracker.get_thumbnail_url(dbtopic)

watching_topics.append(topic)
return watching_topics

Expand Down
109 changes: 51 additions & 58 deletions monitorrent/plugins/clients/qbittorrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,16 @@
from __future__ import print_function
from __future__ import unicode_literals

import json
import six

import requests
from io import BytesIO

from pytz import reference, utc
from pytz import utc
from sqlalchemy import Column, Integer, String

from qbittorrentapi import Client

from monitorrent.db import Base, DBSession
from monitorrent.plugin_managers import register_plugin
from datetime import datetime
import dateutil.parser


class QBittorrentCredentials(Base):
Expand Down Expand Up @@ -59,9 +56,9 @@ class QBittorrentClientPlugin(object):
}]
DEFAULT_PORT = 8080
SUPPORTED_FIELDS = ['download_dir']
REQUEST_FORMAT = "{0}:{1}/"
ADDRESS_FORMAT = "{0}:{1}"

def _get_params(self):
def _get_client(self):
with DBSession() as db:
cred = db.query(QBittorrentCredentials).first()

Expand All @@ -72,14 +69,11 @@ def _get_params(self):
cred.port = self.DEFAULT_PORT

try:
session = requests.Session()
target = self.REQUEST_FORMAT.format(cred.host, cred.port)
payload = {"username": cred.username, "password": cred.password}
response = session.post(target + "login",
data=payload)
if response.status_code != 200 or response.text == 'Fails.':
return False
return {'session': session, 'target': target}
address = self.ADDRESS_FORMAT.format(cred.host, cred.port)

client = Client(host=address, username=cred.username, password=cred.password)
client.app_version()
return QBittorrentClientPlugin._decorate_post(client)
except Exception as e:
return False

Expand All @@ -102,81 +96,80 @@ def set_settings(self, settings):
cred.password = settings.get('password', None)

def check_connection(self):
return self._get_params()
return self._get_client()

def find_torrent(self, torrent_hash):
parameters = self._get_params()
if not parameters:
client = self._get_client()
if not client:
return False

try:
# qbittorrent uses case sensitive lower case hash
torrent_hash = torrent_hash.lower()
torrents = parameters['session'].get(parameters['target'] + "query/torrents")
array = json.loads(torrents.text)
torrent = next(torrent for torrent in array if torrent['hash'] == torrent_hash)
if torrent:
time = torrent.get('added_on', None)
result_date = None
if time is not None:
if isinstance(time, six.string_types):
result_date = dateutil.parser.parse(time).replace(tzinfo=reference.LocalTimezone())\
.astimezone(utc)
else:
result_date = datetime.fromtimestamp(time, utc)
torrents = client.torrents_info(hashes=[torrent_hash.lower()])
if torrents:
time = torrents[0].info.added_on
result_date = datetime.fromtimestamp(time, utc)
return {
"name": torrent['name'],
"name": torrents[0].name,
"date_added": result_date
}
return False
except Exception as e:
return False

def get_download_dir(self):
parameters = self._get_params()
if not parameters:
client = self._get_client()
if not client:
return None

try:
response = parameters['session'].get(parameters['target'] + 'query/preferences')
response.raise_for_status()
result = response.json()
return six.text_type(result['save_path'])
result = client.app_default_save_path()
return six.text_type(result)
except:
return None

def add_torrent(self, torrent, torrent_settings):
"""
:type torrent_settings: clients.TopicSettings | None
"""
parameters = self._get_params()
if not parameters:
client = self._get_client()
if not client:
return False

try:
files = {"torrents": BytesIO(torrent)}
data = None
if torrent_settings is not None:
data = {}
if torrent_settings.download_dir is not None:
data['savepath'] = torrent_settings.download_dir
r = parameters['session'].post(parameters['target'] + "command/upload", data=data, files=files)
return r.status_code == 200
savepath = None
auto_tmm = None
if torrent_settings is not None and torrent_settings.download_dir is not None:
savepath = torrent_settings.download_dir
auto_tmm = False

res = client.torrents_add(save_path=savepath, use_auto_torrent_management=auto_tmm, torrent_contents=[('file.torrent', torrent)])
return res
except:
return False

# TODO switch to remove torrent with data
def remove_torrent(self, torrent_hash):
parameters = self._get_params()
if not parameters:
client = self._get_client()
if not client:
return False

try:
#qbittorrent uses case sensitive lower case hash
torrent_hash = torrent_hash.lower()
payload = {"hashes": torrent_hash}
r = parameters['session'].post(parameters['target'] + "command/delete", data=payload)
return r.status_code == 200
client.torrents_delete(hashes=[torrent_hash.lower()])
return True
except:
return False

@staticmethod
def _decorate_post(client):
def _post_decorator(func):
def _post_wrapper(*args, **kwargs):
if 'torrent_contents' in kwargs:
kwargs['files'] = kwargs['torrent_contents']
del kwargs['torrent_contents']
return func(*args, **kwargs)
return _post_wrapper

client._post = _post_decorator(client._post)
return client


register_plugin('client', 'qbittorrent', QBittorrentClientPlugin())
27 changes: 17 additions & 10 deletions monitorrent/plugins/trackers/lostfilm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import sys
import re
import requests
import cloudscraper
import traceback
import six
from enum import Enum
Expand All @@ -20,6 +21,7 @@

PLUGIN_NAME = 'lostfilm.tv'

scraper = cloudscraper.create_scraper()

class LostFilmTVSeries(Topic):
__tablename__ = "lostfilmtv_series"
Expand Down Expand Up @@ -185,7 +187,7 @@ def upgrade_3_to_4(engine, operations_factory):
tracker_settings = settings_manager.tracker_settings

old_url = 'https://www.lostfilm.tv/browse.php?cat={0}'.format(cat)
url_response = requests.get(old_url, **tracker_settings.get_requests_kwargs())
url_response = scraper.get(old_url, **tracker_settings.get_requests_kwargs())

soup = get_soup(url_response.text)
meta_content = soup.find('meta').attrs['content']
Expand All @@ -194,7 +196,7 @@ def upgrade_3_to_4(engine, operations_factory):
if redirect_url.startswith('/'):
redirect_url = redirect_url[1:]

redirect_url = u'http://www.lostfilm.tv/{0}'.format(redirect_url)
redirect_url = u'https://www.lostfilm.tv/{0}'.format(redirect_url)
url = LostFilmShow.get_seasons_url(redirect_url)

if url is None:
Expand Down Expand Up @@ -452,7 +454,7 @@ class LostFilmTVTracker(object):

login_url = "https://login1.bogi.ru/login.php?referer=https%3A%2F%2Fwww.lostfilm.tv%2F"
profile_url = 'https://www.lostfilm.tv/my.php'
download_url_pattern = 'https://www.lostfilm.tv/v_search.php?c={cat}&s={season}&e={episode:02d}'
download_url_pattern = 'https://www.lostfilm.tv/v_search.php?a={cat}{season:03d}{episode:03d}'
netloc = 'www.lostfilm.tv'

def __init__(self, session=None):
Expand All @@ -462,21 +464,23 @@ def setup(self, session=None):
self.session = session

def login(self, email, password):
params = {"act": "users", "type": "login", "mail": email, "pass": password, "rem": 1}
response = requests.post("http://www.lostfilm.tv/ajaxik.php", params, verify=False)
params = {"act": "users", "type": "login", "mail": email, "pass": password, "rem": 1, "need_captcha": "", "captcha": ""}
response = scraper.post("https://www.lostfilm.tv/ajaxik.php", params)

result = response.json()
if 'error' in result:
raise LostFilmTVLoginFailedException(result['error'])
if 'need_captcha' in result:
raise LostFilmTVLoginFailedException('Captcha requested. Nothing can do about it for now, sorry :(')

self.session = response.cookies['lf_session']

def verify(self):
cookies = self.get_cookies()
if not cookies:
return False
my_settings_url = 'http://www.lostfilm.tv/my_settings'
r1 = requests.get(my_settings_url, headers=self._headers, cookies=cookies,
my_settings_url = 'https://www.lostfilm.tv/my_settings'
r1 = scraper.get(my_settings_url, headers=self._headers, cookies=cookies,
**self.tracker_settings.get_requests_kwargs())
return r1.url == my_settings_url and '<meta http-equiv="refresh" content="0; url=/">' not in r1.text

Expand All @@ -496,7 +500,7 @@ def parse_url(self, url, parse_series=False):
if url is None:
return None

response = requests.get(url, headers=self._headers, allow_redirects=False,
response = scraper.get(url, headers=self._headers, allow_redirects=False,
**self.tracker_settings.get_requests_kwargs())
if response.status_code != 200 or response.url != url \
or '<meta http-equiv="refresh" content="0; url=/">' in response.text:
Expand Down Expand Up @@ -572,14 +576,14 @@ def parse_download(table):
cookies = self.get_cookies()

download_redirect_url = self.download_url_pattern.format(cat=cat, season=season, episode=episode)
download_redirect = requests.get(download_redirect_url, headers=self._headers, cookies=cookies,
download_redirect = scraper.get(download_redirect_url, headers=self._headers, cookies=cookies,
**self.tracker_settings.get_requests_kwargs())

soup = get_soup(download_redirect.text)
meta_content = soup.find('meta').attrs['content']
download_page_url = meta_content.split(';')[1].strip()[4:]

download_page = requests.get(download_page_url, headers=self._headers,
download_page = scraper.get(download_page_url, headers=self._headers,
**self.tracker_settings.get_requests_kwargs())

soup = get_soup(download_page.text)
Expand Down Expand Up @@ -681,6 +685,9 @@ def prepare_add_topic(self, url):

return settings

def get_thumbnail_url(self, dbtopic):
return "https://static.lostfilm.tv/Images/{0}/Posters/icon.jpg".format(dbtopic.cat)

def login(self):
"""
:rtype: LoginResult
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "monitorrent",
"version": "1.1.11",
"version": "1.1.12",
"description": "",
"main": "index.js",
"directories": {
Expand Down
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
pytz==2018.9
cheroot==6.5.4
requests==2.21.0
SQLAlchemy==1.3.2
cloudscraper==1.2.18
SQLAlchemy==1.3.12
SQLAlchemy-Enum34==1.0.1
transmissionrpc==0.11
beautifulsoup4==4.7.1
deluge-client==1.7.0
qbittorrent-api==0.5.1
feedparser==5.2.1
alembic==1.0.8
falcon==1.4.1
Expand Down
Loading

0 comments on commit 2dad33b

Please sign in to comment.