class RuTracker: def __init__(self, expire=0, size=0): self.cache_catalog = Cache('rutracker_catalog.db') self.cache_profile = Cache('rutracker_profile.db', expire, size) self.http = RuTrackerHTTP() self.re = { 'is_int': re.compile(r'^([0-9]+)', re.U), 'is_float': re.compile(r'^([0-9]{1,10}\.[0-9]+)', re.U) } self.html = Clear() self.status = { '%': 'check', 'D': 'repeat', '!': 'nodesc', '©': 'copyright', '∏': 'moder', 'x': 'close', '∑': 'absorb', '#': 'doubtful', '*': 'nocheck', '?': 'neededit', 'T': 'temp', '√': 'ok' } # API def get(self, id=None, page=1): """ Получение списка директорий и раздач На вход функции надо подавать следующие параметры: id - [int] id директории page - [int] номер страницы Возвращает словарь, состоящий из следующих полей: pages - [list] кортеж [int] для навигации = (кол-во страниц, предыдущая, текущая, следующая) data - [list] данные выборки, состоящая из следующих полей: id - [int] id (для директорий и топиков ID могут совпадать) name - [str] имя записи type - [str] тип записи (torrent - торрент, folder - директория) size - [int] размер раздачи в байтах seeder - [int] кол-во сидов leecher - [int] кол-во личеров download - [int] кол-во скачиваний торрента comment - [int] кол-во комментариев status - [str] символ отметки модератора status_human - [str] отметка модератора в удобочитаемом виде (описание смотри ниже). Описание возвращаемых отметок status_human: Скачать торрент нельзя (красный): moder - премодерация, check - проверяется, repeat - повтор, nodesc - не оформлено, copyright - закрыто правообладателем, close - закрыто, absorb - поглощено. Скачать торрент можно (желтый): nocheck - не проверено, neededit - недооформлено, doubtful - сомнительно, temp - временная Скачать торрент можно (зеленый): ok - проверено """ # INDEX if id is None: html = self.http.get('http://rutracker.org/forum/index.php') if not html: return html res = [] r = re.compile(r'<div\sid="forums_wrap">(.+)<div\sid="board_stats">', re.U|re.S).search(html) if r: r = re.compile(r'<h4\sclass="forumlink"><a\shref="\./viewforum\.php\?f=([0-9]+)">(.+?)</a></h4>', re.U|re.S).findall(r.group(1)) if r: res = [{'id': int(i), 'name': self.html.string(x), 'type': 'folder'} for i, x in r] if not res: return None return {'pages': (1, 0, 1, 0), 'data': res} else: page_query = '' if page > 1: page_query = '&start=' + str(50*(page-1)) html = self.http.get('http://rutracker.org/forum/viewforum.php?f=' + str(id) + '&sort=2' + page_query) if not html: return html pages = self._compile_pages(html) folder = [] torrent = [] group_list = re.compile(r'<table class="forumline forum">(.+?)</table>', re.U|re.S).findall(html) if group_list: for group in group_list: # вытаскиваем папки (если есть) r = re.compile(r'<h4\sclass="forumlink"><a\shref="viewforum\.php\?f=([0-9]+)">(.+?)</a></h4>', re.U|re.S).findall(group) if r: folder.extend([{'id': int(i), 'name': self.html.string(x), 'type': 'folder'} for i, x in r]) # нарубаем на строчки топиков topic_list = group.split(u'topicSep">') if len(topic_list) > 1: topic_list = topic_list[1:] for html in topic_list: # вытаскиваем id for text in re.compile(r'<tr\sid="tr\-[0-9]+"(.+?)</tr>', re.U|re.S).findall(html): item = self._compile_topic(text) if item: torrent.append(item) folder.extend(torrent) return {'pages': pages, 'data': folder} def search(self, search, folder=None, index=None, ignore=None): """ Поиск по РуТрекеру На вход функции надо подавать следующие параметры: search - [str] поисковая строка запроса (Unicode) folder - [list] список ID директорий, в которых необходимо искать (None - искать везде) Возвращает словарь, аналогичный выводу метода GET """ if isinstance(search, unicode): search = search.encode('windows-1251') # проверяем авторизацию html = self.http.get('http://rutracker.org/forum/index.php') if not html: return html # готовим запрос для получения дерева разделов if folder: if not isinstance(folder, list) and not isinstance(folder, tuple): folder = [folder] else: if index is not None: if not isinstance(index, list) and not isinstance(index, tuple): index = [index] if not isinstance(ignore, list) and not isinstance(ignore, tuple): ignore = [ignore] if not index and not ignore: folder = [] else: folder = self._load_catalog(index, ignore) if not folder: return folder # готовим запрос params = [('nm', search), ('o', 10), ('s', 2), ('prev_my', 0), ('prev_new', 0), ('prev_oop', 0), ('submit', r'Поиск')] params.extend([('f[]', x) for x in folder]) # делаем поиск html = self.http.post('http://rutracker.org/forum/tracker.php', params) if not html: return html res = [] table = re.compile('id="tor\-tbl">(.+?)</table>', re.U|re.S).search(html) if table: for tr in re.compile('<tr\sclass="tCenter\shl\-tr">(.+?)</tr>', re.U|re.S).findall(table.group(1)): item = self._compile_topic(tr, True) if item: res.append(item) return {'pages': (1, 0, 1, 0), 'data': res} def profile(self, id): """ Получение дополнительной информации о раздачи На вход функции надо подавать следующие параметры: id - [int] id топика с раздачей Возвращает словарь, состоящий из: descript - [str] описание на RuTracker cover - [str] url обложки screenshot - [list] Список url скриншотов """ return self.cache_profile.get('profile:' + str(id), self._profile, id) def comment(self, id, page=1): """ Получение комментариев раздачи На вход функции надо подавать следующие параметры: id - [int] id топика page - [int] номер страницы Возвращает словарь, состоящий из следующих полей: pages - [list] кортеж [int] для навигации = (кол-во страниц, предыдущая, текущая, следующая) data - [list] данные выборки - список словарей, состоящих из следующих полей: nick - [str] ник автора комментария usertime - [str] стаж юзера count - [str] кол-во сообщений у юзера location - [str] откуда юзер time - [str] время добавления комментария message - [str] комментарий """ page_query = '' if page > 1: page_query = '&start=' + str(30*(page-1)) html = self.http.get('http://rutracker.org/forum/viewtopic.php?t=' + str(id) + page_query) if not html: return html res = { 'pages': self._compile_pages(html), 'comments': [] } # нарубаем страницу по постам rows = re.compile('<tbody id="post_[0-9]+" class="row1|2">(.+?)<!\-\-/post_body\-\->', re.U|re.S).findall(html) if rows: if page == 1: rows.pop(0) if rows: # функция для очистки комментариев def _def_subn1(m): return u'<div class="q-wrap"><div class="q">' + self.html.string(m.group(1)) + u':\n' def _def_subn2(m): r = u'[BR][I]' + m.group(1).replace(u'[I]', u'').replace(u'[/I]', u'') + u'[/I][BR]' n = 1 while n: r, n = re.compile(u'\[BR\]\[BR\]', re.U|re.S).subn(u'[BR]', r) return r for html in rows: comment = { 'nick': None, 'usertime': None, 'count': None, 'location': u'', 'time': None, 'message': None } # вытаскиваем ник r = re.compile('<p class="nick[^>]+>([^<]+)</p>', re.U).search(html) if r: comment['nick'] = self.html.string(r.group(1).strip()) # смотрим стаж r = re.compile(u'<p class="joined"><em>Стаж:</em>([^<]+)</p>', re.U).search(html) if r: comment['usertime'] = r.group(1).strip() # смотрим кол-во коментов у юзера r = re.compile(u'<p class="posts"><em>Сообщений:</em>([^<]+)</p>', re.U).search(html) if r: comment['count'] = r.group(1).strip() # смотрим город юзера r = re.compile(u'<p class="from"><em>Откуда:</em>([^<]+)</p>', re.U).search(html) if r: comment['location'] = r.group(1).strip() # смотрим страну юзера r = re.compile('<p class="flag"><img [^>]*title="([^"]+)"[^>]*></p>', re.U).search(html) if r: if comment['location']: comment['location'] += u', ' comment['location'] += r.group(1).strip() # смотрим время коммента r = re.compile('<a class="small" href="\./viewtopic.php\?p=[^>]+>([0-9]{1,2}\-[^\-]+\-[0-9]{2} [0-9]{1,2}\:[0-9]{1,2})</a>', re.U).search(html) if r: comment['time'] = r.group(1).strip() # вытаскиваем тело коммента r = re.compile('<div class="post_body"[^>]+>(.+)$', re.U|re.S).search(html) if r: html = r.group(1).strip() if html: # заменяем что можем... for reg, rep in ( ('<span class="post\-b">([^(?:</span>)]+)</span>', u'[B]\g<1>[/B]'), ): html = re.compile(reg, re.U|re.S).sub(rep, html) # конвертируем цитаты html, n = re.compile('<div class="q-wrap">\s*<div class="q" head="([^"]+)">', re.U|re.S).subn(_def_subn1, html) n = 1 while n: html, n = re.compile('<div class="q-wrap">\s*<div class="q">(.+?)</div>\s*</div>', re.U|re.S).subn(_def_subn2, html) # прогоняем через полную очистку comment['message'] = self.html.text(html) if comment['nick'] and comment['message']: res['comments'].append(comment) return res def download(self, id): """ Скачивание торрента раздачи На вход функции надо подавать следующие параметры: id - [str] топика с раздачей Возвращает торрент или None (в случае неудачи) """ return self.http.download(id) # PRIVATE def _compile_pages(self, text): r = re.compile(u'<p style="float\: left">Страница <b>([0-9]+)</b> из <b>([0-9]+)</b></p>', re.U|re.S).search(text) if r: current = int(r.group(1)) total = int(r.group(2)) next = current + 1 if next > total: next = 0 return total, current-1, current, next return 1, 0, 1, 0 def _compile_topic(self, text, is_search=False): r = re.compile(r'<a\s[^>]*href="\./viewtopic\.php\?t=([0-9]+)"[^>]*>(.+?)</a>', re.U|re.S).search(text) if r: id = r.group(1) name = self.html.string(r.group(2)) r = re.compile(r'<a[^>]+href="http://dl\.rutracker\.org/forum/dl\.php\?t=' + id + '"[^>]*>(.+?)</a>', re.U|re.S).search(text) if r: size = self._compile_size(r.group(1)) if size and name: item = self._create_torrent(int(id), name) item['size'] = size r = re.compile(r'"tor-icon[^>]+>([^<]+)<', re.U|re.S).search(text) if r: stat = r.group(1) try: status = self.status[stat] except KeyError: pass else: item['status'] = self.html.char(stat) item['status_human'] = status if is_search: item['comment'] = -1 query = (('download', '<td\sclass="row4\ssmall">([0-9]+)</td>'), ('seeder', '<td\sclass="row4\sseedmed"><b>([0-9]+)</b></td>'), ('leecher', '<td\sclass="row4\sleechmed"[^>]+><b>([0-9]+)</b></td>')) else: query = (('comment', u'<span title="Ответов">([0-9]+)</span>'), ('download', u'title="Торрент скачан">[^<]*<b>([0-9]+)</b>[^<]*</p>'), ('seeder', 'title="Seeders"><b>([0-9]+)</b></span>'), ('leecher', 'title="Leechers"><b>([0-9]+)</b></span>')) for tag, reg in query: r = re.compile(reg, re.U|re.S).search(text) if r: item[tag] = int(r.group(1)) return item return None def _create_torrent(self, id, name): return { 'id': id, 'name': name, 'type': 'torrent', 'size': 0, 'seeder': 0, 'leecher': 0, 'download': 0, 'comment': 0, 'status': None, 'status_human': None } def _compile_size(self, text): text = self.html.string(text.replace(u'↓', u'')) if text: text = text.lower() prefix = 1 for p, v in ((u'kb', 1024), (u'mb', 1024*1024), (u'gb', 1024*1024*1024), (u'tb', 1024*1024*1024*1024)): if text.find(p) != -1: prefix = v text = text.replace(p, u'').strip() break num = self.re['is_float'].search(text) if num: return int(float(prefix)*float(num.group(1))) num = self.re['is_int'].search(text) if num: return prefix*int(num.group(1)) return None def _profile(self, id): html = self.http.guest('http://rutracker.org/forum/viewtopic.php?t=' + str(id)) if not html: return False, html res = { 'descript': None, 'cover': None, 'screenshot': None } r = re.compile('<table class="topic" id="topic_main" cellpadding="0" cellspacing="0">(.+?)<legend>Download</legend>', re.U|re.S).search(html) if r: html = r.group(1) # ищем коверы (перебирая все возможные варианты хостингов картинок) for api in (self.pic_hosting_fastpic, ): cover = api('cover', html) if cover: res['cover'] = cover break # вытаскиваем блок со скриншотами r = re.compile(u'<h3 class="sp-title">Скриншоты(.+?)</div>', re.U|re.S).search(html) if r: body = r.group(1) # ищем скрины (перебирая все возможные варианты хостингов картинок) for api in (self.pic_hosting_fastpic, ): screenshot = api('screenshot', body) if screenshot: res['screenshot'] = screenshot break # пытаемся получить текст описания r = re.compile('<div class="post_body"[^>]+>(.+)', re.U|re.S).search(html) if r: html = r.group(1) # режем и заменяем все что можем... for reg, rep in ( (u'<div class="sp-wrap">.+?</div>', u''), # удаляем все спойлеры (u'<var[^>]+>[^<]+</var>', u''), # удаляем все изображения (u'<span class="post\-hr">\-</span>', u'\n'), # удаляем HR (u'<span class="post\-b">([^<]+)</span>', u'[COLOR FF0DA09E]\g<1>[/COLOR]') # заменяем болды ): html = re.compile(reg, re.U|re.S).sub(rep, html) # прогоняем через полную очистку html = self.html.text(html) if html: res['descript'] = html return True, res # CATALOG def _load_catalog(self, index, ignore): catalog = self.cache_catalog.get('catalog', self._load_catalog_http) if not catalog: return [] res = [] for key, folders in catalog.iteritems(): if index is None or key in index: res.extend([x for x in folders if x not in ignore]) return res def _load_catalog_http(self): html = self.http.get('http://rutracker.org/forum/tracker.php') if not html: return html r = re.compile('<select id="fs-main"(.+?)</select>', re.U|re.S).search(html) if not r: return None res = {} root = None for cat, is_root_forum in re.compile('<option id="fs\-([0-9]+)"([^>]+)>', re.U).findall(r.group(1)): cat = int(cat) if is_root_forum.find('root_forum') != -1: root = cat res[root] = [cat] elif root: res[root].append(cat) return (86400 if res else False), res # day # SCREENSHOT def pic_hosting_fastpic(self, img, html): if img == 'cover': r = re.compile('<var[^>]+class="postImg postImgAligned img\-right"[^>]+title="(http\://[0-9a-z]+\.fastpic\.ru/big/[0-9a-f/]+\.[a-z]{3,4})"[^>]*>', re.U|re.S).search(html) if r: return r.group(1) return None else: res = [] for r in re.compile('<a[^>]*href="http\://fastpic\.ru/view/[^\.]+\.([a-z]{3,4})\.html"[^>]*>[.]*?<var[^>]+title="(http\://[0-9a-z]+\.fastpic\.ru/thumb/[0-9a-f/]+)\.[a-z]{3,4}"[^>]*>', re.U|re.S).findall(html): res.append('.'.join([r[1].replace('thumb', 'big'), r[0]])) return res if res else None
class RuTracker: def __init__(self, expire=0, size=0): self.cache_catalog = Cache('rutracker_catalog.db') self.cache_profile = Cache('rutracker_profile.db', expire, size) self.http = RuTrackerHTTP() self.re = { 'is_int': re.compile(r'^([0-9]+)', re.U), 'is_float': re.compile(r'^([0-9]{1,10}\.[0-9]+)', re.U), 'hash': re.compile(r'<span id="tor-hash">([0-9A-F]{40})</span>', re.U) } self.html = Clear() self.status = { '%': 'check', 'D': 'repeat', '!': 'nodesc', '©': 'copyright', '∏': 'moder', 'x': 'close', '∑': 'absorb', '#': 'doubtful', '*': 'nocheck', '?': 'neededit', 'T': 'temp', '√': 'ok' } # API def get(self, id=None, page=1): """ Получение списка директорий и раздач На вход функции надо подавать следующие параметры: id - [int] id директории page - [int] номер страницы Возвращает словарь, состоящий из следующих полей: pages - [list] кортеж [int] для навигации = (кол-во страниц, предыдущая, текущая, следующая) data - [list] данные выборки, состоящая из следующих полей: id - [int] id (для директорий и топиков ID могут совпадать) name - [str] имя записи type - [str] тип записи (torrent - торрент, folder - директория) size - [int] размер раздачи в байтах seeder - [int] кол-во сидов leecher - [int] кол-во личеров download - [int] кол-во скачиваний торрента comment - [int] кол-во комментариев status - [str] символ отметки модератора status_human - [str] отметка модератора в удобочитаемом виде (описание смотри ниже). Описание возвращаемых отметок status_human: Скачать торрент нельзя (красный): moder - премодерация, check - проверяется, repeat - повтор, nodesc - не оформлено, copyright - закрыто правообладателем, close - закрыто, absorb - поглощено. Скачать торрент можно (желтый): nocheck - не проверено, neededit - недооформлено, doubtful - сомнительно, temp - временная Скачать торрент можно (зеленый): ok - проверено """ # INDEX if id is None: html = self.http.get('http://rutracker.lib/forum/index.php') if not html: return html res = [] r = re.compile( r'<div\sid="forums_wrap">(.+)<div\sclass="bottom_info">', re.U | re.S).search(html) if r: r = re.compile( r'<h4\sclass="forumlink"><a\shref="viewforum\.php\?f=([0-9]+)">(.+?)</a></h4>', re.U | re.S).findall(r.group(1)) if r: res = [{ 'id': int(i), 'name': self.html.string(x), 'type': 'folder' } for i, x in r] if not res: return None return {'pages': (1, 0, 1, 0), 'data': res} else: page_query = '' if page > 1: page_query = '&start=' + str(50 * (page - 1)) html = self.http.get( 'http://rutracker.lib/forum/viewforum.php?f=' + str(id) + '&sort=2' + page_query) if not html: return html pages = self._compile_pages(html) folder = [] torrent = [] group_list = re.compile( r'<table class="[^"]*forumline forum">(.+?)</table>', re.U | re.S).findall(html) if group_list: for group in group_list: # вытаскиваем папки (если есть) r = re.compile( r'<h4\sclass="forumlink"><a\shref="viewforum\.php\?f=([0-9]+)">(.+?)</a></h4>', re.U | re.S).findall(group) if r: folder.extend([{ 'id': int(i), 'name': self.html.string(x), 'type': 'folder' } for i, x in r]) # нарубаем на строчки топиков topic_list = group.split(u'topicSep">') if len(topic_list) > 1: topic_list = topic_list[1:] for html in topic_list: # вытаскиваем id for text in re.compile( r'<tr\sid="tr\-[0-9]+"(.+?)</tr>', re.U | re.S).findall(html): item = self._compile_topic(text) if item: torrent.append(item) folder.extend(torrent) return {'pages': pages, 'data': folder} def search(self, search, folder=None, index=None, ignore=None): """ Поиск по РуТрекеру На вход функции надо подавать следующие параметры: search - [str] поисковая строка запроса (Unicode) folder - [list] список ID директорий, в которых необходимо искать (None - искать везде) Возвращает словарь, аналогичный выводу метода GET """ if isinstance(search, unicode): search = search.encode('windows-1251') # проверяем авторизацию html = self.http.get('http://rutracker.lib/forum/index.php') if not html: return html # готовим запрос для получения дерева разделов if folder: if not isinstance(folder, list) and not isinstance(folder, tuple): folder = [folder] else: if index is not None: if not isinstance(index, list) and not isinstance( index, tuple): index = [index] if ignore is not None: if not isinstance(ignore, list) and not isinstance( ignore, tuple): ignore = [ignore] if not index and not ignore: folder = [] else: folder = self._load_catalog(index, ignore) if not folder: return folder # готовим запрос params = [('nm', search), ('o', 10), ('s', 2), ('prev_my', 0), ('prev_new', 0), ('prev_oop', 0), ('submit', r'Поиск')] params.extend([('f[]', x) for x in folder]) # делаем поиск html = self.http.post('http://rutracker.lib/forum/tracker.php', params) if not html: return html res = [] table = re.compile('id="tor\-tbl">(.+?)</table>', re.U | re.S).search(html) if table: for tr in re.compile('<tr\sclass="tCenter\shl\-tr">(.+?)</tr>', re.U | re.S).findall(table.group(1)): item = self._compile_topic(tr, True) if item: res.append(item) return {'pages': (1, 0, 1, 0), 'data': res} def profile(self, id): """ Получение дополнительной информации о раздачи На вход функции надо подавать следующие параметры: id - [int] id топика с раздачей Возвращает словарь, состоящий из: descript - [str] описание на RuTracker cover - [str] url обложки screenshot - [list] Список url скриншотов """ return self.cache_profile.get('profile:' + str(id), self._profile, id) def comment(self, id, page=1): """ Получение комментариев раздачи На вход функции надо подавать следующие параметры: id - [int] id топика page - [int] номер страницы Возвращает словарь, состоящий из следующих полей: pages - [list] кортеж [int] для навигации = (кол-во страниц, предыдущая, текущая, следующая) data - [list] данные выборки - список словарей, состоящих из следующих полей: nick - [str] ник автора комментария usertime - [str] стаж юзера count - [str] кол-во сообщений у юзера location - [str] откуда юзер time - [str] время добавления комментария message - [str] комментарий """ page_query = '' if page > 1: page_query = '&start=' + str(30 * (page - 1)) html = self.http.get('http://rutracker.lib/forum/viewtopic.php?t=' + str(id) + page_query) if not html: return html res = {'pages': self._compile_pages(html), 'comments': []} # нарубаем страницу по постам rows = re.compile( '<tbody id="post_[0-9]+" class="row1|2">(.+?)<!\-\-/post_body\-\->', re.U | re.S).findall(html) if rows: if page == 1: rows.pop(0) if rows: # функция для очистки комментариев def _def_subn1(m): return u'<div class="q-wrap"><div class="q">' + self.html.string( m.group(1)) + u':\n' def _def_subn2(m): r = u'[BR][I]' + m.group(1).replace(u'[I]', u'').replace( u'[/I]', u'') + u'[/I][BR]' n = 1 while n: r, n = re.compile(u'\[BR\]\[BR\]', re.U | re.S).subn(u'[BR]', r) return r for html in rows: comment = { 'nick': None, 'usertime': None, 'count': None, 'location': u'', 'time': None, 'message': None } # вытаскиваем ник r = re.compile('<p class="nick[^>]+>([^<]+)</p>', re.U).search(html) if r: comment['nick'] = self.html.string(r.group(1).strip()) # смотрим стаж r = re.compile( u'<p class="joined"><em>Стаж:</em>([^<]+)</p>', re.U).search(html) if r: comment['usertime'] = r.group(1).strip() # смотрим кол-во коментов у юзера r = re.compile( u'<p class="posts"><em>Сообщений:</em>([^<]+)</p>', re.U).search(html) if r: comment['count'] = r.group(1).strip() # смотрим город юзера r = re.compile( u'<p class="from"><em>Откуда:</em>([^<]+)</p>', re.U).search(html) if r: comment['location'] = r.group(1).strip() # смотрим страну юзера r = re.compile( '<p class="flag"><img [^>]*title="([^"]+)"[^>]*></p>', re.U).search(html) if r: if comment['location']: comment['location'] += u', ' comment['location'] += r.group(1).strip() # смотрим время коммента r = re.compile( '<a class="small" href="\./viewtopic.php\?p=[^>]+>([0-9]{1,2}\-[^\-]+\-[0-9]{2} [0-9]{1,2}\:[0-9]{1,2})</a>', re.U).search(html) if r: comment['time'] = r.group(1).strip() # вытаскиваем тело коммента r = re.compile('<div class="post_body"[^>]+>(.+)$', re.U | re.S).search(html) if r: html = r.group(1).strip() if html: # заменяем что можем... for reg, rep in ( ('<span class="post\-b">([^(?:</span>)]+)</span>', u'[B]\g<1>[/B]'), ): html = re.compile(reg, re.U | re.S).sub(rep, html) # конвертируем цитаты html, n = re.compile( '<div class="q-wrap">\s*<div class="q" head="([^"]+)">', re.U | re.S).subn(_def_subn1, html) n = 1 while n: html, n = re.compile( '<div class="q-wrap">\s*<div class="q">(.+?)</div>\s*</div>', re.U | re.S).subn(_def_subn2, html) # прогоняем через полную очистку comment['message'] = self.html.text(html) if comment['nick'] and comment['message']: res['comments'].append(comment) return res def download(self, id): """ Скачивание торрента раздачи На вход функции надо подавать следующие параметры: id - [str] топика с раздачей Возвращает торрент или None (в случае неудачи) """ return self.http.download(id) def hash(self, id): """ Получение инфо-хеша раздачи На вход функции надо подавать следующие параметры: id - [str] топика с раздачей Возвращает шеснадцатеричное число хэша (в виде строки) или None (в случае неудачи) """ return self.cache_profile.get('hash:' + str(id), self._hash, id) def magnet(self, id): """ Получение инфо-хеша раздачи На вход функции надо подавать следующие параметры: id - [str] топика с раздачей Возвращает шеснадцатеричное число хэша (в виде строки) или None (в случае неудачи) """ hash = self.hash(id) if hash: return 'magnet:?xt=urn:btih:' + hash return hash # PRIVATE def _compile_pages(self, text): r = re.compile( u'<p style="float\: left">Страница <b>([0-9]+)</b> из <b>([0-9]+)</b></p>', re.U | re.S).search(text) if r: current = int(r.group(1)) total = int(r.group(2)) next = current + 1 if next > total: next = 0 return total, current - 1, current, next return 1, 0, 1, 0 def _compile_topic(self, text, is_search=False): r = re.compile( r'<a\s[^>]*href="viewtopic\.php\?t=([0-9]+)"[^>]*>(.+?)</a>', re.U | re.S).search(text) if r: id = r.group(1) name = self.html.string(r.group(2)) r = re.compile( r'<a[^>]+href="dl\.php\?t=' + id + '"[^>]*>(.+?)</a>', re.U | re.S).search(text) if r: size = self._compile_size(r.group(1)) if size and name: item = self._create_torrent(int(id), name) item['size'] = size r = re.compile(r'"tor-icon[^>]+>([^<]+)<', re.U | re.S).search(text) if r: stat = r.group(1) try: status = self.status[stat] except KeyError: pass else: item['status'] = self.html.char(stat) item['status_human'] = status if is_search: item['comment'] = -1 query = (( 'download', '<td\sclass="row4\ssmall">([0-9]+)</td>' ), ( 'seeder', '<b class="seedmed">([0-9]+)</b></td>' ), ('leecher', '<td\sclass="row4 leechmed"[^>]+><b>([0-9]+)</b></td>' )) else: query = (( 'comment', u'<span title="Ответов">([0-9]+)</span>' ), ('download', u'title="Торрент скачан">[^<]*<b>([0-9]+)</b>[^<]*</p>' ), ('seeder', 'title="Seeders"><b>([0-9]+)</b></span>'), ('leecher', 'title="Leechers"><b>([0-9]+)</b></span>')) for tag, reg in query: r = re.compile(reg, re.U | re.S).search(text) if r: item[tag] = int(r.group(1)) return item return None def _create_torrent(self, id, name): return { 'id': id, 'name': name, 'type': 'torrent', 'size': 0, 'seeder': 0, 'leecher': 0, 'download': 0, 'comment': 0, 'status': None, 'status_human': None } def _compile_size(self, text): text = self.html.string(text.replace(u'↓', u'')) if text: text = text.lower() prefix = 1 for p, v in ((u'kb', 1024), (u'mb', 1024 * 1024), (u'gb', 1024 * 1024 * 1024), (u'tb', 1024 * 1024 * 1024 * 1024)): if text.find(p) != -1: prefix = v text = text.replace(p, u'').strip() break num = self.re['is_float'].search(text) if num: return int(float(prefix) * float(num.group(1))) num = self.re['is_int'].search(text) if num: return prefix * int(num.group(1)) return None def _hash(self, id): html = self.http.get('http://rutracker.lib/forum/viewtopic.php?t=' + str(id)) if not html: return False, html r = self.re['hash'].search(html) if not r: return False, None return True, str(r.group(1)) def _profile(self, id): html = self.http.guest('http://rutracker.lib/forum/viewtopic.php?t=' + str(id)) if not html: return False, html res = {'descript': None, 'cover': None, 'screenshot': None} r = re.compile( '<div class="post_body"[^>]+>(.+?)<legend>Download</legend>', re.U | re.S).search(html) if r: html = r.group(1) # ищем коверы (перебирая все возможные варианты хостингов картинок) for api in (self.pic_hosting_fastpic, ): cover = api('cover', html) if cover: res['cover'] = cover break # вытаскиваем блок со скриншотами r = re.compile(u'<span>Скриншоты</span></div>(.+?)</div>', re.U | re.S).search(html) if r: body = r.group(1) # ищем скрины (перебирая все возможные варианты хостингов картинок) for api in (self.pic_hosting_fastpic, ): screenshot = api('screenshot', body) if screenshot: res['screenshot'] = screenshot break # пытаемся получить текст описания # режем и заменяем все что можем... for reg, rep in ( (u'<div class="sp\-wrap">.+?<div class="sp-body">.+?</div>', u''), # удаляем все спойлеры (u'<var[^>]+>[^<]+</var>', u''), # удаляем все изображения (u'<span class="post\-hr">\-</span>', u'\n'), # удаляем HR (u'<span class="post\-b">([^<]+)</span>', u'[COLOR FF0DA09E]\g<1>[/COLOR]') # заменяем болды ): html = re.compile(reg, re.U | re.S).sub(rep, html) # прогоняем через полную очистку html = self.html.text(html) if html: res['descript'] = html return True, res # CATALOG def _load_catalog(self, index, ignore): catalog = self.cache_catalog.get('catalog', self._load_catalog_http) if not catalog: return [] res = [] for key, folders in catalog.iteritems(): if index is None or key in index: res.extend([x for x in folders if x not in ignore]) return res def _load_catalog_http(self): html = self.http.get('http://rutracker.lib/forum/tracker.php') if not html: return html r = re.compile('<select id="fs-main"(.+?)</select>', re.U | re.S).search(html) if not r: return None res = {} root = None for cat, is_root_forum in re.compile( '<option id="fs\-([0-9]+)"([^>]+)>', re.U).findall(r.group(1)): cat = int(cat) if is_root_forum.find('root_forum') != -1: root = cat res[root] = [cat] elif root: res[root].append(cat) return (86400 if res else False), res # day # SCREENSHOT def pic_hosting_fastpic(self, img, html): if img == 'cover': r = re.compile( '<var[^>]+class="postImg postImgAligned img\-right"[^>]+title="(http\://[0-9a-z]+\.fastpic\.ru/big/[0-9a-f/]+\.[a-z]{3,4})"[^>]*>', re.U | re.S).search(html) if r: return r.group(1) return None else: res = [] for r in re.compile( '<a[^>]*href="http\://fastpic\.ru/view/[^\.]+\.([a-z]{3,4})\.html"[^>]*>[.]*?<var[^>]+title="(http\://[0-9a-z]+\.fastpic\.ru/thumb/[0-9a-f/]+)\.[a-z]{3,4}"[^>]*>', re.U | re.S).findall(html): res.append('.'.join([r[1].replace('thumb', 'big'), r[0]])) return res if res else None