From 8bfeb88e34a42eddbe19aa8bbd0790bc508b8b78 Mon Sep 17 00:00:00 2001 From: Maxim Likhachev Date: Sun, 20 Oct 2019 07:18:02 +0500 Subject: [PATCH] vkdigest: python3 --- vkdigest.py | 408 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100755 vkdigest.py diff --git a/vkdigest.py b/vkdigest.py new file mode 100755 index 0000000..f097ad0 --- /dev/null +++ b/vkdigest.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (C) 2019, Maxim Lihachev, +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import re +import sys +import json +import vk_api +import getopt +import string +import datetime +import configparser +import lib.markup + +from lib.markup import oneliner as _ + +from smtplib import SMTP_SSL as SMTP +from email.mime.text import MIMEText + +############################################################################ + + +def usage(): + print(''' + vkdigest — сценарий для получения сообщений из сообществ vk.com + с выводом в html и/или отправкой по электронной почте. + + Аргументы командной строки: + -h --help - вывести справку по использованию + -f --file - загрузить сообщества из файла + -u --url - открыть сообщество по адресу + -m --mail - отправить дайжест по электронной почте + -s --subj - тема сообщения, макросы: {DATE}, {URL}, {COMMENT} + -t --title - HTML-заголовок, то же самое, что и --subj + -c --cli - вывести дайжест на экран (по умолчанию) + -o --out - вывести дайжест в файл + ''') + sys.exit(2) + +############################################################################ + + +# Конфигурационный файл +CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'conf/vkdigest.ini') + +# Вывод на экран +# -c | --cli +CLI_OUTPUT = True + +# Вывод в файл +# -f +# --file +FILE_OUTPUT = '' + +# Отправка почты по умолчанию +SEND_MAIL = False + +# Путь до css-файла +CSS_FILE = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.path.pardir, 'css/vkdigest.css') +) + +############################################################################ + + +def readConfig(config_file): + '''Чтение конфигурационного файла''' + global settings + settings = configparser.ConfigParser() + settings._interpolation = configparser.ExtendedInterpolation() + settings.read(config_file) + return settings + + +def opt(parameter): + '''Получение опции из файла''' + setting = parameter.split(":") + return settings.get(setting[0], setting[1]) + + +def enabled(opt): + return opt.lower() in ("yes", "true", "t", "1") + + +def timestamp_to_date(timestamp, fmt='%Y-%m-%d %H:%M:%S'): + '''Преобразование временного штампа в читаемую дату''' + return datetime.datetime.fromtimestamp(int(timestamp)).strftime(fmt) + + +def today(): + '''Текущая дата''' + return int( + datetime.datetime.strptime( + datetime.datetime.today().strftime('%Y-%m-%d'), '%Y-%m-%d' + ).strftime("%s") + ) + + +############################################################################ + +def make_title(): + '''Заголовок страницы и тема письма''' + return MAIL_SUBJECT.format( + URL=opt('mail:url'), + COMMENT=opt('mail:comment'), + DATE=timestamp_to_date(today(), '%Y-%m-%d') + ) + + +# Псевдоним для функции send_email +make_subject = make_title + + +def send_email(message): + '''Отправка письма с дайжестом''' + subject = make_subject() + + try: + msg = MIMEText(message, 'html') + msg['Subject'] = subject + msg['From'] = opt('mail:sender') + + conn = SMTP(opt('mail:SMTPserver')) + conn.set_debuglevel(False) + conn.login(opt('mail:username'), opt('mail:password')) + try: + conn.sendmail( + opt('mail:sender'), opt('mail:destination'), msg.as_string() + ) + finally: + conn.quit() + + except Exception as exc: + sys.exit("mail failed; %s" % str(exc)) + + +############################################################################ + +def auth(login, password): + '''Аутентификация в vk.com''' + vk_session = vk_api.VkApi(login, password) + try: + vk_session.auth() + return vk_session.get_api() + except vk_api.AuthError as error_msg: + print(error_msg) + return + + +def only_today(wall, date=None): + '''Записи за указанную дату''' + return [post for post in wall if post['date'] >= today()] + + +def get_group_url(group): + '''Адрес группы''' + return group.split(' ')[0] + + +def get_group_name(url): + '''HTTP-имя группы''' + return url.split('/')[-1] + + +def get_attachments(attachments, type): + '''Получение вложений''' + return [attachment[type] + for attachment + in attachments + if attachment['type'] == type] + + +def group_info(name): + '''Информация о сообществе''' + # TODO: если репост со страницы пользователя, то вставляет название группы + return vk.groups.getById(group_ids=name)[0] + + +def wall_url(group_id, post_id): + '''Ссылка на конкретный пост''' + return "http://vk.com/wall" + str(group_id) + '_' + str(post_id) + + +def wall(name): + '''Получение записей со стены сообщества''' + id = group_info(get_group_name(name))['id'] + + content = vk.wall.get(owner_id=-id)['items'] + + if enabled(opt('digest:only_today')): + content = only_today(content) + + return content + +############################################################################ + + +def html_toc(groups): + '''Содержание дайжеста со ссылками на группы''' + HTML.h2("Сообщества:") + HTML.ul() + for group in groups: + if group.strip(): + group_name = get_group_name(get_group_url(group)) + info = group_info(group_name) + + HTML.li(_.a(info['name'], href='#' + group_name)) + HTML.ul.close() + HTML.br() + HTML.hr() + + +def groups_info(input_file): + '''Информация о группах, перечисленных в файле''' + f = open(input_file, "r") + groups = f.readlines() + f.close() + + if len(groups) > 1: + html_toc(groups) + + for group in groups: + if group.strip(): + url = get_group_url(group) + MAIL_URL = url + + if '#' in group: + MAIL_COMMENT = group.split('#')[1] + + group_info_html(url) + wall_html(url) + + +def group_info_html(name): + '''Информация о группе в формате HTML''' + group_name = get_group_name(name) + info = group_info(group_name) + + HTML.div(class_='group-info') + HTML.img(src=info['photo_50']) + + if enabled(opt('digest:add_links')): + HTML.h1(_.a(info['name'], href="http://vk.com/" + info['screen_name']), + id=group_name + ) + else: + HTML.h1(info['name'], id=group_name) + + HTML.div.close() + HTML.br() + HTML.hr() + + +def wall_html(name): + '''Сообщения со стены сообщества в формате HTML''' + info = wall(name) + + for post in info: + if post['text'] or ('attachments' in post): + post_html(post) + # Репост + elif 'copy_history' in post: + post_html(post['copy_history'][0], True) + + +def photo_size(photo, get_size="max"): + '''Ссылка на фотографию большего или меньшего размера''' + index = -1 if get_size == "max" else 0 + + size = sorted([p['type'] for p in photo['sizes']])[index] + + return list(filter(lambda p: p['type'] == size, photo['sizes']))[0]['url'] + + +def post_html(content, repost=False): + '''Информация о посте в формате HTML''' + HTML.div(class_="post") + + if repost: + original_group = group_info(abs(content['owner_id'])) + original_name = original_group['name'] + original_url = wall_url(content['from_id'], content['id']) + + # Дата поста + HTML.h4() + + if enabled(opt('digest:add_links')): + HTML.add(_.a(timestamp_to_date(content['date']), href=wall_url(content['from_id'], content['id'])) + (' via ' + _.a(original_name, href=original_url) if repost else '')) + else: + HTML.add(timestamp_to_date(content['date']) + (' via ' + original_name if repost else '')) + + HTML.h4.close() + + # Текст поста + HTML.p(tuple(content['text'].splitlines())) + + # Документы + if 'attachments' in content: + for photo in get_attachments(content['attachments'], 'photo'): + HTML.a(_.img(src=photo_size(photo, "min")), href=photo_size(photo, "max")) + + for document in get_attachments(content['attachments'], 'doc'): + HTML.ul(_.li(_.a(document['title'], href=document['url']))) + + for link in get_attachments(content['attachments'], 'link'): + HTML.ul(_.li(_.a(link['title'], href=link['url']))) + + HTML.br() + HTML.div.close() #
+ HTML.hr() + +############################################################################ + + +try: + opts, args = getopt.getopt(sys.argv[1:], 'f:u:s:t:o:mch', ['file=', 'url=', 'subj=', 'title=', 'out=', 'mail', 'cli', 'help']) +except getopt.GetoptError: + usage() + +if not opts: + usage() + +input_file = '' +url = '' + +CLI_OUTPUT_FLAG = False + +for option, arg in opts: + if option in ('-h', '--help'): + usage() + elif option in ('-f', '--file'): + input_file = arg + elif option in ('-u', '--url'): + url = arg + elif option in ('-s', '--subj'): + MAIL_SUBJECT = arg + elif option in ('-t', '--title'): + MAIL_SUBJECT = arg + elif option in ('-o', '--out'): + FILE_OUTPUT = arg + CLI_OUTPUT = False + elif option in ('-m', '--mail'): + SEND_MAIL = True + CLI_OUTPUT = False + elif option in ('-c', '--cli'): + CLI_OUTPUT_FLAG = True + else: + usage() + +if CLI_OUTPUT_FLAG: + CLI_OUTPUT = True + +############################################################################ + +# Чтение файла настроек +settings = [] +readConfig(CONFIG_FILE) + +MAIL_SUBJECT = opt('mail:subject') + +# Учётная запись vk.com +vk = auth(opt('vk:username'), opt('vk:password')) + +HTML = lib.markup.page() + +HTML.init( + title=make_title(), + lang="ru", + charset="utf-8", + css=(CSS_FILE if os.path.isfile(CSS_FILE) else "") +) + +if input_file: + if os.path.isfile(input_file): + groups_info(input_file) + else: + print("Неправильный входной файл ", input_file) +elif url: + group_info_html(url) + wall_html(url) +else: + usage() + +############################################################################ + +if SEND_MAIL: + send_email(HTML()) + +if FILE_OUTPUT: + with open(FILE_OUTPUT, 'w') as outfile: + outfile.write(HTML()) + +if CLI_OUTPUT: + print(HTML)