Merge branch 'bashrc2:main' into infra-provisioning

main
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
"""
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 = (
'message', 'bio', 'autoCW', 'password', 'passwordconfirm',
'instanceDescription', 'instanceDescriptionShort',
'subject', 'location', 'imageDescription'
'subject', 'location', 'imageDescription', 'importBlocks',
'importFollows', 'importTheme'
)
if debug:
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 path_is_video
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 get_cw_list_variable
from blocking import load_cw_lists
@ -6163,6 +6165,9 @@ class PubServer(BaseHTTPRequestHandler):
if b'--LYNX' in post_bytes:
boundary = '--LYNX'
if debug:
print('post_bytes: ' + str(post_bytes))
if boundary:
# get the various avatar, banner and background images
actor_changed = True
@ -6171,8 +6176,8 @@ class PubServer(BaseHTTPRequestHandler):
'banner', 'search_banner',
'instanceLogo',
'left_col_image', 'right_col_image',
'submitImportFollows',
'submitImportTheme'
'importFollows',
'importTheme'
)
profile_media_types_uploaded = {}
for m_type in profile_media_types:
@ -6205,7 +6210,7 @@ class PubServer(BaseHTTPRequestHandler):
if m_type == 'instanceLogo':
filename_base = \
base_dir + '/accounts/login.temp'
elif m_type == 'submitImportTheme':
elif m_type == 'importTheme':
if not os.path.isdir(base_dir + '/imports'):
os.mkdir(base_dir + '/imports')
filename_base = \
@ -6216,7 +6221,7 @@ class PubServer(BaseHTTPRequestHandler):
except OSError:
print('EX: _profile_edit unable to delete ' +
filename_base)
elif m_type == 'submitImportFollows':
elif m_type == 'importFollows':
filename_base = \
acct_dir(base_dir, nickname, domain) + \
'/import_following.csv'
@ -6236,7 +6241,7 @@ class PubServer(BaseHTTPRequestHandler):
' media, zip, csv or font filename in POST')
continue
if m_type == 'submitImportFollows':
if m_type == 'importFollows':
if os.path.isfile(filename_base):
print(nickname + ' imported follows csv')
else:
@ -6244,7 +6249,7 @@ class PubServer(BaseHTTPRequestHandler):
nickname)
continue
if m_type == 'submitImportTheme':
if m_type == 'importTheme':
if nickname == admin_nickname or \
is_artist(base_dir, nickname):
if import_theme(base_dir, filename):
@ -6316,6 +6321,14 @@ class PubServer(BaseHTTPRequestHandler):
cookie, calling_domain)
self.server.postreq_busy = False
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
fields = \
@ -7807,6 +7820,50 @@ class PubServer(BaseHTTPRequestHandler):
else:
add_account_blocks(base_dir,
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.
# The allow list for incoming DMs,
@ -8363,6 +8420,25 @@ class PubServer(BaseHTTPRequestHandler):
self._write(export_binary)
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,
base_dir: str, debug: bool,
getreq_start_time) -> None:
@ -17293,9 +17369,15 @@ class PubServer(BaseHTTPRequestHandler):
return
if authorized and '/exports/' in self.path:
self._get_exported_theme(self.path,
self.server.base_dir,
self.server.domain_full)
if 'blocks.csv' in self.path:
self._get_exported_blocks(self.path,
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
# 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:
read_file.seek(0, 0)
read_file.write(post_id + content)
except Exception as ex:
except OSError as ex:
print('EX: Failed to mark post as read' + str(ex))
else:
with open(read_posts_filename, 'w+', encoding='utf-8') as read_file:

View File

@ -1178,6 +1178,14 @@ h3 {
font-size: var(--font-size);
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 {
float: none;
width: 25px;
@ -2000,6 +2008,14 @@ h3 {
font-size: var(--font-size-mobile);
color: var(--title-color);
}
.container img.postScopeIcon {
float: none;
width: 50px;
margin: 0 0;
padding: 0 0;
border-radius: 0;
vertical-align: -6px;
}
blockquote {
font-size: var(--quote-font-size-mobile);
}
@ -2812,6 +2828,14 @@ h3 {
font-size: var(--font-size-tiny);
color: var(--title-color);
}
.container img.postScopeIcon {
float: none;
width: 50px;
margin: 0 0;
padding: 0 0;
border-radius: 0;
vertical-align: -6px;
}
blockquote {
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')
else:
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. ' +
recipient + ' ' + domain_match)
else:
@ -2693,7 +2695,7 @@ def _receive_announce(recent_posts_cache: {},
announced_actor_nickname + '@' + announced_actor_domain)
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,
message_json['object'])
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(message_json['object'])
return True
# add actor to the list of announcers for a post
update_announce_collection(recent_posts_cache, base_dir, post_filename,
message_json['actor'], nickname, domain, debug)
if debug:
@ -3179,7 +3182,9 @@ def _valid_post_content(base_dir: str, nickname: str, domain: str,
'allow comments: ' + original_post_id)
return False
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
if debug:
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”,
but they are really just websites which can speak with each other using
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
href="https://github.com/mastodon/mastodon">Mastodon</a> are well known,
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>
</figure>
<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
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

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)
# 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.
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.write(approve_handle_full + '\n' +
content)
except Exception as ex:
except OSError as ex:
print('WARN: Manual follow accept. ' +
'Failed to write entry to followers file ' + str(ex))
else:

View File

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

View File

@ -5995,7 +5995,17 @@ def _test_extract_text_fields_from_post():
'116202748023898664511855843036\r\nContent-Disposition: ' + \
'form-data; name="attachpic"; filename=""\r\nContent-Type: ' + \
'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
fields = extract_text_fields_in_post(None, boundary, debug, form_data)
assert fields['submitPost'] == 'Submit'
@ -6006,6 +6016,9 @@ def _test_extract_text_fields_from_post():
assert fields['location'] == ''
assert fields['imageDescription'] == ''
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():

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

View File

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

View File

@ -194,6 +194,9 @@
"Ask a question": "Fer una pregunta",
"Possible answers": "Respostes possibles",
"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",
"announces": "anuncia",
"Previous month": "Mes anterior",
@ -614,5 +617,7 @@
"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",
"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",
"Possible answers": "Atebion posib",
"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",
"announces": "yn cyhoeddi",
"Previous month": "Y mis blaenorol",
@ -614,5 +617,7 @@
"Buy link": "Prynu dolen",
"Buy links are allowed from the following domains": "Caniateir dolenni prynu o'r parthau canlynol",
"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",
"Possible answers": "Mögliche Antworten",
"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",
"announces": "kündigt an",
"Previous month": "Vorheriger Monat",
@ -614,5 +617,7 @@
"Buy link": "Link kaufen",
"Buy links are allowed from the following domains": "Kauflinks sind von den folgenden Domains erlaubt",
"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": "Κάνε μια ερώτηση",
"Possible answers": "Πιθανές απαντήσεις",
"replying to": "απαντώντας σε",
"publicly replying to": "απαντώντας δημόσια σε",
"replying to followers": "απαντώντας στους ακόλουθους",
"replying unlisted": "απαντώντας εκτός λίστας",
"replying to themselves": "απαντώντας στον εαυτό τους",
"announces": "ανακοινώνει",
"Previous month": "Προηγούμενος μήνας",
@ -614,5 +617,7 @@
"Buy link": "Σύνδεσμος αγοράς",
"Buy links are allowed from the following domains": "Οι σύνδεσμοι αγοράς επιτρέπονται από τους παρακάτω τομείς",
"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": "publicly replying to",
"replying to followers": "replying to followers",
"replying unlisted": "replying to 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"
"Media creator": "Media creator",
"Import Blocks": "Import Blocks",
"Export Blocks": "Export Blocks"
}

View File

@ -194,6 +194,9 @@
"Ask a question": "Haz una pregunta",
"Possible answers": "Respuestas posibles",
"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",
"announces": "anuncia",
"Previous month": "Mes anterior",
@ -614,5 +617,7 @@
"Buy link": "Enlace de compra",
"Buy links are allowed from the following domains": "Se permiten enlaces de compra de los siguientes dominios",
"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": "یه سوال بپرس",
"Possible answers": "پاسخ های ممکن",
"replying to": "در حال پاسخ دادن به",
"publicly replying to": "علنی پاسخ دادن به",
"replying to followers": "پاسخ به دنبال کنندگان",
"replying unlisted": "در حال پاسخ به فهرست نشده",
"replying to themselves": "به خودشان پاسخ می دهند",
"announces": "اعلام می کند",
"Previous month": "ماه گذشته",
@ -614,5 +617,7 @@
"Buy link": "لینک خرید",
"Buy links are allowed from the following domains": "لینک خرید از دامنه های زیر مجاز است",
"Media license": "مجوز رسانه",
"Media creator": "سازنده رسانه"
"Media creator": "سازنده رسانه",
"Import Blocks": "وارد کردن موارد مسدود شده",
"Export Blocks": "نمونه های مسدود شده را صادر کنید"
}

View File

@ -194,6 +194,9 @@
"Ask a question": "Poser une question",
"Possible answers": "Des réponses possibles",
"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",
"announces": "annonce",
"Previous month": "Le mois précédent",
@ -614,5 +617,7 @@
"Buy link": "Acheter un lien",
"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 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",
"Possible answers": "Freagraí féideartha",
"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",
"announces": "fógraíonn",
"Previous month": "An mhí roimhe seo",
@ -614,5 +617,7 @@
"Buy link": "Ceannaigh nasc",
"Buy links are allowed from the following domains": "Ceadaítear naisc cheannaigh ó na fearainn seo a leanas",
"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": "प्रश्न पूछें",
"Possible answers": "संभावित उत्तर",
"replying to": "करने के लिए जवाब दे",
"publicly replying to": "सार्वजनिक रूप से जवाब दे रहा है",
"replying to followers": "अनुयायियों को जवाब देना",
"replying unlisted": "असूचीबद्ध उत्तर दे रहा है",
"replying to themselves": "खुद को जवाब दे",
"announces": "की घोषणा",
"Previous month": "पिछ्ला महिना",
@ -614,5 +617,7 @@
"Buy link": "लिंक खरीदें",
"Buy links are allowed from the following domains": "निम्नलिखित डोमेन से खरीदें लिंक की अनुमति है",
"Media license": "मीडिया लाइसेंस",
"Media creator": "मीडिया निर्माता"
"Media creator": "मीडिया निर्माता",
"Import Blocks": "अवरोधित उदाहरण आयात करें",
"Export Blocks": "निर्यात अवरुद्ध उदाहरण"
}

View File

@ -194,6 +194,9 @@
"Ask a question": "Fai una domanda",
"Possible answers": "Possibili risposte",
"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",
"announces": "annuncia",
"Previous month": "Il mese scorso",
@ -614,5 +617,7 @@
"Buy link": "Link per l'acquisto",
"Buy links are allowed from the following domains": "I link di acquisto sono consentiti dai seguenti domini",
"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": "質問する",
"Possible answers": "可能な答え",
"replying to": "に返信する",
"publicly replying to": "公に返信する",
"replying to followers": "フォロワーへの返信",
"replying unlisted": "リストにない返信",
"replying to themselves": "自分に返信する",
"announces": "発表する",
"Previous month": "前月",
@ -614,5 +617,7 @@
"Buy link": "購入リンク",
"Buy links are allowed from the following domains": "次のドメインからの購入リンクが許可されています",
"Media license": "メディアライセンス",
"Media creator": "メディアクリエーター"
"Media creator": "メディアクリエーター",
"Import Blocks": "ブロックされたインスタンスのインポート",
"Export Blocks": "ブロックされたインスタンスのエクスポート"
}

View File

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

View File

@ -194,6 +194,9 @@
"Ask a question": "Pirsek bipirsin",
"Possible answers": "Bersivên gengaz",
"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",
"announces": "îlan dike",
"Previous month": "Meha berê",
@ -614,5 +617,7 @@
"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",
"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",
"Possible answers": "Mogelijke antwoorden",
"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",
"announces": "kondigt aan",
"Previous month": "Vorige maand",
@ -614,5 +617,7 @@
"Buy link": "koop link",
"Buy links are allowed from the following domains": "Kooplinks zijn toegestaan vanaf de volgende domeinen",
"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",
"Possible answers": "Possible answers",
"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",
"announces": "announces",
"Previous month": "Previous month",
@ -610,5 +613,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"
"Media creator": "Media creator",
"Import Blocks": "Import Blocks",
"Export Blocks": "Export Blocks"
}

View File

@ -194,6 +194,9 @@
"Ask a question": "Zadać pytanie",
"Possible answers": "Możliwe odpowiedzi",
"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",
"announces": "ogłasza",
"Previous month": "Poprzedni miesiac",
@ -614,5 +617,7 @@
"Buy link": "Kup Link",
"Buy links are allowed from the following domains": "Kupuj linki są dozwolone z następujących domen",
"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",
"Possible answers": "Possíveis respostas",
"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",
"announces": "anuncia",
"Previous month": "Mês anterior",
@ -614,5 +617,7 @@
"Buy link": "Link de compra",
"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 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": "Задайте вопрос",
"Possible answers": "Возможные ответы",
"replying to": "отвечая на",
"publicly replying to": "публично отвечая на",
"replying to followers": "отвечаю подписчикам",
"replying unlisted": "ответ не в списке",
"replying to themselves": "отвечая на себя",
"announces": "анонсов",
"Previous month": "Предыдущий месяц",
@ -614,5 +617,7 @@
"Buy link": "Купить ссылку",
"Buy links are allowed from the following domains": "Ссылки на покупку разрешены со следующих доменов",
"Media license": "Медиа лицензия",
"Media creator": "Создатель медиа"
"Media creator": "Создатель медиа",
"Import Blocks": "Импорт заблокированных экземпляров",
"Export Blocks": "Экспорт заблокированных экземпляров"
}

View File

@ -194,6 +194,9 @@
"Ask a question": "Uliza Swali",
"Possible answers": "Majibu yawezekana",
"replying to": "kujibu kwa",
"publicly replying to": "kujibu hadharani",
"replying to followers": "kujibu wafuasi",
"replying unlisted": "kujibu bila kuorodheshwa",
"replying to themselves": "kujibu wenyewe",
"announces": "inatangaza",
"Previous month": "Mwezi uliopita",
@ -614,5 +617,7 @@
"Buy link": "Nunua kiungo",
"Buy links are allowed from the following domains": "Viungo vya kununua vinaruhusiwa kutoka kwa vikoa vifuatavyo",
"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",
"Possible answers": "Olası cevaplar",
"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",
"announces": "duyurur",
"Previous month": "Geçtiğimiz ay",
@ -614,5 +617,7 @@
"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",
"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": "Задайте питання",
"Possible answers": "Можливі відповіді",
"replying to": "відповідаючи на",
"publicly replying to": "публічно відповідаючи на",
"replying to followers": "відповідаючи підписникам",
"replying unlisted": "відповідати не в списку",
"replying to themselves": "відповідаючи собі",
"announces": "оголошує",
"Previous month": "Попередній місяць",
@ -614,5 +617,7 @@
"Buy link": "Купити посилання",
"Buy links are allowed from the following domains": "Посилання на купівлю дозволено з таких доменів",
"Media license": "Медіа ліцензія",
"Media creator": "Творець медіа"
"Media creator": "Творець медіа",
"Import Blocks": "Імпортувати заблоковані екземпляри",
"Export Blocks": "Експортувати заблоковані екземпляри"
}

View File

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

View File

@ -194,6 +194,9 @@
"Ask a question": "问一个问题",
"Possible answers": "可能的答案",
"replying to": "回覆",
"publicly replying to": "公开回复",
"replying to followers": "回复关注者",
"replying unlisted": "回复不公开",
"replying to themselves": "回覆自己",
"announces": "宣布",
"Previous month": "前一个月",
@ -614,5 +617,7 @@
"Buy link": "购买链接",
"Buy links are allowed from the following domains": "允许来自以下域的购买链接",
"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):
continue
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
if to_address.endswith('followers'):
return False
@ -2428,7 +2430,9 @@ def is_public_post(post_json_object: {}) -> bool:
if not post_json_object['object'].get('to'):
return False
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 False
@ -2471,7 +2475,9 @@ def is_unlisted_post(post_json_object: {}) -> bool:
if not has_followers:
return False
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 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)
cached_post_filename = \
get_cached_post_filename(base_dir, nickname, domain, post_json_object)
if not cached_post_filename:
return False
# create the cache directory if needed
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:
fp_cache.write(post_html)
return True
except Exception as ex:
except OSError as ex:
print('ERROR: saving post to cache, ' + str(ex))
return False
@ -916,7 +918,9 @@ def _get_like_icon_html(nickname: str, domain_full: 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: {},
is_moderation_post: bool,
translate: {},
@ -924,7 +928,8 @@ def _get_bookmark_icon_html(nickname: str, domain_full: str,
post_start_time, box_name: str,
page_number_param: str,
timeline_post_bookmark: str,
first_post_id: str) -> str:
first_post_id: str,
post_url: str) -> str:
"""Returns html for bookmark icon/button
"""
bookmark_str = ''
@ -932,6 +937,9 @@ def _get_bookmark_icon_html(nickname: str, domain_full: str,
if is_moderation_post:
return bookmark_str
if not locate_post(base_dir, nickname, domain, post_url):
return bookmark_str
bookmark_icon = 'bookmark_inactive.png'
bookmark_link = 'bookmark'
bookmark_emoji = ''
@ -1375,14 +1383,27 @@ def _reply_to_yourself_html(translate: {}) -> 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: {},
post_json_object: {},
nickname: str) -> str:
"""Returns the html title for a reply to an unknown handle
"""
replying_to_str = 'replying to'
if translate.get(replying_to_str):
replying_to_str = translate[replying_to_str]
replying_to_str = _replying_to_with_scope(post_json_object, translate)
post_id = post_json_object['object']['inReplyTo']
post_link = '/users/' + nickname + '?convthread=' + \
post_id.replace('/', '--')
@ -1412,9 +1433,7 @@ def _reply_with_unknown_path_html(translate: {},
"""Returns html title for a reply with an unknown path
eg. does not contain /statuses/
"""
replying_to_str = 'replying to'
if translate.get(replying_to_str):
replying_to_str = translate[replying_to_str]
replying_to_str = _replying_to_with_scope(post_json_object, translate)
post_id = post_json_object['object']['inReplyTo']
post_link = '/users/' + nickname + '?convthread=' + \
post_id.replace('/', '--')
@ -1430,12 +1449,11 @@ def _reply_with_unknown_path_html(translate: {},
def _get_reply_html(translate: {},
in_reply_to: str, reply_display_name: str,
nickname: str) -> str:
nickname: str,
post_json_object: {}) -> str:
"""Returns html title for a reply
"""
replying_to_str = 'replying to'
if translate.get(replying_to_str):
replying_to_str = translate[replying_to_str]
replying_to_str = _replying_to_with_scope(post_json_object, translate)
post_link = '/users/' + nickname + '?convthread=' + \
in_reply_to.replace('/', '--')
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')
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:
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'],
theme_name, system_language,
box_name)
with open(announce_filename + '.tts', 'w+',
encoding='utf-8') as ttsfile:
ttsfile.write('\n')
try:
with open(announce_filename + '.tts', 'w+',
encoding='utf-8') as ttsfile:
ttsfile.write('\n')
except OSError:
print('EX: unable to write tts ' +
announce_filename + '.tts')
is_announced = True
@ -2161,6 +2184,26 @@ def individual_post_as_html(signing_priv_key_pem: str,
actor_nickname = 'dev'
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)
if display_name:
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')
bookmark_str = \
_get_bookmark_icon_html(nickname, domain_full,
post_json_object,
is_moderation_post,
translate,
_get_bookmark_icon_html(base_dir, nickname, domain,
domain_full, post_json_object,
is_moderation_post, translate,
enable_timing_log,
post_start_time, box_name,
page_number_param,
timeline_post_bookmark,
first_post_id)
first_post_id, message_id)
_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)
if map_str:
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']
if map_str and attrib:
# is this being sent by the author?
if '://' + domain_full + '/users/' + nickname in attrib:
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 \
box_name != 'tlmedia' and box_name != 'tlbookmarks' and \
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,
post_json_object, post_html)
cached_json, post_html)
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')

View File

@ -92,6 +92,7 @@ from roles import is_devops
from session import site_is_verified
THEME_FORMATS = '.zip, .gz'
BLOCKFILE_FORMATS = '.csv'
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
if profile_json.get('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)
if locked_account:
@ -246,6 +252,10 @@ def html_profile_after_search(recent_posts_cache: {}, max_recent_posts: int,
profile_description = ''
if profile_json.get('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
if not profile_json.get('outbox'):
if debug:
@ -468,7 +478,7 @@ def _get_profile_header(base_dir: str, http_prefix: str, nickname: str,
' <b>' + occupation_name + '</b><br>\n'
html_str += \
' <h1>' + remove_html(display_name) + '\n</h1>\n' + \
' <h1>' + display_name + '\n</h1>\n' + \
occupation_str
html_str += \
@ -577,7 +587,7 @@ def _get_profile_header_after_search(nickname: str, default_timeline: str,
display_name = search_nickname
html_str += \
' <h1>\n' + \
' ' + remove_html(display_name) + '\n' + \
' ' + display_name + '\n' + \
' </h1>\n' + \
' <p><b>@' + search_nickname + '@' + search_domain_full + \
'</b><br>\n'
@ -692,15 +702,17 @@ def html_profile(signing_priv_key_pem: str,
domain, port = get_domain_from_actor(profile_json['id'])
if not domain:
return ""
display_name = remove_html(profile_json['name'])
display_name = \
add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain,
profile_json['name'], True, translate)
display_name, False, translate)
domain_full = get_full_domain(domain, port)
profile_description = profile_json['summary']
profile_description = \
add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain,
profile_json['summary'], False, translate)
profile_description, False, translate)
if profile_description:
profile_description = standardize_text(profile_description)
posts_button = 'button'
@ -1527,8 +1539,8 @@ def _html_edit_profile_graphic_design(base_dir: str, translate: {}) -> str:
graphics_str += \
' <label class="labels">' + \
translate['Import Theme'] + '</label>\n'
graphics_str += ' <input type="file" id="import_theme" '
graphics_str += 'name="submitImportTheme" '
graphics_str += ' <input type="file" id="importTheme" '
graphics_str += 'name="importTheme" '
graphics_str += 'accept="' + THEME_FORMATS + '">\n'
graphics_str += \
' <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',
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.'
edit_profile_form += \
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 += \
'<p><label class="labels">' + \
translate['Import Follows'] + '</label>\n'
edit_profile_form += '<input type="file" id="import_follows" '
edit_profile_form += 'name="submitImportFollows" '
edit_profile_form += '<input type="file" id="importFollows" '
edit_profile_form += 'name="importFollows" '
edit_profile_form += 'accept=".csv"></p>\n'
edit_profile_form += \

View File

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

View File

@ -564,13 +564,17 @@ def post_contains_public(post_json_object: {}) -> bool:
return contains_public
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
break
if not contains_public:
if post_json_object['object'].get('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
break
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:
post_html = file.read()
break
except Exception as ex:
except OSError as ex:
print('ERROR: load_individual_post_as_html_from_cache ' +
str(tries) + ' ' + str(ex))
# no sleep