Snippets/ShioriFeed.py

389 lines
18 KiB
Python
Raw Normal View History

2023-02-13 13:46:26 +01:00
#!/usr/bin/env python3
2023-02-13 13:58:37 +01:00
# *----------------------------------------------------------------------* #
2023-02-15 17:54:25 +01:00
# | [ ShioriFeed 🔖 (OctoSpacc) ] | #
2023-02-13 13:58:37 +01:00
# | Simple service for getting an Atom/RSS feed from your Shiori profile | #
2023-02-15 17:54:25 +01:00
# *----------------------------------------------------------------------* #
Version = '2023-02-15'
2023-02-13 13:58:37 +01:00
# *----------------------------------------------------------------------* #
2023-02-15 17:54:25 +01:00
# *-------------------------------------------* #
# | Configuration | #
# *-------------------------------------------* #
2023-02-13 13:58:37 +01:00
Host = ('localhost', 8176)
2023-02-14 00:08:27 +01:00
Debug = False
2023-02-15 17:54:25 +01:00
UserAgent = f'ShioriFeed v{Version} at {Host[0]}'
# *-------------------------------------------* #
2023-02-13 13:58:37 +01:00
# External Requirements: urllib3
2023-02-14 00:08:27 +01:00
# TODO:
2023-02-15 17:54:25 +01:00
# - Cheking if Content mode content is actually present, otherwise fall back to Archive mode or original link (using API data is unreliable it seems)
2023-02-14 00:08:27 +01:00
# - Atom feed
# - Actually valid RSS
2023-02-15 17:54:25 +01:00
# - XML stylesheet
# - Filtering (tags, etc.)
2023-02-14 00:08:27 +01:00
# - Write privacy policy
# - Fix the URL copy thing
2023-02-15 17:54:25 +01:00
# - Minification
2023-02-14 00:08:27 +01:00
2023-02-13 13:58:37 +01:00
# *-------------------------------------------------------------------------* #
2023-02-13 13:46:26 +01:00
import traceback
import json
2023-02-15 17:54:25 +01:00
from base64 import urlsafe_b64decode as b64UrlDecode, urlsafe_b64encode as b64UrlEncode, standard_b64encode as b64Encode
2023-02-13 13:58:37 +01:00
from html import escape as HtmlEscape
2023-02-13 13:46:26 +01:00
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
from urllib.request import urlopen, Request
import threading
2023-02-13 13:58:37 +01:00
HomeTemplate = '''\
2023-02-13 13:46:26 +01:00
<!DOCTYPE html>
2023-02-13 13:58:37 +01:00
<html lang="en">
2023-02-13 13:46:26 +01:00
<head>
2023-02-13 13:58:37 +01:00
<meta charset="UTF-8"/>
2023-02-13 13:46:26 +01:00
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
2023-02-14 00:08:27 +01:00
<!--
"bookmark" Emoji icon - Copyright 2021 Google Inc. All Rights Reserved.
<https://fonts.google.com/noto/specimen/Noto+Color+Emoji/about>
<https://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web>
-->
<link rel="shortcut icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAMAAAC8EZcfAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAMAUExURUdwTPRBM/FLNfRBM/NEM+5QNvRBM85ONfRBM/RBM/VBM/RBM+dCM4hMM/RBM/RBM/RBM/RBM+1UN/RBM/VBM/NFNPRBM/RBM/RBM/VBM/RBM/RBM/RBM+xXN/RBM990PvRBM/RBM/RBM/RBM/RBM/RBM/RBM/FJNPRBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM35HMYhMM4hMM4hMM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/JHNIhMM4hMM/RBM/RBM/RBM91zPvRBM/RBM/RBM4hMM5JQNIhMM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM/RBM990PohMM4dLM990PodLM4hMM4pNM+B0Pt90PolMM4ZMM4hMM4hMM4hMM4hMM990PvRBM95zPfRBM4dMM990PohMM4hMM990PohMM990PvRBM990PvRBM990PohMM990PqdJM41OM990Pt90Pt90PvRBM5VRNIhMM990PohMM4hMM4hMM4hMM45MM4tNM990PohMM4hMM9JtPbFfOE0xKt90Pn5HMs5rPN90PthxPd50Pt90PrljOU0xKt90Ps1FM0wxKrxjOYRJMtVuPUwxKk0xKlQ0K7tjOV84LK9JM99MzvRBM990PohMM/zszodMM/Q/MeB0Pvzw0fzu0PZBM4lMM00xKtpyPfQ+MZtUNd1zPuB1Po5NM9BsPItNM/d9avRFNu1BM5dSNfzmyfVYSPrIrvVeTfZlU/VQQKJXNqZZNrpjOZFQNPiLd/zqzPzhxPzrzZROM8lpOvZqWfvWuvvdwPVSQvq4n/idh6pJM7xGM1MzK9ZvPdRuPbRgOL9lOcxrPPvQtPq9pPVLPPRIOfmslZ5WNXtFMd5DM8VnOveDb/iSfWg7LfmzmviXgvrHraNJM+ZCM8pqPNlEM/Z1Yviii/rLsLFHM81EM9JCMq5eN+F1Pl44LPmkjvztz7M6MPmjjJtKM4g4LZ04Lm5AMK1dN0dwTGi1UKgAAAEAdFJOUwBICTUPBPgB/d8T/gL8B/o/7w312QXnKX9r6vIjCjH6xdbMFsLtixu9ZE3PLaxYvwQlB+4rtnE6j6bT5JSdHBj89yAUVVzcojGbYVLzDNt2hEOXZ8m5JlWw5kikRDizGcP04z6HejS5qLNwn54T6myc9SaoznvvXDzijmF7jnrqxtbTyKcb+a9baW+uZzC22fYt+eChs/lIh+rWHJB6TqtP7u7//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAl04dNAAAKGUlEQVR42u2cd1AbVx7HVxI6kBDIBiyLJjC9Y7rBgA0xxhSbXkzcu+MWx44vPZfitOu95Pos0i6WqQKBAZtiA3ZccAvujnEj8SW55JLT1cytyorFaFcr6a20M8fvD8OInXmfeb/2fb99FgTN2Iyxznates6DzXzL1q1f/4PnnFnL98y6esxWPsXWTfR/ul5nAdueYSfgO+vrDbZ/FRvdvGudCgesX/niHPYBrvrR+OUGnHD9S/6sA/xjd2PrlWYcMeBlJ7YBJqyQI+rBDiPh07vYRrhkg1yOnLrS1sBWQuctW92wTRxrNhKyzstPbEqSy+WDeK4EvMy6TJm/ZAXm5tt4IAa8xL5qs/o7GGErTnjgRfaVw4Q1RMKVq9hHePBJLFVaPzQQ7mdhX35CSziA5/I2DzYSYrn8tzZDojzFQt1wEItD9XFDS/npMhYSJrwhR058YHDyd5ezkHD1islE+cn3TD4iyi6riUp2dxTht4vlyGCzfgt/k2DigWDpvBxXsSSdZzfBv2wZsbHN3+SGh2HDoxfmT3s8JsUF1pkgyk4Hum0HDmwLJ6byVjnSrg/Dy6+ufty9nAwYt4y5dsBzf+c1LcnPiHu4cTcehm0DOxOnPl4hgSdtB5dxvrW/XW+oeYTDkmizGx6Gg0kbiY8LwzwJfHBOIeOF+e1HKhOtdy2mGxrH6hsa2m4jOwlR6Be7GJ5iXjFMZ6zbGC709/+YKLH3yuXtx5vbrrTLd09GYVol/LjFMqxs30O6cW1Qv44gDpa/jbU8devtduzHCwbn8/ME0/hgcSqz5+iNu5Fv2vAD5zpCZ6vTCmwEwf6Rr9DXQg/pbNiEScqY1dGb3NRXjEfi134pwv+Q+KYcN7d3tR9k7xDDJi0wm9ks2TrZebXTI2PzrSs2Em7FPpTFu8IkVs5srdmyAum+aiQM+PlGQ9Iu32oETHo3ocgLJjXPSIbPxEkEwoaOV3duSTQkuB4PaVS/teHPd8gBYUEas2H43l6kexwHbO5Gdr+55H1sG99/XYenvn3lasej44MXj5AThjAbhomb9yInjrepsF1s+OoDbWFx++Effvf772vFNfaHZpWqQaW6PHDhE3LCcn+GCYsR9eD4h83Nl8e7dZUFud/0i1//CcH4xg3OV11t/5icMIfhapi4Jwnz5YnW1m61jk/e+FFTU9OljxrVx/HgVHW0IxfIvSzh2GOsgOgLs24Lv8YI/ys/0fGVITbbvsE+vUju5GimldeWNW5yot3HAL9GGgfGmxuwTVQ1j6kx6va/kBPWCpnWNZs3EPj0PkYaEXXrWEdb26OBRt2+UjjZJYxpbSja8mQxAfBSU9O/v/jy4ikEOTEw0G7wPdUWenozPn5PrFtjREQuXfrPkcOH4TsX5MTQpIhCxhNF19/qdm7QxSKC3P+HftkjfyV4HrlAAQhnyOwxJkzYswZjRP71Jb4skRB5PiiHgjAiG7KHJSYs2fT8J4eNyx752Ohi+SZhXBAFYb6dJsfu3j7EZe+cwgm18r+6ihxQXGqXcYNwgefUdS8aAIv3aAVtcgZVKouY56uufezoBt9p1+/fHr1W9HYhJ/QpYhrPedb0INNFodsbdQYtWy2gEoezmOWbk7fQxKpfvFX8+uaD+DPcCKpiE83oUdlD6muyj/1q49rJPuG+nQoQjs9ljo8bRnJ0857yFOUOYof5YMYAo3xJ1iwgJqefgBrQtYCpcigqIA19otyjymJ9OWRK2fjHk66ZObkkVR3EjwDPMvMizaOSYkn8XORXBZs3X28+IzmSQrEp+TxsE52FeUEwHQstZEQdLqBa06dqQWkBpZqZ8nQcE4Q1YhiYSZgYsWdngAOEBUwobClAQHgpA205WQCS0As8oWgBzHJCnoTlhPxyoIBwxiyWbyH4PeSDjUIGCGMELCd0LgUMCC8FXLH95oEmFESB7cupi0ETLgSrHIJDQAPCPoVA9WGNJ3DCUG+QGpubDhwQ9n0F5DmFtxA8YU4YwLOes9QVPKG4AOB5ObwSPCDsEgtw5lDkywAhHA9ubuOeZSMLanqyBK7tzfWyBQiFj90wiSioAVYQI3Mswevv60InidBzDyd6UZKCCGpK7LHPEsDezx98esOAiN7sPKr8vB8lmRIDKzeypRYQ3rx+qOf63X4MEYX7HvQoD13vIi036bkOcDJ6XqlRDh8d6oXPPTyr1CiUtygeDgF0sc8/nz5gS+ewQqFQnrz29wmFRvvbeZRKwxaBeRkwl/6gAT12VsulUCqV2h8KzVALpbpJBXOJII52uUZvTOjJDDbc2ULdmcvDgcz8w+j35FtTAHtOo2bmxBFAXj0uiqcdhENEPs3ZY6jZw1QRiJqdRveM13J6REMAHLkLmyUMlYKoiJF01fW94SlbODLUZZZQvK8agLrOpBeGd4kbqCXsOX/TLCEczbHdzeERdJK48zE+jHD4Vr95Qp9XbHczz3zLQz89q1RMt9Fe84Ti2GTbq+Fsc3ynz5jiUygn+mjs/rwaW/UNX0o9W0f7rk3n02A9ZXjkwTnzewj7hi
2023-02-13 13:58:37 +01:00
<title>ShioriFeed 🔖</title>
<meta name="description" content="Simple service for getting an Atom/RSS feed from your Shiori profile"/>
<meta property="og:title" content="ShioriFeed 🔖"/>
<meta property="og:description" content="Simple service for getting an Atom/RSS feed from your Shiori profile"/>
2023-02-13 13:46:26 +01:00
<style>
2023-02-15 17:54:25 +01:00
:root {
--cFore0: #232323;
--cFore1: #292929;
--cAccent: #f44336;
--cBack0: #e9e9e9;
--cBack1: #ffffff;
/*--cGray: #c9c9c9;*/
}
@media (prefers-color-scheme: dark) {
:root {
--cFore0: #ffffff;
--cFore1: #eeeeee;
--cBack0: #292929;
--cBack1: #1f1f1f;
--cGray: #606060;
}
}
2023-02-13 13:58:37 +01:00
* { box-sizing: border-box; }
2023-02-15 17:54:25 +01:00
.Underline { text-decoration: underline; }
.NoSelect {
user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
2023-02-13 13:46:26 +01:00
body {
2023-02-15 17:54:25 +01:00
color: var(--cFore0);
background: var(--cBack0);
2023-02-14 00:08:27 +01:00
font-family: "Source Sans Pro", sans-serif;
2023-02-13 13:46:26 +01:00
margin: 0px;
padding-top: 24px;
padding-bottom: 24px;
padding-left: 10%;
padding-right: 10%;
2023-02-13 13:58:37 +01:00
word-break: break-word;
2023-02-13 13:46:26 +01:00
}
2023-02-15 17:54:25 +01:00
a { color: var(--cAccent); }
2023-02-13 13:58:37 +01:00
form > label { padding: 8px; }
form > label > span { padding-bottom: 4px; }
form > label, form > label > span {
2023-02-13 13:46:26 +01:00
display: inline-block;
2023-02-13 13:58:37 +01:00
width: 100%;
2023-02-13 13:46:26 +01:00
}
textarea {
width: 100%;
height: 5em;
2023-02-13 13:58:37 +01:00
font-size: large;
2023-02-13 13:46:26 +01:00
resize: none;
}
2023-02-15 17:54:25 +01:00
input { height: 2em; }
input[type="submit"] { font-size: large; }
input, textarea, details { border-radius: 2px; }
input, textarea {
width: 100%;
color: var(--cFore1);
background: var(--cBack1);
border: none;
}
2023-02-13 13:58:37 +01:00
details {
2023-02-15 17:54:25 +01:00
background: var(--cBack1)/*var(--cGray)*/;
2023-02-13 13:58:37 +01:00
padding: 8px;
}
details > summary > h4 { display: inline; }
2023-02-14 00:08:27 +01:00
span.Separator {
display: inline-block;
width: 0.25em;
height: 0.25em;
margin: 0.25em;
vertical-align: middle;
2023-02-15 17:54:25 +01:00
background: var(--cFore1);
2023-02-14 00:08:27 +01:00
}
2023-02-13 13:58:37 +01:00
/* {{PostCss}} */
2023-02-13 13:46:26 +01:00
</style>
</head>
<body>
2023-02-15 17:54:25 +01:00
<div class="NoSelect">
<h2>ShioriFeed 🔖</h2>
<p class="PostObscure">
Enter the details of your account on a
<a href="https://github.com/go-shiori/">Shiori</a>
server to get an Atom/RSS feed link.
</p>
<p class="PostObscure">
<small>Note: still a work-in-progress!</small>
</p>
<br />
<!-- {{PostResult}} -->
<p class="PostObscure">
<form action="./" method="POST">
<label class="PostObscure">
<span>Server <small>(must start with protocol prefix)</small>:</span>
<input type="text" name="Remote" placeholder="http[s]://..."/>
</label>
<br />
<label class="PostObscure">
<span>Username:</span>
<input type="text" name="Username" placeholder="erre"/>
</label>
<br />
<label class="PostObscure">
<span>Password:</span>
<input type="password" name="Password" placeholder="**********"/>
</label>
<br />
<label class="PostObscure">
<span>&nbsp;</span>
<input type="submit" value="Submit"/>
</label>
</form>
</p>
<br />
</div>
2023-02-13 13:58:37 +01:00
<!--
2023-02-15 17:54:25 +01:00
NOTE TO SELF-HOSTERS:
You should probably either adjust or remove this :)
For sure you should at least write your own domain.
-->
2023-02-13 13:46:26 +01:00
<p>
<details>
2023-02-15 17:54:25 +01:00
<summary class="NoSelect">
<!-- Change the domain if self-hosting! -->
2023-02-13 13:58:37 +01:00
<h4>Privacy Policy</h4>
2023-02-15 17:54:25 +01:00
(applies to <em class="Underline">ShioriFeed.Octt.eu.org</em>)
2023-02-13 13:46:26 +01:00
</summary>
2023-02-15 17:54:25 +01:00
<!--<ul>
<li>-->
I still have to write this... tough luck.
I'm not yet actively inviting anyone to use this instance right now,
if you're worried about your security then just host the software yourself.
<!-- </li>
</ul>-->
2023-02-13 13:46:26 +01:00
</details>
</p>
2023-02-15 17:54:25 +01:00
<p class="NoSelect">
<span>v. {{Version}}</span>
2023-02-14 00:08:27 +01:00
<span class="Separator"></span>
2023-02-13 13:46:26 +01:00
<a href="https://gitlab.com/octospacc/Snippets/-/blob/main/ShioriFeed.py">Source Code</a>
</p>
2023-02-13 13:58:37 +01:00
<script>
var Box = document.querySelector('textarea');
if (Box) {
2023-02-14 00:08:27 +01:00
//BoxFocused = false;
2023-02-13 13:58:37 +01:00
Box.value = location.origin + Box.value.substring('http[s]://<THIS SHIORIFEED SERVER ADDRESS>'.length);
2023-02-14 00:08:27 +01:00
//Box.onfocusout = function() { console.log(1); BoxFocused = false; };
//Box.onfocusin = function() { console.log(2); BoxFocused = true; };
Box.onclick = function() {
try {
//if (BoxFocused) {
navigator.clipboard.writeText(Box.value);
alert('Copied to clipboard!');
//};
} catch(e) {};
};
2023-02-13 13:58:37 +01:00
};
</script>
2023-02-13 13:46:26 +01:00
</body>
</html>
2023-02-15 17:54:25 +01:00
'''.replace('{{Version}}', Version)
def RetDebugIf():
return f'\n\n{traceback.format_exc()}' if Debug else ''
2023-02-13 13:46:26 +01:00
2023-02-13 13:58:37 +01:00
def SessionHash(Remote, Username, Password):
return f'{hash(Remote)}{hash(Username)}{hash(Password)}'
2023-02-15 17:54:25 +01:00
def MkFeed(Data, Remote, Username, Session, Type='RSS'):
2023-02-13 13:46:26 +01:00
Feed = ''
2023-02-13 13:58:37 +01:00
Date = Data['bookmarks'][0]['modified'] if Data['bookmarks'] else ''
2023-02-13 13:46:26 +01:00
for Mark in Data['bookmarks']:
2023-02-15 17:54:25 +01:00
Id = Mark['id']
Link = 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'<![CDATA[<a href="{Link}"><img src="{Remote}/bookmark/{Id}/thumb"/></a>]]>' 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'<![CDATA[<a href="{Link}"><img src="data:{ImgData["Content-Type"]};base64,{b64Encode(ImgData["Body"]).decode()}"/></a><br /><br />]]>' if ImgData else ''
Content = f'{HtmlEscape(GetContent(Remote, f"bookmark/{Id}/content", Session)["Body"].decode())}'
if Type == 'Atom':
Feed += f'''
'''
elif Type == 'RSS':
Feed += f'''
2023-02-13 13:46:26 +01:00
<item>
2023-02-13 13:58:37 +01:00
<title>{HtmlEscape(Mark['title'])}</title>
2023-02-14 00:08:27 +01:00
<description>{Cover}{HtmlEscape(Mark['excerpt'])}</description>
2023-02-15 17:54:25 +01:00
<author>{Mark['author']}</author>
<content:encoded type="text/html">{Content}</content:encoded>
2023-02-14 00:08:27 +01:00
<link>{Link}</link>
2023-02-13 13:46:26 +01:00
<pubDate>{Mark['modified']}</pubDate>
2023-02-15 17:54:25 +01:00
<guid isPermaLink="false">{Link}</guid>
2023-02-13 13:46:26 +01:00
</item>
2023-02-15 17:54:25 +01:00
'''
if Type == 'Atom':
return f'''\
2023-02-13 13:46:26 +01:00
'''
2023-02-15 17:54:25 +01:00
elif Type == 'RSS':
return f'''\
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/">
2023-02-13 13:46:26 +01:00
<channel>
2023-02-13 13:58:37 +01:00
<title>ShioriFeed ({HtmlEscape(Username)}) 🔖</title>
2023-02-13 13:46:26 +01:00
<pubDate>{Date}</pubDate>
<lastBuildDate>{Date}</lastBuildDate>
{Feed}
</channel>
</rss>
2023-02-15 17:54:25 +01:00
'''
2023-02-13 13:46:26 +01:00
2023-02-15 17:54:25 +01:00
def MkUrl(Post, Type='RSS'):
2023-02-13 13:58:37 +01:00
Args = {}
#Args = Post.split('&')
for Arg in Post.split('&'):
Arg = Arg.split('=')
Args.update({Arg[0]: Arg[1]})
return f'''\
http[s]://<THIS SHIORIFEED SERVER ADDRESS>\
/{Args['Remote']}\
2023-02-15 17:54:25 +01:00
/{b64UrlEncode(Args['Username'].encode()).decode()}\
/{b64UrlEncode(Args['Password'].encode()).decode()}\
2023-02-14 00:08:27 +01:00
/{Type}.xml'''
2023-02-13 13:58:37 +01:00
2023-02-13 13:46:26 +01:00
def GetSession(Remote, Username, Password):
try:
Rq = urlopen(Request(f'{Remote}/api/login',
data=json.dumps({'username': Username, 'password': Password, 'remember': True, 'owner': True}).encode(),
2023-02-15 17:54:25 +01:00
headers={'User-Agent': UserAgent}))
2023-02-13 13:46:26 +01:00
if Rq.code == 200:
2023-02-13 13:58:37 +01:00
Data = {SessionHash(Remote, Username, Password): json.loads(Rq.read().decode())['session']}
2023-02-13 13:46:26 +01:00
Sessions.update(Data)
2023-02-15 17:54:25 +01:00
return {'Code': 200, 'Body': Data}
else:
return {'Code': Rq.code, 'Body': f'[{Rq.code}] External Server Error\n\n{Rq.read().decode()}'}
except Exception:
return {'Code': 500, 'Body': f'[500] Internal Server Error{RetDebugIf()}'}
def GetContent(Remote, Path, Session):
try:
Rq = urlopen(Request(f'{Remote}/{Path}', headers={'X-Session-Id': Session, 'User-Agent': UserAgent}))
if Rq.code == 200:
return {'Code': 200, 'Body': Rq.read(), 'Content-Type': Rq.headers['Content-Type']}
2023-02-13 13:58:37 +01:00
else:
2023-02-15 17:54:25 +01:00
return {'Code': Rq.code, 'Body': f'[{Rq.code}] External Server Error\n\n{Rq.read().decode()}'.encode()}
except Exception:
return {'Code': 500, 'Body': f'[500] Internal Server Error{RetDebugIf()}'.encode()}
2023-02-13 13:46:26 +01:00
def RqHandle(Path, Attempt=0):
try:
2023-02-13 13:58:37 +01:00
Rs = {}
Args = Path.strip().removeprefix('/').removesuffix('/').strip().split('/')
if Args[0] == '':
2023-02-15 17:54:25 +01:00
return {'Code': 200, 'Body': HomeTemplate, 'Content-Type': 'text/html'}
2023-02-13 13:46:26 +01:00
else:
2023-02-14 00:08:27 +01:00
Shift = 1 if Args[-1].lower().startswith(('atom.xml', 'rss.xml')) else 0
Remote = '/'.join(Args[:-(2+Shift)])
2023-02-15 17:54:25 +01:00
Username = b64UrlDecode(Args[-(2+Shift)]).decode()
Password = b64UrlDecode(Args[-(1+Shift)]).decode()
2023-02-13 13:58:37 +01:00
if not SessionHash(Remote, Username, Password) in Sessions:
TrySession = GetSession(Remote, Username, Password)
if TrySession['Code'] != 200:
return TrySession
2023-02-15 17:54:25 +01:00
Session = Sessions[SessionHash(Remote, Username, Password)]
2023-02-13 13:46:26 +01:00
Rq = urlopen(Request(f'{Remote}/api/bookmarks', headers={
2023-02-15 17:54:25 +01:00
'X-Session-Id': Session,
'User-Agent': UserAgent}))
2023-02-13 13:46:26 +01:00
Rs['Code'] = Rq.code
if Rq.code == 200:
2023-02-15 17:54:25 +01:00
# Shiori got us JSON data, parse it and return our result
Rs['Body'] = MkFeed(json.loads(Rq.read().decode()), Remote, Username, Session)
2023-02-13 13:46:26 +01:00
Rs['Content-Type'] = 'application/xml'
elif Rq.code == 500 and Attempt < 1:
2023-02-15 17:54:25 +01:00
# We probably got an expired Session-Id, let's renew it and retry
2023-02-13 13:58:37 +01:00
TrySession = GetSession(Remote, Username, Password)
if TrySession['Code'] != 200:
return TrySession
2023-02-13 13:46:26 +01:00
return ReqHandle(Path, Attempt+1)
else:
Rs['Body'] = f'[{Rq.code}] External Server Error\n\n{Rq.read().decode()}'
2023-02-13 13:58:37 +01:00
return Rs
2023-02-15 17:54:25 +01:00
except Exception:
return {'Code': 500, 'Body': f'[500] Internal Server Error{RetDebugIf()}'}
2023-02-13 13:46:26 +01:00
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
Rs = RqHandle(self.path)
self.send_response(Rs['Code'])
2023-02-13 13:58:37 +01:00
self.send_header('Content-Type', Rs['Content-Type'] if 'Content-Type' in Rs else 'text/plain')
2023-02-13 13:46:26 +01:00
self.end_headers()
self.wfile.write(Rs['Body'].encode())
def do_POST(self):
2023-02-13 13:58:37 +01:00
try:
if self.path == '/':
Body = HomeTemplate.replace('<!-- {{PostResult}} -->', f'''
2023-02-13 13:46:26 +01:00
<p>
2023-02-14 00:08:27 +01:00
Here's your RSS feed:
2023-02-13 13:58:37 +01:00
<textarea readonly="true">{MkUrl(self.rfile.read(int(self.headers['Content-Length'])).decode())}</textarea>
2023-02-13 13:46:26 +01:00
</p>
<br />
2023-02-13 13:58:37 +01:00
''').replace('/* {{PostCss}} */', '.PostObscure { opacity: 0.5; }')
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(Body.encode())
else:
self.send_response(400)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b'[400] Bad Request')
except Exception:
self.send_response(500)
2023-02-13 13:46:26 +01:00
self.send_header('Content-Type', 'text/plain')
self.end_headers()
2023-02-15 17:54:25 +01:00
self.wfile.write((f'[500] Internal Server Error{RetDebugIf()}').encode())
2023-02-13 13:58:37 +01:00
# https://stackoverflow.com/a/3389505
def log_message(self, format, *args):
2023-02-13 13:46:26 +01:00
return
2023-02-13 13:58:37 +01:00
# https://stackoverflow.com/a/51559006
2023-02-13 13:46:26 +01:00
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
pass
def Serve():
ThreadedHTTPServer(Host, Handler).serve_forever()
if __name__ == '__main__':
Sessions = {}
try:
Serve()
except KeyboardInterrupt:
pass