Merge branch 'bashrc2:main' into infra-provisioning

merge-requests/30/head
Naveen Kumar 2023-02-14 10:51:01 +05:30 committed by GitHub
commit b117ec7c70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 611 additions and 93 deletions

View File

@ -1460,3 +1460,155 @@ def get_cw_list_variable(list_name: str) -> str:
"""Returns the variable associated with a CW list """Returns the variable associated with a CW list
""" """
return 'list' + list_name.replace(' ', '').replace("'", '') return 'list' + list_name.replace(' ', '').replace("'", '')
def import_blocking_file(base_dir: str, nickname: str, domain: str,
lines: []) -> bool:
"""Imports blocked domains for a given account
"""
if not lines:
return False
if len(lines) < 2:
return False
if not lines[0].startswith('#domain,#') or \
'comment' not in lines[0]:
return False
fieldnames = lines[0].split(',')
comment_field_index = 0
for field_str in fieldnames:
if 'comment' in field_str:
break
comment_field_index += 1
if comment_field_index >= len(fieldnames):
return False
account_directory = acct_dir(base_dir, nickname, domain)
blocking_filename = \
account_directory + '/blocking.txt'
blocking_reasons_filename = \
account_directory + '/blocking_reasons.txt'
existing_lines = []
if os.path.isfile(blocking_filename):
try:
with open(blocking_filename, 'r', encoding='utf-8') as fp_blocks:
existing_lines = fp_blocks.read().splitlines()
except OSError:
print('EX: ' +
'unable to import existing blocked instances from file ' +
blocking_filename)
existing_reasons = []
if os.path.isfile(blocking_reasons_filename):
try:
with open(blocking_reasons_filename,
'r', encoding='utf-8') as fp_blocks:
existing_reasons = fp_blocks.read().splitlines()
except OSError:
print('EX: ' +
'unable to import existing ' +
'blocked instance reasons from file ' +
blocking_reasons_filename)
append_blocks = []
append_reasons = []
for line_str in lines:
if line_str.startswith('#'):
continue
block_fields = line_str.split(',')
blocked_domain_name = block_fields[0].strip()
if ' ' in blocked_domain_name or \
'.' not in blocked_domain_name:
continue
if blocked_domain_name in existing_lines:
# already blocked
continue
append_blocks.append(blocked_domain_name)
blocked_comment = ''
if '"' in line_str:
quote_section = line_str.split('"')
if len(quote_section) > 1:
blocked_comment = quote_section[1]
append_reasons.append(blocked_domain_name + ' ' +
blocked_comment)
if not blocked_comment:
if len(block_fields) > comment_field_index:
blocked_comment = block_fields[comment_field_index].strip()
if blocked_comment:
if blocked_comment.startswith('"'):
blocked_comment = blocked_comment.replace('"', '')
if blocked_comment not in existing_reasons:
append_reasons.append(blocked_domain_name + ' ' +
blocked_comment)
if not append_blocks:
return True
try:
with open(blocking_filename, 'a+', encoding='utf-8') as fp_blocks:
for new_block in append_blocks:
fp_blocks.write(new_block + '\n')
except OSError:
print('EX: ' +
'unable to append imported blocks to ' +
blocking_filename)
try:
with open(blocking_reasons_filename, 'a+',
encoding='utf-8') as fp_blocks:
for new_reason in append_reasons:
fp_blocks.write(new_reason + '\n')
except OSError:
print('EX: ' +
'unable to append imported block reasons to ' +
blocking_reasons_filename)
return True
def export_blocking_file(base_dir: str, nickname: str, domain: str) -> str:
"""exports account level blocks in a csv format
"""
account_directory = acct_dir(base_dir, nickname, domain)
blocking_filename = \
account_directory + '/blocking.txt'
blocking_reasons_filename = \
account_directory + '/blocking_reasons.txt'
blocks_header = \
'#domain,#severity,#reject_media,#reject_reports,' + \
'#public_comment,#obfuscate\n'
if not os.path.isfile(blocking_filename):
return blocks_header
blocking_lines = []
if os.path.isfile(blocking_filename):
try:
with open(blocking_filename, 'r', encoding='utf-8') as fp_block:
blocking_lines = fp_block.read().splitlines()
except OSError:
print('EX: export_blocks failed to read ' + blocking_filename)
blocking_reasons = []
if os.path.isfile(blocking_reasons_filename):
try:
with open(blocking_reasons_filename, 'r',
encoding='utf-8') as fp_block:
blocking_reasons = fp_block.read().splitlines()
except OSError:
print('EX: export_blocks failed to read ' +
blocking_reasons_filename)
blocks_str = blocks_header
for blocked_domain in blocking_lines:
blocked_domain = blocked_domain.strip()
if blocked_domain.startswith('#'):
continue
reason_str = ''
for reason_line in blocking_reasons:
if reason_line.startswith(blocked_domain + ' '):
reason_str = reason_line.split(' ', 1)[1]
break
blocks_str += \
blocked_domain + ',suspend,false,false,"' + \
reason_str + '",false\n'
return blocks_str

View File

@ -1705,7 +1705,8 @@ def extract_text_fields_in_post(post_bytes, boundary: str, debug: bool,
fields_with_semicolon_allowed = ( fields_with_semicolon_allowed = (
'message', 'bio', 'autoCW', 'password', 'passwordconfirm', 'message', 'bio', 'autoCW', 'password', 'passwordconfirm',
'instanceDescription', 'instanceDescriptionShort', 'instanceDescription', 'instanceDescriptionShort',
'subject', 'location', 'imageDescription' 'subject', 'location', 'imageDescription', 'importBlocks',
'importFollows', 'importTheme'
) )
if debug: if debug:
if 'password' not in message_fields: if 'password' not in message_fields:

100
daemon.py
View File

@ -147,6 +147,8 @@ from media import replace_twitter
from media import attach_media from media import attach_media
from media import path_is_video from media import path_is_video
from media import path_is_audio from media import path_is_audio
from blocking import import_blocking_file
from blocking import export_blocking_file
from blocking import add_account_blocks from blocking import add_account_blocks
from blocking import get_cw_list_variable from blocking import get_cw_list_variable
from blocking import load_cw_lists from blocking import load_cw_lists
@ -6163,6 +6165,9 @@ class PubServer(BaseHTTPRequestHandler):
if b'--LYNX' in post_bytes: if b'--LYNX' in post_bytes:
boundary = '--LYNX' boundary = '--LYNX'
if debug:
print('post_bytes: ' + str(post_bytes))
if boundary: if boundary:
# get the various avatar, banner and background images # get the various avatar, banner and background images
actor_changed = True actor_changed = True
@ -6171,8 +6176,8 @@ class PubServer(BaseHTTPRequestHandler):
'banner', 'search_banner', 'banner', 'search_banner',
'instanceLogo', 'instanceLogo',
'left_col_image', 'right_col_image', 'left_col_image', 'right_col_image',
'submitImportFollows', 'importFollows',
'submitImportTheme' 'importTheme'
) )
profile_media_types_uploaded = {} profile_media_types_uploaded = {}
for m_type in profile_media_types: for m_type in profile_media_types:
@ -6205,7 +6210,7 @@ class PubServer(BaseHTTPRequestHandler):
if m_type == 'instanceLogo': if m_type == 'instanceLogo':
filename_base = \ filename_base = \
base_dir + '/accounts/login.temp' base_dir + '/accounts/login.temp'
elif m_type == 'submitImportTheme': elif m_type == 'importTheme':
if not os.path.isdir(base_dir + '/imports'): if not os.path.isdir(base_dir + '/imports'):
os.mkdir(base_dir + '/imports') os.mkdir(base_dir + '/imports')
filename_base = \ filename_base = \
@ -6216,7 +6221,7 @@ class PubServer(BaseHTTPRequestHandler):
except OSError: except OSError:
print('EX: _profile_edit unable to delete ' + print('EX: _profile_edit unable to delete ' +
filename_base) filename_base)
elif m_type == 'submitImportFollows': elif m_type == 'importFollows':
filename_base = \ filename_base = \
acct_dir(base_dir, nickname, domain) + \ acct_dir(base_dir, nickname, domain) + \
'/import_following.csv' '/import_following.csv'
@ -6236,7 +6241,7 @@ class PubServer(BaseHTTPRequestHandler):
' media, zip, csv or font filename in POST') ' media, zip, csv or font filename in POST')
continue continue
if m_type == 'submitImportFollows': if m_type == 'importFollows':
if os.path.isfile(filename_base): if os.path.isfile(filename_base):
print(nickname + ' imported follows csv') print(nickname + ' imported follows csv')
else: else:
@ -6244,7 +6249,7 @@ class PubServer(BaseHTTPRequestHandler):
nickname) nickname)
continue continue
if m_type == 'submitImportTheme': if m_type == 'importTheme':
if nickname == admin_nickname or \ if nickname == admin_nickname or \
is_artist(base_dir, nickname): is_artist(base_dir, nickname):
if import_theme(base_dir, filename): if import_theme(base_dir, filename):
@ -6316,6 +6321,14 @@ class PubServer(BaseHTTPRequestHandler):
cookie, calling_domain) cookie, calling_domain)
self.server.postreq_busy = False self.server.postreq_busy = False
return return
elif 'name="submitExportBlocks"' in post_bytes_str:
print('submitExportBlocks')
blocks_download_path = actor_str + '/exports/blocks.csv'
print('submitExportBlocks path=' + blocks_download_path)
self._redirect_headers(blocks_download_path,
cookie, calling_domain)
self.server.postreq_busy = False
return
# extract all of the text fields into a dict # extract all of the text fields into a dict
fields = \ fields = \
@ -7807,6 +7820,50 @@ class PubServer(BaseHTTPRequestHandler):
else: else:
add_account_blocks(base_dir, add_account_blocks(base_dir,
nickname, domain, '') nickname, domain, '')
# import blocks from csv file
if fields.get('importBlocks'):
blocks_str = fields['importBlocks']
while blocks_str.startswith('\n'):
blocks_str = blocks_str[1:]
blocks_lines = blocks_str.split('\n')
if import_blocking_file(base_dir, nickname, domain,
blocks_lines):
print('blocks imported for ' + nickname)
else:
print('blocks not imported for ' + nickname)
if fields.get('importFollows'):
filename_base = \
acct_dir(base_dir, nickname, domain) + \
'/import_following.csv'
follows_str = fields['importFollows']
while follows_str.startswith('\n'):
follows_str = follows_str[1:]
try:
with open(filename_base, 'w+',
encoding='utf-8') as fp_foll:
fp_foll.write(follows_str)
except OSError:
print('EX: unable to write imported follows ' +
filename_base)
if fields.get('importTheme'):
if not os.path.isdir(base_dir + '/imports'):
os.mkdir(base_dir + '/imports')
filename_base = \
base_dir + '/imports/newtheme.zip'
if os.path.isfile(filename_base):
try:
os.remove(filename_base)
except OSError:
print('EX: _profile_edit unable to delete ' +
filename_base)
if nickname == admin_nickname or \
is_artist(base_dir, nickname):
if import_theme(base_dir, filename_base):
print(nickname + ' uploaded a theme')
else:
print('Only admin or artist can import a theme')
# Save DM allowed instances list. # Save DM allowed instances list.
# The allow list for incoming DMs, # The allow list for incoming DMs,
@ -8363,6 +8420,25 @@ class PubServer(BaseHTTPRequestHandler):
self._write(export_binary) self._write(export_binary)
self._404() self._404()
def _get_exported_blocks(self, path: str, base_dir: str,
domain: str,
calling_domain: str) -> None:
"""Returns an exported blocks csv file
"""
filename = path.split('/exports/', 1)[1]
filename = base_dir + '/exports/' + filename
nickname = get_nickname_from_actor(path)
if nickname:
blocks_str = export_blocking_file(base_dir, nickname, domain)
if blocks_str:
msg = blocks_str.encode('utf-8')
msglen = len(msg)
self._set_headers('text/csv',
msglen, None, calling_domain, False)
self._write(msg)
return
self._404()
def _get_fonts(self, calling_domain: str, path: str, def _get_fonts(self, calling_domain: str, path: str,
base_dir: str, debug: bool, base_dir: str, debug: bool,
getreq_start_time) -> None: getreq_start_time) -> None:
@ -17293,9 +17369,15 @@ class PubServer(BaseHTTPRequestHandler):
return return
if authorized and '/exports/' in self.path: if authorized and '/exports/' in self.path:
self._get_exported_theme(self.path, if 'blocks.csv' in self.path:
self.server.base_dir, self._get_exported_blocks(self.path,
self.server.domain_full) self.server.base_dir,
self.server.domain,
calling_domain)
else:
self._get_exported_theme(self.path,
self.server.base_dir,
self.server.domain_full)
return return
# get fonts # get fonts

View File

@ -182,7 +182,7 @@ def _mark_post_as_read(actor: str, post_id: str, post_category: str) -> None:
if post_id not in content: if post_id not in content:
read_file.seek(0, 0) read_file.seek(0, 0)
read_file.write(post_id + content) read_file.write(post_id + content)
except Exception as ex: except OSError as ex:
print('EX: Failed to mark post as read' + str(ex)) print('EX: Failed to mark post as read' + str(ex))
else: else:
with open(read_posts_filename, 'w+', encoding='utf-8') as read_file: with open(read_posts_filename, 'w+', encoding='utf-8') as read_file:

View File

@ -1178,6 +1178,14 @@ h3 {
font-size: var(--font-size); font-size: var(--font-size);
color: var(--title-color); color: var(--title-color);
} }
.container img.postScopeIcon {
float: none;
width: 30px;
margin: 0 0;
padding: 0 0;
border-radius: 0;
vertical-align: -6px;
}
figcaption img.emojiheader { figcaption img.emojiheader {
float: none; float: none;
width: 25px; width: 25px;
@ -2000,6 +2008,14 @@ h3 {
font-size: var(--font-size-mobile); font-size: var(--font-size-mobile);
color: var(--title-color); color: var(--title-color);
} }
.container img.postScopeIcon {
float: none;
width: 50px;
margin: 0 0;
padding: 0 0;
border-radius: 0;
vertical-align: -6px;
}
blockquote { blockquote {
font-size: var(--quote-font-size-mobile); font-size: var(--quote-font-size-mobile);
} }
@ -2812,6 +2828,14 @@ h3 {
font-size: var(--font-size-tiny); font-size: var(--font-size-tiny);
color: var(--title-color); color: var(--title-color);
} }
.container img.postScopeIcon {
float: none;
width: 50px;
margin: 0 0;
padding: 0 0;
border-radius: 0;
vertical-align: -6px;
}
blockquote { blockquote {
font-size: var(--quote-font-size-tiny); font-size: var(--quote-font-size-tiny);
} }

View File

@ -822,7 +822,9 @@ def _inbox_post_recipients_add(base_dir: str, http_prefix: str, to_list: [],
handle + ' does not exist') handle + ' does not exist')
else: else:
if debug: if debug:
if recipient.endswith('#Public'): if recipient.endswith('#Public') or \
recipient == 'as:Public' or \
recipient == 'Public':
print('DEBUG: #Public recipient is too non-specific. ' + print('DEBUG: #Public recipient is too non-specific. ' +
recipient + ' ' + domain_match) recipient + ' ' + domain_match)
else: else:
@ -2693,7 +2695,7 @@ def _receive_announce(recent_posts_cache: {},
announced_actor_nickname + '@' + announced_actor_domain) announced_actor_nickname + '@' + announced_actor_domain)
return False return False
# is this post in the outbox of the person? # is this post in the inbox or outbox of the account?
post_filename = locate_post(base_dir, nickname, domain, post_filename = locate_post(base_dir, nickname, domain,
message_json['object']) message_json['object'])
if not post_filename: if not post_filename:
@ -2701,6 +2703,7 @@ def _receive_announce(recent_posts_cache: {},
print('DEBUG: announce post not found in inbox or outbox') print('DEBUG: announce post not found in inbox or outbox')
print(message_json['object']) print(message_json['object'])
return True return True
# add actor to the list of announcers for a post
update_announce_collection(recent_posts_cache, base_dir, post_filename, update_announce_collection(recent_posts_cache, base_dir, post_filename,
message_json['actor'], nickname, domain, debug) message_json['actor'], nickname, domain, debug)
if debug: if debug:
@ -3179,7 +3182,9 @@ def _valid_post_content(base_dir: str, nickname: str, domain: str,
'allow comments: ' + original_post_id) 'allow comments: ' + original_post_id)
return False return False
if invalid_ciphertext(message_json['object']['content']): if invalid_ciphertext(message_json['object']['content']):
print('REJECT: malformed ciphertext in content') print('REJECT: malformed ciphertext in content ' +
message_json['object']['id'] + ' ' +
message_json['object']['content'])
return False return False
if debug: if debug:
print('ACCEPT: post content is valid') print('ACCEPT: post content is valid')

Binary file not shown.

View File

@ -48,7 +48,8 @@ beginning. The ActivityPub protocol was initially called
creation. Fediverse servers are typically referred to as “instances”, creation. Fediverse servers are typically referred to as “instances”,
but they are really just websites which can speak with each other using but they are really just websites which can speak with each other using
the vocabulary of ActivityPub. Choosing an instance is the same as the vocabulary of ActivityPub. Choosing an instance is the same as
choosing a website that you trust to handle your data.</p> choosing a website that you trust to handle your data. This is <em>the
social web</em>.</p>
<p>Servers such as <a <p>Servers such as <a
href="https://github.com/mastodon/mastodon">Mastodon</a> are well known, href="https://github.com/mastodon/mastodon">Mastodon</a> are well known,
but these are aimed at large scale deployments on powerful hardware but these are aimed at large scale deployments on powerful hardware
@ -622,6 +623,9 @@ shortcuts</em> link at the bottom of the left column.</p>
<figcaption aria-hidden="true">Keyboard shortcuts screen</figcaption> <figcaption aria-hidden="true">Keyboard shortcuts screen</figcaption>
</figure> </figure>
<h1 id="calendar">Calendar</h1> <h1 id="calendar">Calendar</h1>
<p><em>“There is nothing in this world that does not have a decisive
moment”</em></p>
<p> Henri Cartier-Bresson</p>
<p>The calendar is not yet a standardized feature of the fediverse as a <p>The calendar is not yet a standardized feature of the fediverse as a
whole, but has existed in Friendica and Zot instances for a long time. whole, but has existed in Friendica and Zot instances for a long time.
Being able to attach a date and time to a post and then have it appear Being able to attach a date and time to a post and then have it appear

View File

@ -465,6 +465,10 @@ At the bottom of the timeline there will usually be an arrow icon to go to the n
![Keyboard shortcuts screen](manual-shortcuts.png) ![Keyboard shortcuts screen](manual-shortcuts.png)
# Calendar # Calendar
*"There is nothing in this world that does not have a decisive moment"*
-- Henri Cartier-Bresson
The calendar is not yet a standardized feature of the fediverse as a whole, but has existed in Friendica and Zot instances for a long time. Being able to attach a date and time to a post and then have it appear on your calendar and perhaps also the calendars of your followers is quite useful for organizing things with minimum effort. Until such time as federated calendar functionality becomes more standardized this may only work between Epicyon instances. The calendar is not yet a standardized feature of the fediverse as a whole, but has existed in Friendica and Zot instances for a long time. Being able to attach a date and time to a post and then have it appear on your calendar and perhaps also the calendars of your followers is quite useful for organizing things with minimum effort. Until such time as federated calendar functionality becomes more standardized this may only work between Epicyon instances.
Calendar events are really just ordinary posts with a date, time and perhaps also a location attached to them. Posts with *Public* scope which have a date and time will appear on the calendars of your followers, unless they have opted out of receiving calendar events from you. Calendar events are really just ordinary posts with a date, time and perhaps also a location attached to them. Posts with *Public* scope which have a date and time will appear on the calendars of your followers, unless they have opted out of receiving calendar events from you.

View File

@ -293,7 +293,7 @@ def manual_approve_follow_request(session, session_onion, session_i2p,
followers_file.seek(0, 0) followers_file.seek(0, 0)
followers_file.write(approve_handle_full + '\n' + followers_file.write(approve_handle_full + '\n' +
content) content)
except Exception as ex: except OSError as ex:
print('WARN: Manual follow accept. ' + print('WARN: Manual follow accept. ' +
'Failed to write entry to followers file ' + str(ex)) 'Failed to write entry to followers file ' + str(ex))
else: else:

View File

@ -456,7 +456,9 @@ def _is_public_feed_post(item: {}, person_posts: {}, debug: bool) -> bool:
if this_item.get('to'): if this_item.get('to'):
is_public = False is_public = False
for recipient in this_item['to']: for recipient in this_item['to']:
if recipient.endswith('#Public'): if recipient.endswith('#Public') or \
recipient == 'as:Public' or \
recipient == 'Public':
is_public = True is_public = True
break break
if not is_public: if not is_public:
@ -465,7 +467,9 @@ def _is_public_feed_post(item: {}, person_posts: {}, debug: bool) -> bool:
if item.get('to'): if item.get('to'):
is_public = False is_public = False
for recipient in item['to']: for recipient in item['to']:
if recipient.endswith('#Public'): if recipient.endswith('#Public') or \
recipient == 'as:Public' or \
recipient == 'Public':
is_public = True is_public = True
break break
if not is_public: if not is_public:
@ -1402,7 +1406,12 @@ def _create_post_mentions(cc_url: str, new_post: {},
to_cc = new_post['object']['cc'] to_cc = new_post['object']['cc']
if len(to_recipients) != 1: if len(to_recipients) != 1:
return return
if to_recipients[0].endswith('#Public') and \ to_public_recipient = False
if to_recipients[0].endswith('#Public') or \
to_recipients[0] == 'as:Public' or \
to_recipients[0] == 'Public':
to_public_recipient = True
if to_public_recipient and \
cc_url.endswith('/followers'): cc_url.endswith('/followers'):
for tag in tags: for tag in tags:
if tag['type'] != 'Mention': if tag['type'] != 'Mention':
@ -1582,7 +1591,9 @@ def _create_post_base(base_dir: str,
is_public = False is_public = False
for recipient in to_recipients: for recipient in to_recipients:
if recipient.endswith('#Public'): if recipient.endswith('#Public') or \
recipient == 'as:Public' or \
recipient == 'Public':
is_public = True is_public = True
break break
@ -2834,7 +2845,9 @@ def _add_followers_to_public_post(post_json_object: {}) -> None:
if len(post_json_object['to']) == 0: if len(post_json_object['to']) == 0:
return return
if not post_json_object['to'][0].endswith('#Public'): if not post_json_object['to'][0].endswith('#Public'):
return if not post_json_object['to'][0] == 'as:Public':
if not post_json_object['to'][0] == 'Public':
return
if post_json_object.get('cc'): if post_json_object.get('cc'):
return return
post_json_object['cc'] = post_json_object['actor'] + '/followers' post_json_object['cc'] = post_json_object['actor'] + '/followers'
@ -2846,7 +2859,9 @@ def _add_followers_to_public_post(post_json_object: {}) -> None:
if len(post_json_object['object']['to']) == 0: if len(post_json_object['object']['to']) == 0:
return return
if not post_json_object['object']['to'][0].endswith('#Public'): if not post_json_object['object']['to'][0].endswith('#Public'):
return if not post_json_object['object']['to'][0] == 'as:Public':
if not post_json_object['object']['to'][0] == 'Public':
return
if post_json_object['object'].get('cc'): if post_json_object['object'].get('cc'):
return return
post_json_object['object']['cc'] = \ post_json_object['object']['cc'] = \
@ -3222,7 +3237,9 @@ def _send_to_named_addresses(server, session, session_onion, session_i2p,
continue continue
if '/' not in address: if '/' not in address:
continue continue
if address.endswith('#Public'): if address.endswith('#Public') or \
address == 'as:Public' or \
address == 'Public':
continue continue
if address.endswith('/followers'): if address.endswith('/followers'):
continue continue
@ -3231,7 +3248,9 @@ def _send_to_named_addresses(server, session, session_onion, session_i2p,
address = recipients_object[rtype] address = recipients_object[rtype]
if address: if address:
if '/' in address: if '/' in address:
if address.endswith('#Public'): if address.endswith('#Public') or \
address == 'as:Public' or \
address == 'Public':
continue continue
if address.endswith('/followers'): if address.endswith('/followers'):
continue continue
@ -3900,7 +3919,8 @@ def _add_post_string_to_timeline(post_str: str, boxname: str,
('"Create"' in post_str or '"Update"' in post_str))): ('"Create"' in post_str or '"Update"' in post_str))):
if boxname == 'dm': if boxname == 'dm':
if '#Public' in post_str or '/followers' in post_str: if '#Public' in post_str or \
'/followers' in post_str:
return False return False
elif boxname == 'tlreplies': elif boxname == 'tlreplies':
if box_actor not in post_str: if box_actor not in post_str:

View File

@ -5995,7 +5995,17 @@ def _test_extract_text_fields_from_post():
'116202748023898664511855843036\r\nContent-Disposition: ' + \ '116202748023898664511855843036\r\nContent-Disposition: ' + \
'form-data; name="attachpic"; filename=""\r\nContent-Type: ' + \ 'form-data; name="attachpic"; filename=""\r\nContent-Type: ' + \
'application/octet-stream\r\n\r\n\r\n----------------------' + \ 'application/octet-stream\r\n\r\n\r\n----------------------' + \
'-------116202748023898664511855843036--\r\n' '-------116202748023898664511855843036--\r\n' + \
'Content-Disposition: form-data; name="importBlocks"; ' + \
'filename="wildebeest_suspend.csv"\r\nContent-Type: ' + \
'text/csv\r\n\r\n#domain,#severity,#reject_media,#reject_reports,' + \
'#public_comment,#obfuscate\nbgp.social,suspend,false,false,' + \
'"Wildebeest",false\ncesko.social,suspend,false,false,' + \
'"Wildebeest",false\ncloudflare.social,suspend,false,false,' + \
'"Wildebeest",false\ndogfood.social,suspend,false,false,' + \
'"Wildebeest",false\ndomo.cafe,suspend,false,false,"Wildebeest",' + \
'false\nemaw.social,suspend,false,false\n\r\n ' + \
'-----------------------------116202748023898664511855843036--\r\n'
debug = False debug = False
fields = extract_text_fields_in_post(None, boundary, debug, form_data) fields = extract_text_fields_in_post(None, boundary, debug, form_data)
assert fields['submitPost'] == 'Submit' assert fields['submitPost'] == 'Submit'
@ -6006,6 +6016,9 @@ def _test_extract_text_fields_from_post():
assert fields['location'] == '' assert fields['location'] == ''
assert fields['imageDescription'] == '' assert fields['imageDescription'] == ''
assert fields['message'] == 'This is a ; test' assert fields['message'] == 'This is a ; test'
if not fields['importBlocks'][1:].startswith('#domain,#severity,'):
print(fields['importBlocks'])
assert fields['importBlocks'][1:].startswith('#domain,#severity,')
def _test_speaker_replace_link(): def _test_speaker_replace_link():

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -194,6 +194,9 @@
"Ask a question": "طرح سؤال", "Ask a question": "طرح سؤال",
"Possible answers": "إجابات ممكنة", "Possible answers": "إجابات ممكنة",
"replying to": "الرد على", "replying to": "الرد على",
"publicly replying to": "الرد علنًا على",
"replying to followers": "الرد على المتابعين",
"replying unlisted": "الرد غير مدرج",
"replying to themselves": "الرد على أنفسهم", "replying to themselves": "الرد على أنفسهم",
"announces": "يعلن", "announces": "يعلن",
"Previous month": "الشهر الماضى", "Previous month": "الشهر الماضى",
@ -614,5 +617,7 @@
"Buy link": "رابط شراء", "Buy link": "رابط شراء",
"Buy links are allowed from the following domains": "روابط الشراء مسموح بها من المجالات التالية", "Buy links are allowed from the following domains": "روابط الشراء مسموح بها من المجالات التالية",
"Media license": "رخصة وسائل الإعلام", "Media license": "رخصة وسائل الإعلام",
"Media creator": "صانع الوسائط" "Media creator": "صانع الوسائط",
"Import Blocks": "استيراد مثيلات محظورة",
"Export Blocks": "تصدير المثيلات المحظورة"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "প্রশ্ন জিজ্ঞাসা কর", "Ask a question": "প্রশ্ন জিজ্ঞাসা কর",
"Possible answers": "সম্ভাব্য উত্তর", "Possible answers": "সম্ভাব্য উত্তর",
"replying to": "এর জবাব", "replying to": "এর জবাব",
"publicly replying to": "প্রকাশ্যে উত্তর দিচ্ছেন",
"replying to followers": "অনুগামীদের উত্তর",
"replying unlisted": "অতালিকাভুক্ত উত্তর",
"replying to themselves": "নিজেদের জবাব দিচ্ছে", "replying to themselves": "নিজেদের জবাব দিচ্ছে",
"announces": "ঘোষণা করে", "announces": "ঘোষণা করে",
"Previous month": "পূর্ববর্তী মাস", "Previous month": "পূর্ববর্তী মাস",
@ -614,5 +617,7 @@
"Buy link": "সংযোগ কেনা", "Buy link": "সংযোগ কেনা",
"Buy links are allowed from the following domains": "নিম্নলিখিত ডোমেনগুলি থেকে লিঙ্কগুলি কেনার অনুমতি দেওয়া হয়েছে", "Buy links are allowed from the following domains": "নিম্নলিখিত ডোমেনগুলি থেকে লিঙ্কগুলি কেনার অনুমতি দেওয়া হয়েছে",
"Media license": "মিডিয়া লাইসেন্স", "Media license": "মিডিয়া লাইসেন্স",
"Media creator": "মিডিয়া নির্মাতা" "Media creator": "মিডিয়া নির্মাতা",
"Import Blocks": "অবরুদ্ধ দৃষ্টান্ত আমদানি করুন",
"Export Blocks": "অবরুদ্ধ দৃষ্টান্ত রপ্তানি করুন"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Fer una pregunta", "Ask a question": "Fer una pregunta",
"Possible answers": "Respostes possibles", "Possible answers": "Respostes possibles",
"replying to": "responent", "replying to": "responent",
"publicly replying to": "responent públicament",
"replying to followers": "responent als seguidors",
"replying unlisted": "responent sense llistar",
"replying to themselves": "responent-se", "replying to themselves": "responent-se",
"announces": "anuncia", "announces": "anuncia",
"Previous month": "Mes anterior", "Previous month": "Mes anterior",
@ -614,5 +617,7 @@
"Buy link": "Enllaç de compra", "Buy link": "Enllaç de compra",
"Buy links are allowed from the following domains": "Els enllaços de compra es permeten des dels dominis següents", "Buy links are allowed from the following domains": "Els enllaços de compra es permeten des dels dominis següents",
"Media license": "Llicència de mitjans", "Media license": "Llicència de mitjans",
"Media creator": "Creador de mitjans" "Media creator": "Creador de mitjans",
"Import Blocks": "Importa instàncies bloquejades",
"Export Blocks": "Exporta instàncies bloquejades"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Gofyn cwestiwn", "Ask a question": "Gofyn cwestiwn",
"Possible answers": "Atebion posib", "Possible answers": "Atebion posib",
"replying to": "ateb i", "replying to": "ateb i",
"publicly replying to": "yn ateb yn gyhoeddus",
"replying to followers": "ateb i ddilynwyr",
"replying unlisted": "ateb heb ei restru",
"replying to themselves": "ateb iddynt eu hunain", "replying to themselves": "ateb iddynt eu hunain",
"announces": "yn cyhoeddi", "announces": "yn cyhoeddi",
"Previous month": "Y mis blaenorol", "Previous month": "Y mis blaenorol",
@ -614,5 +617,7 @@
"Buy link": "Prynu dolen", "Buy link": "Prynu dolen",
"Buy links are allowed from the following domains": "Caniateir dolenni prynu o'r parthau canlynol", "Buy links are allowed from the following domains": "Caniateir dolenni prynu o'r parthau canlynol",
"Media license": "Trwydded cyfryngau", "Media license": "Trwydded cyfryngau",
"Media creator": "Crëwr cyfryngau" "Media creator": "Crëwr cyfryngau",
"Import Blocks": "Mewnforio Achosion wedi'u Rhwystro",
"Export Blocks": "Allforio Achosion wedi'u Rhwystro"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Stelle eine Frage", "Ask a question": "Stelle eine Frage",
"Possible answers": "Mögliche Antworten", "Possible answers": "Mögliche Antworten",
"replying to": "antworten auf", "replying to": "antworten auf",
"publicly replying to": "öffentlich antworten",
"replying to followers": "followern antworten",
"replying unlisted": "antworten nicht aufgeführt",
"replying to themselves": "sich selbst antworten", "replying to themselves": "sich selbst antworten",
"announces": "kündigt an", "announces": "kündigt an",
"Previous month": "Vorheriger Monat", "Previous month": "Vorheriger Monat",
@ -614,5 +617,7 @@
"Buy link": "Link kaufen", "Buy link": "Link kaufen",
"Buy links are allowed from the following domains": "Kauflinks sind von den folgenden Domains erlaubt", "Buy links are allowed from the following domains": "Kauflinks sind von den folgenden Domains erlaubt",
"Media license": "Medienlizenz", "Media license": "Medienlizenz",
"Media creator": "Mediengestalter" "Media creator": "Mediengestalter",
"Import Blocks": "Blockierte Instanzen importieren",
"Export Blocks": "Blockierte Instanzen exportieren"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Κάνε μια ερώτηση", "Ask a question": "Κάνε μια ερώτηση",
"Possible answers": "Πιθανές απαντήσεις", "Possible answers": "Πιθανές απαντήσεις",
"replying to": "απαντώντας σε", "replying to": "απαντώντας σε",
"publicly replying to": "απαντώντας δημόσια σε",
"replying to followers": "απαντώντας στους ακόλουθους",
"replying unlisted": "απαντώντας εκτός λίστας",
"replying to themselves": "απαντώντας στον εαυτό τους", "replying to themselves": "απαντώντας στον εαυτό τους",
"announces": "ανακοινώνει", "announces": "ανακοινώνει",
"Previous month": "Προηγούμενος μήνας", "Previous month": "Προηγούμενος μήνας",
@ -614,5 +617,7 @@
"Buy link": "Σύνδεσμος αγοράς", "Buy link": "Σύνδεσμος αγοράς",
"Buy links are allowed from the following domains": "Οι σύνδεσμοι αγοράς επιτρέπονται από τους παρακάτω τομείς", "Buy links are allowed from the following domains": "Οι σύνδεσμοι αγοράς επιτρέπονται από τους παρακάτω τομείς",
"Media license": "Άδεια ΜΜΕ", "Media license": "Άδεια ΜΜΕ",
"Media creator": "Δημιουργός πολυμέσων" "Media creator": "Δημιουργός πολυμέσων",
"Import Blocks": "Εισαγωγή αποκλεισμένων παρουσιών",
"Export Blocks": "Εξαγωγή αποκλεισμένων παρουσιών"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Ask a question", "Ask a question": "Ask a question",
"Possible answers": "Possible answers", "Possible answers": "Possible answers",
"replying to": "replying to", "replying to": "replying to",
"publicly replying to": "publicly replying to",
"replying to followers": "replying to followers",
"replying unlisted": "replying to unlisted",
"replying to themselves": "replying to themselves", "replying to themselves": "replying to themselves",
"announces": "announces", "announces": "announces",
"Previous month": "Previous month", "Previous month": "Previous month",
@ -614,5 +617,7 @@
"Buy link": "Buy link", "Buy link": "Buy link",
"Buy links are allowed from the following domains": "Buy links are allowed from the following domains", "Buy links are allowed from the following domains": "Buy links are allowed from the following domains",
"Media license": "Media license", "Media license": "Media license",
"Media creator": "Media creator" "Media creator": "Media creator",
"Import Blocks": "Import Blocks",
"Export Blocks": "Export Blocks"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Haz una pregunta", "Ask a question": "Haz una pregunta",
"Possible answers": "Respuestas posibles", "Possible answers": "Respuestas posibles",
"replying to": "respondiendo a", "replying to": "respondiendo a",
"publicly replying to": "respondiendo públicamente a",
"replying to followers": "en respuesta a las seguidoras",
"replying unlisted": "respondiendo no listada",
"replying to themselves": "respondiéndose a sí mismo", "replying to themselves": "respondiéndose a sí mismo",
"announces": "anuncia", "announces": "anuncia",
"Previous month": "Mes anterior", "Previous month": "Mes anterior",
@ -614,5 +617,7 @@
"Buy link": "Enlace de compra", "Buy link": "Enlace de compra",
"Buy links are allowed from the following domains": "Se permiten enlaces de compra de los siguientes dominios", "Buy links are allowed from the following domains": "Se permiten enlaces de compra de los siguientes dominios",
"Media license": "Licencia de medios", "Media license": "Licencia de medios",
"Media creator": "Creadora de medios" "Media creator": "Creadora de medios",
"Import Blocks": "Importar instancias bloqueadas",
"Export Blocks": "Exportar instancias bloqueadas"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "یه سوال بپرس", "Ask a question": "یه سوال بپرس",
"Possible answers": "پاسخ های ممکن", "Possible answers": "پاسخ های ممکن",
"replying to": "در حال پاسخ دادن به", "replying to": "در حال پاسخ دادن به",
"publicly replying to": "علنی پاسخ دادن به",
"replying to followers": "پاسخ به دنبال کنندگان",
"replying unlisted": "در حال پاسخ به فهرست نشده",
"replying to themselves": "به خودشان پاسخ می دهند", "replying to themselves": "به خودشان پاسخ می دهند",
"announces": "اعلام می کند", "announces": "اعلام می کند",
"Previous month": "ماه گذشته", "Previous month": "ماه گذشته",
@ -614,5 +617,7 @@
"Buy link": "لینک خرید", "Buy link": "لینک خرید",
"Buy links are allowed from the following domains": "لینک خرید از دامنه های زیر مجاز است", "Buy links are allowed from the following domains": "لینک خرید از دامنه های زیر مجاز است",
"Media license": "مجوز رسانه", "Media license": "مجوز رسانه",
"Media creator": "سازنده رسانه" "Media creator": "سازنده رسانه",
"Import Blocks": "وارد کردن موارد مسدود شده",
"Export Blocks": "نمونه های مسدود شده را صادر کنید"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Poser une question", "Ask a question": "Poser une question",
"Possible answers": "Des réponses possibles", "Possible answers": "Des réponses possibles",
"replying to": "répondre à", "replying to": "répondre à",
"publicly replying to": "répondre publiquement à",
"replying to followers": "répondre aux abonnés",
"replying unlisted": "réponse non répertoriée",
"replying to themselves": "se répondre à eux-mêmes", "replying to themselves": "se répondre à eux-mêmes",
"announces": "annonce", "announces": "annonce",
"Previous month": "Le mois précédent", "Previous month": "Le mois précédent",
@ -614,5 +617,7 @@
"Buy link": "Acheter un lien", "Buy link": "Acheter un lien",
"Buy links are allowed from the following domains": "Les liens d'achat sont autorisés à partir des domaines suivants", "Buy links are allowed from the following domains": "Les liens d'achat sont autorisés à partir des domaines suivants",
"Media license": "Licence média", "Media license": "Licence média",
"Media creator": "Créateur de médias" "Media creator": "Créateur de médias",
"Import Blocks": "Importer des instances bloquées",
"Export Blocks": "Exporter les instances bloquées"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Ceist a chur", "Ask a question": "Ceist a chur",
"Possible answers": "Freagraí féideartha", "Possible answers": "Freagraí féideartha",
"replying to": "ag freagairt", "replying to": "ag freagairt",
"publicly replying to": "ag freagairt go poiblí ar",
"replying to followers": "ag freagairt do leantóirí",
"replying unlisted": "freagra neamhliostaithe",
"replying to themselves": "ag freagairt dóibh féin", "replying to themselves": "ag freagairt dóibh féin",
"announces": "fógraíonn", "announces": "fógraíonn",
"Previous month": "An mhí roimhe seo", "Previous month": "An mhí roimhe seo",
@ -614,5 +617,7 @@
"Buy link": "Ceannaigh nasc", "Buy link": "Ceannaigh nasc",
"Buy links are allowed from the following domains": "Ceadaítear naisc cheannaigh ó na fearainn seo a leanas", "Buy links are allowed from the following domains": "Ceadaítear naisc cheannaigh ó na fearainn seo a leanas",
"Media license": "Ceadúnas meáin", "Media license": "Ceadúnas meáin",
"Media creator": "Cruthaitheoir meáin" "Media creator": "Cruthaitheoir meáin",
"Import Blocks": "Iompórtáil Cásanna Blocáilte",
"Export Blocks": "Easpórtáil Cásanna Blocáilte"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "प्रश्न पूछें", "Ask a question": "प्रश्न पूछें",
"Possible answers": "संभावित उत्तर", "Possible answers": "संभावित उत्तर",
"replying to": "करने के लिए जवाब दे", "replying to": "करने के लिए जवाब दे",
"publicly replying to": "सार्वजनिक रूप से जवाब दे रहा है",
"replying to followers": "अनुयायियों को जवाब देना",
"replying unlisted": "असूचीबद्ध उत्तर दे रहा है",
"replying to themselves": "खुद को जवाब दे", "replying to themselves": "खुद को जवाब दे",
"announces": "की घोषणा", "announces": "की घोषणा",
"Previous month": "पिछ्ला महिना", "Previous month": "पिछ्ला महिना",
@ -614,5 +617,7 @@
"Buy link": "लिंक खरीदें", "Buy link": "लिंक खरीदें",
"Buy links are allowed from the following domains": "निम्नलिखित डोमेन से खरीदें लिंक की अनुमति है", "Buy links are allowed from the following domains": "निम्नलिखित डोमेन से खरीदें लिंक की अनुमति है",
"Media license": "मीडिया लाइसेंस", "Media license": "मीडिया लाइसेंस",
"Media creator": "मीडिया निर्माता" "Media creator": "मीडिया निर्माता",
"Import Blocks": "अवरोधित उदाहरण आयात करें",
"Export Blocks": "निर्यात अवरुद्ध उदाहरण"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Fai una domanda", "Ask a question": "Fai una domanda",
"Possible answers": "Possibili risposte", "Possible answers": "Possibili risposte",
"replying to": "rispondendo a", "replying to": "rispondendo a",
"publicly replying to": "rispondendo pubblicamente a",
"replying to followers": "rispondere ai follower",
"replying unlisted": "rispondendo non in elenco",
"replying to themselves": "rispondendo a se stessi", "replying to themselves": "rispondendo a se stessi",
"announces": "annuncia", "announces": "annuncia",
"Previous month": "Il mese scorso", "Previous month": "Il mese scorso",
@ -614,5 +617,7 @@
"Buy link": "Link per l'acquisto", "Buy link": "Link per l'acquisto",
"Buy links are allowed from the following domains": "I link di acquisto sono consentiti dai seguenti domini", "Buy links are allowed from the following domains": "I link di acquisto sono consentiti dai seguenti domini",
"Media license": "Licenza multimediale", "Media license": "Licenza multimediale",
"Media creator": "Creatore multimediale" "Media creator": "Creatore multimediale",
"Import Blocks": "Importa istanze bloccate",
"Export Blocks": "Esporta istanze bloccate"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "質問する", "Ask a question": "質問する",
"Possible answers": "可能な答え", "Possible answers": "可能な答え",
"replying to": "に返信する", "replying to": "に返信する",
"publicly replying to": "公に返信する",
"replying to followers": "フォロワーへの返信",
"replying unlisted": "リストにない返信",
"replying to themselves": "自分に返信する", "replying to themselves": "自分に返信する",
"announces": "発表する", "announces": "発表する",
"Previous month": "前月", "Previous month": "前月",
@ -614,5 +617,7 @@
"Buy link": "購入リンク", "Buy link": "購入リンク",
"Buy links are allowed from the following domains": "次のドメインからの購入リンクが許可されています", "Buy links are allowed from the following domains": "次のドメインからの購入リンクが許可されています",
"Media license": "メディアライセンス", "Media license": "メディアライセンス",
"Media creator": "メディアクリエーター" "Media creator": "メディアクリエーター",
"Import Blocks": "ブロックされたインスタンスのインポート",
"Export Blocks": "ブロックされたインスタンスのエクスポート"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "질문하기", "Ask a question": "질문하기",
"Possible answers": "가능한 답변", "Possible answers": "가능한 답변",
"replying to": "답장", "replying to": "답장",
"publicly replying to": "공개적으로 답장",
"replying to followers": "팔로워에게 회신",
"replying unlisted": "목록에 없는 회신",
"replying to themselves": "스스로에게 답장", "replying to themselves": "스스로에게 답장",
"announces": "발표하다", "announces": "발표하다",
"Previous month": "지난달", "Previous month": "지난달",
@ -614,5 +617,7 @@
"Buy link": "구매 링크", "Buy link": "구매 링크",
"Buy links are allowed from the following domains": "다음 도메인에서 구매 링크가 허용됩니다.", "Buy links are allowed from the following domains": "다음 도메인에서 구매 링크가 허용됩니다.",
"Media license": "미디어 라이센스", "Media license": "미디어 라이센스",
"Media creator": "미디어 크리에이터" "Media creator": "미디어 크리에이터",
"Import Blocks": "차단된 인스턴스 가져오기",
"Export Blocks": "차단된 인스턴스 내보내기"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Pirsek bipirsin", "Ask a question": "Pirsek bipirsin",
"Possible answers": "Bersivên gengaz", "Possible answers": "Bersivên gengaz",
"replying to": "bersivandin", "replying to": "bersivandin",
"publicly replying to": "bi raya giştî re bersiv dide",
"replying to followers": "bersiva şagirtan dide",
"replying unlisted": "bersivê nelîste",
"replying to themselves": "bersiva xwe didin", "replying to themselves": "bersiva xwe didin",
"announces": "îlan dike", "announces": "îlan dike",
"Previous month": "Meha berê", "Previous month": "Meha berê",
@ -614,5 +617,7 @@
"Buy link": "Girêdanê bikirin", "Buy link": "Girêdanê bikirin",
"Buy links are allowed from the following domains": "Zencîreyên kirînê ji domên jêrîn têne destûr kirin", "Buy links are allowed from the following domains": "Zencîreyên kirînê ji domên jêrîn têne destûr kirin",
"Media license": "Lîsansa medyayê", "Media license": "Lîsansa medyayê",
"Media creator": "Afirînerê medyayê" "Media creator": "Afirînerê medyayê",
"Import Blocks": "Mînakên Astengkirî Import",
"Export Blocks": "Mînakên Astengkirî Export"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Een vraag stellen", "Ask a question": "Een vraag stellen",
"Possible answers": "Mogelijke antwoorden", "Possible answers": "Mogelijke antwoorden",
"replying to": "reageren op", "replying to": "reageren op",
"publicly replying to": "openbaar beantwoorden",
"replying to followers": "reageren op volgers",
"replying unlisted": "niet vermeld reageren",
"replying to themselves": "zichzelf beantwoorden", "replying to themselves": "zichzelf beantwoorden",
"announces": "kondigt aan", "announces": "kondigt aan",
"Previous month": "Vorige maand", "Previous month": "Vorige maand",
@ -614,5 +617,7 @@
"Buy link": "koop link", "Buy link": "koop link",
"Buy links are allowed from the following domains": "Kooplinks zijn toegestaan vanaf de volgende domeinen", "Buy links are allowed from the following domains": "Kooplinks zijn toegestaan vanaf de volgende domeinen",
"Media license": "Media licentie", "Media license": "Media licentie",
"Media creator": "Media-maker" "Media creator": "Media-maker",
"Import Blocks": "Importeer geblokkeerde instanties",
"Export Blocks": "Exporteer geblokkeerde instanties"
} }

View File

@ -190,6 +190,9 @@
"Ask a question": "Ask a question", "Ask a question": "Ask a question",
"Possible answers": "Possible answers", "Possible answers": "Possible answers",
"replying to": "replying to", "replying to": "replying to",
"publicly replying to": "publicly replying to",
"replying to followers": "replying to followers",
"replying unlisted": "replying unlisted",
"replying to themselves": "replying to themselves", "replying to themselves": "replying to themselves",
"announces": "announces", "announces": "announces",
"Previous month": "Previous month", "Previous month": "Previous month",
@ -610,5 +613,7 @@
"Buy link": "Buy link", "Buy link": "Buy link",
"Buy links are allowed from the following domains": "Buy links are allowed from the following domains", "Buy links are allowed from the following domains": "Buy links are allowed from the following domains",
"Media license": "Media license", "Media license": "Media license",
"Media creator": "Media creator" "Media creator": "Media creator",
"Import Blocks": "Import Blocks",
"Export Blocks": "Export Blocks"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Zadać pytanie", "Ask a question": "Zadać pytanie",
"Possible answers": "Możliwe odpowiedzi", "Possible answers": "Możliwe odpowiedzi",
"replying to": "odpowiadając na", "replying to": "odpowiadając na",
"publicly replying to": "publicznie odpowiadac na",
"replying to followers": "odpowiadanie obserwującym",
"replying unlisted": "odpowiadanie niepubliczne",
"replying to themselves": "odpowiadając sobie", "replying to themselves": "odpowiadając sobie",
"announces": "ogłasza", "announces": "ogłasza",
"Previous month": "Poprzedni miesiac", "Previous month": "Poprzedni miesiac",
@ -614,5 +617,7 @@
"Buy link": "Kup Link", "Buy link": "Kup Link",
"Buy links are allowed from the following domains": "Kupuj linki są dozwolone z następujących domen", "Buy links are allowed from the following domains": "Kupuj linki są dozwolone z następujących domen",
"Media license": "Licencja medialna", "Media license": "Licencja medialna",
"Media creator": "Kreator mediów" "Media creator": "Kreator mediów",
"Import Blocks": "Importuj zablokowane instancje",
"Export Blocks": "Eksportuj zablokowane instancje"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Faça uma pergunta", "Ask a question": "Faça uma pergunta",
"Possible answers": "Possíveis respostas", "Possible answers": "Possíveis respostas",
"replying to": "respondendo a", "replying to": "respondendo a",
"publicly replying to": "respondendo publicamente a",
"replying to followers": "respondendo aos seguidores",
"replying unlisted": "respondendo não listado",
"replying to themselves": "respondendo a si mesmos", "replying to themselves": "respondendo a si mesmos",
"announces": "anuncia", "announces": "anuncia",
"Previous month": "Mês anterior", "Previous month": "Mês anterior",
@ -614,5 +617,7 @@
"Buy link": "Link de compra", "Buy link": "Link de compra",
"Buy links are allowed from the following domains": "Links de compra são permitidos nos seguintes domínios", "Buy links are allowed from the following domains": "Links de compra são permitidos nos seguintes domínios",
"Media license": "Licença de mídia", "Media license": "Licença de mídia",
"Media creator": "Criador de mídia" "Media creator": "Criador de mídia",
"Import Blocks": "Importar instâncias bloqueadas",
"Export Blocks": "Exportar instâncias bloqueadas"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Задайте вопрос", "Ask a question": "Задайте вопрос",
"Possible answers": "Возможные ответы", "Possible answers": "Возможные ответы",
"replying to": "отвечая на", "replying to": "отвечая на",
"publicly replying to": "публично отвечая на",
"replying to followers": "отвечаю подписчикам",
"replying unlisted": "ответ не в списке",
"replying to themselves": "отвечая на себя", "replying to themselves": "отвечая на себя",
"announces": "анонсов", "announces": "анонсов",
"Previous month": "Предыдущий месяц", "Previous month": "Предыдущий месяц",
@ -614,5 +617,7 @@
"Buy link": "Купить ссылку", "Buy link": "Купить ссылку",
"Buy links are allowed from the following domains": "Ссылки на покупку разрешены со следующих доменов", "Buy links are allowed from the following domains": "Ссылки на покупку разрешены со следующих доменов",
"Media license": "Медиа лицензия", "Media license": "Медиа лицензия",
"Media creator": "Создатель медиа" "Media creator": "Создатель медиа",
"Import Blocks": "Импорт заблокированных экземпляров",
"Export Blocks": "Экспорт заблокированных экземпляров"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Uliza Swali", "Ask a question": "Uliza Swali",
"Possible answers": "Majibu yawezekana", "Possible answers": "Majibu yawezekana",
"replying to": "kujibu kwa", "replying to": "kujibu kwa",
"publicly replying to": "kujibu hadharani",
"replying to followers": "kujibu wafuasi",
"replying unlisted": "kujibu bila kuorodheshwa",
"replying to themselves": "kujibu wenyewe", "replying to themselves": "kujibu wenyewe",
"announces": "inatangaza", "announces": "inatangaza",
"Previous month": "Mwezi uliopita", "Previous month": "Mwezi uliopita",
@ -614,5 +617,7 @@
"Buy link": "Nunua kiungo", "Buy link": "Nunua kiungo",
"Buy links are allowed from the following domains": "Viungo vya kununua vinaruhusiwa kutoka kwa vikoa vifuatavyo", "Buy links are allowed from the following domains": "Viungo vya kununua vinaruhusiwa kutoka kwa vikoa vifuatavyo",
"Media license": "Leseni ya media", "Media license": "Leseni ya media",
"Media creator": "Muundaji wa media" "Media creator": "Muundaji wa media",
"Import Blocks": "Ingiza Matukio Yaliyozuiwa",
"Export Blocks": "Hamisha Matukio Yaliyozuiwa"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Bir soru sor", "Ask a question": "Bir soru sor",
"Possible answers": "Olası cevaplar", "Possible answers": "Olası cevaplar",
"replying to": "yanıtlamak", "replying to": "yanıtlamak",
"publicly replying to": "herkese açık olarak yanıtlama",
"replying to followers": "takipçilere cevap verme",
"replying unlisted": "liste dışı yanıtlama",
"replying to themselves": "kendilerine cevap vermek", "replying to themselves": "kendilerine cevap vermek",
"announces": "duyurur", "announces": "duyurur",
"Previous month": "Geçtiğimiz ay", "Previous month": "Geçtiğimiz ay",
@ -614,5 +617,7 @@
"Buy link": "Bağlantı satın al", "Buy link": "Bağlantı satın al",
"Buy links are allowed from the following domains": "Aşağıdaki alanlardan satın alma bağlantılarına izin verilir", "Buy links are allowed from the following domains": "Aşağıdaki alanlardan satın alma bağlantılarına izin verilir",
"Media license": "Medya lisansı", "Media license": "Medya lisansı",
"Media creator": "Medya yaratıcısı" "Media creator": "Medya yaratıcısı",
"Import Blocks": "Engellenen Örnekleri İçe Aktar",
"Export Blocks": "Engellenen Örnekleri Dışa Aktar"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "Задайте питання", "Ask a question": "Задайте питання",
"Possible answers": "Можливі відповіді", "Possible answers": "Можливі відповіді",
"replying to": "відповідаючи на", "replying to": "відповідаючи на",
"publicly replying to": "публічно відповідаючи на",
"replying to followers": "відповідаючи підписникам",
"replying unlisted": "відповідати не в списку",
"replying to themselves": "відповідаючи собі", "replying to themselves": "відповідаючи собі",
"announces": "оголошує", "announces": "оголошує",
"Previous month": "Попередній місяць", "Previous month": "Попередній місяць",
@ -614,5 +617,7 @@
"Buy link": "Купити посилання", "Buy link": "Купити посилання",
"Buy links are allowed from the following domains": "Посилання на купівлю дозволено з таких доменів", "Buy links are allowed from the following domains": "Посилання на купівлю дозволено з таких доменів",
"Media license": "Медіа ліцензія", "Media license": "Медіа ліцензія",
"Media creator": "Творець медіа" "Media creator": "Творець медіа",
"Import Blocks": "Імпортувати заблоковані екземпляри",
"Export Blocks": "Експортувати заблоковані екземпляри"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "פרעגן אַ קשיא", "Ask a question": "פרעגן אַ קשיא",
"Possible answers": "מעגלעך ענטפֿערס", "Possible answers": "מעגלעך ענטפֿערס",
"replying to": "ענטפערן צו", "replying to": "ענטפערן צו",
"publicly replying to": "עפנטלעך ענטפער צו",
"replying to followers": "ענטפער צו אנהענגערס",
"replying unlisted": "ענטפערן אַנליסטיד",
"replying to themselves": "ענטפערן צו זיך", "replying to themselves": "ענטפערן צו זיך",
"announces": "אַנאַונסיז", "announces": "אַנאַונסיז",
"Previous month": "פֿריִערדיקע חודש", "Previous month": "פֿריִערדיקע חודש",
@ -614,5 +617,7 @@
"Buy link": "קויפן לינק", "Buy link": "קויפן לינק",
"Buy links are allowed from the following domains": "קויפן פֿאַרבינדונגען זענען ערלויבט פֿון די פאלגענדע דאָומיינז", "Buy links are allowed from the following domains": "קויפן פֿאַרבינדונגען זענען ערלויבט פֿון די פאלגענדע דאָומיינז",
"Media license": "מעדיע דערלויבעניש", "Media license": "מעדיע דערלויבעניש",
"Media creator": "מעדיע באשעפער" "Media creator": "מעדיע באשעפער",
"Import Blocks": "ימפּאָרט בלאַקט ינסטאַנסיז",
"Export Blocks": "עקספּאָרט בלאַקט ינסטאַנסיז"
} }

View File

@ -194,6 +194,9 @@
"Ask a question": "问一个问题", "Ask a question": "问一个问题",
"Possible answers": "可能的答案", "Possible answers": "可能的答案",
"replying to": "回覆", "replying to": "回覆",
"publicly replying to": "公开回复",
"replying to followers": "回复关注者",
"replying unlisted": "回复不公开",
"replying to themselves": "回覆自己", "replying to themselves": "回覆自己",
"announces": "宣布", "announces": "宣布",
"Previous month": "前一个月", "Previous month": "前一个月",
@ -614,5 +617,7 @@
"Buy link": "购买链接", "Buy link": "购买链接",
"Buy links are allowed from the following domains": "允许来自以下域的购买链接", "Buy links are allowed from the following domains": "允许来自以下域的购买链接",
"Media license": "媒体许可证", "Media license": "媒体许可证",
"Media creator": "媒体创作者" "Media creator": "媒体创作者",
"Import Blocks": "导入被阻止的实例",
"Export Blocks": "导出被阻止的实例"
} }

View File

@ -2096,7 +2096,9 @@ def is_dm(post_json_object: {}) -> bool:
if not post_json_object['object'].get(field_name): if not post_json_object['object'].get(field_name):
continue continue
for to_address in post_json_object['object'][field_name]: for to_address in post_json_object['object'][field_name]:
if to_address.endswith('#Public'): if to_address.endswith('#Public') or \
to_address == 'as:Public' or \
to_address == 'Public':
return False return False
if to_address.endswith('followers'): if to_address.endswith('followers'):
return False return False
@ -2428,7 +2430,9 @@ def is_public_post(post_json_object: {}) -> bool:
if not post_json_object['object'].get('to'): if not post_json_object['object'].get('to'):
return False return False
for recipient in post_json_object['object']['to']: for recipient in post_json_object['object']['to']:
if recipient.endswith('#Public'): if recipient.endswith('#Public') or \
recipient == 'as:Public' or \
recipient == 'Public':
return True return True
return False return False
@ -2471,7 +2475,9 @@ def is_unlisted_post(post_json_object: {}) -> bool:
if not has_followers: if not has_followers:
return False return False
for recipient in post_json_object['object']['cc']: for recipient in post_json_object['object']['cc']:
if recipient.endswith('#Public'): if recipient.endswith('#Public') or \
recipient == 'as:Public' or \
recipient == 'Public':
return True return True
return False return False

View File

@ -353,6 +353,8 @@ def _save_individual_post_as_html_to_cache(base_dir: str,
get_cached_post_directory(base_dir, nickname, domain) get_cached_post_directory(base_dir, nickname, domain)
cached_post_filename = \ cached_post_filename = \
get_cached_post_filename(base_dir, nickname, domain, post_json_object) get_cached_post_filename(base_dir, nickname, domain, post_json_object)
if not cached_post_filename:
return False
# create the cache directory if needed # create the cache directory if needed
if not os.path.isdir(html_post_cache_dir): if not os.path.isdir(html_post_cache_dir):
@ -362,7 +364,7 @@ def _save_individual_post_as_html_to_cache(base_dir: str,
with open(cached_post_filename, 'w+', encoding='utf-8') as fp_cache: with open(cached_post_filename, 'w+', encoding='utf-8') as fp_cache:
fp_cache.write(post_html) fp_cache.write(post_html)
return True return True
except Exception as ex: except OSError as ex:
print('ERROR: saving post to cache, ' + str(ex)) print('ERROR: saving post to cache, ' + str(ex))
return False return False
@ -916,7 +918,9 @@ def _get_like_icon_html(nickname: str, domain_full: str,
return like_str return like_str
def _get_bookmark_icon_html(nickname: str, domain_full: str, def _get_bookmark_icon_html(base_dir: str,
nickname: str, domain: str,
domain_full: str,
post_json_object: {}, post_json_object: {},
is_moderation_post: bool, is_moderation_post: bool,
translate: {}, translate: {},
@ -924,7 +928,8 @@ def _get_bookmark_icon_html(nickname: str, domain_full: str,
post_start_time, box_name: str, post_start_time, box_name: str,
page_number_param: str, page_number_param: str,
timeline_post_bookmark: str, timeline_post_bookmark: str,
first_post_id: str) -> str: first_post_id: str,
post_url: str) -> str:
"""Returns html for bookmark icon/button """Returns html for bookmark icon/button
""" """
bookmark_str = '' bookmark_str = ''
@ -932,6 +937,9 @@ def _get_bookmark_icon_html(nickname: str, domain_full: str,
if is_moderation_post: if is_moderation_post:
return bookmark_str return bookmark_str
if not locate_post(base_dir, nickname, domain, post_url):
return bookmark_str
bookmark_icon = 'bookmark_inactive.png' bookmark_icon = 'bookmark_inactive.png'
bookmark_link = 'bookmark' bookmark_link = 'bookmark'
bookmark_emoji = '' bookmark_emoji = ''
@ -1375,14 +1383,27 @@ def _reply_to_yourself_html(translate: {}) -> str:
return title_str return title_str
def _replying_to_with_scope(post_json_object: {}, translate: {}) -> str:
"""Returns the replying to string
"""
replying_to_str = 'replying to'
if is_followers_post(post_json_object):
replying_to_str = 'replying to followers'
elif is_public_post(post_json_object):
replying_to_str = 'publicly replying to'
elif is_unlisted_post(post_json_object):
replying_to_str = 'replying unlisted'
if translate.get(replying_to_str):
replying_to_str = translate[replying_to_str]
return replying_to_str
def _reply_to_unknown_html(translate: {}, def _reply_to_unknown_html(translate: {},
post_json_object: {}, post_json_object: {},
nickname: str) -> str: nickname: str) -> str:
"""Returns the html title for a reply to an unknown handle """Returns the html title for a reply to an unknown handle
""" """
replying_to_str = 'replying to' replying_to_str = _replying_to_with_scope(post_json_object, translate)
if translate.get(replying_to_str):
replying_to_str = translate[replying_to_str]
post_id = post_json_object['object']['inReplyTo'] post_id = post_json_object['object']['inReplyTo']
post_link = '/users/' + nickname + '?convthread=' + \ post_link = '/users/' + nickname + '?convthread=' + \
post_id.replace('/', '--') post_id.replace('/', '--')
@ -1412,9 +1433,7 @@ def _reply_with_unknown_path_html(translate: {},
"""Returns html title for a reply with an unknown path """Returns html title for a reply with an unknown path
eg. does not contain /statuses/ eg. does not contain /statuses/
""" """
replying_to_str = 'replying to' replying_to_str = _replying_to_with_scope(post_json_object, translate)
if translate.get(replying_to_str):
replying_to_str = translate[replying_to_str]
post_id = post_json_object['object']['inReplyTo'] post_id = post_json_object['object']['inReplyTo']
post_link = '/users/' + nickname + '?convthread=' + \ post_link = '/users/' + nickname + '?convthread=' + \
post_id.replace('/', '--') post_id.replace('/', '--')
@ -1430,12 +1449,11 @@ def _reply_with_unknown_path_html(translate: {},
def _get_reply_html(translate: {}, def _get_reply_html(translate: {},
in_reply_to: str, reply_display_name: str, in_reply_to: str, reply_display_name: str,
nickname: str) -> str: nickname: str,
post_json_object: {}) -> str:
"""Returns html title for a reply """Returns html title for a reply
""" """
replying_to_str = 'replying to' replying_to_str = _replying_to_with_scope(post_json_object, translate)
if translate.get(replying_to_str):
replying_to_str = translate[replying_to_str]
post_link = '/users/' + nickname + '?convthread=' + \ post_link = '/users/' + nickname + '?convthread=' + \
in_reply_to.replace('/', '--') in_reply_to.replace('/', '--')
return ' ' + \ return ' ' + \
@ -1543,7 +1561,8 @@ def _get_post_title_reply_html(base_dir: str,
_log_post_timing(enable_timing_log, post_start_time, '13.6') _log_post_timing(enable_timing_log, post_start_time, '13.6')
title_str += \ title_str += \
_get_reply_html(translate, in_reply_to, reply_display_name, nickname) _get_reply_html(translate, in_reply_to, reply_display_name,
nickname, post_json_object)
if mitm: if mitm:
title_str += _mitm_warning_html(translate) title_str += _mitm_warning_html(translate)
@ -2133,9 +2152,13 @@ def individual_post_as_html(signing_priv_key_pem: str,
translate, post_json_object['actor'], translate, post_json_object['actor'],
theme_name, system_language, theme_name, system_language,
box_name) box_name)
with open(announce_filename + '.tts', 'w+', try:
encoding='utf-8') as ttsfile: with open(announce_filename + '.tts', 'w+',
ttsfile.write('\n') encoding='utf-8') as ttsfile:
ttsfile.write('\n')
except OSError:
print('EX: unable to write tts ' +
announce_filename + '.tts')
is_announced = True is_announced = True
@ -2161,6 +2184,26 @@ def individual_post_as_html(signing_priv_key_pem: str,
actor_nickname = 'dev' actor_nickname = 'dev'
actor_domain, _ = get_domain_from_actor(post_actor) actor_domain, _ = get_domain_from_actor(post_actor)
# scope icon before display name
if is_followers_post(post_json_object):
title_str += \
' <img loading="lazy" decoding="async" src="/' + \
'icons/scope_followers.png" class="postScopeIcon" title="' + \
translate['Only to followers'] + ':" alt="' + \
translate['Only to followers'] + ':"/>\n'
elif is_unlisted_post(post_json_object):
title_str += \
' <img loading="lazy" decoding="async" src="/' + \
'icons/scope_unlisted.png" class="postScopeIcon" title="' + \
translate['Not on public timeline'] + ':" alt="' + \
translate['Not on public timeline'] + ':"/>\n'
elif is_reminder(post_json_object):
title_str += \
' <img loading="lazy" decoding="async" src="/' + \
'icons/scope_reminder.png" class="postScopeIcon" title="' + \
translate['Scheduled note to yourself'] + ':" alt="' + \
translate['Scheduled note to yourself'] + ':"/>\n'
display_name = get_display_name(base_dir, post_actor, person_cache) display_name = get_display_name(base_dir, post_actor, person_cache)
if display_name: if display_name:
if len(display_name) < 2 or \ if len(display_name) < 2 or \
@ -2295,15 +2338,14 @@ def individual_post_as_html(signing_priv_key_pem: str,
_log_post_timing(enable_timing_log, post_start_time, '12.5') _log_post_timing(enable_timing_log, post_start_time, '12.5')
bookmark_str = \ bookmark_str = \
_get_bookmark_icon_html(nickname, domain_full, _get_bookmark_icon_html(base_dir, nickname, domain,
post_json_object, domain_full, post_json_object,
is_moderation_post, is_moderation_post, translate,
translate,
enable_timing_log, enable_timing_log,
post_start_time, box_name, post_start_time, box_name,
page_number_param, page_number_param,
timeline_post_bookmark, timeline_post_bookmark,
first_post_id) first_post_id, message_id)
_log_post_timing(enable_timing_log, post_start_time, '12.9') _log_post_timing(enable_timing_log, post_start_time, '12.9')
@ -2652,8 +2694,10 @@ def individual_post_as_html(signing_priv_key_pem: str,
translate) translate)
if map_str: if map_str:
map_str = '<center>\n' + map_str + '</center>\n' map_str = '<center>\n' + map_str + '</center>\n'
if map_str and post_json_object['object'].get('attributedTo'): attrib = None
if post_json_object['object'].get('attributedTo'):
attrib = post_json_object['object']['attributedTo'] attrib = post_json_object['object']['attributedTo']
if map_str and attrib:
# is this being sent by the author? # is this being sent by the author?
if '://' + domain_full + '/users/' + nickname in attrib: if '://' + domain_full + '/users/' + nickname in attrib:
location_domain = location_str location_domain = location_str
@ -2729,10 +2773,13 @@ def individual_post_as_html(signing_priv_key_pem: str,
if not show_public_only and store_to_cache and \ if not show_public_only and store_to_cache and \
box_name != 'tlmedia' and box_name != 'tlbookmarks' and \ box_name != 'tlmedia' and box_name != 'tlbookmarks' and \
box_name != 'bookmarks': box_name != 'bookmarks':
cached_json = post_json_object
if announce_json_object:
cached_json = announce_json_object
_save_individual_post_as_html_to_cache(base_dir, nickname, domain, _save_individual_post_as_html_to_cache(base_dir, nickname, domain,
post_json_object, post_html) cached_json, post_html)
update_recent_posts_cache(recent_posts_cache, max_recent_posts, update_recent_posts_cache(recent_posts_cache, max_recent_posts,
post_json_object, post_html) cached_json, post_html)
_log_post_timing(enable_timing_log, post_start_time, '19') _log_post_timing(enable_timing_log, post_start_time, '19')

View File

@ -92,6 +92,7 @@ from roles import is_devops
from session import site_is_verified from session import site_is_verified
THEME_FORMATS = '.zip, .gz' THEME_FORMATS = '.zip, .gz'
BLOCKFILE_FORMATS = '.csv'
def _valid_profile_preview_post(post_json_object: {}, def _valid_profile_preview_post(post_json_object: {},
@ -226,6 +227,11 @@ def html_profile_after_search(recent_posts_cache: {}, max_recent_posts: int,
display_name = search_nickname display_name = search_nickname
if profile_json.get('name'): if profile_json.get('name'):
display_name = profile_json['name'] display_name = profile_json['name']
display_name = remove_html(display_name)
display_name = \
add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain,
display_name, False, translate)
locked_account = get_locked_account(profile_json) locked_account = get_locked_account(profile_json)
if locked_account: if locked_account:
@ -246,6 +252,10 @@ def html_profile_after_search(recent_posts_cache: {}, max_recent_posts: int,
profile_description = '' profile_description = ''
if profile_json.get('summary'): if profile_json.get('summary'):
profile_description = profile_json['summary'] profile_description = profile_json['summary']
profile_description = \
add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain,
profile_description, False, translate)
outbox_url = None outbox_url = None
if not profile_json.get('outbox'): if not profile_json.get('outbox'):
if debug: if debug:
@ -468,7 +478,7 @@ def _get_profile_header(base_dir: str, http_prefix: str, nickname: str,
' <b>' + occupation_name + '</b><br>\n' ' <b>' + occupation_name + '</b><br>\n'
html_str += \ html_str += \
' <h1>' + remove_html(display_name) + '\n</h1>\n' + \ ' <h1>' + display_name + '\n</h1>\n' + \
occupation_str occupation_str
html_str += \ html_str += \
@ -577,7 +587,7 @@ def _get_profile_header_after_search(nickname: str, default_timeline: str,
display_name = search_nickname display_name = search_nickname
html_str += \ html_str += \
' <h1>\n' + \ ' <h1>\n' + \
' ' + remove_html(display_name) + '\n' + \ ' ' + display_name + '\n' + \
' </h1>\n' + \ ' </h1>\n' + \
' <p><b>@' + search_nickname + '@' + search_domain_full + \ ' <p><b>@' + search_nickname + '@' + search_domain_full + \
'</b><br>\n' '</b><br>\n'
@ -692,15 +702,17 @@ def html_profile(signing_priv_key_pem: str,
domain, port = get_domain_from_actor(profile_json['id']) domain, port = get_domain_from_actor(profile_json['id'])
if not domain: if not domain:
return "" return ""
display_name = remove_html(profile_json['name'])
display_name = \ display_name = \
add_emoji_to_display_name(session, base_dir, http_prefix, add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain, nickname, domain,
profile_json['name'], True, translate) display_name, False, translate)
domain_full = get_full_domain(domain, port) domain_full = get_full_domain(domain, port)
profile_description = profile_json['summary']
profile_description = \ profile_description = \
add_emoji_to_display_name(session, base_dir, http_prefix, add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain, nickname, domain,
profile_json['summary'], False, translate) profile_description, False, translate)
if profile_description: if profile_description:
profile_description = standardize_text(profile_description) profile_description = standardize_text(profile_description)
posts_button = 'button' posts_button = 'button'
@ -1527,8 +1539,8 @@ def _html_edit_profile_graphic_design(base_dir: str, translate: {}) -> str:
graphics_str += \ graphics_str += \
' <label class="labels">' + \ ' <label class="labels">' + \
translate['Import Theme'] + '</label>\n' translate['Import Theme'] + '</label>\n'
graphics_str += ' <input type="file" id="import_theme" ' graphics_str += ' <input type="file" id="importTheme" '
graphics_str += 'name="submitImportTheme" ' graphics_str += 'name="importTheme" '
graphics_str += 'accept="' + THEME_FORMATS + '">\n' graphics_str += 'accept="' + THEME_FORMATS + '">\n'
graphics_str += \ graphics_str += \
' <label class="labels">' + \ ' <label class="labels">' + \
@ -2032,6 +2044,20 @@ def _html_edit_profile_filtering(base_dir: str, nickname: str, domain: str,
edit_text_area(translate['Blocked accounts'], None, 'blocked', edit_text_area(translate['Blocked accounts'], None, 'blocked',
blocked_str, 200, '', False) blocked_str, 200, '', False)
# import and export blocks
edit_profile_form += \
' <label class="labels">' + \
translate['Import Blocks'] + '</label>\n'
edit_profile_form += ' <input type="file" id="importBlocks" '
edit_profile_form += 'name="importBlocks" '
edit_profile_form += 'accept="' + BLOCKFILE_FORMATS + '">\n'
edit_profile_form += \
' <label class="labels">' + \
translate['Export Blocks'] + '</label><br>\n'
edit_profile_form += \
' <button type="submit" class="button" ' + \
'name="submitExportBlocks">➤</button><br>\n'
idx = 'Direct messages are always allowed from these instances.' idx = 'Direct messages are always allowed from these instances.'
edit_profile_form += \ edit_profile_form += \
edit_text_area(translate['Direct Message permitted instances'], None, edit_text_area(translate['Direct Message permitted instances'], None,
@ -2255,8 +2281,8 @@ def _html_edit_profile_import_export(nickname: str, domain: str,
edit_profile_form += \ edit_profile_form += \
'<p><label class="labels">' + \ '<p><label class="labels">' + \
translate['Import Follows'] + '</label>\n' translate['Import Follows'] + '</label>\n'
edit_profile_form += '<input type="file" id="import_follows" ' edit_profile_form += '<input type="file" id="importFollows" '
edit_profile_form += 'name="submitImportFollows" ' edit_profile_form += 'name="importFollows" '
edit_profile_form += 'accept=".csv"></p>\n' edit_profile_form += 'accept=".csv"></p>\n'
edit_profile_form += \ edit_profile_form += \

View File

@ -333,8 +333,8 @@ def html_theme_designer(base_dir: str,
export_import_str += \ export_import_str += \
' <label class="labels">' + \ ' <label class="labels">' + \
translate['Import Theme'] + '</label>\n' translate['Import Theme'] + '</label>\n'
export_import_str += ' <input type="file" id="import_theme" ' export_import_str += ' <input type="file" id="importTheme" '
export_import_str += 'name="submitImportTheme" ' export_import_str += 'name="importTheme" '
export_import_str += 'accept="' + theme_formats + '">\n' export_import_str += 'accept="' + theme_formats + '">\n'
export_import_str += \ export_import_str += \
' <label class="labels">' + \ ' <label class="labels">' + \

View File

@ -564,13 +564,17 @@ def post_contains_public(post_json_object: {}) -> bool:
return contains_public return contains_public
for to_address in post_json_object['object']['to']: for to_address in post_json_object['object']['to']:
if to_address.endswith('#Public'): if to_address.endswith('#Public') or \
to_address == 'as:Public' or \
to_address == 'Public':
contains_public = True contains_public = True
break break
if not contains_public: if not contains_public:
if post_json_object['object'].get('cc'): if post_json_object['object'].get('cc'):
for to_address2 in post_json_object['object']['cc']: for to_address2 in post_json_object['object']['cc']:
if to_address2.endswith('#Public'): if to_address2.endswith('#Public') or \
to_address2 == 'as:Public' or \
to_address2 == 'Public':
contains_public = True contains_public = True
break break
return contains_public return contains_public
@ -1062,7 +1066,7 @@ def load_individual_post_as_html_from_cache(base_dir: str,
with open(cached_post_filename, 'r', encoding='utf-8') as file: with open(cached_post_filename, 'r', encoding='utf-8') as file:
post_html = file.read() post_html = file.read()
break break
except Exception as ex: except OSError as ex:
print('ERROR: load_individual_post_as_html_from_cache ' + print('ERROR: load_individual_post_as_html_from_cache ' +
str(tries) + ' ' + str(ex)) str(tries) + ' ' + str(ex))
# no sleep # no sleep