def posts_list(self): """ Making list of posts with senders' IDs and count of likes. :return: list of posts """ if self.plist: print("OK") return posts = self._get_posts() task = len(posts) packs = make_packs(posts, 2) self.bar.set_text(_("Обработка постов")) workers = [ threading.Thread(target=self._process_post_pack, args=(pack, )) for pack in packs ] for w in workers: w.start() while True: alive = [w.is_alive() for w in workers] if alive == [False, False]: break self.bar.set_value(percents(len(self.plist), task)) time.sleep(0.005) Logger.info(NAME + _("Обработано %s постов"), len(self.plist)) self.plist = self.plist[:self.posts_lim] self.bar.finish()
class About(TabbedPanel): link = Object rst = Object about_text = _("О программе") description = _( "[b][size=28]ScadsStats 1.0[/size][/b]\n" "Вычисление активных пользователей на стенах ВКонтакте.[color=3366bb]\n" "[ref=https://vk.com/sysrqtech]Сообщество ВК[/ref][/color]") credits_text = _("Благодарности") authors = _("Авторы") translators = _("Переводчики") designers = _("Дизайнеры") license_item = _("Лицензия") loading = _("Загрузка...") help_item = _("Помочь нам") help_text = _("Вы перенаправлены на страницу репозитория.\n" "Сделайте программу лучше!") def __init__(self, **kwargs): super(About, self).__init__(**kwargs) self.bind(current_tab=self.on_current_tab) def on_current_tab(self, *args): if args[1].text == _("Лицензия"): self.rst.text = open(SCRIPTDIR + "/docs/license.rst").read() elif args[1].text == _("Помочь нам"): open_url("https://github.com/SysRq-Tech/ScadsStats")
def gather_stats(self): """ Gathering statistics for likers. :return: dictionary with user's information and general count of likes """ self.posts_list() self.commentators() self.bar.set_text(_("Обработка пользователей")) comm_unique = list({uid for uid in self.comm_list}) result = [] did = 0 task = len(comm_unique) users_data = self.users(comm_unique) self.bar.set_text(_("Обработка пользователей")) for commentator in users_data: count = self.comm_list.count(commentator["id"]) if "deactivated" in commentator: commentator["screen_name"] = commentator["deactivated"].upper() self.bar.set_value(percents(did, task)) result.append((count, commentator)) did += 1 self.bar.finish() return result
class Update(BoxLayout): no = Object text_no = _("Нет") yes = Object text_yes = _("Да") version = Object upd_text = Object
def start(self): """ Gathering statistics """ if self.started: return self.started = True group = self.group_input.text from_date = self.from_input.text to_date = self.to_input.text posts = self.posts_input.text mode = self.mode.text if not group: warning(_("Укажите стену")) self._restore() return if not posts: posts = 0 else: posts = int(posts) try: if mode == _("Пишущие"): method = Stats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) elif mode == _("Лайкаемые"): method = FavoritesStats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) elif mode == _("Лайкеры"): method = LikersStats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) elif mode == _("Обсуждаемые"): method = DiscussedStats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) else: method = CommentatorsStats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) except Stop as err: warning(err.args[0]) self._restore() return bucket = queue.Queue() thread = ExcThread(bucket, target=method, after=self._restore).start() threading.Thread(target=self.watch, args=(bucket, )).start()
def gather_stats(self): """ Gathering statistics for commented posts. :return: dictionary with user's information and general count of comments to his/her posts """ self.posts_list() self.bar.set_text(_("Обработка пользователей")) data = [val["data"] for val in self.plist] users = {val[0]: 0 for val in data} result = [] for user, likes, comments in data: users[user] += comments items_list = list(users.items()) users_list = [key[0] for key in items_list] comments_list = [key[-1] for key in items_list] users_data = self.users(users_list) self.bar.set_text(_("Обработка пользователей")) for user, likes in zip(users_data, comments_list): if "deactivated" in user: user["screen_name"] = user["deactivated"].upper() self.bar.set_value(percents(likes, comments_list)) result.append((likes, user)) self.bar.finish() return result
def __init__(self, name, bar, posts_lim=0, from_lim="0.0.0", to_lim="0.0.0"): """ Run set_bar() and loggers() functions before calling. :param name: ID or screen name :param posts_lim: limit for posts :param from_lim: date of the earliest post :param to_lim: date of the latest post """ self.bar = bar self.plist = [] self.likers_list = [] self.comm_list = [] self.id_list = [] self.screen_name = name self.dir_opened = None self.cache = "{}/{}.dat".format(TEMP, self.screen_name) self.savedir = os.path.join(SAVEDIR, self.screen_name) # ID of a wall self.wall = resolve(self.screen_name)["id"] # limit for posts if not posts_lim: self.posts_lim = api.wall.get(owner_id=self.wall, count=1)["count"] else: self.posts_lim = posts_lim Logger.info(NAME + _("Ограничено до %s постов"), self.posts_lim) # date limit try: date_list = [int(num) for num in from_lim.split(".") + to_lim.split(".")] assert len(date_list) == 6 assert date_list[2] > 2000 or not date_list[2] assert date_list[-1] > 2000 except (AssertionError, ValueError): raise Stop(_("Неправильный формат даты!")) if not sum(date_list[:3]): # if result is 0 self.from_lim = 0 else: self.from_lim = time.mktime((date_list[2], date_list[1], date_list[0], 0, 0, 0, 0, 0, -1)) Logger.info(NAME + _("Будут получены посты с даты %s"), from_lim) if not sum(date_list[3:]): self.to_lim = infinity else: self.to_lim = time.mktime((date_list[5], date_list[4], date_list[3], 23, 59, 59, 0, 0, -1)) Logger.info(NAME + _("Будут получены посты до даты %s"), to_lim) if os.path.isfile(self.cache): with open(self.cache, "rb") as cache: loaded = pickle.load(cache) if loaded[4] >= self.from_lim \ and loaded[5] <= self.to_lim \ and loaded[6] <= self.posts_lim: self.plist, self.likers_list, self.comm_list, self.dir_opened = loaded[:4] Logger.info(NAME + _("Кэш стены загруженен"))
class Token(BoxLayout): login = Object login_text = _("Войти") token = Object token_hint = _("полный URL, полученный по инструкции выше") link = Object token_manual = _( "1) Откройте [color=3366bb][ref=http://vk.cc/3T1J9A]страницу авторизации[/ref][/color]\n" + "2) Войдите и дайте разрешения приложению\n" + "3) Скопируйте текст из адресной строки\n" + "4) Вставьте его ниже!")
def token_auth(self, popup): try: get_api(access_token=self.content.token.text) except vk.exceptions.VkAuthError: warning(_("Неверный токен!")) else: popup.dismiss()
def upgrade(version, upd_log): """ Upgrading program :param upd_log: Label object :param version: version name for VK Stats """ upd_log.text = _("Скачиваю новую версию...") installer = TEMP + "/VK_Stats.exe" with open(installer, 'wb') as file: file.write(requests.get( "https://github.com/SysRq-Tech/ScadsStats/releases/download/{}/WIN.exe".format(version)).content) upd_log.text = _("Запускаю установщик...") os.startfile(installer) upd_log.text = _("Завершаю работу приложения...") time.sleep(1.5) os._exit(0)
def send_api_request(self, request, captcha_response=None): """ Modified method with immunity to timeout and bad internet :param request: VK API method :param captcha_response: captcha dictionary """ url = self.API_URL + request._method_name method_args = request._api._method_default_args.copy() method_args.update(stringify_values(request._method_args)) access_token = self.access_token if access_token: method_args['access_token'] = access_token if captcha_response: method_args['captcha_sid'] = captcha_response['sid'] method_args['captcha_key'] = captcha_response['key'] timeout = request._api._timeout try: response = self.requests_session.post(url, method_args, timeout=timeout) except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError): Logger.warning(NAME + _("Операция прервана по тайм-ауту")) time.sleep(5) response = self.send_api_request(request, captcha_response=captcha_response) return response
def get_user_name(): """ :return: user's name and surname for Account menu """ data = api.users.get()[0] return _("Вы авторизованы как [b]{first_name} {last_name}[/b]").format( **data)
def auth(self, popup): try: login(self.login.text, self.password.text) except vk.exceptions.VkAuthError: warning(_("Неверный логин или пароль!")) else: popup.dismiss()
def saveto(): content = Saveto() popup = Popup(title=_("Выберите папку"), title_size='16pt', content=content, size_hint=(0.9, 0.9)) content.select.bind(on_press=Partial(content.save, popup)) popup.open()
def results(folder): """ Setting folder for results :param folder: path to directory where the program will save results """ global SAVEDIR Logger.info(NAME + _("Результаты будут сохранены в %s"), folder) SAVEDIR = folder
def account(self): content = Account() popup = Popup(title=_("Аккаунт"), title_size='16pt', content=content, size_hint=(0.8, 0.6)) content.relogin.bind(on_press=Partial(self.login, parent=popup)) popup.open()
def about(): content = About() popup = Popup(title=_("О ScadsStats"), title_size='16pt', content=content, size_hint=(0.95, 0.95)) content.link.bind(on_ref_press=Partial(open_url, "https://vk.com/sysrqtech")) popup.open()
def start(self): """ Gathering statistics """ if self.started: return self.started = True group = self.group_input.text from_date = self.from_input.text to_date = self.to_input.text posts = self.posts_input.text mode = self.mode.text if not group: warning(_("Укажите стену")) self._restore() return if not posts: posts = 0 else: posts = int(posts) try: if mode == _("Пишущие"): method = Stats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) elif mode == _("Лайкаемые"): method = FavoritesStats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) elif mode == _("Лайкеры"): method = LikersStats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) elif mode == _("Обсуждаемые"): method = DiscussedStats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) else: method = CommentatorsStats(group, Bar(self.bar, self.bar_text), posts_lim=posts, from_lim=from_date, to_lim=to_date) except Stop as err: warning(err.args[0]) self._restore() return bucket = queue.Queue() thread = ExcThread(bucket, target=method, after=self._restore).start() threading.Thread(target=self.watch, args=(bucket,)).start()
class Saveto(BoxLayout): select = Object select_text = _("Выбрать") chooser = Object def save(self, popup): selection = self.chooser.selection results(selection[0] if selection else SAVEDIR) popup.dismiss()
def about(): content = About() popup = Popup(title=_("О ScadsStats"), title_size='16pt', content=content, size_hint=(0.95, 0.95)) content.link.bind( on_ref_press=Partial(open_url, "https://vk.com/sysrqtech")) popup.open()
def upgrade(version, upd_log): """ Upgrading program :param upd_log: Label object :param version: version name for VK Stats """ upd_log.text = _("Скачиваю новую версию...") installer = TEMP + "/VK_Stats.exe" with open(installer, 'wb') as file: file.write( requests.get( "https://github.com/SysRq-Tech/ScadsStats/releases/download/{}/WIN.exe" .format(version)).content) upd_log.text = _("Запускаю установщик...") os.startfile(installer) upd_log.text = _("Завершаю работу приложения...") time.sleep(1.5) os._exit(0)
def datepicker(self): self.date = None content = Date(self.date, self.date_input) popup = Popup(title=_("Ограничение по дате"), title_size='16pt', content=content, size_hint=(0.7, 0.9)) content.ok.bind( on_press=Partial(content.set_date, content.from_date.active_date, content.to_date.active_date, popup)) popup.open()
def commentators(self): """ Users who commented posts. :return: lists of posts and commentators """ if self.comm_list: return self.id_list = [data["id"] for data in self.plist] self.bar.set_text(_("Получение комментаторов")) self._get_comm(task=len(self.id_list))
def likers(self): """ Users who liked posts. :return: lists of likers """ if self.likers_list: return self.id_list = [data["id"] for data in self.plist] self.bar.set_text(_("Получение лайкеров")) self._get_likers(task=len(self.id_list))
def update_check(): """ Checking for updates """ status = upd_check() if status is None: all_ok(_("Вы используете последнюю версию!"), title=_("Нечего делать ;)")) else: if not mustdie: warning(_("Используйте пакетный менеджер для обновления")) else: content = Update() content.version.text = _("Найдено обновление до {}!").format(status) + "\n" + _("Обновиться") + "?" popup = Popup(title=_("Найдено обновление!"), title_size='16pt', content=content, size_hint=(0.8, 0.7)) content.no.bind(on_press=popup.dismiss) content.yes.bind(on_press=Partial(upgrade, status, content.upd_text)) popup.open()
def show(self, text, kill=False): self.message.text = text popup = Popup(title=_("Предупреждение"), title_size='16pt', content=self, size_hint=(0.85, 0.6), auto_dismiss=False) if kill: self.ok.bind(on_press=Partial(os._exit, 1)) else: self.ok.bind(on_press=popup.dismiss) popup.open()
class Login(BoxLayout): log_in = Object log_in_text = _("Войти") by_token = Object by_token_text = _("Без пароля") login = Object login_text = _("Логин:") password = Object password_text = _("Пароль:") def token_auth(self, popup): try: get_api(access_token=self.content.token.text) except vk.exceptions.VkAuthError: warning(_("Неверный токен!")) else: popup.dismiss() def use_token(self, parent_popup, force=False): parent_popup.dismiss() self.content = Token() popup = Popup(title=_("Вход по токену"), title_size='16pt', content=self.content, size_hint=(0.8, 0.65)) if force: popup.auto_dismiss = False self.content.link.bind( on_ref_press=Partial(open_url, "http://vk.cc/3T1J9A")) self.content.login.bind(on_press=Partial(self.token_auth, popup)) popup.open() def auth(self, popup): try: login(self.login.text, self.password.text) except vk.exceptions.VkAuthError: warning(_("Неверный логин или пароль!")) else: popup.dismiss()
def gather_stats(self): """ Gathering statistics [POSTERS]. :return: tuple with user's information and count of posts """ self.posts_list() self.bar.set_text(_("Обработка пользователей")) from_ids = [uid["data"][0] for uid in self.plist] from_ids_unique = list({uid for uid in from_ids}) from_list = [] data = self.users(from_ids_unique) self.bar.set_text(_("Обработка пользователей")) for user in data: if "deactivated" in user: # if user is deleted or banned user["screen_name"] = user["deactivated"].upper() posts_from_user = from_ids.count(user["id"]) self.bar.set_value(percents(user, data)) from_list.append((posts_from_user, user)) self.bar.finish() return from_list
def use_token(self, parent_popup, force=False): parent_popup.dismiss() self.content = Token() popup = Popup(title=_("Вход по токену"), title_size='16pt', content=self.content, size_hint=(0.8, 0.65)) if force: popup.auto_dismiss = False self.content.link.bind(on_ref_press=Partial(open_url, "http://vk.cc/3T1J9A")) self.content.login.bind(on_press=Partial(self.token_auth, popup)) popup.open()
def login(force=False, parent=None): if parent is not None: parent.dismiss() content = Login() popup = Popup(title=_("Вход по паролю"), title_size='16pt', content=content, size_hint=(0.8, 0.55)) if force: popup.auto_dismiss = False content.by_token.bind(on_press=Partial(content.use_token, popup, force=force)) content.log_in.bind(on_press=Partial(content.auth, popup)) popup.open()
def login(force=False, parent=None): if parent is not None: parent.dismiss() content = Login() popup = Popup(title=_("Вход по паролю"), title_size='16pt', content=content, size_hint=(0.8, 0.55)) if force: popup.auto_dismiss = False content.by_token.bind( on_press=Partial(content.use_token, popup, force=force)) content.log_in.bind(on_press=Partial(content.auth, popup)) popup.open()
def use_token(self, parent_popup, force=False): parent_popup.dismiss() self.content = Token() popup = Popup(title=_("Вход по токену"), title_size='16pt', content=self.content, size_hint=(0.8, 0.65)) if force: popup.auto_dismiss = False self.content.link.bind( on_ref_press=Partial(open_url, "http://vk.cc/3T1J9A")) self.content.login.bind(on_press=Partial(self.token_auth, popup)) popup.open()
def _get_posts(self): posts = [] thousands_range = self.posts_lim // 1000 + (1 if self.posts_lim % 1000 else 0) offset = 0 self.bar.set_text(_("Получение постов")) for post in range(thousands_range): if offset > 0: if self._check_limit(posts[-1]) or len(posts) > self.posts_lim: return posts self.bar.set_value(percents(offset, self.posts_lim)) posts.extend(api.execute.wallGetThousand(owner_id=self.wall, offset=offset)) offset += 1000 self.bar.finish() return posts
def check(self, *args): """ Checking for access to VKontakte """ try: if "token.txt" in os.listdir(HOME): token = open(HOME + "/token.txt").read() try: get_api(access_token=token) except vk.exceptions.VkAuthError: self.login(force=True) else: self.login(force=True) except requests.exceptions.ConnectionError: warning(_("Проверьте Ваше интернет-соединение"), kill=True)
def posts_list(self): """ Making list of posts with senders' IDs and count of likes. :return: list of posts """ if self.plist: print("OK") return posts = self._get_posts() task = len(posts) packs = make_packs(posts, 2) self.bar.set_text(_("Обработка постов")) workers = [threading.Thread(target=self._process_post_pack, args=(pack,)) for pack in packs] for w in workers: w.start() while True: alive = [w.is_alive() for w in workers] if alive == [False, False]: break self.bar.set_value(percents(len(self.plist), task)) time.sleep(0.005) Logger.info(NAME + _("Обработано %s постов"), len(self.plist)) self.plist = self.plist[:self.posts_lim] self.bar.finish()
def users(self, users_list): """ List of information about users :param users_list: list of users' IDs """ result = [] task = len(users_list) self.bar.set_text(_("Получение пользователей")) while users_list: users = ",".join([str(user) for user in users_list[:1000] if user > 0]) self.bar.set_value(percents(len(result), task)) data = api.users.get(user_ids=users, fields="screen_name") result.extend(data) del users_list[:1000] self.bar.finish() return result
def _get_posts(self): posts = [] thousands_range = self.posts_lim // 1000 + (1 if self.posts_lim % 1000 else 0) offset = 0 self.bar.set_text(_("Получение постов")) for post in range(thousands_range): if offset > 0: if self._check_limit(posts[-1]) or len(posts) > self.posts_lim: return posts self.bar.set_value(percents(offset, self.posts_lim)) posts.extend( api.execute.wallGetThousand(owner_id=self.wall, offset=offset)) offset += 1000 self.bar.finish() return posts
def users(self, users_list): """ List of information about users :param users_list: list of users' IDs """ result = [] task = len(users_list) self.bar.set_text(_("Получение пользователей")) while users_list: users = ",".join( [str(user) for user in users_list[:1000] if user > 0]) self.bar.set_value(percents(len(result), task)) data = api.users.get(user_ids=users, fields="screen_name") result.extend(data) del users_list[:1000] self.bar.finish() return result
def resolve(url): """ Resolving VKontakte URLs :param url: address of group or profile :return: {"id": <ID of wall>, "name": <screen name>, "title": <title or name/surname>} """ wall_data = api.utils.resolveScreenName(screen_name=url.split("/")[-1]) if not wall_data: raise Stop(_("Неверный URL")) wall_type = wall_data["type"] obj_id = wall_data["object_id"] if wall_type == "group": group_data = api.groups.getById(group_ids=obj_id)[0] screen_name = group_data["screen_name"] title = group_data["name"] wall_id = "-" + str(obj_id) else: profile = api.users.get(user_ids=obj_id, fields="screen_name")[0] screen_name = profile["screen_name"] title = "{first_name} {last_name}".format(**profile) wall_id = obj_id return {"id": wall_id, "name": screen_name, "title": title}
def update_check(): """ Checking for updates """ status = upd_check() if status is None: all_ok(_("Вы используете последнюю версию!"), title=_("Нечего делать ;)")) else: if not mustdie: warning(_("Используйте пакетный менеджер для обновления")) else: content = Update() content.version.text = _("Найдено обновление до {}!").format( status) + "\n" + _("Обновиться") + "?" popup = Popup(title=_("Найдено обновление!"), title_size='16pt', content=content, size_hint=(0.8, 0.7)) content.no.bind(on_press=popup.dismiss) content.yes.bind( on_press=Partial(upgrade, status, content.upd_text)) popup.open()
def on_current_tab(self, *args): if args[1].text == _("Лицензия"): self.rst.text = open(SCRIPTDIR + "/docs/license.rst").read() elif args[1].text == _("Помочь нам"): open_url("https://github.com/SysRq-Tech/ScadsStats")
def info(message, title=_("Некоторая информация")): Info().show(message, title)
def all_ok(message, title=_("Всё OK")): AllOk().show(message, title)
def __call__(self, mode="Writers"): """ Exporting statistics. :param mode: prefix for file """ self.bar.set_text(_("Инициализация")) api.stats.trackVisitor() data = self.gather_stats() self.savedir = os.path.join(SAVEDIR, self.screen_name, mode.lower()) if not os.path.isdir(self.savedir): os.makedirs(self.savedir, exist_ok=True) self.bar.set_text(_("Сохранение результатов")) res_txt = os.path.join(self.savedir, mode.lower() + ".txt") res_csv = os.path.join(self.savedir, mode.lower() + ".csv") res_html = os.path.join(self.savedir, mode.lower() + ".html") for file in res_txt, res_csv, res_html: if os.path.isfile(file): os.remove(file) txt_file = open(res_txt, mode="a") print(_("РЕЖИМ СТАТИСТИКИ:"), mode.upper(), file=txt_file) csv_file = open(res_csv, mode="a", newline="") writer = csv.writer(csv_file) writer.writerow(["URL", _("Имя"), _("Счёт")]) html_file = open(res_html, mode="a") html_header = open(SCRIPTDIR + "/html/stats_header.html").read() html_item = open(SCRIPTDIR + "/html/stats_item.html").read() html_item_inactive = open(SCRIPTDIR + "/html/stats_item_inactive.html").read() html_end = open(SCRIPTDIR + "/html/stats_end.html").read() print(html_header.format(title=mode, user=_("Пользователь"), count=_("Счёт")), file=html_file) Logger.info(NAME + _("Сохранение результатов в %s"), self.savedir) task = len(data) place = [0, infinity] for did in range(1, len(data) + 1): if not data: break max_object = max(data, key=lambda sequence: sequence[0]) max_count = max_object[0] if max_count < place[1]: place[1] = max_count place[0] += 1 max_index = data.index(max_object) user_data = data.pop(max_index)[1] if max_count > 0: prefix = "" if user_data["screen_name"] in ["DELETED", "BANNED"] else "https://vk.com/" user_string = "{2}. {1}{screen_name} ({first_name} {last_name}): {0}".format(max_count, prefix, place[0], **user_data) print(user_string, file=txt_file) writer.writerow([prefix + user_data["screen_name"], "{first_name} {last_name}".format(**user_data), max_count]) if prefix: print(html_item.format(place[0], max_count, **user_data), file=html_file) else: print(html_item_inactive.format(place[0], max_count, **user_data), file=html_file) if not did % 50: for file in txt_file, csv_file, html_file: file.flush() self.bar.set_value(percents(did, task)) time.sleep(0.005) print(html_end.format(_("Получить программу")), file=html_file) for file in txt_file, csv_file, html_file: file.close() if not self.dir_opened == os.path.join(SAVEDIR, self.screen_name): if mustdie: os.startfile(self.savedir) elif platform == "linux": os.system("xdg-open '{}'".format(self.savedir)) self.dir_opened = os.path.join(SAVEDIR, self.screen_name) with open(self.cache, "wb") as cache: pickle.dump([self.plist, self.likers_list, self.comm_list, self.dir_opened, self.from_lim, self.to_lim, self.posts_lim], file=cache) self._restore() all_ok(_("Сделано!"))
def get_user_name(): """ :return: user's name and surname for Account menu """ data = api.users.get()[0] return _("Вы авторизованы как [b]{first_name} {last_name}[/b]").format(**data)