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:
|
||||
blocked_lines = fp_blocked.readlines()
|
||||
# remove newlines
|
||||
for index in range(len(blocked_lines)):
|
||||
for index, _ in enumerate(blocked_lines):
|
||||
blocked_lines[index] = blocked_lines[index].replace('\n', '')
|
||||
# update the cache
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
|
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_edit_blog
|
||||
from blog import get_blog_address
|
||||
from webapp_podcast import html_podcast_episode
|
||||
from webapp_theme_designer import html_theme_designer
|
||||
from webapp_minimalbutton import set_minimal
|
||||
from webapp_minimalbutton import is_minimal
|
||||
|
@ -14062,6 +14063,36 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._write(msg)
|
||||
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
|
||||
if html_getreq and authorized and users_in_path and \
|
||||
'/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
|
||||
|
||||
|
||||
def _convert_rs_sto_activity_pub(base_dir: str, http_prefix: str,
|
||||
domain: str, port: int,
|
||||
newswire: {},
|
||||
translate: {},
|
||||
recent_posts_cache: {},
|
||||
max_recent_posts: int,
|
||||
session, cached_webfingers: {},
|
||||
person_cache: {},
|
||||
federation_list: [],
|
||||
send_threads: [], post_log: [],
|
||||
max_mirrored_articles: int,
|
||||
allow_local_network_access: bool,
|
||||
system_language: str,
|
||||
low_bandwidth: bool,
|
||||
content_license_url: str) -> None:
|
||||
def _convert_rss_to_activitypub(base_dir: str, http_prefix: str,
|
||||
domain: str, port: int,
|
||||
newswire: {},
|
||||
translate: {},
|
||||
recent_posts_cache: {},
|
||||
max_recent_posts: int,
|
||||
session, cached_webfingers: {},
|
||||
person_cache: {},
|
||||
federation_list: [],
|
||||
send_threads: [], post_log: [],
|
||||
max_mirrored_articles: int,
|
||||
allow_local_network_access: bool,
|
||||
system_language: str,
|
||||
low_bandwidth: bool,
|
||||
content_license_url: str) -> None:
|
||||
"""Converts rss items in a newswire into posts
|
||||
"""
|
||||
if not newswire:
|
||||
|
@ -627,6 +627,10 @@ def _convert_rs_sto_activity_pub(base_dir: str, http_prefix: str,
|
|||
'<br><a href="' + post_url + '">' + \
|
||||
translate['Read more...'] + '</a>'
|
||||
|
||||
# podcast_properties = None
|
||||
# if len(item) > 8:
|
||||
# podcast_properties = item[8]
|
||||
|
||||
followers_only = False
|
||||
# NOTE: the id when the post is created will not be
|
||||
# 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:
|
||||
os.remove(filename + '.arrived')
|
||||
except OSError:
|
||||
print('EX: _convert_rs_sto_activity_pub ' +
|
||||
print('EX: _convert_rss_to_activitypub ' +
|
||||
'unable to delete ' + filename + '.arrived')
|
||||
|
||||
# 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('Converting newswire to activitypub format')
|
||||
_convert_rs_sto_activity_pub(base_dir,
|
||||
http_prefix, domain, port,
|
||||
new_newswire, translate,
|
||||
httpd.recent_posts_cache,
|
||||
httpd.max_recent_posts,
|
||||
httpd.session,
|
||||
httpd.cached_webfingers,
|
||||
httpd.person_cache,
|
||||
httpd.federation_list,
|
||||
httpd.send_threads,
|
||||
httpd.postLog,
|
||||
httpd.max_mirrored_articles,
|
||||
httpd.allow_local_network_access,
|
||||
httpd.system_language,
|
||||
httpd.low_bandwidth,
|
||||
httpd.content_license_url)
|
||||
_convert_rss_to_activitypub(base_dir,
|
||||
http_prefix, domain, port,
|
||||
new_newswire, translate,
|
||||
httpd.recent_posts_cache,
|
||||
httpd.max_recent_posts,
|
||||
httpd.session,
|
||||
httpd.cached_webfingers,
|
||||
httpd.person_cache,
|
||||
httpd.federation_list,
|
||||
httpd.send_threads,
|
||||
httpd.postLog,
|
||||
httpd.max_mirrored_articles,
|
||||
httpd.allow_local_network_access,
|
||||
httpd.system_language,
|
||||
httpd.low_bandwidth,
|
||||
httpd.content_license_url)
|
||||
print('Newswire feed converted to ActivityPub')
|
||||
|
||||
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,
|
||||
mirrored: bool,
|
||||
tags: [],
|
||||
max_tags: int, session, debug: bool) -> None:
|
||||
max_tags: int, session, debug: bool,
|
||||
podcast_properties: {}) -> None:
|
||||
"""Update the newswire dictionary
|
||||
"""
|
||||
# remove any markup
|
||||
|
@ -246,7 +247,8 @@ def _add_newswire_dict_entry(base_dir: str, domain: str,
|
|||
description,
|
||||
moderated,
|
||||
post_tags,
|
||||
mirrored
|
||||
mirrored,
|
||||
podcast_properties
|
||||
]
|
||||
|
||||
|
||||
|
@ -382,6 +384,138 @@ def _xml2str_to_hashtag_categories(base_dir: str, xml_str: str,
|
|||
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,
|
||||
moderated: bool, mirrored: bool,
|
||||
max_posts_per_source: int,
|
||||
|
@ -421,9 +555,11 @@ def _xml2str_to_dict(base_dir: str, domain: str, xml_str: str,
|
|||
continue
|
||||
if '</pubDate>' not in rss_item:
|
||||
continue
|
||||
|
||||
title = rss_item.split('<title>')[1]
|
||||
title = _remove_cdata(title.split('</title>')[0])
|
||||
title = remove_html(title)
|
||||
|
||||
description = ''
|
||||
if '<description>' in rss_item and '</description>' in rss_item:
|
||||
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 = description.split('</media:description>')[0]
|
||||
description = remove_html(description)
|
||||
link = rss_item.split('<link>')[1]
|
||||
link = link.split('</link>')[0]
|
||||
if '://' not in link:
|
||||
|
||||
link, link_mime_type = get_link_from_rss_item(rss_item)
|
||||
if not link:
|
||||
continue
|
||||
|
||||
item_domain = link.split('://')[1]
|
||||
if '/' in item_domain:
|
||||
item_domain = item_domain.split('/')[0]
|
||||
|
||||
if is_blocked_domain(base_dir, item_domain):
|
||||
continue
|
||||
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):
|
||||
post_filename = ''
|
||||
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,
|
||||
result, pub_date_str,
|
||||
title, link,
|
||||
votes_status, post_filename,
|
||||
description, moderated,
|
||||
mirrored, [], 32, session, debug)
|
||||
mirrored, [], 32, session, debug,
|
||||
podcast_properties)
|
||||
post_ctr += 1
|
||||
if post_ctr >= max_posts_per_source:
|
||||
break
|
||||
if post_ctr > 0:
|
||||
print('Added ' + str(post_ctr) +
|
||||
' rss 2.0 feed items to newswire')
|
||||
print('Added ' + str(post_ctr) + ' rss 2.0 feed items to newswire')
|
||||
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 = description.split('</media:description>')[0]
|
||||
description = remove_html(description)
|
||||
link = rss_item.split('<link>')[1]
|
||||
link = link.split('</link>')[0]
|
||||
if '://' not in link:
|
||||
|
||||
link, link_mime_type = get_link_from_rss_item(rss_item)
|
||||
if not link:
|
||||
continue
|
||||
|
||||
item_domain = link.split('://')[1]
|
||||
if '/' in item_domain:
|
||||
item_domain = item_domain.split('/')[0]
|
||||
|
||||
if is_blocked_domain(base_dir, item_domain):
|
||||
continue
|
||||
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):
|
||||
post_filename = ''
|
||||
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,
|
||||
result, pub_date_str,
|
||||
title, link,
|
||||
votes_status, post_filename,
|
||||
description, moderated,
|
||||
mirrored, [], 32, session, debug)
|
||||
mirrored, [], 32, session, debug,
|
||||
podcast_properties)
|
||||
post_ctr += 1
|
||||
if post_ctr >= max_posts_per_source:
|
||||
break
|
||||
if post_ctr > 0:
|
||||
print('Added ' + str(post_ctr) +
|
||||
' rss 1.0 feed items to newswire')
|
||||
print('Added ' + str(post_ctr) + ' rss 1.0 feed items to newswire')
|
||||
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 = description.split('</media:description>')[0]
|
||||
description = remove_html(description)
|
||||
link = atom_item.split('<link>')[1]
|
||||
link = link.split('</link>')[0]
|
||||
if '://' not in link:
|
||||
|
||||
link, link_mime_type = get_link_from_rss_item(atom_item)
|
||||
if not link:
|
||||
continue
|
||||
|
||||
item_domain = link.split('://')[1]
|
||||
if '/' in item_domain:
|
||||
item_domain = item_domain.split('/')[0]
|
||||
|
||||
if is_blocked_domain(base_dir, item_domain):
|
||||
continue
|
||||
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):
|
||||
post_filename = ''
|
||||
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,
|
||||
result, pub_date_str,
|
||||
title, link,
|
||||
votes_status, post_filename,
|
||||
description, moderated,
|
||||
mirrored, [], 32, session, debug)
|
||||
mirrored, [], 32, session, debug,
|
||||
podcast_properties)
|
||||
post_ctr += 1
|
||||
if post_ctr >= max_posts_per_source:
|
||||
break
|
||||
if post_ctr > 0:
|
||||
print('Added ' + str(post_ctr) +
|
||||
' atom feed items to newswire')
|
||||
print('Added ' + str(post_ctr) + ' atom feed items to newswire')
|
||||
return result
|
||||
|
||||
|
||||
|
@ -732,7 +883,8 @@ def _json_feed_v1to_dict(base_dir: str, domain: str, xml_str: str,
|
|||
title, link,
|
||||
votes_status, post_filename,
|
||||
description, moderated,
|
||||
mirrored, [], 32, session, debug)
|
||||
mirrored, [], 32, session, debug,
|
||||
None)
|
||||
post_ctr += 1
|
||||
if post_ctr >= max_posts_per_source:
|
||||
break
|
||||
|
@ -805,7 +957,8 @@ def _atom_feed_yt_to_dict(base_dir: str, domain: str, xml_str: str,
|
|||
title, link,
|
||||
votes_status, post_filename,
|
||||
description, moderated, mirrored,
|
||||
[], 32, session, debug)
|
||||
[], 32, session, debug,
|
||||
None)
|
||||
post_ctr += 1
|
||||
if post_ctr >= max_posts_per_source:
|
||||
break
|
||||
|
@ -829,24 +982,24 @@ def _xml_str_to_dict(base_dir: str, domain: str, xml_str: str,
|
|||
max_posts_per_source,
|
||||
max_feed_item_size_kb,
|
||||
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,
|
||||
xml_str, moderated, mirrored,
|
||||
max_posts_per_source, max_feed_item_size_kb,
|
||||
max_categories_feedItem_size_kb,
|
||||
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,
|
||||
xml_str, moderated, mirrored,
|
||||
max_posts_per_source, max_feed_item_size_kb,
|
||||
max_categories_feedItem_size_kb,
|
||||
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,
|
||||
xml_str, moderated, mirrored,
|
||||
max_posts_per_source, max_feed_item_size_kb,
|
||||
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,
|
||||
xml_str, moderated, mirrored,
|
||||
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,
|
||||
description, moderated, False,
|
||||
tags_from_post,
|
||||
max_tags, session, debug)
|
||||
max_tags, session, debug,
|
||||
None)
|
||||
|
||||
ctr += 1
|
||||
if ctr >= max_blogs_per_account:
|
||||
|
|
|
@ -143,7 +143,7 @@ def _person_receive_update_outbox(recent_posts_cache: {},
|
|||
if 'attachment' not in actor_json:
|
||||
continue
|
||||
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'] != \
|
||||
'PropertyValue':
|
||||
continue
|
||||
|
|
|
@ -797,7 +797,7 @@ def person_upgrade_actor(base_dir: str, person_json: {},
|
|||
update_actor = True
|
||||
else:
|
||||
# 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]
|
||||
if oc_item.get('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:
|
||||
return False
|
||||
|
||||
for index in range(len(actor_json['hasOccupation'])):
|
||||
for index, _ in enumerate(actor_json['hasOccupation']):
|
||||
occupation_item = actor_json['hasOccupation'][index]
|
||||
if not isinstance(occupation_item, dict):
|
||||
continue
|
||||
|
|
|
@ -503,7 +503,7 @@ def _post_to_speaker_json(base_dir: str, http_prefix: str,
|
|||
follows = fp_foll.readlines()
|
||||
if len(follows) > 0:
|
||||
follow_requests_exist = True
|
||||
for i in range(len(follows)):
|
||||
for i, _ in enumerate(follows):
|
||||
follows[i] = follows[i].strip()
|
||||
follow_requests_list = follows
|
||||
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 newsdaemon import hashtag_rule_tree
|
||||
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 parse_feed_date
|
||||
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:
|
||||
print('testGetActorFromInReplyTo')
|
||||
print('test_get_actor_from_in_reply_to')
|
||||
in_reply_to = \
|
||||
'https://fosstodon.org/users/bashrc/statuses/107400700612621140'
|
||||
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
|
||||
|
||||
|
||||
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():
|
||||
base_dir = os.getcwd()
|
||||
print('Running tests...')
|
||||
|
@ -6381,6 +6494,8 @@ def run_all_tests():
|
|||
'message_json', 'liked_post_json'])
|
||||
_test_checkbox_names()
|
||||
_test_functions()
|
||||
_test_get_link_from_rss_item()
|
||||
_test_xml_podcast_dict()
|
||||
_test_get_actor_from_in_reply_to()
|
||||
_test_valid_emoji_content()
|
||||
_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',
|
||||
'suspended.css', 'calendar.css', 'blog.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:
|
||||
|
|
10
utils.py
10
utils.py
|
@ -559,7 +559,7 @@ def get_followers_list(base_dir: str,
|
|||
|
||||
with open(filename, 'r') as foll_file:
|
||||
lines = foll_file.readlines()
|
||||
for i in range(len(lines)):
|
||||
for i, _ in enumerate(lines):
|
||||
lines[i] = lines[i].strip()
|
||||
return lines
|
||||
return []
|
||||
|
@ -2126,7 +2126,7 @@ def _search_virtual_box_posts(base_dir: str, nickname: str, domain: str,
|
|||
|
||||
if '+' in search_str:
|
||||
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()
|
||||
print('SEARCH: ' + str(search_words))
|
||||
else:
|
||||
|
@ -2178,7 +2178,7 @@ def search_box_posts(base_dir: str, nickname: str, domain: str,
|
|||
|
||||
if '+' in search_str:
|
||||
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()
|
||||
print('SEARCH: ' + str(search_words))
|
||||
else:
|
||||
|
@ -2811,7 +2811,7 @@ def set_occupation_name(actor_json: {}, name: str) -> bool:
|
|||
return False
|
||||
if not isinstance(actor_json['hasOccupation'], list):
|
||||
return False
|
||||
for index in range(len(actor_json['hasOccupation'])):
|
||||
for index, _ in enumerate(actor_json['hasOccupation']):
|
||||
occupation_item = actor_json['hasOccupation'][index]
|
||||
if not isinstance(occupation_item, dict):
|
||||
continue
|
||||
|
@ -2831,7 +2831,7 @@ def set_occupation_skills_list(actor_json: {}, skills_list: []) -> bool:
|
|||
return False
|
||||
if not isinstance(actor_json['hasOccupation'], list):
|
||||
return False
|
||||
for index in range(len(actor_json['hasOccupation'])):
|
||||
for index, _ in enumerate(actor_json['hasOccupation']):
|
||||
occupation_item = actor_json['hasOccupation'][index]
|
||||
if not isinstance(occupation_item, dict):
|
||||
continue
|
||||
|
|
|
@ -263,6 +263,19 @@ def _html_newswire(base_dir: str, newswire: {}, nickname: str, moderator: bool,
|
|||
'<img loading="lazy" src="' + favicon_url + '" ' + \
|
||||
'alt="" ' + _get_broken_fav_substitute() + '/>'
|
||||
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
|
||||
if moderated_item and 'vote:' + nickname in item[2]:
|
||||
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 = limit_repeated_words(title, 6)
|
||||
html_str += '<p class="newswireItemVotedOn">' + \
|
||||
'<a href="' + url + '" target="_blank" ' + \
|
||||
'<a href="' + link_url + '" target="_blank" ' + \
|
||||
'rel="nofollow noopener noreferrer">' + \
|
||||
'<span class="newswireItemVotedOn">' + \
|
||||
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)
|
||||
if moderator and moderated_item:
|
||||
html_str += '<p class="newswireItemModerated">' + \
|
||||
'<a href="' + url + '" target="_blank" ' + \
|
||||
'<a href="' + link_url + '" target="_blank" ' + \
|
||||
'rel="nofollow noopener noreferrer">' + \
|
||||
favicon_link + title + '</a>' + total_votes_str
|
||||
html_str += ' ' + date_shown
|
||||
|
@ -318,7 +331,7 @@ def _html_newswire(base_dir: str, newswire: {}, nickname: str, moderator: bool,
|
|||
html_str += '</p>\n'
|
||||
else:
|
||||
html_str += '<p class="newswireItem">' + \
|
||||
'<a href="' + url + '" target="_blank" ' + \
|
||||
'<a href="' + link_url + '" target="_blank" ' + \
|
||||
'rel="nofollow noopener noreferrer">' + \
|
||||
favicon_link + title + '</a>' + total_votes_str
|
||||
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)
|
||||
|
||||
max_feed_length = 10
|
||||
hashtag_feed = \
|
||||
rss2tag_header(hashtag, http_prefix, domain_full)
|
||||
for index in range(len(lines)):
|
||||
hashtag_feed = rss2tag_header(hashtag, http_prefix, domain_full)
|
||||
for index, _ in enumerate(lines):
|
||||
post_id = lines[index].strip('\n').strip('\r')
|
||||
if ' ' not in post_id:
|
||||
nickname = get_nickname_from_actor(post_id)
|
||||
|
|
Loading…
Reference in New Issue