commit 21a67c3562077d0a98eae25a1c9a9cbf4ac8d5a6 Author: AB Date: Sun May 24 15:35:15 2020 +0300 Init diff --git a/__pycache__/database.cpython-38.pyc b/__pycache__/database.cpython-38.pyc new file mode 100644 index 0000000..1657684 Binary files /dev/null and b/__pycache__/database.cpython-38.pyc differ diff --git a/__pycache__/rutracker.cpython-38.pyc b/__pycache__/rutracker.cpython-38.pyc new file mode 100644 index 0000000..a058601 Binary files /dev/null and b/__pycache__/rutracker.cpython-38.pyc differ diff --git a/data.sqlite b/data.sqlite new file mode 100644 index 0000000..daed304 Binary files /dev/null and b/data.sqlite differ diff --git a/database.py b/database.py new file mode 100644 index 0000000..8de0ffa --- /dev/null +++ b/database.py @@ -0,0 +1,293 @@ +""" +.. module:: models + :synopsis: Contains database action primitives. +.. moduleauthor:: AB +""" + +import sqlite3 +import logging + +log = logging.getLogger(__name__) + + +# class DataBase create or use existent SQLite database file. It provides +# high-level methods for database. +class DataBase: + """This class create or use existent SQLite database file. It provides + high-level methods for database.""" + def __init__(self, scheme, basefile='data.sqlite'): + """ + Constructor creates new SQLite database if + it doesn't exist. Uses SQL code from file for DB init. + :param scheme: sql filename + :type scheme: string + :return: None + """ + self.scheme = '' + self.basefile = basefile + try: + conn = self.connect(basefile=basefile) + except: + log.debug('Could not connect to DataBase.') + return None + with open(scheme, 'r') as scheme_sql: + sql = scheme_sql.read() + self.scheme = sql + if conn is not None: + try: + cursor = conn.cursor() + cursor.executescript(sql) + except Exception as e: + log.debug('Could not create scheme - %s', e) + else: + log.debug("Error! cannot create the database connection.") + log.info('DB created.') + self.close(conn) + + def connect(self, basefile): + """ + Create connect object for basefile + :param basefile: SQLite database filename + :type basefile: string + :return: sqlite3 connect object + """ + #log.debug("Open connection to %s", basefile) + return sqlite3.connect(basefile, check_same_thread=False) + + def execute(self, sql, params): + """ + Execute SQL code. First of all connect to self.basefile. Close + connection after execution. + :param sql: SQL code + :type sql: string + :return: list of response. Empty list when no rows are available. + """ + conn = self.connect(basefile=self.basefile) + log.debug("Executing: %s %s", sql, params) + cursor = conn.cursor() + cursor.execute(sql, params) + conn.commit() + result = cursor.fetchall() + self.close(conn) + return result + + def close(self, conn): + """ + Close connection object instance. + :param conn: sqlite3 connection object + :type conn: object + :return: None + """ + #log.debug("Close connection to %s", self.basefile) + conn.close() + + def add_mod(self, file_meta, author='Anonymous'): + secure_name = file_meta['secure_name'] + real_name = file_meta['real_name'] + mime = file_meta['mime'] + file_hash = file_meta['hash'] + title = file_meta['title'] + sample = file_meta['sample'] + message = file_meta['message'] + metaphone = file_meta['metaphone'] + sql = """INSERT OR IGNORE INTO + mods('secure_name', 'real_name', 'mime', 'hash', + 'author', 'title', 'sample', 'message', 'metaphone') + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""" + self.execute(sql, ( + secure_name, + real_name, + mime, + file_hash, + author, + title, + sample, + message, + metaphone, + )) + return True + + def get_mods(self, limit, offset): + sql = """SELECT + rowid, real_name, title, mime, + strftime('%s', date) as str_time, author, date, hash, secure_name + FROM mods LIMIT ?,?""" + mods = list() + result = self.execute(sql, (offset, limit)) + for mod in result: + mods.append( + { + 'id': mod[0], + 'real_name': mod[1], + 'title': mod[2], + 'mimetype': mod[3], + 'str_time': mod[4], + 'author': mod[5], + 'time': mod[6], + 'hash': mod[7], + 'secure_name': mod[8], + } + ) + return mods + + def get_mod(self, mod_id): + sql = """SELECT + rowid, real_name, secure_name, mime, + strftime('%s', date) as str_time, author, date, + hash, title, sample, message + FROM mods WHERE rowid = ?""" + result = self.execute(sql, (mod_id,)) + if result: + meta = result[0] + mod = { + 'id': meta[0], + 'real_name': meta[1], + 'secure_name': meta[2], + 'mimetype': meta[3], + 'time': meta[4], + 'author': meta[5], + 'str_time': meta[6], + 'hash': meta[7], + 'title': meta[8], + 'sample': meta[9], + 'message': meta[10], + } + else: + mod = list() + return mod + + def find_mod(self, param=None): + """ + Looking for mod dublicates. + :param param: name or hash of module to search. + :type param: string + :return: list + """ + sql = """SELECT rowid FROM mods WHERE real_name == ? OR + hash == ? ORDER BY rowid DESC LIMIT 1""" + result = self.execute(sql, (param, param)) + return result + + def search(self, query): + """ + Perform module search through the base. + """ + sql = """SELECT rowid, secure_name, title, mime, date, + strftime('%s', date) as str_time FROM mods + WHERE + secure_name LIKE ? OR + title LIKE ? OR + message LIKE ? OR + sample LIKE ?""" + query_mask = f"%{query}%" + result = self.execute(sql, tuple(query_mask for i in range(0, 4))) + log.debug(result) + return result + + def signin(self, name, password): + """ + auth client + """ + result = {"status": False, 'message': 'User is invalid.'} + sql = "SELECT name, password FROM users WHERE name = ?" + ret = self.execute(sql, (name,)) + if len(ret) == 0: + result = {'status': False, 'message': 'User doesn\'t exist'} + elif len(ret) == 1: + stored_hash = ret[0][1] + print(stored_hash, password) + print(verify_password(stored_hash, password)) + if verify_password(stored_hash, password): + result = {"status": True, 'message': 'User is valid.'} + return result + + def copy_to_history(self, tor_id): + sql = "SELECT * FROM torrents WHERE id = ?" + attrs = self.execute(sql, (tor_id,))[0] + print(attrs) + sql = """INSERT OR IGNORE INTO torrents_history( + 'id', + 'info_hash', + 'forum_id', + 'poster_id', + 'size', + 'reg_time', + 'tor_status', + 'seeders', + 'topic_title', + 'seeder_last_seen' + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? )""" + self.execute(sql, attrs) + + def get_attr(self, tor_id, attr): + sql = """SELECT %s FROM torrents WHERE id = ? ORDER BY reg_time DESC LIMIT 1""" % attr + return self.execute(sql, (tor_id,))[0][0] + + def update(self, tor_data): + self.copy_to_history(tor_data["id"]) + sql = """UPDATE torrents SET + 'info_hash' = ?, + 'forum_id' = ?, + 'poster_id' = ?, + 'size' = ?, + 'reg_time' = ?, + 'tor_status' = ?, + 'seeders' = ?, + 'topic_title' = ?, + 'seeder_last_seen' = ? + WHERE id = ? + """ + self.execute(sql, ( + tor_data["info_hash"], + tor_data["forum_id"], + tor_data["poster_id"], + int(tor_data["size"]), + int(tor_data["reg_time"]), + tor_data["tor_status"], + tor_data["seeders"], + tor_data["topic_title"], + tor_data["seeder_last_seen"], + tor_data["id"], + )) + + def save_tor(self, tor_data, chat_instance): + sql = """INSERT OR IGNORE INTO torrents( + 'id', + 'info_hash', + 'forum_id', + 'poster_id', + 'size', + 'reg_time', + 'tor_status', + 'seeders', + 'topic_title', + 'seeder_last_seen', + 'user_id' + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? )""" + self.execute(sql, ( + tor_data["id"], + tor_data["info_hash"], + tor_data["forum_id"], + tor_data["poster_id"], + int(tor_data["size"]), + int(tor_data["reg_time"]), + tor_data["tor_status"], + tor_data["seeders"], + tor_data["topic_title"], + tor_data["seeder_last_seen"], + chat_instance['id'], + )) + sql = """INSERT OR IGNORE INTO users( + 'id', + 'username', + 'first_name', + 'last_name' + ) VALUES (?, ?, ?, ?)""" + self.execute(sql, ( + chat_instance['id'], + chat_instance['username'], + chat_instance['first_name'], + chat_instance['last_name'], + )) + return True + diff --git a/main.py b/main.py new file mode 100644 index 0000000..6de5e88 --- /dev/null +++ b/main.py @@ -0,0 +1,61 @@ +from rutracker import Torrent +from datetime import datetime +from database import DataBase +from telegram import * +from telegram.ext import Updater, MessageHandler, CommandHandler, filters +from urllib import parse +import logging + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +log = logging.getLogger(__name__) + +def sizeof_fmt(num, suffix='B'): + for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: + if abs(num) < 1024.0: + return "%3.1f%s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s%s" % (num, 'Yi', suffix) + +def main(): + """Run bot.""" + + def add(update, context): + if 'https://rutracker.org' in update.message.text: + try: + tor_id = parse.parse_qs(parse.urlsplit(update.message.text).query)['t'][0] + except KeyError: + log.warn("URL provided doesn't contains any torrent id.") + update.message.reply_text("URL provided doesn't contains any torrent id.") + return + else: + update.message.reply_text("Send me a URL to rutracker.org topic.") + return + log.debug(update.message.chat) + torrent = Torrent(tor_id, update.message.chat) + reg_time = datetime.utcfromtimestamp(int(torrent.meta['reg_time']) + ).strftime('%b-%d') + msg = f"""{torrent.meta['topic_title']} +Size: {sizeof_fmt(torrent.meta['size'])} +Hash: {torrent.meta['info_hash']} +Updated: {reg_time}""" + log.info(msg) + update.message.reply_text(msg, parse_mode='HTML') + + + def hello(update, context): + update.message.reply_text( + 'Hello {}'.format(update.message.from_user.first_name)) + + + updater = Updater("539189256:AAHqrL6nTmv5g0mGoPQownO0vrNRvhPFq7I", use_context=True) + + updater.dispatcher.add_handler(MessageHandler(filters.Filters.text, add)) + + updater.start_polling() + updater.idle() + + +if __name__ == '__main__': + main() diff --git a/rutracker.py b/rutracker.py new file mode 100644 index 0000000..db50679 --- /dev/null +++ b/rutracker.py @@ -0,0 +1,37 @@ +import urllib.request, json +from database import DataBase +import logging +import re + +log = logging.getLogger(__name__) + +class Torrent: + def __init__(self, tor_id, chat_instance): + self.db = DataBase("scheme.sql") + self.api_url = "http://api.rutracker.org/v1/" + self.meta = self.get_tor_topic_data(tor_id) + log.debug("Torrent info: %s", self.meta) + self.db.save_tor(self.meta, chat_instance) + + def get_tor_topic_data(self, tor_id): + data = dict() + with urllib.request.urlopen( + "{}/get_tor_topic_data?by=topic_id&val={}".format( + self.api_url, tor_id)) as url: + data = json.loads(url.read().decode()) + data = data["result"][tor_id] + data["id"] = tor_id + return data + + def is_outdated(self): + stored_reg_time = int(self.db.get_attr(self.meta["id"], 'reg_time')) + actual_reg_time = self.meta["reg_time"] + return actual_reg_time != stored_reg_time + + def update(self): + self.db.update(self.meta) + + def episodes(self): + ep_str = re.search(r"\[\d+(\+\d+)?(-\d+)?( +)?(из)?( +)?\d+(\+\d+)?(-\d+)?\]", self.meta["topic_title"]).group(0) + return ep_str + diff --git a/scheme.sql b/scheme.sql new file mode 100644 index 0000000..4c1750e --- /dev/null +++ b/scheme.sql @@ -0,0 +1,37 @@ +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "torrents" ( + "id" TEXT NOT NULL, + "info_hash" TEXT NOT NULL, + "forum_id" TEXT NOT NULL, + "poster_id" TEXT, + "size" INT NOT NULL, + "reg_time" INT NOT NULL, + "tor_status" TEXT, + "seeders" TEXT, + "topic_title" TEXT, + "seeder_last_seen" TEXT, + "user_id" TEXT, + PRIMARY KEY("id") +); +CREATE TABLE IF NOT EXISTS "torrents_history" ( + rowid INTEGER PRIMARY KEY AUTOINCREMENT, + "id" TEXT NOT NULL, + "info_hash" TEXT NOT NULL, + "forum_id" TEXT NOT NULL, + "poster_id" TEXT, + "size" INT NOT NULL, + "reg_time" INT NOT NULL, + "tor_status" TEXT, + "seeders" TEXT, + "topic_title" TEXT, + "user_id" TEXT, + "seeder_last_seen" TEXT +); +CREATE TABLE IF NOT EXISTS "users" ( + id INTEGER PRIMARY KEY, + username TEXT, + first_name TEXT, + last_name TEXT + ); +COMMIT; +