1 changed files with 408 additions and 0 deletions
@ -0,0 +1,408 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
# -*- coding: utf-8 -*- |
||||||
|
|
||||||
|
# Copyright (C) 2019, Maxim Lihachev, <envrm@yandex.ru> |
||||||
|
# |
||||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
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 <filename> |
||||||
|
# --file <filename> |
||||||
|
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() # <div class="post"> |
||||||
|
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) |
Loading…
Reference in new issue