mirror of
https://github.com/viq/NewsBlur.git
synced 2025-04-13 09:38:09 +00:00
227 lines
8.5 KiB
Python
227 lines
8.5 KiB
Python
import datetime
|
|
import re
|
|
|
|
import redis
|
|
from django.conf import settings
|
|
from django.contrib.sites.models import Site
|
|
from django.core.mail import EmailMultiAlternatives
|
|
from django.template.loader import render_to_string
|
|
from django.urls import reverse
|
|
from django.utils.html import linebreaks
|
|
|
|
from apps.notifications.models import MUserFeedNotification
|
|
from apps.notifications.tasks import QueueNotifications
|
|
from apps.profile.models import MSentEmail, Profile
|
|
from apps.reader.models import UserSubscription, UserSubscriptionFolders
|
|
from apps.rss_feeds.models import Feed, MFetchHistory, MStory
|
|
from utils import log as logging
|
|
from utils.scrubber import Scrubber
|
|
from utils.story_functions import linkify
|
|
|
|
|
|
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)
|
|
except Feed.MultipleObjectsReturned:
|
|
feeds = Feed.objects.filter(feed_address=feed_address)[:1]
|
|
if feeds.count():
|
|
feed = feeds[0]
|
|
except Feed.DoesNotExist:
|
|
feed = None
|
|
|
|
# If not found, check among titles user has subscribed to
|
|
if not feed:
|
|
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()
|
|
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)
|
|
|
|
feed.last_update = datetime.datetime.now()
|
|
feed.last_story_date = datetime.datetime.now()
|
|
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"],
|
|
"story_content": story_content,
|
|
"story_author_name": params["from"],
|
|
"story_permalink": "https://%s%s"
|
|
% (
|
|
Site.objects.get_current().domain,
|
|
reverse("newsletter-story", kwargs={"story_hash": story_hash}),
|
|
),
|
|
"story_guid": params["signature"],
|
|
}
|
|
|
|
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")
|
|
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()
|
|
|
|
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
|
|
|
|
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:
|
|
return params["body-html"]
|
|
if "stripped-html" in params and not force_plain:
|
|
return params["stripped-html"]
|
|
if "body-plain" in params:
|
|
return linkify(linebreaks(params["body-plain"]))
|
|
|
|
if force_plain:
|
|
return self._get_content(params, force_plain=False)
|
|
|
|
def _clean_content(self, content):
|
|
original = content
|
|
scrubber = Scrubber()
|
|
content = scrubber.scrub(content)
|
|
if len(content) < len(original) * 0.01:
|
|
content = original
|
|
content = content.replace("!important", "")
|
|
return content
|
|
|
|
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],))
|
|
|
|
if MUserFeedNotification.feed_has_users(feed.pk) > 0:
|
|
QueueNotifications.delay(feed.pk, 1)
|