You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
stacosys/app/services/processor.py

232 lines
7.5 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import re
from datetime import datetime
from threading import Thread
from queue import Queue
import chardet
from jinja2 import Environment, FileSystemLoader
from app.models.site import Site
from app.models.comment import Comment
logger = logging.getLogger(__name__)
queue = Queue()
proc = None
env = None
class Processor(Thread):
def stop(self):
logger.info("stop requested")
self.is_running = False
def run(self):
self.is_running = True
while self.is_running:
msg = queue.get()
if msg['request'] == 'new_comment':
new_comment(msg['data'])
#elif msg['type'] == 'reply_comment_email':
# reply_comment_email(req['From'], req['Subject'], req['Body'])
#elif req['type'] == 'unsubscribe':
# unsubscribe_reader(req['email'], req['article'])
else:
logger.info("Dequeue unknown request " + msg)
def new_comment(data):
try:
token = data.get('token', '')
url = data.get('url', '')
author_name = data.get('author', '')
author_email = data.get('email', '')
author_site = data.get('site', '')
message = data.get('message', '')
subscribe = data.get('subscribe', '')
# create a new comment row
site = Site.select().where(Site.token == token).get()
logger.info('new comment received: %s' % data)
if author_site and author_site[:4] != 'http':
author_site = 'http://' + author_site
created = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
comment = Comment(site=site, url=url, author_name=author_name,
author_site=author_site, author_email=author_email,
content=message, created=created, published=None)
comment.save()
1 / 0
# Render email body template
email_body = get_template('new_comment').render(url=url, comment=comment)
# Send email
mail(pecosys.get_config('post', 'from_email'),
pecosys.get_config('post', 'to_email'),
'[' + branch_name + '-' + article + ']', email_body)
# Reader subscribes to further comments
if subscribe and email:
subscribe_reader(email, article, url)
logger.debug("new comment processed ")
except:
logger.exception("new_comment failure")
def reply_comment_email(from_email, subject, message):
try:
m = re.search('\[(\d+)\-(\w+)\]', subject)
branch_name = m.group(1)
article = m.group(2)
message = decode_best_effort(message)
# safe logic: no answer or unknown answer is a go for publishing
if message[:2].upper() == 'NO':
logger.info('discard comment: %s' % branch_name)
email_body = get_template('drop_comment').render(original=message)
mail(pecosys.get_config('post', 'from_email'),
pecosys.get_config('post', 'to_email'),
'Re: ' + subject, email_body)
else:
if pecosys.get_config("git", "disabled"):
logger.debug("GIT usage disabled (debug mode)")
else:
git.merge(branch_name)
if pecosys.get_config("git", "remote"):
git.push()
logger.info('commit comment: %s' % branch_name)
# send approval confirmation email to admin
email_body = get_template('approve_comment').render(original=message)
mail(pecosys.get_config('post', 'from_email'),
pecosys.get_config('post', 'to_email'),
'Re: ' + subject, email_body)
# notify reader once comment is published
reader_email, article_url = get_email_metadata(message)
if reader_email:
notify_reader(reader_email, article_url)
# notify subscribers every time a new comment is published
notify_subscribers(article)
if pecosys.get_config("git", "disabled"):
logger.debug("GIT usage disabled (debug mode)")
else:
git.branch("-D", branch_name)
except:
logger.exception("new email failure")
def get_email_metadata(message):
# retrieve metadata reader email and URL from email body sent by admin
email = ""
url = ""
m = re.search('email:\s(.+@.+\..+)', message)
if m:
email = m.group(1)
m = re.search('url:\s(.+)', message)
if m:
url = m.group(1)
return (email, url)
def subscribe_reader(email, article, url):
logger.info("subscribe reader %s to %s (%s)" % (email, article, url))
db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json')
db.insert({'email': email, 'article': article, 'url': url})
def unsubscribe_reader(email, article):
logger.info("unsubscribe reader %s from %s" % (email, article))
db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json')
db.remove((where('email') == email) & (where('article') == article))
def notify_subscribers(article):
logger.info('notify subscribers for article %s' % article)
db = TinyDB(pecosys.get_config('global', 'cwd') + '/db.json')
for item in db.search(where('article') == article):
logger.info(item)
to_email = item['email']
logger.info("notify reader %s for article %s" % (to_email, article))
unsubscribe_url = pecosys.get_config('subscription', 'url') + '?email=' + to_email + '&article=' + article
email_body = get_template('notify_subscriber').render(article_url=item['url'],
unsubscribe_url=unsubscribe_url)
subject = get_template('notify_message').render()
mail(pecosys.get_config('subscription', 'from_email'), to_email, subject, email_body)
def notify_reader(email, url):
logger.info('notify reader: email %s about URL %s' % (email, url))
email_body = get_template('notify_reader').render(article_url=url)
subject = get_template('notify_message').render()
mail(pecosys.get_config('subscription', 'from_email'), email, subject, email_body)
def decode_best_effort(string):
info = chardet.detect(string)
if info['confidence'] < 0.5:
return string.decode('utf8', errors='replace')
else:
return string.decode(info['encoding'], errors='replace')
def mail(from_email, to_email, subject, *messages):
# Create the container (outer) email message.
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = to_email
msg.preamble = subject
for message in messages:
part = MIMEText(message, 'plain')
msg.attach(part)
s = smtplib.SMTP(pecosys.get_config('smtp', 'host'),
pecosys.get_config('smtp', 'port'))
if(pecosys.get_config('smtp', 'starttls')):
s.starttls()
s.login(pecosys.get_config('smtp', 'login'),
pecosys.get_config('smtp', 'password'))
s.sendmail(from_email, to_email, msg.as_string())
s.quit()
def get_template(name):
return env.get_template(pecosys.get_config('global', 'lang') + '/' + name + '.tpl')
def enqueue(something):
queue.put(something)
def get_processor():
return proc
def start(template_dir):
global proc, env
# initialize Jinja 2 templating
logger.info("load templates from directory %s" % template_dir)
env = Environment(loader=FileSystemLoader(template_dir))
# start processor thread
proc = Processor()
proc.start()