mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-04-13 09:42:01 +00:00
579 lines
20 KiB
Python
579 lines
20 KiB
Python
import base64
|
|
import datetime
|
|
import os
|
|
import urllib.parse
|
|
|
|
import lxml.html
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.contrib.auth import login as login_user
|
|
from django.contrib.auth import logout as logout_user
|
|
from django.core.mail import mail_admins
|
|
from django.http import HttpResponse
|
|
from django.shortcuts import render
|
|
|
|
from apps.profile.models import Profile
|
|
from apps.reader.forms import LoginForm, SignupForm
|
|
from apps.reader.models import RUserStory, UserSubscription, UserSubscriptionFolders
|
|
from apps.rss_feeds.models import Feed, MStarredStory, MStarredStoryCounts
|
|
from apps.rss_feeds.text_importer import TextImporter
|
|
from apps.social.models import MSharedStory, MSocialProfile, MSocialSubscription
|
|
from utils import json_functions as json
|
|
from utils import log as logging
|
|
from utils.feed_functions import relative_timesince
|
|
from utils.user_functions import ajax_login_required, get_user
|
|
from utils.view_functions import required_params
|
|
|
|
|
|
@json.json_view
|
|
def login(request):
|
|
code = -1
|
|
errors = None
|
|
user_agent = request.environ.get("HTTP_USER_AGENT", "")
|
|
ip = request.META.get("HTTP_X_FORWARDED_FOR", None) or request.META["REMOTE_ADDR"]
|
|
|
|
if not user_agent or user_agent.lower() in ["nativehost"]:
|
|
errors = dict(user_agent="You must set a user agent to login.")
|
|
logging.user(request, "~FG~BB~SK~FRBlocked ~FGAPI Login~SN~FW: %s / %s" % (user_agent, ip))
|
|
elif request.method == "POST":
|
|
form = LoginForm(data=request.POST)
|
|
if form.errors:
|
|
errors = form.errors
|
|
if form.is_valid():
|
|
login_user(request, form.get_user(), backend="django.contrib.auth.backends.ModelBackend")
|
|
logging.user(request, "~FG~BB~SKAPI Login~SN~FW: %s / %s" % (user_agent, ip))
|
|
code = 1
|
|
else:
|
|
errors = dict(method="Invalid method. Use POST. You used %s" % request.method)
|
|
|
|
return dict(code=code, errors=errors)
|
|
|
|
|
|
@json.json_view
|
|
def signup(request):
|
|
code = -1
|
|
errors = None
|
|
ip = request.META.get("HTTP_X_FORWARDED_FOR", None) or request.META["REMOTE_ADDR"]
|
|
|
|
if request.method == "POST":
|
|
form = SignupForm(data=request.POST)
|
|
if form.errors:
|
|
errors = form.errors
|
|
if form.is_valid():
|
|
try:
|
|
new_user = form.save()
|
|
login_user(request, new_user, backend="django.contrib.auth.backends.ModelBackend")
|
|
logging.user(request, "~FG~SB~BBAPI NEW SIGNUP: ~FW%s / %s" % (new_user.email, ip))
|
|
code = 1
|
|
except forms.ValidationError as e:
|
|
errors = [e.args[0]]
|
|
else:
|
|
errors = dict(method="Invalid method. Use POST. You used %s" % request.method)
|
|
|
|
return dict(code=code, errors=errors)
|
|
|
|
|
|
@json.json_view
|
|
def logout(request):
|
|
code = 1
|
|
logging.user(request, "~FG~BBAPI Logout~FW")
|
|
logout_user(request)
|
|
|
|
return dict(code=code)
|
|
|
|
|
|
def add_site_load_script(request, token):
|
|
code = 0
|
|
usf = None
|
|
profile = None
|
|
user_profile = None
|
|
starred_counts = {}
|
|
|
|
def image_base64(image_name, path="icons/circular/"):
|
|
image_file = open(os.path.join(settings.MEDIA_ROOT, "img/%s%s" % (path, image_name)), "rb")
|
|
return base64.b64encode(image_file.read()).decode("utf-8")
|
|
|
|
accept_image = image_base64("newuser_icn_setup.png")
|
|
error_image = image_base64("newuser_icn_sharewith_active.png")
|
|
new_folder_image = image_base64("g_icn_arrow_right.png")
|
|
add_image = image_base64("g_icn_expand_hover.png")
|
|
|
|
try:
|
|
profiles = Profile.objects.filter(secret_token=token)
|
|
if profiles:
|
|
profile = profiles[0]
|
|
usf = UserSubscriptionFolders.objects.get(user=profile.user)
|
|
user_profile = MSocialProfile.get_user(user_id=profile.user.pk)
|
|
starred_counts = MStarredStoryCounts.user_counts(profile.user.pk)
|
|
else:
|
|
code = -1
|
|
except Profile.DoesNotExist:
|
|
code = -1
|
|
except UserSubscriptionFolders.DoesNotExist:
|
|
code = -1
|
|
|
|
return render(
|
|
request,
|
|
"api/share_bookmarklet.js",
|
|
{
|
|
"code": code,
|
|
"token": token,
|
|
"folders": (usf and usf.folders) or [],
|
|
"user": profile and profile.user or {},
|
|
"user_profile": user_profile and json.encode(user_profile.canonical()) or {},
|
|
"starred_counts": json.encode(starred_counts),
|
|
"accept_image": accept_image,
|
|
"error_image": error_image,
|
|
"add_image": add_image,
|
|
"new_folder_image": new_folder_image,
|
|
},
|
|
content_type="application/javascript",
|
|
)
|
|
|
|
|
|
def add_site(request, token):
|
|
code = 0
|
|
get_post = getattr(request, request.method)
|
|
url = get_post.get("url")
|
|
folder = get_post.get("folder")
|
|
new_folder = get_post.get("new_folder")
|
|
callback = get_post.get("callback", "")
|
|
|
|
if not url:
|
|
code = -1
|
|
else:
|
|
try:
|
|
profile = Profile.objects.get(secret_token=token)
|
|
if new_folder:
|
|
usf, _ = UserSubscriptionFolders.objects.get_or_create(user=profile.user)
|
|
usf.add_folder(folder, new_folder)
|
|
folder = new_folder
|
|
code, message, us = UserSubscription.add_subscription(
|
|
user=profile.user, feed_address=url, folder=folder, bookmarklet=True
|
|
)
|
|
except Profile.DoesNotExist:
|
|
code = -1
|
|
|
|
if code > 0:
|
|
message = "OK"
|
|
|
|
logging.user(profile.user, "~FRAdding URL from site: ~SB%s (in %s)" % (url, folder), request=request)
|
|
|
|
return HttpResponse(
|
|
callback
|
|
+ "("
|
|
+ json.encode(
|
|
{
|
|
"code": code,
|
|
"message": message,
|
|
"usersub": us and us.feed_id,
|
|
}
|
|
)
|
|
+ ")",
|
|
content_type="text/plain",
|
|
)
|
|
|
|
|
|
@ajax_login_required
|
|
def add_site_authed(request):
|
|
code = 0
|
|
url = request.GET["url"]
|
|
folder = request.GET["folder"]
|
|
new_folder = request.GET.get("new_folder")
|
|
callback = request.GET["callback"]
|
|
user = get_user(request)
|
|
|
|
if not url:
|
|
code = -1
|
|
else:
|
|
if new_folder:
|
|
usf, _ = UserSubscriptionFolders.objects.get_or_create(user=user)
|
|
usf.add_folder(folder, new_folder)
|
|
folder = new_folder
|
|
code, message, us = UserSubscription.add_subscription(
|
|
user=user, feed_address=url, folder=folder, bookmarklet=True
|
|
)
|
|
|
|
if code > 0:
|
|
message = "OK"
|
|
|
|
logging.user(user, "~FRAdding authed URL from site: ~SB%s (in %s)" % (url, folder), request=request)
|
|
|
|
return HttpResponse(
|
|
callback
|
|
+ "("
|
|
+ json.encode(
|
|
{
|
|
"code": code,
|
|
"message": message,
|
|
"usersub": us and us.feed_id,
|
|
}
|
|
)
|
|
+ ")",
|
|
content_type="text/plain",
|
|
)
|
|
|
|
|
|
def check_share_on_site(request, token):
|
|
code = 0
|
|
story_url = request.GET["story_url"]
|
|
rss_url = request.GET.get("rss_url")
|
|
callback = request.GET["callback"]
|
|
other_stories = None
|
|
same_stories = None
|
|
usersub = None
|
|
message = None
|
|
user = None
|
|
users = {}
|
|
your_story = None
|
|
same_stories = None
|
|
other_stories = None
|
|
previous_stories = None
|
|
|
|
if not story_url:
|
|
code = -1
|
|
else:
|
|
try:
|
|
user_profile = Profile.objects.get(secret_token=token)
|
|
user = user_profile.user
|
|
except Profile.DoesNotExist:
|
|
code = -1
|
|
|
|
logging.user(request.user, "~FBFinding feed (check_share_on_site): %s" % rss_url)
|
|
feed = Feed.get_feed_from_url(rss_url, create=False, fetch=False)
|
|
if not feed:
|
|
rss_url = urllib.parse.urljoin(story_url, rss_url)
|
|
logging.user(request.user, "~FBFinding feed (check_share_on_site): %s" % rss_url)
|
|
feed = Feed.get_feed_from_url(rss_url, create=False, fetch=False)
|
|
if not feed:
|
|
logging.user(request.user, "~FBFinding feed (check_share_on_site): %s" % story_url)
|
|
feed = Feed.get_feed_from_url(story_url, create=False, fetch=False)
|
|
if not feed:
|
|
parsed_url = urllib.parse.urlparse(story_url)
|
|
base_url = "%s://%s%s" % (parsed_url.scheme, parsed_url.hostname, parsed_url.path)
|
|
logging.user(request.user, "~FBFinding feed (check_share_on_site): %s" % base_url)
|
|
feed = Feed.get_feed_from_url(base_url, create=False, fetch=False)
|
|
if not feed:
|
|
logging.user(request.user, "~FBFinding feed (check_share_on_site): %s" % (base_url + "/"))
|
|
feed = Feed.get_feed_from_url(base_url + "/", create=False, fetch=False)
|
|
|
|
if feed and user:
|
|
try:
|
|
usersub = UserSubscription.objects.filter(user=user, feed=feed)
|
|
except UserSubscription.DoesNotExist:
|
|
usersub = None
|
|
if user:
|
|
feed_id = feed and feed.pk
|
|
your_story, same_stories, other_stories = MSharedStory.get_shared_stories_from_site(
|
|
feed_id, user_id=user.pk, story_url=story_url
|
|
)
|
|
previous_stories = MSharedStory.objects.filter(user_id=user.pk).order_by("-shared_date").limit(3)
|
|
previous_stories = [
|
|
{
|
|
"user_id": story.user_id,
|
|
"story_title": story.story_title,
|
|
"comments": story.comments,
|
|
"shared_date": story.shared_date,
|
|
"relative_date": relative_timesince(story.shared_date),
|
|
"blurblog_permalink": story.blurblog_permalink(),
|
|
}
|
|
for story in previous_stories
|
|
]
|
|
|
|
user_ids = set([user_profile.user.pk])
|
|
for story in same_stories:
|
|
user_ids.add(story["user_id"])
|
|
for story in other_stories:
|
|
user_ids.add(story["user_id"])
|
|
|
|
profiles = MSocialProfile.profiles(user_ids)
|
|
for profile in profiles:
|
|
users[profile.user_id] = {
|
|
"username": profile.username,
|
|
"photo_url": profile.photo_url,
|
|
}
|
|
|
|
logging.user(user, "~BM~FCChecking share from site: ~SB%s" % (story_url), request=request)
|
|
|
|
response = HttpResponse(
|
|
callback
|
|
+ "("
|
|
+ json.encode(
|
|
{
|
|
"code": code,
|
|
"message": message,
|
|
"feed": feed,
|
|
"subscribed": bool(usersub),
|
|
"your_story": your_story,
|
|
"same_stories": same_stories,
|
|
"other_stories": other_stories,
|
|
"previous_stories": previous_stories,
|
|
"users": users,
|
|
}
|
|
)
|
|
+ ")",
|
|
content_type="text/plain",
|
|
)
|
|
response["Access-Control-Allow-Origin"] = "*"
|
|
response["Access-Control-Allow-Methods"] = "GET"
|
|
|
|
return response
|
|
|
|
|
|
@required_params("story_url")
|
|
def share_story(request, token=None):
|
|
code = 0
|
|
story_url = request.POST["story_url"]
|
|
comments = request.POST.get("comments", "")
|
|
title = request.POST.get("title", None)
|
|
content = request.POST.get("content", None)
|
|
rss_url = request.POST.get("rss_url", None)
|
|
feed_id = request.POST.get("feed_id", None) or 0
|
|
feed = None
|
|
message = None
|
|
profile = None
|
|
|
|
if request.user.is_authenticated:
|
|
profile = request.user.profile
|
|
else:
|
|
try:
|
|
profile = Profile.objects.get(secret_token=token)
|
|
except Profile.DoesNotExist:
|
|
code = -1
|
|
if token:
|
|
message = "Not authenticated, couldn't find user by token."
|
|
else:
|
|
message = "Not authenticated, no token supplied and not authenticated."
|
|
|
|
if not profile:
|
|
return HttpResponse(
|
|
json.encode(
|
|
{
|
|
"code": code,
|
|
"message": message,
|
|
"story": None,
|
|
}
|
|
),
|
|
content_type="text/plain",
|
|
)
|
|
|
|
if feed_id:
|
|
feed = Feed.get_by_id(feed_id)
|
|
else:
|
|
if rss_url:
|
|
logging.user(request.user, "~FBFinding feed (share_story): %s" % rss_url)
|
|
feed = Feed.get_feed_from_url(rss_url, create=True, fetch=True)
|
|
if not feed:
|
|
logging.user(request.user, "~FBFinding feed (share_story): %s" % story_url)
|
|
feed = Feed.get_feed_from_url(story_url, create=True, fetch=True)
|
|
if feed:
|
|
feed_id = feed.pk
|
|
|
|
if content:
|
|
content = lxml.html.fromstring(content)
|
|
content.make_links_absolute(story_url)
|
|
content = lxml.html.tostring(content)
|
|
|
|
if not content or not title:
|
|
importer = TextImporter(story=None, story_url=story_url, request=request, debug=settings.DEBUG)
|
|
document = importer.fetch(skip_save=True, return_document=True)
|
|
if not content:
|
|
content = document["content"]
|
|
if not title:
|
|
title = document["title"]
|
|
|
|
shared_story = (
|
|
MSharedStory.objects.filter(user_id=profile.user.pk, story_feed_id=feed_id, story_guid=story_url)
|
|
.limit(1)
|
|
.first()
|
|
)
|
|
if not shared_story:
|
|
story_db = {
|
|
"story_guid": story_url,
|
|
"story_permalink": story_url,
|
|
"story_title": title,
|
|
"story_feed_id": feed_id,
|
|
"story_content": content,
|
|
"story_date": datetime.datetime.now(),
|
|
"user_id": profile.user.pk,
|
|
"comments": comments,
|
|
"has_comments": bool(comments),
|
|
}
|
|
shared_story = MSharedStory.objects.create(**story_db)
|
|
socialsubs = MSocialSubscription.objects.filter(subscription_user_id=profile.user.pk)
|
|
for socialsub in socialsubs:
|
|
socialsub.needs_unread_recalc = True
|
|
socialsub.save()
|
|
logging.user(profile.user, "~BM~FYSharing story from site: ~SB%s: %s" % (story_url, comments))
|
|
message = "Sharing story from site: %s: %s" % (story_url, comments)
|
|
else:
|
|
shared_story.story_content = content
|
|
shared_story.story_title = title
|
|
shared_story.comments = comments
|
|
shared_story.story_permalink = story_url
|
|
shared_story.story_guid = story_url
|
|
shared_story.has_comments = bool(comments)
|
|
shared_story.story_feed_id = feed_id
|
|
shared_story.save()
|
|
logging.user(
|
|
profile.user, "~BM~FY~SBUpdating~SN shared story from site: ~SB%s: %s" % (story_url, comments)
|
|
)
|
|
message = "Updating shared story from site: %s: %s" % (story_url, comments)
|
|
try:
|
|
socialsub = MSocialSubscription.objects.get(
|
|
user_id=profile.user.pk, subscription_user_id=profile.user.pk
|
|
)
|
|
except MSocialSubscription.DoesNotExist:
|
|
socialsub = None
|
|
|
|
if socialsub:
|
|
socialsub.mark_story_ids_as_read(
|
|
[shared_story.story_hash], shared_story.story_feed_id, request=request
|
|
)
|
|
else:
|
|
RUserStory.mark_read(profile.user.pk, shared_story.story_feed_id, shared_story.story_hash)
|
|
|
|
shared_story.publish_update_to_subscribers()
|
|
|
|
response = HttpResponse(
|
|
json.encode(
|
|
{
|
|
"code": code,
|
|
"message": message,
|
|
"story": shared_story,
|
|
}
|
|
),
|
|
content_type="text/plain",
|
|
)
|
|
response["Access-Control-Allow-Origin"] = "*"
|
|
response["Access-Control-Allow-Methods"] = "POST"
|
|
|
|
return response
|
|
|
|
|
|
@required_params("story_url", "title")
|
|
def save_story(request, token=None):
|
|
code = 0
|
|
story_url = request.POST["story_url"]
|
|
user_tags = request.POST.getlist("user_tags") or request.POST.getlist("user_tags[]") or []
|
|
add_user_tag = request.POST.get("add_user_tag", None)
|
|
title = request.POST["title"]
|
|
content = request.POST.get("content", None)
|
|
rss_url = request.POST.get("rss_url", None)
|
|
user_notes = request.POST.get("user_notes", None)
|
|
feed_id = request.POST.get("feed_id", None) or 0
|
|
feed = None
|
|
message = None
|
|
profile = None
|
|
|
|
if request.user.is_authenticated:
|
|
profile = request.user.profile
|
|
else:
|
|
try:
|
|
profile = Profile.objects.get(secret_token=token)
|
|
except Profile.DoesNotExist:
|
|
code = -1
|
|
if token:
|
|
message = "Not authenticated, couldn't find user by token."
|
|
else:
|
|
message = "Not authenticated, no token supplied and not authenticated."
|
|
|
|
if not profile:
|
|
return HttpResponse(
|
|
json.encode(
|
|
{
|
|
"code": code,
|
|
"message": message,
|
|
"story": None,
|
|
}
|
|
),
|
|
content_type="text/plain",
|
|
)
|
|
|
|
if feed_id:
|
|
feed = Feed.get_by_id(feed_id)
|
|
else:
|
|
if rss_url:
|
|
logging.user(request.user, "~FBFinding feed (save_story): %s" % rss_url)
|
|
feed = Feed.get_feed_from_url(rss_url, create=True, fetch=True)
|
|
if not feed:
|
|
logging.user(request.user, "~FBFinding feed (save_story): %s" % story_url)
|
|
feed = Feed.get_feed_from_url(story_url, create=True, fetch=True)
|
|
if feed:
|
|
feed_id = feed.pk
|
|
|
|
if content:
|
|
content = lxml.html.fromstring(content)
|
|
content.make_links_absolute(story_url)
|
|
content = lxml.html.tostring(content)
|
|
else:
|
|
importer = TextImporter(story=None, story_url=story_url, request=request, debug=settings.DEBUG)
|
|
document = importer.fetch(skip_save=True, return_document=True)
|
|
content = document["content"]
|
|
if not title:
|
|
title = document["title"]
|
|
|
|
if add_user_tag:
|
|
user_tags = user_tags + [tag for tag in add_user_tag.split(",")]
|
|
|
|
starred_story = (
|
|
MStarredStory.objects.filter(user_id=profile.user.pk, story_feed_id=feed_id, story_guid=story_url)
|
|
.limit(1)
|
|
.first()
|
|
)
|
|
if not starred_story:
|
|
story_db = {
|
|
"story_guid": story_url,
|
|
"story_permalink": story_url,
|
|
"story_title": title,
|
|
"story_feed_id": feed_id,
|
|
"story_content": content,
|
|
"story_date": datetime.datetime.now(),
|
|
"starred_date": datetime.datetime.now(),
|
|
"user_id": profile.user.pk,
|
|
"user_tags": user_tags,
|
|
"user_notes": user_notes,
|
|
}
|
|
starred_story = MStarredStory.objects.create(**story_db)
|
|
logging.user(profile.user, "~BM~FCStarring story from site: ~SB%s: %s" % (story_url, user_tags))
|
|
message = "Saving story from site: %s: %s" % (story_url, user_tags)
|
|
else:
|
|
starred_story.story_content = content
|
|
starred_story.story_title = title
|
|
starred_story.user_tags = user_tags
|
|
starred_story.story_permalink = story_url
|
|
starred_story.story_guid = story_url
|
|
starred_story.story_feed_id = feed_id
|
|
starred_story.user_notes = user_notes
|
|
starred_story.save()
|
|
logging.user(
|
|
profile.user, "~BM~FC~SBUpdating~SN starred story from site: ~SB%s: %s" % (story_url, user_tags)
|
|
)
|
|
message = "Updating saved story from site: %s: %s" % (story_url, user_tags)
|
|
|
|
MStarredStoryCounts.schedule_count_tags_for_user(request.user.pk)
|
|
|
|
response = HttpResponse(
|
|
json.encode(
|
|
{
|
|
"code": code,
|
|
"message": message,
|
|
"story": starred_story,
|
|
}
|
|
),
|
|
content_type="text/plain",
|
|
)
|
|
response["Access-Control-Allow-Origin"] = "*"
|
|
response["Access-Control-Allow-Methods"] = "POST"
|
|
|
|
return response
|
|
|
|
|
|
def ip_addresses(request):
|
|
# Read local file /srv/newsblur/apps/api/ip_addresses.txt and return that
|
|
with open("/srv/newsblur/apps/api/ip_addresses.txt", "r") as f:
|
|
addresses = f.read()
|
|
|
|
mail_admins(f"IP Addresses accessed from {request.META['REMOTE_ADDR']} by {request.user}", addresses)
|
|
|
|
return HttpResponse(addresses, content_type="text/plain")
|