mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of gitlab.com:bashrc2/epicyon
commit
1a3586b58d
|
@ -290,7 +290,7 @@ def update_blocked_cache(base_dir: str,
|
||||||
with open(global_blocking_filename, 'r') as fp_blocked:
|
with open(global_blocking_filename, 'r') as fp_blocked:
|
||||||
blocked_lines = fp_blocked.readlines()
|
blocked_lines = fp_blocked.readlines()
|
||||||
# remove newlines
|
# remove newlines
|
||||||
for index in range(len(blocked_lines)):
|
for index, _ in enumerate(blocked_lines):
|
||||||
blocked_lines[index] = blocked_lines[index].replace('\n', '')
|
blocked_lines[index] = blocked_lines[index].replace('\n', '')
|
||||||
# update the cache
|
# update the cache
|
||||||
blocked_cache.clear()
|
blocked_cache.clear()
|
||||||
|
|
|
@ -38,7 +38,7 @@ No insults, harassment (sexual or otherwise), condescension, ad hominem, threats
|
||||||
|
|
||||||
Condescension means treating others as inferior. Subtle condescension still violates the Code of Conduct even if not blatantly demeaning.
|
Condescension means treating others as inferior. Subtle condescension still violates the Code of Conduct even if not blatantly demeaning.
|
||||||
|
|
||||||
No stereotyping of or promoting prejudice or discrimination against particular groups or classes/castes of people, including sexism, racism, homophobia, transphobia, age discrimination or discrimination based upon nationality.
|
No stereotyping of or promoting prejudice or discrimination against particular groups or classes/castes of people, including sexism, racism, homophobia, transphobia, denying people their right to join or create a trade union, age discrimination or discrimination based upon nationality.
|
||||||
|
|
||||||
In cases where criticism of ideology or culture remains on-topic, respectfully discuss the ideas.
|
In cases where criticism of ideology or culture remains on-topic, respectfully discuss the ideas.
|
||||||
|
|
||||||
|
|
31
daemon.py
31
daemon.py
|
@ -156,6 +156,7 @@ from blog import html_blog_page
|
||||||
from blog import html_blog_post
|
from blog import html_blog_post
|
||||||
from blog import html_edit_blog
|
from blog import html_edit_blog
|
||||||
from blog import get_blog_address
|
from blog import get_blog_address
|
||||||
|
from webapp_podcast import html_podcast_episode
|
||||||
from webapp_theme_designer import html_theme_designer
|
from webapp_theme_designer import html_theme_designer
|
||||||
from webapp_minimalbutton import set_minimal
|
from webapp_minimalbutton import set_minimal
|
||||||
from webapp_minimalbutton import is_minimal
|
from webapp_minimalbutton import is_minimal
|
||||||
|
@ -14062,6 +14063,36 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self._write(msg)
|
self._write(msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# show a podcast episode
|
||||||
|
if authorized and users_in_path and html_getreq and \
|
||||||
|
'?podepisode=' in self.path:
|
||||||
|
nickname = self.path.split('/users/')[1]
|
||||||
|
if '/' in nickname:
|
||||||
|
nickname = nickname.split('/')[0]
|
||||||
|
episode_timestamp = self.path.split('?podepisode=')[1].strip()
|
||||||
|
episode_timestamp = episode_timestamp.replace('__', ' ')
|
||||||
|
episode_timestamp = episode_timestamp.replace('aa', ':')
|
||||||
|
if self.server.newswire.get(episode_timestamp):
|
||||||
|
pod_episode = self.server.newswire[episode_timestamp]
|
||||||
|
html_str = \
|
||||||
|
html_podcast_episode(self.server.css_cache,
|
||||||
|
self.server.translate,
|
||||||
|
self.server.base_dir,
|
||||||
|
nickname,
|
||||||
|
self.server.domain,
|
||||||
|
pod_episode,
|
||||||
|
self.server.theme_name,
|
||||||
|
self.server.default_timeline,
|
||||||
|
self.server.text_mode_banner,
|
||||||
|
self.server.access_keys)
|
||||||
|
if html_str:
|
||||||
|
msg = html_str.encode('utf-8')
|
||||||
|
msglen = len(msg)
|
||||||
|
self._set_headers('text/html', msglen,
|
||||||
|
None, calling_domain, False)
|
||||||
|
self._write(msg)
|
||||||
|
return
|
||||||
|
|
||||||
# redirect to the welcome screen
|
# redirect to the welcome screen
|
||||||
if html_getreq and authorized and users_in_path and \
|
if html_getreq and authorized and users_in_path and \
|
||||||
'/welcome' not in self.path:
|
'/welcome' not in self.path:
|
||||||
|
|
|
@ -0,0 +1,382 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--avatar-rounding: 10%;
|
||||||
|
--options-bg-color: #282c37;
|
||||||
|
--options-link-bg-color: transparent;
|
||||||
|
--options-fg-color: #dddddd;
|
||||||
|
--options-main-link-color: #999;
|
||||||
|
--options-main-visited-color: #888;
|
||||||
|
--border-color: #505050;
|
||||||
|
--font-size-header: 18px;
|
||||||
|
--font-color-header: #ccc;
|
||||||
|
--font-size: 40px;
|
||||||
|
--font-size2: 24px;
|
||||||
|
--font-size3: 38px;
|
||||||
|
--font-size4: 22px;
|
||||||
|
--font-size5: 20px;
|
||||||
|
--text-entry-foreground: #ccc;
|
||||||
|
--text-entry-background: #111;
|
||||||
|
--time-color: #aaa;
|
||||||
|
--button-text: #FFFFFF;
|
||||||
|
--button-small-text: #FFFFFF;
|
||||||
|
--button-background-hover: #777;
|
||||||
|
--button-background: #999;
|
||||||
|
--button-small-background: #999;
|
||||||
|
--hashtag-margin: 2%;
|
||||||
|
--hashtag-vertical-spacing1: 50px;
|
||||||
|
--hashtag-vertical-spacing2: 100px;
|
||||||
|
--hashtag-vertical-spacing3: 100px;
|
||||||
|
--hashtag-vertical-spacing4: 150px;
|
||||||
|
--hashtag-size1: 30px;
|
||||||
|
--hashtag-size2: 40px;
|
||||||
|
--follow-text-size1: 24px;
|
||||||
|
--follow-text-size2: 40px;
|
||||||
|
--follow-text-entry-width: 90%;
|
||||||
|
--focus-color: white;
|
||||||
|
--petname-width-chars: 16ch;
|
||||||
|
--options-main-link-color-hover: #bbb;
|
||||||
|
--rendering: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Bedstead';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: normal;
|
||||||
|
font-display: block;
|
||||||
|
src: url('./fonts/bedstead.otf') format('opentype');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Bedstead';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
font-display: block;
|
||||||
|
src: url('./fonts/bedstead.otf') format('opentype');
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
background-image: url("podcast-background.jpg");
|
||||||
|
background-size: cover;
|
||||||
|
-webkit-background-size: cover;
|
||||||
|
-moz-background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-color: var(--options-bg-color);
|
||||||
|
color: var(--options-fg-color);
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 600px;
|
||||||
|
image-rendering: var(--rendering);
|
||||||
|
}
|
||||||
|
|
||||||
|
a, u {
|
||||||
|
color: var(--options-fg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited{
|
||||||
|
color: var(--options-main-visited-color);
|
||||||
|
background: var(--options-link-bg-color);
|
||||||
|
font-weight: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link {
|
||||||
|
color: var(--options-main-link-color);
|
||||||
|
background: var(--options-link-bg-color);
|
||||||
|
font-weight: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link:hover {
|
||||||
|
color: var(--options-main-link-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited:hover {
|
||||||
|
color: var(--options-main-link-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus {
|
||||||
|
border: 2px solid var(--focus-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transparent {
|
||||||
|
color: transparent;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 0px;
|
||||||
|
line-height: 0px;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.follow {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--options-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.followAvatar {
|
||||||
|
margin: 0% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.followAvatar img {
|
||||||
|
border-radius: 10%;
|
||||||
|
width: 20%;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imText {
|
||||||
|
font-size: var(--font-size4);
|
||||||
|
color: var(--options-main-link-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pgp {
|
||||||
|
font-size: var(--font-size5);
|
||||||
|
color: var(--options-main-link-color);
|
||||||
|
background: var(--options-link-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background-color: var(--button-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
font-size: var(--font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options img {
|
||||||
|
border-radius: var(--avatar-rounding);
|
||||||
|
background-color: var(--options-bg-color);
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 400px) {
|
||||||
|
textarea {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: var(--font-size4);
|
||||||
|
width: 90%;
|
||||||
|
background-color: var(--text-entry-background);
|
||||||
|
color: var(--text-entry-foreground);
|
||||||
|
}
|
||||||
|
.followText {
|
||||||
|
font-size: var(--follow-text-size1);
|
||||||
|
}
|
||||||
|
input[type=text] {
|
||||||
|
width: var(--follow-text-entry-width);
|
||||||
|
clear: both;
|
||||||
|
max-width: 30%;
|
||||||
|
min-width: var(--petname-width-chars);
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-entry-foreground);
|
||||||
|
background-color: var(--text-entry-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
width: 10ch;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 30px;
|
||||||
|
}
|
||||||
|
.buttonIcon {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 65px;
|
||||||
|
font-size: 24px;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.buttonsmall {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-small-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-small-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
width: 7ch;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 30px;
|
||||||
|
}
|
||||||
|
input[type=checkbox]
|
||||||
|
{
|
||||||
|
-ms-transform: scale(2);
|
||||||
|
-moz-transform: scale(2);
|
||||||
|
-webkit-transform: scale(2);
|
||||||
|
-o-transform: scale(2);
|
||||||
|
transform: scale(2);
|
||||||
|
padding: 10px;
|
||||||
|
margin: 20px 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
textarea {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: var(--font-size);
|
||||||
|
width: 90%;
|
||||||
|
background-color: var(--text-entry-background);
|
||||||
|
color: var(--text-entry-foreground);
|
||||||
|
}
|
||||||
|
.followText {
|
||||||
|
font-size: var(--follow-text-size2);
|
||||||
|
}
|
||||||
|
input[type=text] {
|
||||||
|
width: var(--follow-text-entry-width);
|
||||||
|
clear: both;
|
||||||
|
font-size: var(--font-size);
|
||||||
|
text-align: center;
|
||||||
|
max-width: 50%;
|
||||||
|
min-width: var(--petname-width-chars);
|
||||||
|
color: var(--text-entry-foreground);
|
||||||
|
background-color: var(--text-entry-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: var(--font-size);
|
||||||
|
width: 10ch;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 30px;
|
||||||
|
}
|
||||||
|
.buttonIcon {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 80px;
|
||||||
|
font-size: var(--font-size);
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.buttonsmall {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-small-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-small-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: var(--font-size);
|
||||||
|
width: 7ch;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 30px;
|
||||||
|
}
|
||||||
|
input[type=checkbox]
|
||||||
|
{
|
||||||
|
-ms-transform: scale(4);
|
||||||
|
-moz-transform: scale(4);
|
||||||
|
-webkit-transform: scale(4);
|
||||||
|
-o-transform: scale(4);
|
||||||
|
transform: scale(4);
|
||||||
|
padding: 20px;
|
||||||
|
margin: 30px 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
textarea {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-size: var(--font-size2);
|
||||||
|
width: 90%;
|
||||||
|
background-color: var(--text-entry-background);
|
||||||
|
color: var(--text-entry-foreground);
|
||||||
|
}
|
||||||
|
.followText {
|
||||||
|
font-size: var(--follow-text-size2);
|
||||||
|
}
|
||||||
|
input[type=text] {
|
||||||
|
width: var(--follow-text-entry-width);
|
||||||
|
clear: both;
|
||||||
|
font-size: var(--font-size2);
|
||||||
|
text-align: center;
|
||||||
|
max-width: 50%;
|
||||||
|
min-width: var(--petname-width-chars);
|
||||||
|
color: var(--text-entry-foreground);
|
||||||
|
background-color: var(--text-entry-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: var(--font-size2);
|
||||||
|
width: 10ch;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 30px;
|
||||||
|
}
|
||||||
|
.buttonIcon {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 80px;
|
||||||
|
font-size: var(--font-size2);
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.buttonsmall {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--button-small-background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: var(--button-small-text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: var(--font-size2);
|
||||||
|
width: 7ch;
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 30px;
|
||||||
|
}
|
||||||
|
input[type=checkbox]
|
||||||
|
{
|
||||||
|
-ms-transform: scale(4);
|
||||||
|
-moz-transform: scale(4);
|
||||||
|
-webkit-transform: scale(4);
|
||||||
|
-o-transform: scale(4);
|
||||||
|
transform: scale(4);
|
||||||
|
padding: 20px;
|
||||||
|
margin: 30px 40px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -538,21 +538,21 @@ def _create_news_mirror(base_dir: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _convert_rs_sto_activity_pub(base_dir: str, http_prefix: str,
|
def _convert_rss_to_activitypub(base_dir: str, http_prefix: str,
|
||||||
domain: str, port: int,
|
domain: str, port: int,
|
||||||
newswire: {},
|
newswire: {},
|
||||||
translate: {},
|
translate: {},
|
||||||
recent_posts_cache: {},
|
recent_posts_cache: {},
|
||||||
max_recent_posts: int,
|
max_recent_posts: int,
|
||||||
session, cached_webfingers: {},
|
session, cached_webfingers: {},
|
||||||
person_cache: {},
|
person_cache: {},
|
||||||
federation_list: [],
|
federation_list: [],
|
||||||
send_threads: [], post_log: [],
|
send_threads: [], post_log: [],
|
||||||
max_mirrored_articles: int,
|
max_mirrored_articles: int,
|
||||||
allow_local_network_access: bool,
|
allow_local_network_access: bool,
|
||||||
system_language: str,
|
system_language: str,
|
||||||
low_bandwidth: bool,
|
low_bandwidth: bool,
|
||||||
content_license_url: str) -> None:
|
content_license_url: str) -> None:
|
||||||
"""Converts rss items in a newswire into posts
|
"""Converts rss items in a newswire into posts
|
||||||
"""
|
"""
|
||||||
if not newswire:
|
if not newswire:
|
||||||
|
@ -627,6 +627,10 @@ def _convert_rs_sto_activity_pub(base_dir: str, http_prefix: str,
|
||||||
'<br><a href="' + post_url + '">' + \
|
'<br><a href="' + post_url + '">' + \
|
||||||
translate['Read more...'] + '</a>'
|
translate['Read more...'] + '</a>'
|
||||||
|
|
||||||
|
# podcast_properties = None
|
||||||
|
# if len(item) > 8:
|
||||||
|
# podcast_properties = item[8]
|
||||||
|
|
||||||
followers_only = False
|
followers_only = False
|
||||||
# NOTE: the id when the post is created will not be
|
# NOTE: the id when the post is created will not be
|
||||||
# consistent (it's based on the current time, not the
|
# consistent (it's based on the current time, not the
|
||||||
|
@ -751,7 +755,7 @@ def _convert_rs_sto_activity_pub(base_dir: str, http_prefix: str,
|
||||||
try:
|
try:
|
||||||
os.remove(filename + '.arrived')
|
os.remove(filename + '.arrived')
|
||||||
except OSError:
|
except OSError:
|
||||||
print('EX: _convert_rs_sto_activity_pub ' +
|
print('EX: _convert_rss_to_activitypub ' +
|
||||||
'unable to delete ' + filename + '.arrived')
|
'unable to delete ' + filename + '.arrived')
|
||||||
|
|
||||||
# setting the url here links to the activitypub object
|
# setting the url here links to the activitypub object
|
||||||
|
@ -829,22 +833,22 @@ def run_newswire_daemon(base_dir: str, httpd,
|
||||||
print('No new newswire')
|
print('No new newswire')
|
||||||
|
|
||||||
print('Converting newswire to activitypub format')
|
print('Converting newswire to activitypub format')
|
||||||
_convert_rs_sto_activity_pub(base_dir,
|
_convert_rss_to_activitypub(base_dir,
|
||||||
http_prefix, domain, port,
|
http_prefix, domain, port,
|
||||||
new_newswire, translate,
|
new_newswire, translate,
|
||||||
httpd.recent_posts_cache,
|
httpd.recent_posts_cache,
|
||||||
httpd.max_recent_posts,
|
httpd.max_recent_posts,
|
||||||
httpd.session,
|
httpd.session,
|
||||||
httpd.cached_webfingers,
|
httpd.cached_webfingers,
|
||||||
httpd.person_cache,
|
httpd.person_cache,
|
||||||
httpd.federation_list,
|
httpd.federation_list,
|
||||||
httpd.send_threads,
|
httpd.send_threads,
|
||||||
httpd.postLog,
|
httpd.postLog,
|
||||||
httpd.max_mirrored_articles,
|
httpd.max_mirrored_articles,
|
||||||
httpd.allow_local_network_access,
|
httpd.allow_local_network_access,
|
||||||
httpd.system_language,
|
httpd.system_language,
|
||||||
httpd.low_bandwidth,
|
httpd.low_bandwidth,
|
||||||
httpd.content_license_url)
|
httpd.content_license_url)
|
||||||
print('Newswire feed converted to ActivityPub')
|
print('Newswire feed converted to ActivityPub')
|
||||||
|
|
||||||
if httpd.max_news_posts > 0:
|
if httpd.max_news_posts > 0:
|
||||||
|
|
208
newswire.py
208
newswire.py
|
@ -203,7 +203,8 @@ def _add_newswire_dict_entry(base_dir: str, domain: str,
|
||||||
description: str, moderated: bool,
|
description: str, moderated: bool,
|
||||||
mirrored: bool,
|
mirrored: bool,
|
||||||
tags: [],
|
tags: [],
|
||||||
max_tags: int, session, debug: bool) -> None:
|
max_tags: int, session, debug: bool,
|
||||||
|
podcast_properties: {}) -> None:
|
||||||
"""Update the newswire dictionary
|
"""Update the newswire dictionary
|
||||||
"""
|
"""
|
||||||
# remove any markup
|
# remove any markup
|
||||||
|
@ -246,7 +247,8 @@ def _add_newswire_dict_entry(base_dir: str, domain: str,
|
||||||
description,
|
description,
|
||||||
moderated,
|
moderated,
|
||||||
post_tags,
|
post_tags,
|
||||||
mirrored
|
mirrored,
|
||||||
|
podcast_properties
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -382,6 +384,138 @@ def _xml2str_to_hashtag_categories(base_dir: str, xml_str: str,
|
||||||
False, force)
|
False, force)
|
||||||
|
|
||||||
|
|
||||||
|
def xml_podcast_to_dict(xml_str: str) -> {}:
|
||||||
|
"""podcasting extensions for RSS feeds
|
||||||
|
See https://github.com/Podcastindex-org/podcast-namespace/
|
||||||
|
blob/main/docs/1.0.md
|
||||||
|
"""
|
||||||
|
if '<podcast:' not in xml_str:
|
||||||
|
if '<itunes:' not in xml_str:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
podcast_properties = {
|
||||||
|
"locations": [],
|
||||||
|
"persons": [],
|
||||||
|
"soundbites": [],
|
||||||
|
"transcripts": [],
|
||||||
|
"valueRecipients": [],
|
||||||
|
"trailers": []
|
||||||
|
}
|
||||||
|
|
||||||
|
pod_lines = xml_str.split('<podcast:')
|
||||||
|
ctr = 0
|
||||||
|
for pod_line in pod_lines:
|
||||||
|
if ctr == 0 or '>' not in pod_line:
|
||||||
|
ctr += 1
|
||||||
|
continue
|
||||||
|
if ' ' not in pod_line.split('>')[0]:
|
||||||
|
pod_key = pod_line.split('>')[0].strip()
|
||||||
|
pod_val = pod_line.split('>', 1)[1].strip()
|
||||||
|
if '<' in pod_val:
|
||||||
|
pod_val = pod_val.split('<')[0]
|
||||||
|
podcast_properties[pod_key] = pod_val
|
||||||
|
ctr += 1
|
||||||
|
continue
|
||||||
|
pod_key = pod_line.split(' ')[0]
|
||||||
|
|
||||||
|
pod_fields = (
|
||||||
|
'url', 'geo', 'osm', 'type', 'method', 'group',
|
||||||
|
'owner', 'srcset', 'img', 'role', 'address', 'suggested',
|
||||||
|
'startTime', 'duration', 'href', 'name', 'pubdate',
|
||||||
|
'length', 'season', 'email'
|
||||||
|
)
|
||||||
|
pod_entry = {}
|
||||||
|
for pod_field in pod_fields:
|
||||||
|
if pod_field + '="' not in pod_line:
|
||||||
|
continue
|
||||||
|
pod_str = pod_line.split(pod_field + '="')[1]
|
||||||
|
if '"' not in pod_str:
|
||||||
|
continue
|
||||||
|
pod_val = pod_str.split('"')[0]
|
||||||
|
pod_entry[pod_field] = pod_val
|
||||||
|
|
||||||
|
pod_text = pod_line.split('>')[1]
|
||||||
|
if '<' in pod_text:
|
||||||
|
pod_text = pod_text.split('<')[0].strip()
|
||||||
|
if pod_text:
|
||||||
|
pod_entry['text'] = pod_text
|
||||||
|
|
||||||
|
if pod_key + 's' in podcast_properties:
|
||||||
|
if isinstance(podcast_properties[pod_key + 's'], list):
|
||||||
|
podcast_properties[pod_key + 's'].append(pod_entry)
|
||||||
|
else:
|
||||||
|
podcast_properties[pod_key] = pod_entry
|
||||||
|
else:
|
||||||
|
podcast_properties[pod_key] = pod_entry
|
||||||
|
ctr += 1
|
||||||
|
|
||||||
|
# get the image for the podcast, if it exists
|
||||||
|
podcast_episode_image = None
|
||||||
|
episode_image_tags = ['<itunes:image']
|
||||||
|
for image_tag in episode_image_tags:
|
||||||
|
if image_tag not in xml_str:
|
||||||
|
continue
|
||||||
|
episode_image = xml_str.split(image_tag)[1]
|
||||||
|
if 'href="' in episode_image:
|
||||||
|
episode_image = episode_image.split('href="')[1]
|
||||||
|
if '"' in episode_image:
|
||||||
|
episode_image = episode_image.split('"')[0]
|
||||||
|
podcast_episode_image = episode_image
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if '>' in episode_image:
|
||||||
|
episode_image = episode_image.split('>')[1]
|
||||||
|
if '<' in episode_image:
|
||||||
|
episode_image = episode_image.split('<')[0]
|
||||||
|
if '://' in episode_image and '.' in episode_image:
|
||||||
|
podcast_episode_image = episode_image
|
||||||
|
break
|
||||||
|
|
||||||
|
if podcast_episode_image:
|
||||||
|
podcast_properties['image'] = podcast_episode_image
|
||||||
|
|
||||||
|
if '<itunes:explicit>Y' in xml_str or \
|
||||||
|
'<itunes:explicit>T' in xml_str or \
|
||||||
|
'<itunes:explicit>1' in xml_str:
|
||||||
|
podcast_properties['explicit'] = True
|
||||||
|
else:
|
||||||
|
podcast_properties['explicit'] = False
|
||||||
|
else:
|
||||||
|
if '<podcast:' not in xml_str:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return podcast_properties
|
||||||
|
|
||||||
|
|
||||||
|
def get_link_from_rss_item(rss_item: str) -> (str, str):
|
||||||
|
"""Extracts rss link from rss item string
|
||||||
|
"""
|
||||||
|
mime_type = None
|
||||||
|
|
||||||
|
if '<enclosure ' in rss_item:
|
||||||
|
# get link from audio or video enclosure
|
||||||
|
enclosure = rss_item.split('<enclosure ')[1]
|
||||||
|
if '>' in enclosure:
|
||||||
|
enclosure = enclosure.split('>')[0]
|
||||||
|
if ' type="' in enclosure:
|
||||||
|
mime_type = enclosure.split(' type="')[1]
|
||||||
|
if '"' in mime_type:
|
||||||
|
mime_type = mime_type.split('"')[0]
|
||||||
|
if 'url="' in enclosure and \
|
||||||
|
('"audio/' in enclosure or '"video/' in enclosure):
|
||||||
|
link_str = enclosure.split('url="')[1]
|
||||||
|
if '"' in link_str:
|
||||||
|
link = link_str.split('"')[0]
|
||||||
|
if '://' in link:
|
||||||
|
return link, mime_type
|
||||||
|
|
||||||
|
link = rss_item.split('<link>')[1]
|
||||||
|
link = link.split('</link>')[0]
|
||||||
|
if '://' not in link:
|
||||||
|
return None, None
|
||||||
|
return link, mime_type
|
||||||
|
|
||||||
|
|
||||||
def _xml2str_to_dict(base_dir: str, domain: str, xml_str: str,
|
def _xml2str_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
moderated: bool, mirrored: bool,
|
moderated: bool, mirrored: bool,
|
||||||
max_posts_per_source: int,
|
max_posts_per_source: int,
|
||||||
|
@ -421,9 +555,11 @@ def _xml2str_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
continue
|
continue
|
||||||
if '</pubDate>' not in rss_item:
|
if '</pubDate>' not in rss_item:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
title = rss_item.split('<title>')[1]
|
title = rss_item.split('<title>')[1]
|
||||||
title = _remove_cdata(title.split('</title>')[0])
|
title = _remove_cdata(title.split('</title>')[0])
|
||||||
title = remove_html(title)
|
title = remove_html(title)
|
||||||
|
|
||||||
description = ''
|
description = ''
|
||||||
if '<description>' in rss_item and '</description>' in rss_item:
|
if '<description>' in rss_item and '</description>' in rss_item:
|
||||||
description = rss_item.split('<description>')[1]
|
description = rss_item.split('<description>')[1]
|
||||||
|
@ -434,13 +570,15 @@ def _xml2str_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
description = rss_item.split('<media:description>')[1]
|
description = rss_item.split('<media:description>')[1]
|
||||||
description = description.split('</media:description>')[0]
|
description = description.split('</media:description>')[0]
|
||||||
description = remove_html(description)
|
description = remove_html(description)
|
||||||
link = rss_item.split('<link>')[1]
|
|
||||||
link = link.split('</link>')[0]
|
link, link_mime_type = get_link_from_rss_item(rss_item)
|
||||||
if '://' not in link:
|
if not link:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item_domain = link.split('://')[1]
|
item_domain = link.split('://')[1]
|
||||||
if '/' in item_domain:
|
if '/' in item_domain:
|
||||||
item_domain = item_domain.split('/')[0]
|
item_domain = item_domain.split('/')[0]
|
||||||
|
|
||||||
if is_blocked_domain(base_dir, item_domain):
|
if is_blocked_domain(base_dir, item_domain):
|
||||||
continue
|
continue
|
||||||
pub_date = rss_item.split('<pubDate>')[1]
|
pub_date = rss_item.split('<pubDate>')[1]
|
||||||
|
@ -451,18 +589,21 @@ def _xml2str_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
if _valid_feed_date(pub_date_str):
|
if _valid_feed_date(pub_date_str):
|
||||||
post_filename = ''
|
post_filename = ''
|
||||||
votes_status = []
|
votes_status = []
|
||||||
|
podcast_properties = xml_podcast_to_dict(rss_item)
|
||||||
|
if podcast_properties:
|
||||||
|
podcast_properties['linkMimeType'] = link_mime_type
|
||||||
_add_newswire_dict_entry(base_dir, domain,
|
_add_newswire_dict_entry(base_dir, domain,
|
||||||
result, pub_date_str,
|
result, pub_date_str,
|
||||||
title, link,
|
title, link,
|
||||||
votes_status, post_filename,
|
votes_status, post_filename,
|
||||||
description, moderated,
|
description, moderated,
|
||||||
mirrored, [], 32, session, debug)
|
mirrored, [], 32, session, debug,
|
||||||
|
podcast_properties)
|
||||||
post_ctr += 1
|
post_ctr += 1
|
||||||
if post_ctr >= max_posts_per_source:
|
if post_ctr >= max_posts_per_source:
|
||||||
break
|
break
|
||||||
if post_ctr > 0:
|
if post_ctr > 0:
|
||||||
print('Added ' + str(post_ctr) +
|
print('Added ' + str(post_ctr) + ' rss 2.0 feed items to newswire')
|
||||||
' rss 2.0 feed items to newswire')
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -522,13 +663,15 @@ def _xml1str_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
description = rss_item.split('<media:description>')[1]
|
description = rss_item.split('<media:description>')[1]
|
||||||
description = description.split('</media:description>')[0]
|
description = description.split('</media:description>')[0]
|
||||||
description = remove_html(description)
|
description = remove_html(description)
|
||||||
link = rss_item.split('<link>')[1]
|
|
||||||
link = link.split('</link>')[0]
|
link, link_mime_type = get_link_from_rss_item(rss_item)
|
||||||
if '://' not in link:
|
if not link:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item_domain = link.split('://')[1]
|
item_domain = link.split('://')[1]
|
||||||
if '/' in item_domain:
|
if '/' in item_domain:
|
||||||
item_domain = item_domain.split('/')[0]
|
item_domain = item_domain.split('/')[0]
|
||||||
|
|
||||||
if is_blocked_domain(base_dir, item_domain):
|
if is_blocked_domain(base_dir, item_domain):
|
||||||
continue
|
continue
|
||||||
pub_date = rss_item.split('<dc:date>')[1]
|
pub_date = rss_item.split('<dc:date>')[1]
|
||||||
|
@ -539,18 +682,21 @@ def _xml1str_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
if _valid_feed_date(pub_date_str):
|
if _valid_feed_date(pub_date_str):
|
||||||
post_filename = ''
|
post_filename = ''
|
||||||
votes_status = []
|
votes_status = []
|
||||||
|
podcast_properties = xml_podcast_to_dict(rss_item)
|
||||||
|
if podcast_properties:
|
||||||
|
podcast_properties['linkMimeType'] = link_mime_type
|
||||||
_add_newswire_dict_entry(base_dir, domain,
|
_add_newswire_dict_entry(base_dir, domain,
|
||||||
result, pub_date_str,
|
result, pub_date_str,
|
||||||
title, link,
|
title, link,
|
||||||
votes_status, post_filename,
|
votes_status, post_filename,
|
||||||
description, moderated,
|
description, moderated,
|
||||||
mirrored, [], 32, session, debug)
|
mirrored, [], 32, session, debug,
|
||||||
|
podcast_properties)
|
||||||
post_ctr += 1
|
post_ctr += 1
|
||||||
if post_ctr >= max_posts_per_source:
|
if post_ctr >= max_posts_per_source:
|
||||||
break
|
break
|
||||||
if post_ctr > 0:
|
if post_ctr > 0:
|
||||||
print('Added ' + str(post_ctr) +
|
print('Added ' + str(post_ctr) + ' rss 1.0 feed items to newswire')
|
||||||
' rss 1.0 feed items to newswire')
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -598,13 +744,15 @@ def _atom_feed_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
description = atom_item.split('<media:description>')[1]
|
description = atom_item.split('<media:description>')[1]
|
||||||
description = description.split('</media:description>')[0]
|
description = description.split('</media:description>')[0]
|
||||||
description = remove_html(description)
|
description = remove_html(description)
|
||||||
link = atom_item.split('<link>')[1]
|
|
||||||
link = link.split('</link>')[0]
|
link, link_mime_type = get_link_from_rss_item(atom_item)
|
||||||
if '://' not in link:
|
if not link:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item_domain = link.split('://')[1]
|
item_domain = link.split('://')[1]
|
||||||
if '/' in item_domain:
|
if '/' in item_domain:
|
||||||
item_domain = item_domain.split('/')[0]
|
item_domain = item_domain.split('/')[0]
|
||||||
|
|
||||||
if is_blocked_domain(base_dir, item_domain):
|
if is_blocked_domain(base_dir, item_domain):
|
||||||
continue
|
continue
|
||||||
pub_date = atom_item.split('<updated>')[1]
|
pub_date = atom_item.split('<updated>')[1]
|
||||||
|
@ -615,18 +763,21 @@ def _atom_feed_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
if _valid_feed_date(pub_date_str):
|
if _valid_feed_date(pub_date_str):
|
||||||
post_filename = ''
|
post_filename = ''
|
||||||
votes_status = []
|
votes_status = []
|
||||||
|
podcast_properties = xml_podcast_to_dict(atom_item)
|
||||||
|
if podcast_properties:
|
||||||
|
podcast_properties['linkMimeType'] = link_mime_type
|
||||||
_add_newswire_dict_entry(base_dir, domain,
|
_add_newswire_dict_entry(base_dir, domain,
|
||||||
result, pub_date_str,
|
result, pub_date_str,
|
||||||
title, link,
|
title, link,
|
||||||
votes_status, post_filename,
|
votes_status, post_filename,
|
||||||
description, moderated,
|
description, moderated,
|
||||||
mirrored, [], 32, session, debug)
|
mirrored, [], 32, session, debug,
|
||||||
|
podcast_properties)
|
||||||
post_ctr += 1
|
post_ctr += 1
|
||||||
if post_ctr >= max_posts_per_source:
|
if post_ctr >= max_posts_per_source:
|
||||||
break
|
break
|
||||||
if post_ctr > 0:
|
if post_ctr > 0:
|
||||||
print('Added ' + str(post_ctr) +
|
print('Added ' + str(post_ctr) + ' atom feed items to newswire')
|
||||||
' atom feed items to newswire')
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -732,7 +883,8 @@ def _json_feed_v1to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
title, link,
|
title, link,
|
||||||
votes_status, post_filename,
|
votes_status, post_filename,
|
||||||
description, moderated,
|
description, moderated,
|
||||||
mirrored, [], 32, session, debug)
|
mirrored, [], 32, session, debug,
|
||||||
|
None)
|
||||||
post_ctr += 1
|
post_ctr += 1
|
||||||
if post_ctr >= max_posts_per_source:
|
if post_ctr >= max_posts_per_source:
|
||||||
break
|
break
|
||||||
|
@ -805,7 +957,8 @@ def _atom_feed_yt_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
title, link,
|
title, link,
|
||||||
votes_status, post_filename,
|
votes_status, post_filename,
|
||||||
description, moderated, mirrored,
|
description, moderated, mirrored,
|
||||||
[], 32, session, debug)
|
[], 32, session, debug,
|
||||||
|
None)
|
||||||
post_ctr += 1
|
post_ctr += 1
|
||||||
if post_ctr >= max_posts_per_source:
|
if post_ctr >= max_posts_per_source:
|
||||||
break
|
break
|
||||||
|
@ -829,24 +982,24 @@ def _xml_str_to_dict(base_dir: str, domain: str, xml_str: str,
|
||||||
max_posts_per_source,
|
max_posts_per_source,
|
||||||
max_feed_item_size_kb,
|
max_feed_item_size_kb,
|
||||||
session, debug)
|
session, debug)
|
||||||
elif 'rss version="2.0"' in xml_str:
|
if 'rss version="2.0"' in xml_str:
|
||||||
return _xml2str_to_dict(base_dir, domain,
|
return _xml2str_to_dict(base_dir, domain,
|
||||||
xml_str, moderated, mirrored,
|
xml_str, moderated, mirrored,
|
||||||
max_posts_per_source, max_feed_item_size_kb,
|
max_posts_per_source, max_feed_item_size_kb,
|
||||||
max_categories_feedItem_size_kb,
|
max_categories_feedItem_size_kb,
|
||||||
session, debug)
|
session, debug)
|
||||||
elif '<?xml version="1.0"' in xml_str:
|
if '<?xml version="1.0"' in xml_str:
|
||||||
return _xml1str_to_dict(base_dir, domain,
|
return _xml1str_to_dict(base_dir, domain,
|
||||||
xml_str, moderated, mirrored,
|
xml_str, moderated, mirrored,
|
||||||
max_posts_per_source, max_feed_item_size_kb,
|
max_posts_per_source, max_feed_item_size_kb,
|
||||||
max_categories_feedItem_size_kb,
|
max_categories_feedItem_size_kb,
|
||||||
session, debug)
|
session, debug)
|
||||||
elif 'xmlns="http://www.w3.org/2005/Atom"' in xml_str:
|
if 'xmlns="http://www.w3.org/2005/Atom"' in xml_str:
|
||||||
return _atom_feed_to_dict(base_dir, domain,
|
return _atom_feed_to_dict(base_dir, domain,
|
||||||
xml_str, moderated, mirrored,
|
xml_str, moderated, mirrored,
|
||||||
max_posts_per_source, max_feed_item_size_kb,
|
max_posts_per_source, max_feed_item_size_kb,
|
||||||
session, debug)
|
session, debug)
|
||||||
elif 'https://jsonfeed.org/version/1' in xml_str:
|
if 'https://jsonfeed.org/version/1' in xml_str:
|
||||||
return _json_feed_v1to_dict(base_dir, domain,
|
return _json_feed_v1to_dict(base_dir, domain,
|
||||||
xml_str, moderated, mirrored,
|
xml_str, moderated, mirrored,
|
||||||
max_posts_per_source,
|
max_posts_per_source,
|
||||||
|
@ -1082,7 +1235,8 @@ def _add_account_blogs_to_newswire(base_dir: str, nickname: str, domain: str,
|
||||||
votes, full_post_filename,
|
votes, full_post_filename,
|
||||||
description, moderated, False,
|
description, moderated, False,
|
||||||
tags_from_post,
|
tags_from_post,
|
||||||
max_tags, session, debug)
|
max_tags, session, debug,
|
||||||
|
None)
|
||||||
|
|
||||||
ctr += 1
|
ctr += 1
|
||||||
if ctr >= max_blogs_per_account:
|
if ctr >= max_blogs_per_account:
|
||||||
|
|
|
@ -143,7 +143,7 @@ def _person_receive_update_outbox(recent_posts_cache: {},
|
||||||
if 'attachment' not in actor_json:
|
if 'attachment' not in actor_json:
|
||||||
continue
|
continue
|
||||||
found = False
|
found = False
|
||||||
for attach_idx in range(len(actor_json['attachment'])):
|
for attach_idx, _ in enumerate(actor_json['attachment']):
|
||||||
if actor_json['attachment'][attach_idx]['type'] != \
|
if actor_json['attachment'][attach_idx]['type'] != \
|
||||||
'PropertyValue':
|
'PropertyValue':
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -797,7 +797,7 @@ def person_upgrade_actor(base_dir: str, person_json: {},
|
||||||
update_actor = True
|
update_actor = True
|
||||||
else:
|
else:
|
||||||
# add location if it is missing
|
# add location if it is missing
|
||||||
for index in range(len(person_json['hasOccupation'])):
|
for index, _ in enumerate(person_json['hasOccupation']):
|
||||||
oc_item = person_json['hasOccupation'][index]
|
oc_item = person_json['hasOccupation'][index]
|
||||||
if oc_item.get('hasOccupation'):
|
if oc_item.get('hasOccupation'):
|
||||||
oc_item = oc_item['hasOccupation']
|
oc_item = oc_item['hasOccupation']
|
||||||
|
|
2
roles.py
2
roles.py
|
@ -163,7 +163,7 @@ def _set_actor_role(actor_json: {}, role_name: str) -> bool:
|
||||||
if not category:
|
if not category:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for index in range(len(actor_json['hasOccupation'])):
|
for index, _ in enumerate(actor_json['hasOccupation']):
|
||||||
occupation_item = actor_json['hasOccupation'][index]
|
occupation_item = actor_json['hasOccupation'][index]
|
||||||
if not isinstance(occupation_item, dict):
|
if not isinstance(occupation_item, dict):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -503,7 +503,7 @@ def _post_to_speaker_json(base_dir: str, http_prefix: str,
|
||||||
follows = fp_foll.readlines()
|
follows = fp_foll.readlines()
|
||||||
if len(follows) > 0:
|
if len(follows) > 0:
|
||||||
follow_requests_exist = True
|
follow_requests_exist = True
|
||||||
for i in range(len(follows)):
|
for i, _ in enumerate(follows):
|
||||||
follows[i] = follows[i].strip()
|
follows[i] = follows[i].strip()
|
||||||
follow_requests_list = follows
|
follow_requests_list = follows
|
||||||
post_dm = False
|
post_dm = False
|
||||||
|
|
117
tests.py
117
tests.py
|
@ -150,6 +150,8 @@ from linked_data_sig import generate_json_signature
|
||||||
from linked_data_sig import verify_json_signature
|
from linked_data_sig import verify_json_signature
|
||||||
from newsdaemon import hashtag_rule_tree
|
from newsdaemon import hashtag_rule_tree
|
||||||
from newsdaemon import hashtag_rule_resolve
|
from newsdaemon import hashtag_rule_resolve
|
||||||
|
from newswire import get_link_from_rss_item
|
||||||
|
from newswire import xml_podcast_to_dict
|
||||||
from newswire import get_newswire_tags
|
from newswire import get_newswire_tags
|
||||||
from newswire import parse_feed_date
|
from newswire import parse_feed_date
|
||||||
from newswire import limit_word_lengths
|
from newswire import limit_word_lengths
|
||||||
|
@ -6354,7 +6356,7 @@ def _test_httpsig_base_new(with_digest: bool, base_dir: str,
|
||||||
|
|
||||||
|
|
||||||
def _test_get_actor_from_in_reply_to() -> None:
|
def _test_get_actor_from_in_reply_to() -> None:
|
||||||
print('testGetActorFromInReplyTo')
|
print('test_get_actor_from_in_reply_to')
|
||||||
in_reply_to = \
|
in_reply_to = \
|
||||||
'https://fosstodon.org/users/bashrc/statuses/107400700612621140'
|
'https://fosstodon.org/users/bashrc/statuses/107400700612621140'
|
||||||
reply_actor = get_actor_from_in_reply_to(in_reply_to)
|
reply_actor = get_actor_from_in_reply_to(in_reply_to)
|
||||||
|
@ -6365,6 +6367,117 @@ def _test_get_actor_from_in_reply_to() -> None:
|
||||||
assert reply_actor is None
|
assert reply_actor is None
|
||||||
|
|
||||||
|
|
||||||
|
def _test_xml_podcast_dict() -> None:
|
||||||
|
print('test_xml_podcast_dict')
|
||||||
|
xml_str = \
|
||||||
|
'<?xml version="1.0" encoding="UTF-8" ?>\n' + \
|
||||||
|
'<rss version="2.0" xmlns:podcast="' + \
|
||||||
|
'https://podcastindex.org/namespace/1.0">\n' + \
|
||||||
|
'<podcast:episode>5</podcast:episode>\n' + \
|
||||||
|
'<podcast:chapters ' + \
|
||||||
|
'url="https://whoframed.rodger/ep1_chapters.json" ' + \
|
||||||
|
'type="application/json"/>\n' + \
|
||||||
|
'<podcast:funding ' + \
|
||||||
|
'url="https://whoframed.rodger/donate">' + \
|
||||||
|
'Support the show</podcast:funding>\n' + \
|
||||||
|
'<podcast:images ' + \
|
||||||
|
'srcset="https://whoframed.rodger/images/ep1/' + \
|
||||||
|
'pci_avatar-massive.jpg 1500w, ' + \
|
||||||
|
'https://whoframed.rodger/images/ep1/pci_avatar-middle.jpg 600w, ' + \
|
||||||
|
'https://whoframed.rodger/images/ep1/pci_avatar-small.jpg 300w, ' + \
|
||||||
|
'https://whoframed.rodger/images/ep1/' + \
|
||||||
|
'pci_avatar-microfiche.jpg 50w" />\n' + \
|
||||||
|
'<podcast:location geo="geo:57.4272,34.63763" osm="R472152">' + \
|
||||||
|
'Nowheresville</podcast:location>\n' + \
|
||||||
|
'<podcast:locked owner="podcastowner@whoframed.rodger">yes' + \
|
||||||
|
'</podcast:locked>\n' + \
|
||||||
|
'<podcast:person group="visuals" role="cover art designer" ' + \
|
||||||
|
'href="https://whoframed.rodger/artist/rodgetrabbit">' + \
|
||||||
|
'Rodger Rabbit</podcast:person>\n' + \
|
||||||
|
'<podcast:person href="https://whoframed.rodger" ' + \
|
||||||
|
'img="http://whoframed.rodger/images/rr.jpg">Rodger Rabbit' + \
|
||||||
|
'</podcast:person>\n' + \
|
||||||
|
'<podcast:person href="https://whoframed.rodger" ' + \
|
||||||
|
'img="http://whoframed.rodger/images/jr.jpg">' + \
|
||||||
|
'Jessica Rabbit</podcast:person>\n' + \
|
||||||
|
'<podcast:person role="guest" ' + \
|
||||||
|
'href="https://whoframed.rodger/blog/bettyboop/" ' + \
|
||||||
|
'img="http://whoframed.rodger/images/bb.jpg">' + \
|
||||||
|
'Betty Boop</podcast:person>\n' + \
|
||||||
|
'<podcast:person role="guest" ' + \
|
||||||
|
'href="https://goodto.talk/bobhoskins/" ' + \
|
||||||
|
'img="https://goodto.talk/images/bhosk.jpg">' + \
|
||||||
|
'Bob Hoskins</podcast:person>\n' + \
|
||||||
|
'<podcast:season name="Podcasting 2.0">1</podcast:season>\n' + \
|
||||||
|
'<podcast:soundbite startTime="15.27" duration="8.0" />\n' + \
|
||||||
|
'<podcast:soundbite startTime="21.34" duration="32.0" />\n' + \
|
||||||
|
'<podcast:transcript ' + \
|
||||||
|
'url="https://whoframed.rodger/ep1/transcript.txt" ' + \
|
||||||
|
'type="text/plain" />\n' + \
|
||||||
|
'<podcast:transcript ' + \
|
||||||
|
'url="https://whoframed.rodger/ep2/transcript.txt" ' + \
|
||||||
|
'type="text/plain" />\n' + \
|
||||||
|
'<podcast:transcript ' + \
|
||||||
|
'url="https://whoframed.rodger/ep3/transcript.txt" ' + \
|
||||||
|
'type="text/plain" />\n' + \
|
||||||
|
'<podcast:value type="donate" method="keysend" ' + \
|
||||||
|
'suggested="2.95">\n' + \
|
||||||
|
' <podcast:valueRecipient name="hosting company" ' + \
|
||||||
|
'type="node" address="someaddress1" split="1" />\n' + \
|
||||||
|
' <podcast:valueRecipient name="podcaster" type="node" ' + \
|
||||||
|
'address="someaddress2" split="99" />\n' + \
|
||||||
|
'</podcast:value>\n' + \
|
||||||
|
'</rss>'
|
||||||
|
podcast_properties = xml_podcast_to_dict(xml_str)
|
||||||
|
assert podcast_properties
|
||||||
|
# pprint(podcast_properties)
|
||||||
|
assert podcast_properties.get('valueRecipients')
|
||||||
|
assert podcast_properties.get('persons')
|
||||||
|
assert podcast_properties.get('soundbites')
|
||||||
|
assert podcast_properties.get('locations')
|
||||||
|
assert podcast_properties.get('transcripts')
|
||||||
|
assert podcast_properties.get('episode')
|
||||||
|
assert podcast_properties.get('funding')
|
||||||
|
assert int(podcast_properties['episode']) == 5
|
||||||
|
assert podcast_properties['funding']['text'] == "Support the show"
|
||||||
|
assert podcast_properties['funding']['url'] == \
|
||||||
|
"https://whoframed.rodger/donate"
|
||||||
|
assert len(podcast_properties['transcripts']) == 3
|
||||||
|
assert len(podcast_properties['valueRecipients']) == 2
|
||||||
|
assert len(podcast_properties['persons']) == 5
|
||||||
|
assert len(podcast_properties['locations']) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def _test_get_link_from_rss_item() -> None:
|
||||||
|
print('test_get_link_from_rssitem')
|
||||||
|
rss_item = \
|
||||||
|
'<link>' + \
|
||||||
|
'https://anchor.fm/creativecommons/episodes/' + \
|
||||||
|
'Hessel-van-Oorschot-of-Tribe-of-Noise--Free-Music-Archive-e1crvce' + \
|
||||||
|
'</link>' + \
|
||||||
|
'<pubDate>Wed, 12 Jan 2022 14:28:46 GMT</pubDate>' + \
|
||||||
|
'<enclosure url="https://anchor.fm/s/4d70d828/podcast/' + \
|
||||||
|
'play/46054222/https%3A%2F%2Fd3ctxlq1ktw2nl.cloudfront.net' + \
|
||||||
|
'%2Fstaging%2F2022-0-12%2F7352f28c-a928-ea7a-65ae-' + \
|
||||||
|
'ccb5edffbac1.mp3" length="67247880" type="audio/mpeg"/>'
|
||||||
|
link, mime_type = get_link_from_rss_item(rss_item)
|
||||||
|
assert link
|
||||||
|
assert link.endswith('.mp3')
|
||||||
|
assert mime_type
|
||||||
|
assert mime_type == 'audio/mpeg'
|
||||||
|
|
||||||
|
rss_item = \
|
||||||
|
'<link>' + \
|
||||||
|
'https://anchor.fm/creativecommons/episodes/' + \
|
||||||
|
'Hessel-van-Oorschot-of-Tribe-of-Noise--Free-Music-Archive-e1crvce' + \
|
||||||
|
'</link>' + \
|
||||||
|
'<pubDate>Wed, 12 Jan 2022 14:28:46 GMT</pubDate>'
|
||||||
|
link, mime_type = get_link_from_rss_item(rss_item)
|
||||||
|
assert link
|
||||||
|
assert link.startswith('https://anchor.fm')
|
||||||
|
assert not mime_type
|
||||||
|
|
||||||
|
|
||||||
def run_all_tests():
|
def run_all_tests():
|
||||||
base_dir = os.getcwd()
|
base_dir = os.getcwd()
|
||||||
print('Running tests...')
|
print('Running tests...')
|
||||||
|
@ -6381,6 +6494,8 @@ def run_all_tests():
|
||||||
'message_json', 'liked_post_json'])
|
'message_json', 'liked_post_json'])
|
||||||
_test_checkbox_names()
|
_test_checkbox_names()
|
||||||
_test_functions()
|
_test_functions()
|
||||||
|
_test_get_link_from_rss_item()
|
||||||
|
_test_xml_podcast_dict()
|
||||||
_test_get_actor_from_in_reply_to()
|
_test_get_actor_from_in_reply_to()
|
||||||
_test_valid_emoji_content()
|
_test_valid_emoji_content()
|
||||||
_test_add_cw_lists(base_dir)
|
_test_add_cw_lists(base_dir)
|
||||||
|
|
2
theme.py
2
theme.py
|
@ -107,7 +107,7 @@ def _get_theme_files() -> []:
|
||||||
return ('epicyon.css', 'login.css', 'follow.css',
|
return ('epicyon.css', 'login.css', 'follow.css',
|
||||||
'suspended.css', 'calendar.css', 'blog.css',
|
'suspended.css', 'calendar.css', 'blog.css',
|
||||||
'options.css', 'search.css', 'links.css',
|
'options.css', 'search.css', 'links.css',
|
||||||
'welcome.css', 'graph.css')
|
'welcome.css', 'graph.css', 'podcast.css')
|
||||||
|
|
||||||
|
|
||||||
def is_news_theme_name(base_dir: str, theme_name: str) -> bool:
|
def is_news_theme_name(base_dir: str, theme_name: str) -> bool:
|
||||||
|
|
10
utils.py
10
utils.py
|
@ -559,7 +559,7 @@ def get_followers_list(base_dir: str,
|
||||||
|
|
||||||
with open(filename, 'r') as foll_file:
|
with open(filename, 'r') as foll_file:
|
||||||
lines = foll_file.readlines()
|
lines = foll_file.readlines()
|
||||||
for i in range(len(lines)):
|
for i, _ in enumerate(lines):
|
||||||
lines[i] = lines[i].strip()
|
lines[i] = lines[i].strip()
|
||||||
return lines
|
return lines
|
||||||
return []
|
return []
|
||||||
|
@ -2126,7 +2126,7 @@ def _search_virtual_box_posts(base_dir: str, nickname: str, domain: str,
|
||||||
|
|
||||||
if '+' in search_str:
|
if '+' in search_str:
|
||||||
search_words = search_str.split('+')
|
search_words = search_str.split('+')
|
||||||
for index in range(len(search_words)):
|
for index, _ in enumerate(search_words):
|
||||||
search_words[index] = search_words[index].strip()
|
search_words[index] = search_words[index].strip()
|
||||||
print('SEARCH: ' + str(search_words))
|
print('SEARCH: ' + str(search_words))
|
||||||
else:
|
else:
|
||||||
|
@ -2178,7 +2178,7 @@ def search_box_posts(base_dir: str, nickname: str, domain: str,
|
||||||
|
|
||||||
if '+' in search_str:
|
if '+' in search_str:
|
||||||
search_words = search_str.split('+')
|
search_words = search_str.split('+')
|
||||||
for index in range(len(search_words)):
|
for index, _ in enumerate(search_words):
|
||||||
search_words[index] = search_words[index].strip()
|
search_words[index] = search_words[index].strip()
|
||||||
print('SEARCH: ' + str(search_words))
|
print('SEARCH: ' + str(search_words))
|
||||||
else:
|
else:
|
||||||
|
@ -2811,7 +2811,7 @@ def set_occupation_name(actor_json: {}, name: str) -> bool:
|
||||||
return False
|
return False
|
||||||
if not isinstance(actor_json['hasOccupation'], list):
|
if not isinstance(actor_json['hasOccupation'], list):
|
||||||
return False
|
return False
|
||||||
for index in range(len(actor_json['hasOccupation'])):
|
for index, _ in enumerate(actor_json['hasOccupation']):
|
||||||
occupation_item = actor_json['hasOccupation'][index]
|
occupation_item = actor_json['hasOccupation'][index]
|
||||||
if not isinstance(occupation_item, dict):
|
if not isinstance(occupation_item, dict):
|
||||||
continue
|
continue
|
||||||
|
@ -2831,7 +2831,7 @@ def set_occupation_skills_list(actor_json: {}, skills_list: []) -> bool:
|
||||||
return False
|
return False
|
||||||
if not isinstance(actor_json['hasOccupation'], list):
|
if not isinstance(actor_json['hasOccupation'], list):
|
||||||
return False
|
return False
|
||||||
for index in range(len(actor_json['hasOccupation'])):
|
for index, _ in enumerate(actor_json['hasOccupation']):
|
||||||
occupation_item = actor_json['hasOccupation'][index]
|
occupation_item = actor_json['hasOccupation'][index]
|
||||||
if not isinstance(occupation_item, dict):
|
if not isinstance(occupation_item, dict):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -263,6 +263,19 @@ def _html_newswire(base_dir: str, newswire: {}, nickname: str, moderator: bool,
|
||||||
'<img loading="lazy" src="' + favicon_url + '" ' + \
|
'<img loading="lazy" src="' + favicon_url + '" ' + \
|
||||||
'alt="" ' + _get_broken_fav_substitute() + '/>'
|
'alt="" ' + _get_broken_fav_substitute() + '/>'
|
||||||
moderated_item = item[5]
|
moderated_item = item[5]
|
||||||
|
link_url = url
|
||||||
|
|
||||||
|
# is this a podcast episode?
|
||||||
|
if len(item) > 8:
|
||||||
|
# change the link url to a podcast episode screen
|
||||||
|
podcast_properties = item[8]
|
||||||
|
if podcast_properties:
|
||||||
|
if podcast_properties.get('image'):
|
||||||
|
episode_id = date_str.replace(' ', '__')
|
||||||
|
episode_id = episode_id.replace(':', 'aa')
|
||||||
|
link_url = \
|
||||||
|
'/users/' + nickname + '/?podepisode=' + episode_id
|
||||||
|
|
||||||
html_str += separator_str
|
html_str += separator_str
|
||||||
if moderated_item and 'vote:' + nickname in item[2]:
|
if moderated_item and 'vote:' + nickname in item[2]:
|
||||||
total_votes_str = ''
|
total_votes_str = ''
|
||||||
|
@ -275,7 +288,7 @@ def _html_newswire(base_dir: str, newswire: {}, nickname: str, moderator: bool,
|
||||||
title = remove_long_words(item[0], 16, []).replace('\n', '<br>')
|
title = remove_long_words(item[0], 16, []).replace('\n', '<br>')
|
||||||
title = limit_repeated_words(title, 6)
|
title = limit_repeated_words(title, 6)
|
||||||
html_str += '<p class="newswireItemVotedOn">' + \
|
html_str += '<p class="newswireItemVotedOn">' + \
|
||||||
'<a href="' + url + '" target="_blank" ' + \
|
'<a href="' + link_url + '" target="_blank" ' + \
|
||||||
'rel="nofollow noopener noreferrer">' + \
|
'rel="nofollow noopener noreferrer">' + \
|
||||||
'<span class="newswireItemVotedOn">' + \
|
'<span class="newswireItemVotedOn">' + \
|
||||||
favicon_link + title + '</span></a>' + total_votes_str
|
favicon_link + title + '</span></a>' + total_votes_str
|
||||||
|
@ -305,7 +318,7 @@ def _html_newswire(base_dir: str, newswire: {}, nickname: str, moderator: bool,
|
||||||
title = limit_repeated_words(title, 6)
|
title = limit_repeated_words(title, 6)
|
||||||
if moderator and moderated_item:
|
if moderator and moderated_item:
|
||||||
html_str += '<p class="newswireItemModerated">' + \
|
html_str += '<p class="newswireItemModerated">' + \
|
||||||
'<a href="' + url + '" target="_blank" ' + \
|
'<a href="' + link_url + '" target="_blank" ' + \
|
||||||
'rel="nofollow noopener noreferrer">' + \
|
'rel="nofollow noopener noreferrer">' + \
|
||||||
favicon_link + title + '</a>' + total_votes_str
|
favicon_link + title + '</a>' + total_votes_str
|
||||||
html_str += ' ' + date_shown
|
html_str += ' ' + date_shown
|
||||||
|
@ -318,7 +331,7 @@ def _html_newswire(base_dir: str, newswire: {}, nickname: str, moderator: bool,
|
||||||
html_str += '</p>\n'
|
html_str += '</p>\n'
|
||||||
else:
|
else:
|
||||||
html_str += '<p class="newswireItem">' + \
|
html_str += '<p class="newswireItem">' + \
|
||||||
'<a href="' + url + '" target="_blank" ' + \
|
'<a href="' + link_url + '" target="_blank" ' + \
|
||||||
'rel="nofollow noopener noreferrer">' + \
|
'rel="nofollow noopener noreferrer">' + \
|
||||||
favicon_link + title + '</a>' + total_votes_str
|
favicon_link + title + '</a>' + total_votes_str
|
||||||
html_str += ' <span class="newswireDate">'
|
html_str += ' <span class="newswireDate">'
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
__filename__ = "webapp_podcast.py"
|
||||||
|
__author__ = "Bob Mottram"
|
||||||
|
__license__ = "AGPL3+"
|
||||||
|
__version__ = "1.2.0"
|
||||||
|
__maintainer__ = "Bob Mottram"
|
||||||
|
__email__ = "bob@libreserver.org"
|
||||||
|
__status__ = "Production"
|
||||||
|
__module_group__ = "Web Interface Columns"
|
||||||
|
|
||||||
|
import os
|
||||||
|
import html
|
||||||
|
import urllib.parse
|
||||||
|
from shutil import copyfile
|
||||||
|
from utils import get_config_param
|
||||||
|
from utils import remove_html
|
||||||
|
from media import path_is_audio
|
||||||
|
from webapp_utils import get_broken_link_substitute
|
||||||
|
from webapp_utils import html_header_with_external_style
|
||||||
|
from webapp_utils import html_footer
|
||||||
|
from webapp_utils import html_keyboard_navigation
|
||||||
|
|
||||||
|
|
||||||
|
def _html_podcast_performers(podcast_properties: {}) -> str:
|
||||||
|
"""Returns html for performers of a podcast
|
||||||
|
"""
|
||||||
|
if not podcast_properties.get('persons'):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# list of performers
|
||||||
|
podcast_str = '<div class="performers">\n'
|
||||||
|
podcast_str += ' <center>\n'
|
||||||
|
podcast_str += '<ul>\n'
|
||||||
|
for performer in podcast_properties['persons']:
|
||||||
|
if not performer.get('text'):
|
||||||
|
continue
|
||||||
|
performer_name = performer['text']
|
||||||
|
performer_title = performer_name
|
||||||
|
|
||||||
|
if performer.get('role'):
|
||||||
|
performer_title += ' (' + performer['role'] + ')'
|
||||||
|
if performer.get('group'):
|
||||||
|
performer_title += ', <i>' + performer['group'] + '</i>'
|
||||||
|
performer_title = remove_html(performer_title)
|
||||||
|
|
||||||
|
performer_url = ''
|
||||||
|
if performer.get('href'):
|
||||||
|
performer_url = performer['href']
|
||||||
|
|
||||||
|
performer_img = ''
|
||||||
|
if performer.get('img'):
|
||||||
|
performer_img = performer['img']
|
||||||
|
|
||||||
|
podcast_str += ' <li>\n'
|
||||||
|
podcast_str += ' <figure>\n'
|
||||||
|
podcast_str += ' <a href="' + performer_url + '">\n'
|
||||||
|
podcast_str += \
|
||||||
|
' <img loading="lazy" src="' + performer_img + \
|
||||||
|
'" alt="" />\n'
|
||||||
|
podcast_str += \
|
||||||
|
' <figcaption>' + performer_title + '</figcaption>\n'
|
||||||
|
podcast_str += ' </a>\n'
|
||||||
|
podcast_str += ' </figure>\n'
|
||||||
|
podcast_str += ' </li>\n'
|
||||||
|
|
||||||
|
podcast_str += '</ul>\n'
|
||||||
|
podcast_str += '</div>\n'
|
||||||
|
return podcast_str
|
||||||
|
|
||||||
|
|
||||||
|
def _html_podcast_soundbites(link_url: str, extension: str,
|
||||||
|
podcast_properties: {},
|
||||||
|
translate: {}) -> str:
|
||||||
|
"""Returns html for podcast soundbites
|
||||||
|
"""
|
||||||
|
if not podcast_properties.get('soundbites'):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
podcast_str = '<div class="performers">\n'
|
||||||
|
podcast_str += ' <center>\n'
|
||||||
|
podcast_str += '<ul>\n'
|
||||||
|
ctr = 1
|
||||||
|
for performer in podcast_properties['soundbites']:
|
||||||
|
if not performer.get('startTime'):
|
||||||
|
continue
|
||||||
|
if not performer['startTime'].isdigit():
|
||||||
|
continue
|
||||||
|
if not performer.get('duration'):
|
||||||
|
continue
|
||||||
|
if not performer['duration'].isdigit():
|
||||||
|
continue
|
||||||
|
end_time = str(float(performer['startTime']) +
|
||||||
|
float(performer['duration']))
|
||||||
|
|
||||||
|
podcast_str += ' <li>\n'
|
||||||
|
preview_url = \
|
||||||
|
link_url + '#t=' + performer['startTime'] + ',' + end_time
|
||||||
|
soundbite_title = translate['Preview']
|
||||||
|
if ctr > 0:
|
||||||
|
soundbite_title += ' ' + str(ctr)
|
||||||
|
podcast_str += \
|
||||||
|
' <audio controls>\n' + \
|
||||||
|
' <p>' + soundbite_title + '</p>\n' + \
|
||||||
|
' <source src="' + preview_url + '" type="audio/' + \
|
||||||
|
extension.replace('.', '') + '">' + \
|
||||||
|
translate['Your browser does not support the audio element.'] + \
|
||||||
|
'</audio>\n'
|
||||||
|
podcast_str += ' </li>\n'
|
||||||
|
ctr += 1
|
||||||
|
|
||||||
|
podcast_str += '</ul>\n'
|
||||||
|
podcast_str += '</div>\n'
|
||||||
|
return podcast_str
|
||||||
|
|
||||||
|
|
||||||
|
def html_podcast_episode(css_cache: {}, translate: {},
|
||||||
|
base_dir: str, nickname: str, domain: str,
|
||||||
|
newswire_item: [], theme: str,
|
||||||
|
default_timeline: str,
|
||||||
|
text_mode_banner: str, access_keys: {}) -> str:
|
||||||
|
"""Returns html for a podcast episode, giebn an item from the newswire
|
||||||
|
"""
|
||||||
|
css_filename = base_dir + '/epicyon-podcast.css'
|
||||||
|
if os.path.isfile(base_dir + '/podcast.css'):
|
||||||
|
css_filename = base_dir + '/podcast.css'
|
||||||
|
|
||||||
|
if os.path.isfile(base_dir + '/accounts/podcast-background-custom.jpg'):
|
||||||
|
if not os.path.isfile(base_dir + '/accounts/podcast-background.jpg'):
|
||||||
|
copyfile(base_dir + '/accounts/podcast-background.jpg',
|
||||||
|
base_dir + '/accounts/podcast-background.jpg')
|
||||||
|
|
||||||
|
instance_title = get_config_param(base_dir, 'instanceTitle')
|
||||||
|
podcast_str = \
|
||||||
|
html_header_with_external_style(css_filename, instance_title, None)
|
||||||
|
|
||||||
|
podcast_properties = newswire_item[8]
|
||||||
|
image_url = ''
|
||||||
|
image_src = 'src'
|
||||||
|
if podcast_properties.get('images'):
|
||||||
|
if podcast_properties['images'].get('srcset'):
|
||||||
|
image_url = podcast_properties['images']['srcset']
|
||||||
|
image_src = 'srcset'
|
||||||
|
if not image_url and podcast_properties.get('image'):
|
||||||
|
image_url = podcast_properties['image']
|
||||||
|
|
||||||
|
link_url = newswire_item[1]
|
||||||
|
|
||||||
|
podcast_str += html_keyboard_navigation(text_mode_banner, {}, {})
|
||||||
|
podcast_str += '<br><br>\n'
|
||||||
|
podcast_str += '<div class="options">\n'
|
||||||
|
podcast_str += ' <div class="optionsAvatar">\n'
|
||||||
|
podcast_str += ' <center>\n'
|
||||||
|
podcast_str += ' <a href="' + link_url + '">\n'
|
||||||
|
if image_src == 'srcset':
|
||||||
|
podcast_str += ' <img loading="lazy" srcset="' + image_url + \
|
||||||
|
'" alt="" ' + get_broken_link_substitute() + '/></a>\n'
|
||||||
|
else:
|
||||||
|
podcast_str += ' <img loading="lazy" src="' + image_url + \
|
||||||
|
'" alt="" ' + get_broken_link_substitute() + '/></a>\n'
|
||||||
|
podcast_str += ' </center>\n'
|
||||||
|
podcast_str += ' </div>\n'
|
||||||
|
|
||||||
|
podcast_str += ' <center>\n'
|
||||||
|
audio_extension = None
|
||||||
|
if path_is_audio(link_url):
|
||||||
|
if '.mp3' in link_url:
|
||||||
|
audio_extension = 'mpeg'
|
||||||
|
else:
|
||||||
|
audio_extension = 'ogg'
|
||||||
|
else:
|
||||||
|
if podcast_properties.get('linkMimeType'):
|
||||||
|
if 'audio' in podcast_properties['linkMimeType']:
|
||||||
|
audio_extension = \
|
||||||
|
podcast_properties['linkMimeType'].split('/')[1]
|
||||||
|
# show widgets for soundbites
|
||||||
|
if audio_extension:
|
||||||
|
podcast_str += _html_podcast_soundbites(link_url, audio_extension,
|
||||||
|
podcast_properties,
|
||||||
|
translate)
|
||||||
|
|
||||||
|
# podcast player widget
|
||||||
|
podcast_str += \
|
||||||
|
' <audio controls>\n' + \
|
||||||
|
' <source src="' + link_url + '" type="audio/' + \
|
||||||
|
audio_extension.replace('.', '') + '">' + \
|
||||||
|
translate['Your browser does not support the audio element.'] + \
|
||||||
|
'\n </audio>\n'
|
||||||
|
|
||||||
|
podcast_title = \
|
||||||
|
remove_html(html.unescape(urllib.parse.unquote_plus(newswire_item[0])))
|
||||||
|
if podcast_title:
|
||||||
|
podcast_str += \
|
||||||
|
'<p><label class="podcast-title">' + podcast_title + \
|
||||||
|
'</label></p>\n'
|
||||||
|
if newswire_item[4]:
|
||||||
|
podcast_description = \
|
||||||
|
html.unescape(urllib.parse.unquote_plus(newswire_item[4]))
|
||||||
|
podcast_description = remove_html(podcast_description)
|
||||||
|
if podcast_description:
|
||||||
|
remove_chars = ('Œ', 'â€', 'ğŸ', '<EFBFBD>')
|
||||||
|
for remchar in remove_chars:
|
||||||
|
podcast_description = podcast_description.replace(remchar, '')
|
||||||
|
podcast_str += '<p>' + podcast_description + '</p>\n'
|
||||||
|
|
||||||
|
# donate button
|
||||||
|
if podcast_properties.get('funding'):
|
||||||
|
if podcast_properties['funding'].get('url'):
|
||||||
|
donate_url = podcast_properties['funding']['url']
|
||||||
|
podcast_str += \
|
||||||
|
'<p><a href="' + donate_url + \
|
||||||
|
'"><button class="donateButton">' + translate['Donate'] + \
|
||||||
|
'</button></a></p>\n'
|
||||||
|
|
||||||
|
podcast_str += _html_podcast_performers(podcast_properties)
|
||||||
|
|
||||||
|
podcast_str += ' </center>\n'
|
||||||
|
podcast_str += '</div>\n'
|
||||||
|
|
||||||
|
podcast_str += html_footer()
|
||||||
|
return podcast_str
|
|
@ -941,9 +941,8 @@ def rss_hashtag_search(nickname: str, domain: str, port: int,
|
||||||
domain_full = get_full_domain(domain, port)
|
domain_full = get_full_domain(domain, port)
|
||||||
|
|
||||||
max_feed_length = 10
|
max_feed_length = 10
|
||||||
hashtag_feed = \
|
hashtag_feed = rss2tag_header(hashtag, http_prefix, domain_full)
|
||||||
rss2tag_header(hashtag, http_prefix, domain_full)
|
for index, _ in enumerate(lines):
|
||||||
for index in range(len(lines)):
|
|
||||||
post_id = lines[index].strip('\n').strip('\r')
|
post_id = lines[index].strip('\n').strip('\r')
|
||||||
if ' ' not in post_id:
|
if ' ' not in post_id:
|
||||||
nickname = get_nickname_from_actor(post_id)
|
nickname = get_nickname_from_actor(post_id)
|
||||||
|
|
Loading…
Reference in New Issue