diff --git a/ShioriFeed.py b/ShioriFeed.py
index 6049b65..2c1c641 100755
--- a/ShioriFeed.py
+++ b/ShioriFeed.py
@@ -4,7 +4,7 @@
# | [ ShioriFeed 🔖 (OctoSpacc) ] | #
# | Simple service for getting an Atom/RSS feed from your Shiori profile | #
# *----------------------------------------------------------------------* #
-Version = '2023-02-15'
+Version = '2023-02-16'
# *----------------------------------------------------------------------* #
# *-------------------------------------------* #
@@ -13,30 +13,31 @@ Version = '2023-02-15'
Host = ('localhost', 8176)
Debug = False
UserAgent = f'ShioriFeed v{Version} at {Host[0]}'
+DefFeedType = 'atom'
# *-------------------------------------------* #
# External Requirements: urllib3
# TODO:
# - Cheking if Content mode content is actually present, otherwise fall back to Archive mode or original link (using API data is unreliable it seems)
-# - Atom feed
# - Actually valid RSS
# - XML stylesheet
# - Filtering (tags, etc.)
# - Write privacy policy
# - Fix the URL copy thing
-# - Minification
+# - Minification (?)
# *-------------------------------------------------------------------------* #
-import traceback
import json
+import threading
+import traceback
from base64 import urlsafe_b64decode as b64UrlDecode, urlsafe_b64encode as b64UrlEncode, standard_b64encode as b64Encode
from html import escape as HtmlEscape
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
+from urllib.parse import unquote as UrlUnquote
from urllib.request import urlopen, Request
-import threading
HomeTemplate = '''\
@@ -107,10 +108,12 @@ HomeTemplate = '''\
resize: none;
}
input { height: 2em; }
- input[type="submit"] { font-size: large; }
- input, textarea, details { border-radius: 2px; }
- input, textarea {
+ input[type="submit"], button { font-size: large; }
+ input, textarea, details {
width: 100%;
+ border-radius: 2px;
+ }
+ input, textarea, button {
color: var(--cFore1);
background: var(--cBack1);
border: none;
@@ -181,6 +184,14 @@ HomeTemplate = '''\
Privacy Policy
(applies to ShioriFeed.Octt.eu.org)
+
I still have to write this... tough luck.
@@ -188,6 +199,7 @@ HomeTemplate = '''\
if you're worried about your security then just host the software yourself.
+
ShioriFeed ({HtmlEscape(Username)}) 🔖'
+ Generator = f'ShioriFeed'
+ FeedDate = Data['bookmarks'][0]['modified'] if Data['bookmarks'] else ''
for Mark in Data['bookmarks']:
Id = Mark['id']
- Link = f'{Remote}/bookmark/{Id}/content'
+ EntryTitle = f'{HtmlEscape(Mark["title"])}'
+ EntryAuthor = f'{HtmlEscape(Mark["author"])}' if Mark['author'] else ''
+ EntryLink = f'{Remote}/bookmark/{Id}/content'
# NOTE: when shiori issue #578 is fixed, this should use a thumb URL from the original article HTML to cope with private bookmarks
- Cover = f']]>' if Mark['imageURL'] else ''
+ EntryCover = f'
' if Mark['imageURL'] else ''
# Not so sure about this chief, downloading and embedding EVERY cover image into the XML is slow (~8s per 1 req) and traffic-hungry (~10 simultaneous requests are enough to temporarily DoS the Raspi)
#ImgData = GetContent(Remote, f'bookmark/{Id}/thumb', Session) if Mark['imageURL'] else None
#Cover = f'
]]>' if ImgData else ''
- Content = f'{HtmlEscape(GetContent(Remote, f"bookmark/{Id}/content", Session)["Body"].decode())}'
- if Type == 'Atom':
+ EntryPreview = f'{HtmlEscape(Mark["excerpt"])}]]>'
+ EntryContent = f'{HtmlEscape(GetContent(Remote, f"bookmark/{Id}/content", Session)["Body"].decode())}'
+ if Type == 'atom':
Feed += f'''
-
+
+ {EntryTitle}
+ {EntryAuthor}
+ {EntryPreview}
+ {EntryContent}
+
+ {Mark['modified']}
+ {Mark['modified']}
+ {EntryLink}
+
'''
- elif Type == 'RSS':
+ elif Type == 'rss':
Feed += f'''
- {HtmlEscape(Mark['title'])}
- {Cover}{HtmlEscape(Mark['excerpt'])}
- {Mark['author']}
- {Content}
- {Link}
+ {EntryTitle}
+ {EntryAuthor}
+ {EntryPreview}
+ {EntryContent}
+ {EntryLink}
{Mark['modified']}
- {Link}
+ {EntryLink}
'''
- if Type == 'Atom':
+ if Type == 'atom':
return f'''\
-
+
+
+ {FeedTitle}
+ {Generator}
+ {FeedDate}
+ {Feed}
+
'''
- elif Type == 'RSS':
+ elif Type == 'rss':
return f'''\
- ShioriFeed ({HtmlEscape(Username)}) 🔖
- {Date}
- {Date}
+ {FeedTitle}
+ {Generator}
+ {FeedDate}
+ {FeedDate}
{Feed}
'''
-def MkUrl(Post, Type='RSS'):
+def MkUrl(Post, Type=DefFeedType):
Args = {}
- #Args = Post.split('&')
for Arg in Post.split('&'):
Arg = Arg.split('=')
- Args.update({Arg[0]: Arg[1]})
+ Args.update({Arg[0]: UrlUnquote(Arg[1])})
return f'''\
http[s]://\
/{Args['Remote']}\
@@ -311,8 +343,16 @@ def RqHandle(Path, Attempt=0):
if Args[0] == '':
return {'Code': 200, 'Body': HomeTemplate, 'Content-Type': 'text/html'}
else:
- Shift = 1 if Args[-1].lower().startswith(('atom.xml', 'rss.xml')) else 0
- Remote = '/'.join(Args[:-(2+Shift)])
+ TypeCheck = Args[-1].lower().replace('?', '&').split('&')[0]
+ #Shift = 1 if TypeCheck in ('atom.xml', 'rss.xml', 'atom', 'rss') else 0
+ #Type = Args[-1].lower().split('&')[0] if Shift == 1 else
+ if TypeCheck in ('atom.xml', 'rss.xml', 'atom', 'rss'):
+ Shift = 1
+ FeedType = TypeCheck.split('.')[0]
+ else:
+ Shift = 0
+ FeedType = DefFeedType
+ Remote = '/'.join(Args[:-(2+Shift)]).removesuffix('/')
Username = b64UrlDecode(Args[-(2+Shift)]).decode()
Password = b64UrlDecode(Args[-(1+Shift)]).decode()
if not SessionHash(Remote, Username, Password) in Sessions:
@@ -326,7 +366,7 @@ def RqHandle(Path, Attempt=0):
Rs['Code'] = Rq.code
if Rq.code == 200:
# Shiori got us JSON data, parse it and return our result
- Rs['Body'] = MkFeed(json.loads(Rq.read().decode()), Remote, Username, Session)
+ Rs['Body'] = MkFeed(json.loads(Rq.read().decode()), Remote, Username, Session, FeedType)
Rs['Content-Type'] = 'application/xml'
elif Rq.code == 500 and Attempt < 1:
# We probably got an expired Session-Id, let's renew it and retry
@@ -350,10 +390,12 @@ class Handler(BaseHTTPRequestHandler):
def do_POST(self):
try:
if self.path == '/':
+ Post = self.rfile.read(int(self.headers['Content-Length'])).decode()
Body = HomeTemplate.replace('', f'''
- Here's your RSS feed:
-
+ Here's your feed:
+
+
''').replace('/* {{PostCss}} */', '.PostObscure { opacity: 0.5; }')
@@ -371,9 +413,9 @@ class Handler(BaseHTTPRequestHandler):
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write((f'[500] Internal Server Error{RetDebugIf()}').encode())
- # https://stackoverflow.com/a/3389505
- def log_message(self, format, *args):
- return
+ ##Prevent logging | https://stackoverflow.com/a/3389505
+ #def log_message(self, format, *args):
+ # return
# https://stackoverflow.com/a/51559006
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
pass