NewsBlur-viq/apps/newsletters/models.py

211 lines
8.5 KiB
Python
Raw Normal View History

import datetime
import re
import redis
2016-03-09 11:30:47 -08:00
from django.contrib.sites.models import Site
from django.core.mail import EmailMultiAlternatives
from django.urls import reverse
2016-02-09 17:31:57 -08:00
from django.conf import settings
from django.template.loader import render_to_string
2016-02-09 17:44:33 -08:00
from django.utils.html import linebreaks
from apps.rss_feeds.models import Feed, MStory, MFetchHistory
from apps.reader.models import UserSubscription, UserSubscriptionFolders
from apps.profile.models import Profile, MSentEmail
from utils import log as logging
from utils.story_functions import linkify
2016-02-18 10:21:24 -08:00
from utils.scrubber import Scrubber
class EmailNewsletter:
def receive_newsletter(self, params):
user = self._user_from_email(params['recipient'])
if not user:
return
sender_name, sender_username, sender_domain = self._split_sender(params['from'])
feed_address = self._feed_address(user, "%s@%s" % (sender_username, sender_domain))
try:
usf = UserSubscriptionFolders.objects.get(user=user)
except UserSubscriptionFolders.DoesNotExist:
logging.user(user, "~FRUser does not have a USF, ignoring newsletter.")
return
usf.add_folder('', 'Newsletters')
# First look for the email address
try:
feed = Feed.objects.get(feed_address=feed_address)
2020-11-19 19:59:07 -05:00
except Feed.MultipleObjectsReturned:
2020-11-30 18:34:53 -05:00
feeds = Feed.objects.filter(feed_address=feed_address)[:1]
2020-11-19 19:59:07 -05:00
if feeds.count():
feed = feeds[0]
except Feed.DoesNotExist:
feed = None
# If not found, check among titles user has subscribed to
if not feed:
2020-11-19 19:52:33 -05:00
newsletter_subs = UserSubscription.objects.filter(user=user, feed__feed_address__contains="newsletter:").only('feed')
newsletter_feed_ids = [us.feed.pk for us in newsletter_subs]
feeds = Feed.objects.filter(feed_title__iexact=sender_name, pk__in=newsletter_feed_ids)
if feeds.count():
feed = feeds[0]
# Create a new feed if it doesn't exist by sender name or email
if not feed:
feed = Feed.objects.create(feed_address=feed_address,
feed_link='http://' + sender_domain,
feed_title=sender_name,
fetched_once=True,
known_good=True)
feed.update()
2016-02-09 17:26:57 -08:00
logging.user(user, "~FCCreating newsletter feed: ~SB%s" % (feed))
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
r.publish(user.username, 'reload:%s' % feed.pk)
self._check_if_first_newsletter(user)
2017-11-05 22:28:25 -08:00
feed.last_update = datetime.datetime.now()
2017-11-05 22:30:22 -08:00
feed.last_story_date = datetime.datetime.now()
2017-11-05 22:28:25 -08:00
feed.save()
if feed.feed_title != sender_name:
feed.feed_title = sender_name
feed.save()
try:
usersub = UserSubscription.objects.get(user=user, feed=feed)
except UserSubscription.DoesNotExist:
_, _, usersub = UserSubscription.add_subscription(
user=user,
feed_address=feed_address,
folder='Newsletters'
)
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
r.publish(user.username, 'reload:feeds')
story_hash = MStory.ensure_story_hash(params['signature'], feed.pk)
story_content = self._get_content(params)
plain_story_content = self._get_content(params, force_plain=True)
if len(plain_story_content) > len(story_content):
story_content = plain_story_content
story_content = self._clean_content(story_content)
story_params = {
"story_feed_id": feed.pk,
"story_date": datetime.datetime.fromtimestamp(int(params['timestamp'])),
"story_title": params['subject'],
2016-02-18 10:21:24 -08:00
"story_content": story_content,
"story_author_name": params['from'],
2016-02-29 18:00:52 -05:00
"story_permalink": "https://%s%s" % (
Site.objects.get_current().domain,
reverse('newsletter-story',
kwargs={'story_hash': story_hash})),
"story_guid": params['signature'],
}
2017-11-05 22:28:25 -08:00
try:
story = MStory.objects.get(story_hash=story_hash)
except MStory.DoesNotExist:
story = MStory(**story_params)
story.save()
usersub.needs_unread_recalc = True
usersub.save()
self._publish_to_subscribers(feed, story.story_hash)
MFetchHistory.add(feed_id=feed.pk, fetch_type='push')
2016-02-09 17:26:57 -08:00
logging.user(user, "~FCNewsletter feed story: ~SB%s~SN / ~SB%s" % (story.story_title, feed))
return story
def _check_if_first_newsletter(self, user, force=False):
if not user.email:
return
subs = UserSubscription.objects.filter(user=user)
found_newsletter = False
for sub in subs:
if sub.feed.is_newsletter:
found_newsletter = True
break
if not found_newsletter and not force:
return
params = dict(receiver_user_id=user.pk, email_type='first_newsletter')
try:
MSentEmail.objects.get(**params)
if not force:
# Return if email already sent
return
except MSentEmail.DoesNotExist:
MSentEmail.objects.create(**params)
text = render_to_string('mail/email_first_newsletter.txt', {})
html = render_to_string('mail/email_first_newsletter.xhtml', {})
subject = "Your email newsletters are now being sent to NewsBlur"
msg = EmailMultiAlternatives(subject, text,
from_email='NewsBlur <%s>' % settings.HELLO_EMAIL,
to=['%s <%s>' % (user, user.email)])
msg.attach_alternative(html, "text/html")
msg.send(fail_silently=True)
logging.user(user, "~BB~FM~SBSending first newsletter email to: %s" % user.email)
def _user_from_email(self, email):
tokens = re.search('(\w+)[\+\-\.](\w+)@newsletters.newsblur.com', email)
if not tokens:
return
username, secret_token = tokens.groups()
try:
profiles = Profile.objects.filter(secret_token=secret_token)
if not profiles:
return
profile = profiles[0]
except Profile.DoesNotExist:
return
return profile.user
def _feed_address(self, user, sender_email):
return 'newsletter:%s:%s' % (user.pk, sender_email)
def _split_sender(self, sender):
tokens = re.search('(.*?) <(.*?)@(.*?)>', sender)
if not tokens:
name, domain = sender.split('@')
return name, sender, domain
sender_name, sender_username, sender_domain = tokens.group(1), tokens.group(2), tokens.group(3)
sender_name = sender_name.replace('"', '')
return sender_name, sender_username, sender_domain
2016-02-09 17:44:33 -08:00
def _get_content(self, params, force_plain=False):
if 'body-enriched' in params and not force_plain:
return params['body-enriched']
if 'body-html' in params and not force_plain:
2016-02-09 17:44:33 -08:00
return params['body-html']
if 'stripped-html' in params and not force_plain:
return params['stripped-html']
2016-02-09 17:44:33 -08:00
if 'body-plain' in params:
return linkify(linebreaks(params['body-plain']))
2016-02-09 17:44:33 -08:00
def _clean_content(self, content):
original = content
2016-02-18 10:21:24 -08:00
scrubber = Scrubber()
2016-03-08 15:56:15 -08:00
content = scrubber.scrub(content)
if len(content) < len(original)*0.01:
content = original
2016-03-08 15:56:15 -08:00
content = content.replace('!important', '')
return content
2016-02-18 10:21:24 -08:00
def _publish_to_subscribers(self, feed, story_hash):
try:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
listeners_count = r.publish("%s:story" % feed.pk, 'story:new:%s' % story_hash)
if listeners_count:
logging.debug(" ---> [%-30s] ~FMPublished to %s subscribers" % (feed.log_title[:30], listeners_count))
except redis.ConnectionError:
logging.debug(" ***> [%-30s] ~BMRedis is unavailable for real-time." % (feed.log_title[:30],))