├── requirements.txt ├── setup.py ├── README.md └── pikabu.py /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml, requests, cssselect -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2014 Blackwave 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from distutils.core import setup 18 | 19 | setup( 20 | name='pikabu_api', 21 | version='0.0.1', 22 | description='Неофициальная библиотека имитирующее неофициальное апи для pikabu.ru', 23 | author='Blackwave', 24 | author_email='blackwave-rt@hotmail.com', 25 | url='https://github.com/Blackwave-rt/pikabu/', 26 | py_modules=['pikabu'], 27 | ) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pikabu 2 | ====== 3 | 4 | Неофициальный клиент на Python для pikabu.ru 5 | 6 | pikabu - это источник интересных статей, фотографий и видео, добавляемых пользователями. Вы добавляете пост: фото, видео или историю. 7 | Данная библиотека была создана от безысходности из-за отсутствия официального Апи на сайте pikabu.ru. Автором библиотеки является [Blackwave](http://pikabu.ru/profile/blackwave). 8 | 9 | В ближайшее время планирую добавить публикацию поста/комментария, изменение рейтинга у постов/комментариев и настройки. 10 | 11 | ## Установка 12 | 13 | Из pip: 14 | ```bash 15 | $ sudo pip install pikabu 16 | ``` 17 | Из исходников: 18 | ```bash 19 | $ git clone https://github.com/Blackwave-rt/pikabu && cd pikabu 20 | $ sudo python setup.py install 21 | ``` 22 | Готово! 23 | 24 | ## Быстрый старт 25 | 26 | Все до ужаса просто. 27 | ```python 28 | import pikabu 29 | pika_api = pikabu.Api(login='ваш логин', password='ваш пароль') 30 | ``` 31 | Все дальнейшие операции будут происходить через pika_api. 32 | 33 | Например, получим заоблачный рейтинг пользователя [Admin](http://pikabu.ru/profile/admin): 34 | ```python 35 | import pikabu 36 | pika_api = pikabu.Api(login='ваш логин', password='ваш пароль') 37 | pika_api.users.get("admin", "rating") 38 | ``` 39 | 40 | ## Документация по возможностям 41 | 42 | ###api.posts.get( (string)category, (int)page ) 43 | Возвращает массив постов по выборке: горячее, популярные, свежее. 44 | 45 | Аргументы: [hot|best|new], страница 46 | 47 | Результат: А в результате возвращается массив с объектом "ObjectPosts", который обладает аргументами: 48 | 49 | title - вернет название поста 50 | url - вернет название ссылки 51 | text - вернет текст поста (в случае, если это изображение - вернет None) 52 | image - вернет изображение поста (в случае, если пост текстовый - вернет None) 53 | time - вернет дату создания (два часа назад, etc) 54 | author - вернет ник автора 55 | comments - вернет количество комментариев 56 | rating - вернет рейтинг поста 57 | tags - вернет массив тегов поста 58 | 59 | 60 | ###api.users.get( (string)login ) - вернет объект "ObjectUserInfo", который имеет аргументы: 61 | 62 | Возвращает объект "ObjectUserInfo" 63 | 64 | ####Параметры: 65 | 66 | dor - дата регистрации 67 | rating - рейтинг юзера 68 | comments - количество комментариев 69 | news - возвращает массив вида [количество новостей, в горячем] 70 | actions - возвращает словарь вида {"like":count, "dislike":count} 71 | avatar - возвращает ссылку на аватарку юзера 72 | awards - возвращает список наград 73 | 74 | 75 | ###api.comments.get( (int)post_id ) 76 | Возвращает комментарии к выбранному посту 77 | 78 | api.comments.get(1234567) - вернет объект "ObjectComments" 79 | 80 | Аргументы: 81 | 82 | id - id комментария 83 | rating - рейтинг комментария 84 | author - логин автора 85 | time - время добавления 86 | text - текст комментария 87 | 88 | 89 | ###api.top_tags.get() 90 | Возвращает популярные за сутки теги. Вид: [название тега, количество] 91 | 92 | ###api.posts.search( (string)query, (int)cur_page, (bool)in_hot, (bool)in_pics, (bool)in_text, (bool)in_video ) 93 | Возвращает результаты поиска в виде объекта ObjectPosts. Аргументы соответствуют api.posts.get 94 | 95 | ###api.posts.tag( (string)tag_name, (int)page, (string)category ) 96 | Возвращает отфильтрованные по тегам посты в виде объекта ObjectPosts. Аргументы соответствуют api.posts.get 97 | 98 | ###api.rate.set( (bool)action, (int)type, (int)post_id, (int or None)comment_id ) 99 | Позволяет ставить рейтинг постам и комментариям. 100 | Параметры: 101 | action - True/False +/-, соответственно. 102 | type - 1 - пост, 2 - комментарий 103 | post_id - ид поста 104 | comment_id - ид комментария. Не указывается при type=1 105 | 106 | ###api.comments.add( (string)text, (int)post_id, (int)comment_id ) 107 | Позволяет добавлять комментарии к посту post_id или к комментарию comment_id (обязательно указывайте post_id) 108 | 109 | ###api.register() - возвращает код капчи, которая указывается при вызове api.register.add( (string)login, (string)password, (string)email, (string) captcha_key ) 110 | В случае успеха api.register.add() вернет True. Иначе вернет список с описанием ошибки (логин/почта используется, неверная капча) 111 | ```python 112 | # -*- coding: utf-8 -*- 113 | import pikabu 114 | import base64 115 | 116 | api = pikabu.Api(login="", password="") 117 | g = open("captcha.jpg", "wb") 118 | data = api.register() 119 | captcha = data["image"] 120 | g.write(base64.decodestring(captcha)) 121 | g.close() 122 | captcha_ = raw_input("captcha:") 123 | print api.register.add("логин", "пароль", "почта", captcha_) 124 | ``` 125 | 126 | ## Лицензия 127 | 128 | Библиотека доступна на условиях лицензии Apache версии 2.0 129 | 130 | http://www.apache.org/licenses/LICENSE-2.0 131 | -------------------------------------------------------------------------------- /pikabu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright (c) 2014 Blackwave 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except Exception in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import json 18 | import lxml.html 19 | from lxml import etree 20 | import sys 21 | import requests 22 | import base64 23 | import re 24 | 25 | ENDPOINT = "http://pikabu.ru/" 26 | AUTH_URL = ENDPOINT + 'ajax/ajax_login.php' 27 | XCSRF_TOKEN = None 28 | SITE_REQUEST = requests.Session() 29 | DEFAULT_HEADERS = { 30 | "User-Agent": "Mozilla/4.0 (compatible; MSIE 7.0;\ Windows NT 6.0)", 31 | "Referer": "http://pikabu.ru/", 32 | "Host": "pikabu.ru", 33 | "Origin": "pikabu.ru" 34 | } 35 | POST_HEADERS = { 36 | "Content-Type": "application/x-www-form-urlencoded", 37 | "Accept": "application/json, text/javascript, */*; q=0.01" 38 | } 39 | IS_LOGGED = False 40 | USER_DATA = {"login":None, "password":None} 41 | XPATH_XCSRFTOKEN = "/html/head/script[3]" 42 | XPATH_PIKAPOSTS_TITLE = '//*[@id="num_dig3%s"]' 43 | XPATH_PIKAPOSTS_TEXT = '''//*[@id="story_table_%s"]//tr/td[2]/ 44 | table[@id="story_main_t"]//tr/td/div[2]''' 45 | XPATH_PIKAPOSTS_DESC = '//*[@id="textDiv%s"]/text()' 46 | XPATH_PIKAPOSTS_ATC = '''//*[@id="story_table_%s"]//tr/td[2]/ 47 | table[@id="story_main_t"]//tr/td/div[%s]/span[1]/a[%s]''' 48 | XPATH_PIKAPOSTS_RATE = '//*[@id="num_digs%s"]' 49 | XPATH_PIKAPOSTS_TAGS = '''//*[@id="story_table_%s"]//tr/td[2]/ 50 | table[@id="story_main_t"]//tr/td/div[3]/span[1]/span''' 51 | XPATH_PIKACOM_LIST = '/comments/comment' 52 | XPATH_PIKACOM_RATE = '//*[@id="%s"]//tr[1]/td/noindex/span' 53 | XPATH_PIKACOM_AUT = '//*[@id="%s"]//tr[1]/td/noindex/a[%s]' 54 | XPATH_PIKATAG = '//*[@id="story_main_t"]//tr[2]/td/div/a/span' 55 | XPATH_PIKAUSER_AWARDS = '''//*[@id="wrap"]/table//tr/td[1]/ 56 | table[1]//tr/td[2]/div[1]/table//tr/td[3]/div/a/img''' 57 | XPATH_PIKAUSER = '''//*[@id="wrap"]/table//tr/td[1]/ 58 | table//tr/td[2]/div[1]/table//tr/td[2]/div/text()''' 59 | XPATH_PIKAUSER_MSG = '''//*[@id="right_menu"]/table[1] 60 | //tr[2]/td/ul/li[5]/a/b''' 61 | XPATH_PIKAUSER_LSMSG = '''//*[@id="com2"]//tr[2]/td/div/noindex/div/input''' 62 | RE_PUSER_DOR = re.compile(r'пикабушник\s+уже\s+(.*)') 63 | 64 | 65 | def fetch_url(_url, settings=None, 66 | _data=None, __method="POST", need_auth=True): 67 | """Выполняет запрос к сайту и возвращает результат в виде 68 | страницы 69 | В зависимости от типа запроса, авторизации и в каком доме находится 70 | сатурн - выполняет определенный запрос с подстановкой заголовков. 71 | 72 | """ 73 | global IS_LOGGED 74 | if need_auth and not IS_LOGGED: 75 | url = AUTH_URL 76 | SITE_REQUEST.get(ENDPOINT, headers=DEFAULT_HEADERS) 77 | DEFAULT_HEADERS['X-Csrf-Token'] = SITE_REQUEST.cookies.get_dict()['PHPSESS'] 78 | if USER_DATA['login'] is None: 79 | USER_DATA['login'] = settings.get('login') 80 | USER_DATA['password'] = settings.get('password') 81 | login_data = { 82 | "mode": "login", 83 | "username": USER_DATA['login'], 84 | "password": USER_DATA['password'], 85 | "remember": 0 86 | } 87 | resp = SITE_REQUEST.post(url, data=login_data, headers=DEFAULT_HEADERS) 88 | response = json.loads(resp.text) 89 | if int(response["logined"]) == 0: 90 | print 'Неверно указан логин или пароль' 91 | sys.exit(1) 92 | if int(response["logined"]) == -1: 93 | print response['error'] 94 | sys.exit(1) 95 | IS_LOGGED = True 96 | 97 | if _url is not None: 98 | if _data is not None and len(_data) >= 1: 99 | _headers = DEFAULT_HEADERS 100 | '''_resp = SITE_REQUEST.get(ENDPOINT, headers=DEFAULT_HEADERS) 101 | XCSRF_TOKEN = lxml.html.document_fromstring(_resp.text).xpath( 102 | XPATH_XCSRFTOKEN)[0].text.strip() 103 | XCSRF_TOKEN = XCSRF_TOKEN.split("\n")[2].strip() 104 | XCSRF_TOKEN = XCSRF_TOKEN.replace("'", "").split(": ")[1]''' 105 | XCSRF_TOKEN = SITE_REQUEST.cookies.get_dict()['PHPSESS'] 106 | if __method == "POST": 107 | _headers["Content-Type"] = POST_HEADERS["Content-Type"] 108 | _headers["Access"] = POST_HEADERS["Accept"] 109 | _headers["X-Csrf-Token"] = XCSRF_TOKEN # inject X csrf token 110 | resp = SITE_REQUEST.post(ENDPOINT + _url, 111 | headers=_headers, data=_data) 112 | else: 113 | resp = SITE_REQUEST.get(ENDPOINT + _url, 114 | headers=_headers, params=_data) 115 | else: 116 | resp = SITE_REQUEST.get(ENDPOINT + _url, headers=DEFAULT_HEADERS) 117 | if need_auth: 118 | return resp.text 119 | else: 120 | return resp.content 121 | else: 122 | return False 123 | 124 | 125 | class PikaService(object): 126 | """Абстрактный класс""" 127 | def __init__(self, **settings): 128 | if "login" not in settings or "password" not in settings: 129 | raise ValueError('Нужно указать логин и пароль') 130 | self.settings = settings 131 | 132 | def request(self, url, data=None, method='POST', need_auth=True): 133 | if url is not None: 134 | return fetch_url(url, self.settings, data, method, need_auth) 135 | else: 136 | return False 137 | 138 | 139 | class PikabuPosts(PikaService): 140 | """Вывод постов из горячего, свежего, лучшего""" 141 | def get(self, query, page=1): 142 | _page = self.request('%s?page=%s&twitmode=1' % (query, page)) 143 | if _page is not None: 144 | posts_list = [] 145 | try: 146 | page_body = lxml.html.document_fromstring( 147 | json.loads(_page)["html"].replace("
", "\\n")) 148 | except Exception: 149 | return False 150 | for post_id in json.loads(_page)["news_arr"]: 151 | post_title = page_body.xpath( 152 | XPATH_PIKAPOSTS_TITLE % post_id)[0].text 153 | post_url = page_body.xpath( 154 | XPATH_PIKAPOSTS_TITLE % post_id)[0].get("href") 155 | post_text = page_body.xpath( 156 | XPATH_PIKAPOSTS_TEXT % post_id)[0].text 157 | post_image = page_body.cssselect( 158 | 'table#story_table_%s' % post_id)[0].get("lang") 159 | try: 160 | post_desc = " ".join( 161 | page_body.xpath(XPATH_PIKAPOSTS_DESC % post_id)) 162 | except Exception: 163 | post_desc = None 164 | try: 165 | post_author = page_body.xpath( 166 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 3))[0].text 167 | post_time = page_body.xpath( 168 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 4))[0].text 169 | post_comments = page_body.xpath( 170 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 2))[0].text 171 | except Exception: 172 | post_author = page_body.xpath( 173 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 3))[0].text 174 | post_time = page_body.xpath( 175 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 4))[0].text 176 | post_comments = page_body.xpath( 177 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 2))[0].text 178 | post_rating = page_body.xpath( 179 | XPATH_PIKAPOSTS_RATE % post_id)[0].text 180 | post_tags = [result.text for result in page_body.xpath( 181 | XPATH_PIKAPOSTS_TAGS % post_id)] 182 | posts_list.append(ObjectPosts(post_id, post_title, post_url, 183 | post_text, post_image, post_desc, 184 | post_author, post_time, post_comments, 185 | post_rating, post_tags)) 186 | return posts_list 187 | else: 188 | return False 189 | 190 | def search(self, query, cur_page=1, 191 | in_hot=True, in_pics=True, in_text=True, in_video=True): 192 | """Поиск по сайту с указанием страницы и разделов""" 193 | if query is not None: 194 | if in_hot: 195 | added_params = "&hot_search=on" 196 | if in_pics: 197 | added_params += "&pic_search=on" 198 | if in_text: 199 | added_params += "&text_search=on" 200 | if in_video: 201 | added_params += "&video_search=on" 202 | added_params += "&page=%s" % cur_page 203 | _page = self.request( 204 | 'search.php?q=%s%s' % (query, added_params)) 205 | posts_list = [] 206 | page_body = lxml.html.document_fromstring(_page) 207 | for story in page_body.xpath('//*[@id="stories_container"]/table'): 208 | try: 209 | post_id = int(story.get("attr")) 210 | post_title = page_body.xpath( 211 | XPATH_PIKAPOSTS_TITLE % post_id)[0].text 212 | post_url = page_body.xpath( 213 | XPATH_PIKAPOSTS_TITLE % post_id)[0].get("href") 214 | post_text = page_body.xpath( 215 | XPATH_PIKAPOSTS_TEXT % post_id)[0].text 216 | post_image = page_body.cssselect( 217 | 'table#story_table_%s' % post_id)[0].get("lang") 218 | try: 219 | post_desc = " ".join( 220 | page_body.xpath(XPATH_PIKAPOSTS_DESC % post_id)) 221 | except Exception: 222 | post_desc = None 223 | try: 224 | post_author = page_body.xpath( 225 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 3))[0].text 226 | post_time = page_body.xpath( 227 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 4))[0].text 228 | post_comments = page_body.xpath( 229 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 2))[0].text 230 | except Exception: 231 | post_author = page_body.xpath( 232 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 3))[0].text 233 | post_time = page_body.xpath( 234 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 4))[0].text 235 | post_comments = page_body.xpath( 236 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 2))[0].text 237 | try: 238 | post_rating = page_body.xpath( 239 | XPATH_PIKAPOSTS_RATE % post_id)[0].text 240 | except Exception: 241 | post_rating = "NaN" 242 | post_tags = [result.text for result in page_body.xpath( 243 | XPATH_PIKAPOSTS_TAGS % post_id)] 244 | posts_list.append(ObjectPosts(post_id, post_title, post_url, 245 | post_text, post_image, post_desc, 246 | post_author, post_time, post_comments, 247 | post_rating, post_tags)) 248 | except Exception: 249 | return False 250 | return posts_list 251 | else: 252 | return False 253 | 254 | def tag(self, tag_name, page=1, category="new"): 255 | """Вывод новостей по указанному тегу""" 256 | if tag_name and category is not None: 257 | _page = self.request( 258 | 'tag/%s/%s?page=%s&twitmode=1' % (tag_name, category, page)) 259 | if _page is not None: 260 | posts_list = [] 261 | try: 262 | __page = json.loads(_page)["html"].replace("
", "\\n") 263 | page_body = lxml.html.document_fromstring(__page) 264 | except Exception: 265 | return False 266 | for post_id in json.loads(_page)["news_arr"]: 267 | post_title = page_body.xpath( 268 | XPATH_PIKAPOSTS_TITLE % post_id)[0].text 269 | post_url = page_body.xpath( 270 | XPATH_PIKAPOSTS_TITLE % post_id)[0].get("href") 271 | post_text = page_body.xpath( 272 | XPATH_PIKAPOSTS_TEXT % post_id)[0].text 273 | post_image = page_body.cssselect( 274 | 'table#story_table_%s' % post_id)[0].get("lang") 275 | try: 276 | post_desc = " ".join( 277 | page_body.xpath(XPATH_PIKAPOSTS_DESC % post_id)) 278 | except Exception: 279 | post_desc = None 280 | try: 281 | post_author = page_body.xpath( 282 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 3))[0].text 283 | post_time = page_body.xpath( 284 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 4))[0].text 285 | post_comments = page_body.xpath( 286 | XPATH_PIKAPOSTS_ATC % (post_id, 3, 2))[0].text 287 | except Exception: 288 | post_author = page_body.xpath( 289 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 3))[0].text 290 | post_time = page_body.xpath( 291 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 4))[0].text 292 | post_comments = page_body.xpath( 293 | XPATH_PIKAPOSTS_ATC % (post_id, 2, 2))[0].text 294 | try: 295 | post_rating = page_body.xpath( 296 | XPATH_PIKAPOSTS_RATE % post_id)[0].text 297 | except Exception: 298 | post_rating = "NaN" 299 | post_tags = [result.text for result in page_body.xpath( 300 | XPATH_PIKAPOSTS_TAGS % post_id)] 301 | posts_list.append(ObjectPosts(post_id, post_title, post_url, 302 | post_text, post_image, post_desc, 303 | post_author, post_time, post_comments, 304 | post_rating, post_tags)) 305 | return posts_list 306 | else: 307 | return False 308 | else: 309 | return False 310 | 311 | def add_pic(self, header, description, image, 312 | tags, is_my=False, is_adult=False): 313 | """Постинг картинки на пикабу 314 | Изображение указывается в виде ссылки на компьютере 315 | Теги указываются списком 316 | 317 | """ 318 | pass 319 | 320 | def add_text(self, header, text, tags, is_my=False, is_adult=False): 321 | """Постинг текстовой информации на пикабу""" 322 | pass 323 | 324 | 325 | class PikabuComments(PikaService): 326 | """Вывод комментариев по указанному материалу""" 327 | def get(self, post_id, post_url=''): 328 | if post_id is not None: 329 | _page = self.request("generate_xml_comm.php?id=" + str(post_id)) 330 | if _page is not None: 331 | comment_list = [] 332 | page_body = etree.fromstring(_page.encode("utf-8")) 333 | for item in page_body.xpath(XPATH_PIKACOM_LIST): 334 | comment_id = item.attrib['id'] 335 | comment_rating = item.attrib['rating'] 336 | comment_author = item.attrib['nick'] 337 | comment_parent = item.attrib['answer'] 338 | comment_time = item.attrib['date'] 339 | comment_text = item.text 340 | comment_list.append(ObjectComments(comment_id, 341 | comment_rating, comment_author, 342 | comment_time, comment_text, comment_parent)) 343 | return comment_list 344 | else: 345 | return False 346 | else: 347 | return False 348 | 349 | def add(self, text, post_id, comment_id=0): 350 | """Добавление комментария на пикабу""" 351 | if post_id and text is not None: 352 | _page = self.request("ajax.php", { 353 | 'act': "addcom", 354 | 'id': post_id, 355 | 'comment': text, 356 | 'parentid': comment_id, 357 | 'include': 0, 358 | 'comment_images': 'undefined'}, "POST") 359 | if _page is not None: 360 | try: 361 | return json.loads(_page)["type"] 362 | except Exception: 363 | return False 364 | else: 365 | return False 366 | else: 367 | return False 368 | 369 | class PikabuTopTags(PikaService): 370 | """Список популярных за сутки тегов""" 371 | def get(self, limit=10): 372 | if limit >= 1: 373 | _page = self.request("html.php?id=ad") 374 | if _page is not None: 375 | tag_list = {} 376 | page_body = lxml.html.document_fromstring(_page) 377 | caret = [] 378 | for cur_tag in page_body.xpath(XPATH_PIKATAG): 379 | if len(caret) <= 1: 380 | caret.append(cur_tag.text) 381 | else: 382 | tag_list[len(tag_list) + 1] = caret 383 | caret = [] 384 | caret.append(cur_tag.text) 385 | return tag_list 386 | else: 387 | return False 388 | else: 389 | return False 390 | 391 | 392 | class PikabuUserInfo(PikaService): 393 | """Класс информации о пользователе""" 394 | def __init__(self, **settings): 395 | self.rating = None 396 | self.followers = None 397 | self.messages = None 398 | self.registered = None 399 | self.comments = None 400 | self.news = [] 401 | self.actions = {} 402 | self.awards = [] 403 | self.settings = settings 404 | 405 | def get(self, login, params=""): 406 | """Возвращает информацию о пользователе""" 407 | _page = self.request("profile/" + login) 408 | page_body = lxml.html.document_fromstring(_page) 409 | for x in page_body.xpath( XPATH_PIKAUSER ): 410 | response = re.sub( "\s+", " ", x.encode("utf-8").strip() ) 411 | if len(response) >= 3: 412 | if response.startswith("пикабушник уже"): 413 | parsed = re.search( RE_PUSER_DOR, response ) 414 | if parsed.group():self.registered = parsed.group(1) 415 | else:self.registered = "None" 416 | elif response.startswith("рейтинг"): 417 | parsed = re.sub( "\s", "", response) 418 | try:self.rating = parsed.split(":")[1] 419 | except:self.rating = 0 420 | elif response.startswith("комментариев"): 421 | parsed = re.sub( "\s", "", response) 422 | try:self.comments = parsed.split(":")[1] 423 | except:self.comments = 0 424 | elif response.startswith("добавил постов"): 425 | parsed = re.sub( "\s", "", response) 426 | try:self.news = [x.split(":")[1] for x in parsed.split(",")] 427 | except:self.news = 0 428 | elif response.endswith("минусов"): 429 | self.actions["dislike"] = filter(lambda x: x.isdigit(), response) 430 | elif "плюсов" in response: 431 | self.actions["like"] = filter(lambda x: x.isdigit(), response) 432 | 433 | return ObjectUserInfo(login, self.registered, 434 | self.rating, self.comments, 435 | self.news, self.actions, self.awards) 436 | 437 | 438 | def _awards(self, login): 439 | """Возвращает список наград пользователя""" 440 | if len(self._awards) == 0: 441 | _page = self.request("profile/" + login) 442 | if _page is not None: 443 | page_body = lxml.html.document_fromstring(_page) 444 | self._awards = map( 445 | lambda x: x.get("title").encode('utf-8'), 446 | page_body.xpath(XPATH_PIKAUSER_AWARDS) 447 | ) 448 | else: 449 | pass 450 | else: 451 | pass 452 | return self._awards 453 | 454 | 455 | class PikabuProfile(PikabuUserInfo): 456 | """Профиль авторизованного пользователя""" 457 | def __init__(self, **settings): 458 | self._rating = None 459 | self._followers = None 460 | self._messages = None 461 | self._dor = None 462 | self._comments = None 463 | self._mynews = [] 464 | self._actions = [] 465 | self._awards = [] 466 | self.settings = settings 467 | 468 | def get(self, params=""): 469 | """Возвращает информацию о пользователе""" 470 | if params != "": 471 | if params == "dor": 472 | return self.dor() 473 | if params == "rating": 474 | return self.rating() 475 | if params == "comments": 476 | return self.comments() 477 | if params == "news": 478 | return self.news() 479 | if params == "actions": 480 | return self.actions() 481 | if params == "awards": 482 | return self.awards() 483 | if params == "awards": 484 | return self.awards() 485 | if params == "followers": 486 | return self.followers() 487 | if params == "messages": 488 | return self.messages() 489 | if params == "last_msg": 490 | return self.last_msg() 491 | return ObjectUserInfo(self.settings["login"], self.dor(), 492 | self.rating(), self.comments(), 493 | self.news(), self.actions(), self.awards(), self.followers(), self.messages(), self.last_msg()) 494 | 495 | def dor(self): 496 | """Возвращает дату регистрации пользователя""" 497 | return super(PikabuProfile, self).dor(self.settings["login"]) 498 | 499 | def rating(self): 500 | """Возвращает рейтинг пользователя""" 501 | return super(PikabuProfile, self).rating(self.settings["login"]) 502 | 503 | def comments(self): 504 | """Возвращает количество комментариев""" 505 | return super(PikabuProfile, self).comments(self.settings["login"]) 506 | 507 | def news(self): 508 | """Возвращает количество новостей и их число в горячем""" 509 | return super(PikabuProfile, self).news(self.settings["login"]) 510 | 511 | def actions(self): 512 | """Возвращает массив с плюсами и минусами юзера""" 513 | return super(PikabuProfile, self).actions(self.settings["login"]) 514 | 515 | def awards(self): 516 | """Возвращает массив с наградами пользователя""" 517 | return super(PikabuProfile, self).awards(self.settings["login"]) 518 | 519 | def followers(self): 520 | """Возвращает количество подписчиков""" 521 | if self._followers is None: 522 | _page = self.request("profile/" + self.settings["login"]) 523 | if _page is not None: 524 | page_body = lxml.html.document_fromstring(_page) 525 | self._followers = page_body.xpath( 526 | '//*[@id="subs_num"]')[0].text.strip() 527 | else: 528 | pass 529 | return self._followers 530 | 531 | def messages(self): 532 | """Возвращает количество сообщений пользователю""" 533 | if self._messages is None: 534 | _page = self.request("profile/" + self.settings["login"]) 535 | if _page is not None: 536 | page_body = lxml.html.document_fromstring(_page) 537 | try: 538 | self._messages = page_body.xpath( 539 | XPATH_PIKAUSER_MSG)[0].text 540 | except Exception: 541 | self._messages = 0 542 | else: 543 | pass 544 | return self._messages 545 | 546 | def last_msg(self): 547 | """Возвращает последнее сообщение пользователю""" 548 | _page = self.request("freshitems.php") 549 | if _page is not None: 550 | page_body = lxml.html.document_fromstring(_page) 551 | try: 552 | comment_text = page_body.xpath( 553 | '//*[@id="com2"]//tr[2]/td/div/div')[0].text.strip() 554 | except Exception: 555 | comment_text = None 556 | return False 557 | if comment_text is not None: 558 | comment_id = page_body.xpath( 559 | XPATH_PIKAUSER_LSMSG)[0].get("value") 560 | comment_author = page_body.xpath( 561 | '//*[@id="com2"]//tr[1]/td/noindex/a[3]')[0].text 562 | comment_time = page_body.xpath( 563 | '//*[@id="com2"]//tr[1]/td/noindex/a[4]')[0].text 564 | comment_rating = page_body.xpath( 565 | '//*[@id="com2"]//tr[1]/td/noindex/h6')[0].text 566 | comment_post = page_body.xpath( 567 | '//*[@id="com2"]//tr[1]/td/noindex/a[5]')[0].text 568 | return ObjectComments(comment_id, comment_rating, 569 | comment_author, comment_time, 570 | comment_text, comment_post) 571 | else: 572 | return False 573 | else: 574 | return False 575 | 576 | def set(self, arg, value): 577 | pass 578 | 579 | 580 | class PikabuRegistration(PikaService): 581 | """Класс для регистрации пользователя 582 | После вызова api.register() она возвращает base64 капчи, код которой 583 | нужно указать последним параметром в функции add 584 | """ 585 | def __init__(self, **settings): 586 | self.rv = None 587 | self.first_hidden = None 588 | self.pass_name = None 589 | self.settings = settings 590 | 591 | def __call__(self): 592 | """Возвращает капчу и секретные поля""" 593 | _page = self.request("index.php", None, "POST", False) 594 | page_body = lxml.html.document_fromstring(_page) 595 | self.first_hidden = page_body.xpath( 596 | '//*[@id="form2"]/input[2]')[0].get("name") 597 | self.rv = re.search( 598 | '\$\("\#rv"\)\.val\(\'(\w+)\'\);', page_body.xpath( 599 | '/html/head/script[6]')[0].text).group(1) 600 | self.pass_name = page_body.xpath( 601 | '//*[@id="rm_pass"]')[0].get("name") 602 | _page = self.request( 603 | "kcaptcha/index.php?PHPSESS=%s"%SITE_REQUEST.cookies["PHPSESS"], 604 | None, "POST", False) 605 | return {"image":base64.encodestring(_page)} 606 | 607 | def add(self, login, password, email, captcha_code): 608 | global IS_LOGGED 609 | """Функция проверяет существование юзера, почты и капчу. 610 | Если все круто - регистрирует юзера, если нет - возвращает ошибку 611 | """ 612 | if self.pass_name and self.rv and self.first_hidden is not None: 613 | """Проверяем свободен ли логин""" 614 | check_login = self.request( 615 | "signup.php?avail=" + login, None, "POST", False) 616 | if check_login.endswith("0"): 617 | return {"result":False, 618 | "error":"Логин уже используется"} 619 | 620 | """Проверяем свободен ли ящик""" 621 | check_email = self.request( 622 | "ajax/ajax_login.php", { 623 | "mode": "test_email", 624 | "email": email 625 | }, "POST", False) 626 | if int(check_email) == 1: 627 | return {"result":False, 628 | "error":"Почтовый ящик уже используется"} 629 | 630 | """Проверяем капчу""" 631 | check_captcha = self.request("signup.php", { 632 | "check_captcha": 1, 633 | "captcha": captcha_code 634 | }, "POST", False) 635 | if int(check_captcha) == 1: 636 | return {"result":False, 637 | "error":"Неверно указана капча"} 638 | 639 | _page = self.request("signup.php", { 640 | 'enter_type': "simple", 641 | 'username': login, 642 | self.first_hidden:"", 643 | 'email': email, 644 | 'rv': self.rv, 645 | self.pass_name: password, 646 | 'password2':password, 647 | 'captcha': captcha_code, 648 | 'agree': 1}, "POST", False) 649 | self.settings['login'] = login 650 | self.settings['password'] = password 651 | IS_LOGGED = True 652 | return True 653 | else: 654 | return {"result":False, 655 | "error":"Произошла ошибка, попробуйте получить капчу еще раз!"} 656 | 657 | 658 | class PikabuSetRating(PikaService): 659 | """Смена рейтинга поста/комментария""" 660 | def set(self, action, _type, post_id, comment_id=None): 661 | if _type == 1: 662 | if action: 663 | act = "+" 664 | else: 665 | act = "-" 666 | _page = self.request("ajax/dig.php", {"i": post_id, "type": act}) 667 | if _page is not None: 668 | return _page 669 | else: 670 | return False 671 | else: 672 | if comment_id and post_id is not None: 673 | if action: 674 | act = 1 675 | else: 676 | act = 0 677 | _page = self.request( 678 | "dig.php", { 679 | 'type': "comm", 680 | 'i': comment_id, 681 | 'story': post_id, 682 | 'dir': act}, "GET") 683 | if _page is not None: 684 | return _page 685 | else: 686 | return False 687 | 688 | 689 | class ObjectPosts(): 690 | """Объект с постами""" 691 | def __init__(self, _id, title, url, description, 692 | image, text, author, time, comment, rating, tags): 693 | self.id = _id 694 | self.title = title 695 | self.url = url 696 | self.description = description 697 | self.tags = None 698 | self.image = image 699 | self.text = text 700 | self.author = author 701 | self.time = time 702 | self.comments = comment 703 | self.rating = rating 704 | self.tags = tags 705 | 706 | def tags(self, tags): 707 | self.tags = tags 708 | 709 | 710 | class ObjectComments(): 711 | """Объект с комментариями""" 712 | def __init__(self, _id, rating, author, 713 | time, text, parent=0, post=None): 714 | self.id = _id 715 | self.rating = rating 716 | self.author = author 717 | self.time = time 718 | self.text = text 719 | self.post = post 720 | self.parent = parent 721 | 722 | 723 | class ObjectUserInfo(): 724 | """Объект информации о пользователе""" 725 | def __init__(self, login, dor, rating, comments, news, actions, awards, followers=None, messages=None, last_msg=None): 726 | self.login = login 727 | self.dor = dor 728 | self.rating = rating 729 | self.comments = comments 730 | self.news = news 731 | self.actions = actions 732 | self.awards = awards 733 | self.followers = followers 734 | self.messages = messages 735 | self.last_msg = last_msg 736 | 737 | 738 | class Api: 739 | """Основной класс инициализирующий сервисы пикабу""" 740 | def __init__(self, **settings): 741 | self._settings = settings 742 | self.posts = PikabuPosts(**self._settings) 743 | self.comments = PikabuComments(**self._settings) 744 | self.top_tags = PikabuTopTags(**self._settings) 745 | self.users = PikabuUserInfo(**self._settings) 746 | #self.user_posts = PikabuUserPosts(**self._settings) 747 | self.profile = PikabuProfile(**self._settings) 748 | self.rate = PikabuSetRating(**self._settings) 749 | self.register = PikabuRegistration(**self._settings) --------------------------------------------------------------------------------