Merge branch 'main' of gitlab.com:bashrc2/epicyon

merge-requests/30/head
Bob Mottram 2022-07-05 21:01:56 +01:00
commit ab9257fbb5
42 changed files with 546 additions and 162 deletions

View File

@ -1008,6 +1008,147 @@ def _auto_tag(base_dir: str, nickname: str, domain: str,
append_tags.append('#' + tag_name)
def _get_simplified_content(content: str) -> str:
"""Returns a simplified version of the content suitable for
splitting up into individual words
"""
content_simplified = \
content.replace(',', ' ').replace(';', ' ').replace('- ', ' ')
content_simplified = content_simplified.replace('. ', ' ').strip()
if content_simplified.endswith('.'):
content_simplified = content_simplified[:len(content_simplified)-1]
return content_simplified
def detect_dogwhistles(content: str, dogwhistles: {}) -> {}:
"""Returns a dict containing any detected dogwhistle words
"""
content = remove_html(content).lower()
result = {}
words = _get_simplified_content(content).split(' ')
for whistle, category in dogwhistles.items():
if not category:
continue
ending = False
starting = False
whistle = whistle.lower()
if whistle.startswith('x-'):
whistle = whistle[2:]
ending = True
elif (whistle.startswith('*') or
whistle.startswith('~') or
whistle.startswith('-')):
whistle = whistle[1:]
ending = True
if ending:
prev_wrd = ''
for wrd in words:
wrd2 = (prev_wrd + ' ' + wrd).strip()
if wrd.endswith(whistle) or wrd2.endswith(whistle):
if not result.get(whistle):
result[whistle] = {
"count": 1,
"category": category
}
else:
result[whistle]['count'] += 1
prev_wrd = wrd
continue
if whistle.lower().endswith('-x'):
whistle = whistle[:len(whistle)-2]
starting = True
elif (whistle.endswith('*') or
whistle.endswith('~') or
whistle.endswith('-')):
whistle = whistle[:len(whistle)-1]
starting = True
if starting:
prev_wrd = ''
for wrd in words:
wrd2 = (prev_wrd + ' ' + wrd).strip()
if wrd.startswith(whistle) or wrd2.startswith(whistle):
if not result.get(whistle):
result[whistle] = {
"count": 1,
"category": category
}
else:
result[whistle]['count'] += 1
prev_wrd = wrd
continue
if '*' in whistle:
whistle_start = whistle.split('*', 1)[0]
whistle_end = whistle.split('*', 1)[1]
prev_wrd = ''
for wrd in words:
wrd2 = (prev_wrd + ' ' + wrd).strip()
if ((wrd.startswith(whistle_start) and
wrd.endswith(whistle_end)) or
(wrd2.startswith(whistle_start) and
wrd2.endswith(whistle_end))):
if not result.get(whistle):
result[whistle] = {
"count": 1,
"category": category
}
else:
result[whistle]['count'] += 1
prev_wrd = wrd
continue
prev_wrd = ''
for wrd in words:
wrd2 = (prev_wrd + ' ' + wrd).strip()
if whistle in (wrd, wrd2):
if not result.get(whistle):
result[whistle] = {
"count": 1,
"category": category
}
else:
result[whistle]['count'] += 1
prev_wrd = wrd
return result
def load_dogwhistles(filename: str) -> {}:
"""Loads a list of dogwhistles from file
"""
if not os.path.isfile(filename):
return {}
dogwhistle_lines = []
try:
with open(filename, 'r', encoding='utf-8') as fp_dogwhistles:
dogwhistle_lines = fp_dogwhistles.readlines()
except OSError:
print('EX: unable to load dogwhistles from ' + filename)
return {}
separators = ('->', '=>', ',', ';', '|', '=')
dogwhistles = {}
for line in dogwhistle_lines:
line = remove_eol(line).strip()
if not line:
continue
if line.startswith('#'):
continue
whistle = None
category = None
for sep in separators:
if sep in line:
whistle = line.split(sep, 1)[0].strip()
category = line.split(sep, 1)[1].strip()
break
if not whistle:
whistle = line
dogwhistles[whistle] = category
return dogwhistles
def add_html_tags(base_dir: str, http_prefix: str,
nickname: str, domain: str, content: str,
recipients: [], hashtags: {},
@ -1022,12 +1163,7 @@ def add_html_tags(base_dir: str, http_prefix: str,
content = content.replace('\r', '')
content = content.replace('\n', ' --linebreak-- ')
content = _add_music_tag(content, 'nowplaying')
content_simplified = \
content.replace(',', ' ').replace(';', ' ').replace('- ', ' ')
content_simplified = content_simplified.replace('. ', ' ').strip()
if content_simplified.endswith('.'):
content_simplified = content_simplified[:len(content_simplified)-1]
words = content_simplified.split(' ')
words = _get_simplified_content(content).split(' ')
# remove . for words which are not mentions
new_words = []

140
daemon.py
View File

@ -326,6 +326,7 @@ from utils import has_group_type
from manualapprove import manual_deny_follow_request_thread
from manualapprove import manual_approve_follow_request_thread
from announce import create_announce
from content import load_dogwhistles
from content import valid_url_lengths
from content import contains_invalid_local_links
from content import get_price_from_string
@ -1595,7 +1596,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.max_recent_posts,
self.server.cw_lists,
self.server.lists_enabled,
self.server.content_license_url)
self.server.content_license_url,
self.server.dogwhistles)
def _get_outbox_thread_index(self, nickname: str,
max_outbox_threads_per_account: int) -> int:
@ -3182,7 +3184,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.lists_enabled,
self.server.default_timeline,
reply_is_chat,
bold_reading).encode('utf-8')
bold_reading,
self.server.dogwhistles).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
cookie, calling_domain, False)
@ -3326,7 +3329,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.lists_enabled,
self.server.default_timeline,
reply_is_chat,
bold_reading).encode('utf-8')
bold_reading,
self.server.dogwhistles).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
cookie, calling_domain, False)
@ -3888,7 +3892,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
if hashtag_str:
msg = hashtag_str.encode('utf-8')
msglen = len(msg)
@ -3993,7 +3998,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
if history_str:
msg = history_str.encode('utf-8')
msglen = len(msg)
@ -4073,7 +4079,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
if bookmarks_str:
msg = bookmarks_str.encode('utf-8')
msglen = len(msg)
@ -4243,7 +4250,8 @@ class PubServer(BaseHTTPRequestHandler):
timezone,
self.server.onion_domain,
self.server.i2p_domain,
bold_reading)
bold_reading,
self.server.dogwhistles)
if profile_str:
msg = profile_str.encode('utf-8')
msglen = len(msg)
@ -5098,6 +5106,27 @@ class PubServer(BaseHTTPRequestHandler):
print('EX: _newswire_update unable to delete ' +
filter_newswire_filename)
# save dogwhistle words list
dogwhistles_filename = base_dir + '/accounts/dogwhistles.txt'
if fields.get('dogwhistleWords'):
try:
with open(dogwhistles_filename, 'w+',
encoding='utf-8') as fp_dogwhistles:
fp_dogwhistles.write(fields['dogwhistleWords'])
except OSError:
print('EX: unable to write ' + dogwhistles_filename)
self.server.dogwhistles = \
load_dogwhistles(dogwhistles_filename)
else:
# save an empty file
try:
with open(dogwhistles_filename, 'w+',
encoding='utf-8') as fp_dogwhistles:
fp_dogwhistles.write('')
except OSError:
print('EX: unable to write ' + dogwhistles_filename)
self.server.dogwhistles = {}
# save news tagging rules
hashtag_rules_filename = \
base_dir + '/accounts/hashtagrules.txt'
@ -8471,7 +8500,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
if hashtag_str:
msg = hashtag_str.encode('utf-8')
msglen = len(msg)
@ -8719,7 +8749,8 @@ class PubServer(BaseHTTPRequestHandler):
False, True, False,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
actor_absolute = self._get_instance_url(calling_domain) + actor
actor_path_str = \
@ -9265,7 +9296,8 @@ class PubServer(BaseHTTPRequestHandler):
False, True, False,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
else:
print('WARN: Liked post not found: ' + liked_post_filename)
# clear the icon from the cache so that it gets updated
@ -9450,7 +9482,8 @@ class PubServer(BaseHTTPRequestHandler):
False, True, False,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
else:
print('WARN: Unliked post not found: ' + liked_post_filename)
# clear the icon from the cache so that it gets updated
@ -9664,7 +9697,8 @@ class PubServer(BaseHTTPRequestHandler):
False, True, False,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
else:
print('WARN: Emoji reaction post not found: ' +
reaction_post_filename)
@ -9868,7 +9902,8 @@ class PubServer(BaseHTTPRequestHandler):
False, True, False,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
else:
print('WARN: Unreaction post not found: ' +
reaction_post_filename)
@ -9974,7 +10009,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.cw_lists,
self.server.lists_enabled,
timeline_str, page_number,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -10116,7 +10152,8 @@ class PubServer(BaseHTTPRequestHandler):
False, True, False,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
else:
print('WARN: Bookmarked post not found: ' + bookmark_filename)
# self._post_to_outbox(bookmark_json,
@ -10265,7 +10302,8 @@ class PubServer(BaseHTTPRequestHandler):
False, True, False,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
else:
print('WARN: Unbookmarked post not found: ' +
bookmark_filename)
@ -10375,7 +10413,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.max_like_count,
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled)
self.server.lists_enabled,
self.server.dogwhistles)
if delete_str:
delete_str_len = len(delete_str)
self._set_headers('text/html', delete_str_len,
@ -10502,7 +10541,8 @@ class PubServer(BaseHTTPRequestHandler):
use_cache_only,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
else:
print('WARN: Muted post not found: ' + mute_filename)
@ -10629,7 +10669,8 @@ class PubServer(BaseHTTPRequestHandler):
use_cache_only,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
else:
print('WARN: Unmuted post not found: ' + mute_filename)
if calling_domain.endswith('.onion') and onion_domain:
@ -10758,7 +10799,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -10862,7 +10904,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -11316,7 +11359,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.cw_lists,
self.server.lists_enabled,
'inbox', self.server.default_timeline,
bold_reading)
bold_reading, self.server.dogwhistles)
if not msg:
self._404()
return True
@ -11383,7 +11426,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.cw_lists,
self.server.lists_enabled,
'inbox', self.server.default_timeline,
bold_reading, 'shares')
bold_reading, self.server.dogwhistles,
'shares')
if not msg:
self._404()
return True
@ -11472,7 +11516,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -11788,7 +11833,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
if getreq_start_time:
fitness_performance(getreq_start_time,
self.server.fitness,
@ -11959,7 +12005,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -12119,7 +12166,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.replies)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -12277,7 +12325,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -12435,7 +12484,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -12602,7 +12652,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -12772,7 +12823,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -12892,7 +12944,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled, timezone,
bold_reading)
bold_reading, self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -12986,7 +13038,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -13123,7 +13176,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -13274,7 +13328,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -13422,7 +13477,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signing_priv_key_pem,
self.server.cw_lists,
self.server.lists_enabled,
timezone, bold_reading)
timezone, bold_reading,
self.server.dogwhistles)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -14799,7 +14855,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.lists_enabled,
self.server.default_timeline,
reply_is_chat,
bold_reading).encode('utf-8')
bold_reading,
self.server.dogwhistles).encode('utf-8')
if not msg:
print('Error replying to ' + in_reply_to_url)
self._404()
@ -14942,7 +14999,8 @@ class PubServer(BaseHTTPRequestHandler):
http_prefix,
self.server.default_timeline,
self.server.theme_name,
access_keys)
access_keys,
self.server.dogwhistles)
if msg:
msg = msg.encode('utf-8')
msglen = len(msg)
@ -20967,6 +21025,12 @@ def run_daemon(preferred_podcast_formats: [],
# scan the theme directory for any svg files containing scripts
assert not scan_themes_for_scripts(base_dir)
# load a list of dogwhistle words
dogwhistles_filename = base_dir + '/accounts/dogwhistles.txt'
if not os.path.isfile(dogwhistles_filename):
dogwhistles_filename = base_dir + '/default_dogwhistles.txt'
httpd.dogwhistles = load_dogwhistles(dogwhistles_filename)
# list of preferred podcast formats
# eg ['audio/opus', 'audio/mp3']
httpd.preferred_podcast_formats = preferred_podcast_formats

View File

@ -0,0 +1,15 @@
X-pilled, alt-right terminology
soy boy, alt-right terminology
soyboy, alt-right terminology
soyboi, alt-right terminology
kek, alt-right terminology
groyper, alt-right meme
chad, alt-right meme
globalist*, antisemitism
globalism, antisemitism
fren, alt-right terminology
cuck*, alt-right terminology
*1488, nazism
nazbol, nazism
*1290, antisemitism

View File

@ -131,6 +131,7 @@ from conversation import update_conversation
from webapp_hashtagswarm import html_hash_tag_swarm
from person import valid_sending_actor
from fitnessFunctions import fitness_performance
from content import load_dogwhistles
from content import valid_url_lengths
from content import remove_script
@ -408,7 +409,8 @@ def _inbox_store_post_to_html_cache(recent_posts_cache: {},
lists_enabled: str,
timezone: str,
mitm: bool,
bold_reading: bool) -> None:
bold_reading: bool,
dogwhistles: {}) -> None:
"""Converts the json post into html and stores it in a cache
This enables the post to be quickly displayed later
"""
@ -434,7 +436,7 @@ def _inbox_store_post_to_html_cache(recent_posts_cache: {},
theme_name, system_language, max_like_count,
not_dm, True, True, False, True, False,
cw_lists, lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
def valid_inbox(base_dir: str, nickname: str, domain: str) -> bool:
@ -1106,7 +1108,7 @@ def _receive_edit_to_post(recent_posts_cache: {}, message_json: {},
show_published_date_only: bool,
peertube_instances: [],
theme_name: str, max_like_count: int,
cw_lists: {}) -> bool:
cw_lists: {}, dogwhistles: {}) -> bool:
"""A post was edited
"""
if not has_object_dict(message_json):
@ -1230,7 +1232,7 @@ def _receive_edit_to_post(recent_posts_cache: {}, message_json: {},
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
return True
@ -1250,7 +1252,7 @@ def _receive_update_activity(recent_posts_cache: {}, session, base_dir: str,
show_published_date_only: bool,
peertube_instances: [],
theme_name: str, max_like_count: int,
cw_lists: {}) -> bool:
cw_lists: {}, dogwhistles: {}) -> bool:
"""Receives an Update activity within the POST section of HTTPServer
"""
@ -1290,7 +1292,7 @@ def _receive_update_activity(recent_posts_cache: {}, session, base_dir: str,
show_published_date_only,
peertube_instances,
theme_name, max_like_count,
cw_lists):
cw_lists, dogwhistles):
print('EDITPOST: received ' + message_json['object']['id'])
return True
else:
@ -1340,7 +1342,7 @@ def _receive_like(recent_posts_cache: {},
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str,
bold_reading: bool) -> bool:
bold_reading: bool, dogwhistles: {}) -> bool:
"""Receives a Like activity within the POST section of HTTPServer
"""
if message_json['type'] != 'Like':
@ -1449,7 +1451,7 @@ def _receive_like(recent_posts_cache: {},
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
return True
@ -1469,7 +1471,7 @@ def _receive_undo_like(recent_posts_cache: {},
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str,
bold_reading: bool) -> bool:
bold_reading: bool, dogwhistles: {}) -> bool:
"""Receives an undo like activity within the POST section of HTTPServer
"""
if message_json['type'] != 'Undo':
@ -1568,7 +1570,7 @@ def _receive_undo_like(recent_posts_cache: {},
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
return True
@ -1588,7 +1590,8 @@ def _receive_reaction(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str, bold_reading: bool) -> bool:
lists_enabled: str, bold_reading: bool,
dogwhistles: {}) -> bool:
"""Receives an emoji reaction within the POST section of HTTPServer
"""
if message_json['type'] != 'EmojiReact':
@ -1719,7 +1722,7 @@ def _receive_reaction(recent_posts_cache: {},
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
return True
@ -1741,7 +1744,7 @@ def _receive_undo_reaction(recent_posts_cache: {},
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str,
bold_reading: bool) -> bool:
bold_reading: bool, dogwhistles: {}) -> bool:
"""Receives an undo emoji reaction within the POST section of HTTPServer
"""
if message_json['type'] != 'Undo':
@ -1858,7 +1861,7 @@ def _receive_undo_reaction(recent_posts_cache: {},
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
return True
@ -1876,7 +1879,8 @@ def _receive_bookmark(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: {}, bold_reading: bool) -> bool:
lists_enabled: {}, bold_reading: bool,
dogwhistles: {}) -> bool:
"""Receives a bookmark activity within the POST section of HTTPServer
"""
if not message_json.get('type'):
@ -1973,7 +1977,7 @@ def _receive_bookmark(recent_posts_cache: {},
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
return True
@ -1993,7 +1997,8 @@ def _receive_undo_bookmark(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str, bold_reading: bool) -> bool:
lists_enabled: str, bold_reading: bool,
dogwhistles: {}) -> bool:
"""Receives an undo bookmark activity within the POST section of HTTPServer
"""
if not message_json.get('type'):
@ -2090,7 +2095,8 @@ def _receive_undo_bookmark(recent_posts_cache: {},
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists, lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
dogwhistles)
return True
@ -2186,7 +2192,8 @@ def _receive_announce(recent_posts_cache: {},
allow_deletion: bool,
peertube_instances: [],
max_like_count: int, cw_lists: {},
lists_enabled: str, bold_reading: bool) -> bool:
lists_enabled: str, bold_reading: bool,
dogwhistles: {}) -> bool:
"""Receives an announce activity within the POST section of HTTPServer
"""
if message_json['type'] != 'Announce':
@ -2309,7 +2316,7 @@ def _receive_announce(recent_posts_cache: {},
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
if not announce_html:
print('WARN: Unable to generate html for announce ' +
str(message_json))
@ -3452,7 +3459,7 @@ def _receive_question_vote(server, base_dir: str, nickname: str, domain: str,
theme_name: str, system_language: str,
max_like_count: int,
cw_lists: {}, lists_enabled: bool,
bold_reading: bool) -> None:
bold_reading: bool, dogwhistles: {}) -> None:
"""Updates the votes on a Question/poll
"""
# if this is a reply to a question then update the votes
@ -3505,7 +3512,7 @@ def _receive_question_vote(server, base_dir: str, nickname: str, domain: str,
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
# add id to inbox index
inbox_update_index('inbox', base_dir, handle,
@ -3676,7 +3683,8 @@ def _inbox_after_initial(server, inbox_start_time,
cw_lists: {}, lists_enabled: str,
content_license_url: str,
languages_understood: [],
mitm: bool, bold_reading: bool) -> bool:
mitm: bool, bold_reading: bool,
dogwhistles: {}) -> bool:
""" Anything which needs to be done after initial checks have passed
"""
# if this is a clearnet instance then replace any onion/i2p
@ -3724,7 +3732,7 @@ def _inbox_after_initial(server, inbox_start_time,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled,
bold_reading):
bold_reading, dogwhistles):
if debug:
print('DEBUG: Like accepted from ' + actor)
fitness_performance(inbox_start_time, server.fitness,
@ -3749,7 +3757,7 @@ def _inbox_after_initial(server, inbox_start_time,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled,
bold_reading):
bold_reading, dogwhistles):
if debug:
print('DEBUG: Undo like accepted from ' + actor)
fitness_performance(inbox_start_time, server.fitness,
@ -3775,7 +3783,7 @@ def _inbox_after_initial(server, inbox_start_time,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled,
bold_reading):
bold_reading, dogwhistles):
if debug:
print('DEBUG: Reaction accepted from ' + actor)
fitness_performance(inbox_start_time, server.fitness,
@ -3802,7 +3810,7 @@ def _inbox_after_initial(server, inbox_start_time,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled,
bold_reading):
bold_reading, dogwhistles):
if debug:
print('DEBUG: Undo reaction accepted from ' + actor)
fitness_performance(inbox_start_time, server.fitness,
@ -3829,7 +3837,7 @@ def _inbox_after_initial(server, inbox_start_time,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled,
bold_reading):
bold_reading, dogwhistles):
if debug:
print('DEBUG: Bookmark accepted from ' + actor)
fitness_performance(inbox_start_time, server.fitness,
@ -3856,7 +3864,7 @@ def _inbox_after_initial(server, inbox_start_time,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled,
bold_reading):
bold_reading, dogwhistles):
if debug:
print('DEBUG: Undo bookmark accepted from ' + actor)
fitness_performance(inbox_start_time, server.fitness,
@ -3891,7 +3899,7 @@ def _inbox_after_initial(server, inbox_start_time,
allow_deletion,
peertube_instances,
max_like_count, cw_lists, lists_enabled,
bold_reading):
bold_reading, dogwhistles):
if debug:
print('DEBUG: Announce accepted from ' + actor)
fitness_performance(inbox_start_time, server.fitness,
@ -4033,7 +4041,7 @@ def _inbox_after_initial(server, inbox_start_time,
theme_name, system_language,
max_like_count,
cw_lists, lists_enabled,
bold_reading)
bold_reading, dogwhistles)
fitness_performance(inbox_start_time, server.fitness,
'INBOX', '_receive_question_vote',
debug)
@ -4277,7 +4285,8 @@ def _inbox_after_initial(server, inbox_start_time,
cw_lists,
lists_enabled,
timezone, mitm,
bold_reading)
bold_reading,
dogwhistles)
fitness_performance(inbox_start_time,
server.fitness,
'INBOX',
@ -5237,6 +5246,11 @@ def run_inbox_queue(server,
debug)
inbox_start_time = time.time()
dogwhistles_filename = base_dir + '/accounts/dogwhistles.txt'
if not os.path.isfile(dogwhistles_filename):
dogwhistles_filename = base_dir + '/default_dogwhistles.txt'
dogwhistles = load_dogwhistles(dogwhistles_filename)
# set the id to the same as the post filename
# This makes the filename and the id consistent
# if queue_json['post'].get('id'):
@ -5328,7 +5342,7 @@ def run_inbox_queue(server,
show_published_date_only,
peertube_instances,
theme_name, max_like_count,
cw_lists):
cw_lists, dogwhistles):
if debug:
print('Queue: Update accepted from ' + key_id)
if os.path.isfile(queue_filename):
@ -5456,7 +5470,7 @@ def run_inbox_queue(server,
cw_lists, lists_enabled,
content_license_url,
languages_understood, mitm,
bold_reading)
bold_reading, dogwhistles)
fitness_performance(inbox_start_time, server.fitness,
'INBOX', 'handle_after_initial',
debug)

View File

@ -213,7 +213,8 @@ def post_message_to_outbox(session, translate: {},
max_like_count: int,
max_recent_posts: int, cw_lists: {},
lists_enabled: str,
content_license_url: str) -> bool:
content_license_url: str,
dogwhistles: {}) -> bool:
"""post is received by the outbox
Client to server message post
https://www.w3.org/TR/activitypub/#client-to-server-outbox-delivery
@ -513,7 +514,7 @@ def post_message_to_outbox(session, translate: {},
False, True, use_cache_only,
cw_lists, lists_enabled,
timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
if outbox_announce(recent_posts_cache,
base_dir, message_json, debug):

View File

@ -142,7 +142,8 @@ def _update_post_schedule(base_dir: str, handle: str, httpd,
httpd.max_recent_posts,
httpd.cw_lists,
httpd.lists_enabled,
httpd.content_license_url):
httpd.content_license_url,
httpd.dogwhistles):
index_lines.remove(line)
try:
os.remove(post_filename)

View File

@ -132,6 +132,7 @@ from inbox import valid_inbox
from inbox import valid_inbox_filenames
from inbox import cache_svg_images
from categories import guess_hashtag_category
from content import detect_dogwhistles
from content import remove_script
from content import create_edits_html
from content import content_diff
@ -7326,6 +7327,30 @@ def _test_remove_end_of_line():
assert remove_eol(text) == expected
def _test_dogwhistles():
print('dogwhistles')
dogwhistles = {
"X-hamstered": "hamsterism",
"gerbil": "rodent",
"*snake": "slither",
"start*end": "something"
}
content = 'This text does not contain any dogwhistles'
assert not detect_dogwhistles(content, dogwhistles)
content = 'A gerbil named joe'
assert detect_dogwhistles(content, dogwhistles)
content = 'A rattlesnake.'
assert detect_dogwhistles(content, dogwhistles)
content = 'A startthingend.'
assert detect_dogwhistles(content, dogwhistles)
content = 'This content is unhamstered and yhamstered.'
result = detect_dogwhistles(content, dogwhistles)
assert result
assert result.get('hamstered')
assert result['hamstered']['count'] == 2
assert result['hamstered']['category'] == "hamsterism"
def run_all_tests():
base_dir = os.getcwd()
print('Running tests...')
@ -7343,6 +7368,7 @@ def run_all_tests():
_test_checkbox_names()
_test_thread_functions()
_test_functions()
_test_dogwhistles()
_test_remove_end_of_line()
_test_translation_labels()
_test_color_contrast_value(base_dir)

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "قم بالتبديل إلى عرض الاعتدال",
"Minimize attached images": "تصغير الصور المرفقة",
"SHOW MEDIA": "عرض الوسائط",
"ActivityPub Specification": "مواصفات ActivityPub"
"ActivityPub Specification": "مواصفات ActivityPub",
"Dogwhistle words": "كلمات Dogwhistle",
"Content warnings will be added for the following": "ستتم إضافة تحذيرات المحتوى لما يلي"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "সংযম দৃশ্যে স্যুইচ করুন",
"Minimize attached images": "সংযুক্ত ছবি ছোট করুন",
"SHOW MEDIA": "মিডিয়া দেখান",
"ActivityPub Specification": "ActivityPub স্পেসিফিকেশন"
"ActivityPub Specification": "ActivityPub স্পেসিফিকেশন",
"Dogwhistle words": "কুকুরের হুইসেল শব্দ",
"Content warnings will be added for the following": "নিম্নলিখিত জন্য বিষয়বস্তু সতর্কতা যোগ করা হবে"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Canvia a la visualització de moderació",
"Minimize attached images": "Minimitzar les imatges adjuntes",
"SHOW MEDIA": "MOSTRA ELS MITJANS",
"ActivityPub Specification": "Especificació d'ActivityPub"
"ActivityPub Specification": "Especificació d'ActivityPub",
"Dogwhistle words": "Paraules de xiulet",
"Content warnings will be added for the following": "S'afegiran advertències de contingut per al següent"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Newid i wedd safoni",
"Minimize attached images": "Lleihau delweddau sydd ynghlwm",
"SHOW MEDIA": "DANGOS CYFRYNGAU",
"ActivityPub Specification": "Manyleb GweithgareddPub"
"ActivityPub Specification": "Manyleb GweithgareddPub",
"Dogwhistle words": "Geiriau chwibanogl",
"Content warnings will be added for the following": "Bydd rhybuddion cynnwys yn cael eu hychwanegu ar gyfer y canlynol"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Wechseln Sie zur Moderationsansicht",
"Minimize attached images": "Angehängte Bilder minimieren",
"SHOW MEDIA": "MEDIEN ZEIGEN",
"ActivityPub Specification": "ActivityPub-Spezifikation"
"ActivityPub Specification": "ActivityPub-Spezifikation",
"Dogwhistle words": "Hundepfeife Worte",
"Content warnings will be added for the following": "Inhaltswarnungen werden für Folgendes hinzugefügt"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Μετάβαση σε προβολή εποπτείας",
"Minimize attached images": "Ελαχιστοποιήστε τις συνημμένες εικόνες",
"SHOW MEDIA": "ΔΕΙΤΕ ΜΕΣΑ",
"ActivityPub Specification": "Προδιαγραφές ActivityPub"
"ActivityPub Specification": "Προδιαγραφές ActivityPub",
"Dogwhistle words": "Σφυρίχτρα λέξεις",
"Content warnings will be added for the following": "Θα προστεθούν προειδοποιήσεις περιεχομένου για τα ακόλουθα"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Switch to moderation view",
"Minimize attached images": "Minimize attached images",
"SHOW MEDIA": "SHOW MEDIA",
"ActivityPub Specification": "ActivityPub Specification"
"ActivityPub Specification": "ActivityPub Specification",
"Dogwhistle words": "Dogwhistle words",
"Content warnings will be added for the following": "Content warnings will be added for the following"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Cambiar a la vista de moderación",
"Minimize attached images": "Minimizar imágenes adjuntas",
"SHOW MEDIA": "MOSTRAR MEDIOS",
"ActivityPub Specification": "Especificación de ActivityPub"
"ActivityPub Specification": "Especificación de ActivityPub",
"Dogwhistle words": "Palabras de silbato para perros",
"Content warnings will be added for the following": "Se agregarán advertencias de contenido para lo siguiente"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Passer en mode modération",
"Minimize attached images": "Réduire les images jointes",
"SHOW MEDIA": "AFFICHER LES MÉDIAS",
"ActivityPub Specification": "Spécification ActivityPub"
"ActivityPub Specification": "Spécification ActivityPub",
"Dogwhistle words": "Mots de sifflet de chien",
"Content warnings will be added for the following": "Des avertissements de contenu seront ajoutés pour les éléments suivants"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Athraigh go dtí an t-amharc modhnóireachta",
"Minimize attached images": "Íoslaghdaigh íomhánna ceangailte",
"SHOW MEDIA": "Taispeáin MEÁIN",
"ActivityPub Specification": "Sonraíocht ActivityPub"
"ActivityPub Specification": "Sonraíocht ActivityPub",
"Dogwhistle words": "Focail feadóg mhadra",
"Content warnings will be added for the following": "Cuirfear rabhaidh ábhair leis maidir leis na nithe seo a leanas"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "मॉडरेशन दृश्य पर स्विच करें",
"Minimize attached images": "संलग्न छवियों को छोटा करें",
"SHOW MEDIA": "मीडिया दिखाएं",
"ActivityPub Specification": "गतिविधिपब विशिष्टता"
"ActivityPub Specification": "गतिविधिपब विशिष्टता",
"Dogwhistle words": "कुत्ते की सीटी शब्द",
"Content warnings will be added for the following": "निम्नलिखित के लिए सामग्री चेतावनियां जोड़ दी जाएंगी"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Passa alla visualizzazione moderazione",
"Minimize attached images": "Riduci al minimo le immagini allegate",
"SHOW MEDIA": "MOSTRA MEDIA",
"ActivityPub Specification": "Specifica ActivityPub"
"ActivityPub Specification": "Specifica ActivityPub",
"Dogwhistle words": "Parole da fischietto",
"Content warnings will be added for the following": "Verranno aggiunti avvisi sui contenuti per quanto segue"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "モデレートビューに切り替えます",
"Minimize attached images": "添付画像を最小限に抑える",
"SHOW MEDIA": "メディアを表示",
"ActivityPub Specification": "ActivityPubの仕様"
"ActivityPub Specification": "ActivityPubの仕様",
"Dogwhistle words": "犬笛の言葉",
"Content warnings will be added for the following": "以下のコンテンツ警告が追加されます"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "검토 보기로 전환",
"Minimize attached images": "첨부된 이미지 최소화",
"SHOW MEDIA": "미디어 표시",
"ActivityPub Specification": "ActivityPub 사양"
"ActivityPub Specification": "ActivityPub 사양",
"Dogwhistle words": "개 휘파람 단어",
"Content warnings will be added for the following": "다음에 대한 콘텐츠 경고가 추가됩니다."
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Biguherîne bo dîtina moderatoriyê",
"Minimize attached images": "Wêneyên pêvekirî kêm bikin",
"SHOW MEDIA": "MEDYA NÎŞAN DE",
"ActivityPub Specification": "Specification ActivityPub"
"ActivityPub Specification": "Specification ActivityPub",
"Dogwhistle words": "Peyvên kûçikê",
"Content warnings will be added for the following": "Hişyariyên naverokê dê ji bo jêrîn werin zêdekirin"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Overschakelen naar moderatieweergave",
"Minimize attached images": "Bijgevoegde afbeeldingen minimaliseren",
"SHOW MEDIA": "TOON MEDIA",
"ActivityPub Specification": "ActivityPub-specificatie"
"ActivityPub Specification": "ActivityPub-specificatie",
"Dogwhistle words": "Hondenfluitwoorden",
"Content warnings will be added for the following": "Er worden inhoudswaarschuwingen toegevoegd voor het volgende:"
}

View File

@ -561,5 +561,7 @@
"Switch to moderation view": "Switch to moderation view",
"Minimize attached images": "Minimize attached images",
"SHOW MEDIA": "SHOW MEDIA",
"ActivityPub Specification": "ActivityPub Specification"
"ActivityPub Specification": "ActivityPub Specification",
"Dogwhistle words": "Dogwhistle words",
"Content warnings will be added for the following": "Content warnings will be added for the following"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Przełącz na widok moderacji",
"Minimize attached images": "Zminimalizuj załączone obrazy",
"SHOW MEDIA": "POKAŻ MEDIA",
"ActivityPub Specification": "Specyfikacja ActivityPub"
"ActivityPub Specification": "Specyfikacja ActivityPub",
"Dogwhistle words": "Słowa gwizdka na psa",
"Content warnings will be added for the following": "Ostrzeżenia dotyczące treści zostaną dodane do następujących"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Mudar para a visualização de moderação",
"Minimize attached images": "Minimizar imagens anexadas",
"SHOW MEDIA": "MOSTRAR MÍDIA",
"ActivityPub Specification": "Especificação do ActivityPub"
"ActivityPub Specification": "Especificação do ActivityPub",
"Dogwhistle words": "Palavras de apito",
"Content warnings will be added for the following": "Avisos de conteúdo serão adicionados para os seguintes"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Перейти в режим модерации",
"Minimize attached images": "Свернуть прикрепленные изображения",
"SHOW MEDIA": "ПОКАЗАТЬ МЕДИА",
"ActivityPub Specification": "Спецификация ActivityPub"
"ActivityPub Specification": "Спецификация ActivityPub",
"Dogwhistle words": "Собачий свисток",
"Content warnings will be added for the following": "Предупреждения о содержании будут добавлены для следующих"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Badili hadi mwonekano wa udhibiti",
"Minimize attached images": "Punguza picha zilizoambatishwa",
"SHOW MEDIA": "ONESHA VYOMBO VYA HABARI",
"ActivityPub Specification": "Vipimo vya ActivityPub"
"ActivityPub Specification": "Vipimo vya ActivityPub",
"Dogwhistle words": "Maneno ya mbwa",
"Content warnings will be added for the following": "Maonyo ya maudhui yataongezwa kwa yafuatayo"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Denetleme görünümüne geç",
"Minimize attached images": "Ekli resimleri simge durumuna küçült",
"SHOW MEDIA": "MEDYA GÖSTER",
"ActivityPub Specification": "ActivityPub Spesifikasyonu"
"ActivityPub Specification": "ActivityPub Spesifikasyonu",
"Dogwhistle words": "İtiraf sözleri",
"Content warnings will be added for the following": "Aşağıdakiler için içerik uyarıları eklenecek"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "Перейти до режиму модерації",
"Minimize attached images": "Мінімізуйте вкладені зображення",
"SHOW MEDIA": "ПОКАЗАТИ ЗМІ",
"ActivityPub Specification": "Специфікація ActivityPub"
"ActivityPub Specification": "Специфікація ActivityPub",
"Dogwhistle words": "Собачі слова",
"Content warnings will be added for the following": "Попередження про вміст буде додано для наступних"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "באַשטימען צו מאַדעריישאַן מיינונג",
"Minimize attached images": "מינאַמייז אַטאַטשט בילדער",
"SHOW MEDIA": "ווייַז מעדיע",
"ActivityPub Specification": "ActivityPub באַשרייַבונג"
"ActivityPub Specification": "ActivityPub באַשרייַבונג",
"Dogwhistle words": "דאָגווהיסטלע ווערטער",
"Content warnings will be added for the following": "אינהאַלט וואָרנינגז וועט זיין מוסיף פֿאַר די פאלגענדע"
}

View File

@ -565,5 +565,7 @@
"Switch to moderation view": "切换到审核视图",
"Minimize attached images": "最小化附加图像",
"SHOW MEDIA": "展示媒体",
"ActivityPub Specification": "ActivityPub 规范"
"ActivityPub Specification": "ActivityPub 规范",
"Dogwhistle words": "狗哨的话",
"Content warnings will be added for the following": "将为以下内容添加内容警告"
}

View File

@ -549,7 +549,7 @@ def html_newswire_mobile(css_cache: {}, base_dir: str, nickname: str,
def html_edit_newswire(css_cache: {}, translate: {}, base_dir: str, path: str,
domain: str, port: int, http_prefix: str,
default_timeline: str, theme: str,
access_keys: {}) -> str:
access_keys: {}, dogwhistles: {}) -> str:
"""Shows the edit newswire screen
"""
if '/users/' not in path:
@ -645,6 +645,22 @@ def html_edit_newswire(css_cache: {}, translate: {}, base_dir: str, path: str,
'name="filteredWordsNewswire" style="height:50vh" ' + \
'spellcheck="true">' + filter_str + '</textarea>\n'
dogwhistle_str = ''
for whistle, category in dogwhistles.items():
if not category:
continue
dogwhistle_str += whistle + ' -> ' + category + '\n'
edit_newswire_form += \
' <br><b><label class="labels">' + \
translate['Dogwhistle words'] + '</label></b>\n'
edit_newswire_form += ' <br><label class="labels">' + \
translate['Content warnings will be added for the following'] + \
':</label>'
edit_newswire_form += ' <textarea id="message" ' + \
'name="dogwhistleWords" style="height:50vh" ' + \
'spellcheck="true">' + dogwhistle_str + '</textarea>\n'
hashtag_rules_str = ''
hashtag_rules_filename = \
base_dir + '/accounts/hashtagrules.txt'

View File

@ -38,7 +38,8 @@ def html_confirm_delete(server, css_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str) -> str:
cw_lists: {}, lists_enabled: str,
dogwhistles: {}) -> str:
"""Shows a screen asking to confirm the deletion of a post
"""
if '/statuses/' not in message_id:
@ -90,7 +91,7 @@ def html_confirm_delete(server, css_cache: {},
theme_name, system_language, max_like_count,
False, False, False, False, False, False,
cw_lists, lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
delete_post_str += '<center>'
delete_post_str += \
' <p class="followText">' + \

View File

@ -224,7 +224,8 @@ def html_new_post(css_cache: {}, media_instance: bool, translate: {},
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
box_name: str,
reply_is_chat: bool, bold_reading: bool) -> str:
reply_is_chat: bool, bold_reading: bool,
dogwhistles: {}) -> str:
"""New post screen
"""
reply_str = ''
@ -303,7 +304,7 @@ def html_new_post(css_cache: {}, media_instance: bool, translate: {},
False, False, False,
cw_lists, lists_enabled,
timezone, False,
bold_reading)
bold_reading, dogwhistles)
reply_str = '<input type="hidden" ' + \
'name="replyTo" value="' + in_reply_to + '">\n'

View File

@ -38,7 +38,8 @@ def _html_front_screen_posts(recent_posts_cache: {}, max_recent_posts: int,
max_like_count: int,
signing_priv_key_pem: str, cw_lists: {},
lists_enabled: str,
bold_reading: bool) -> str:
bold_reading: bool,
dogwhistles: {}) -> str:
"""Shows posts on the front screen of a news instance
These should only be public blog posts from the features timeline
which is the blog timeline of the news actor
@ -89,7 +90,7 @@ def _html_front_screen_posts(recent_posts_cache: {}, max_recent_posts: int,
True, False, False,
cw_lists, lists_enabled,
timezone, False,
bold_reading)
bold_reading, dogwhistles)
if post_str:
profile_str += post_str + separator_str
ctr += 1
@ -120,7 +121,8 @@ def html_front_screen(signing_priv_key_pem: str,
extra_json: {},
page_number: int,
max_items_per_page: int,
cw_lists: {}, lists_enabled: str) -> str:
cw_lists: {}, lists_enabled: str,
dogwhistles: {}) -> str:
"""Show the news instance front screen
"""
bold_reading = False
@ -195,7 +197,7 @@ def html_front_screen(signing_priv_key_pem: str,
max_like_count,
signing_priv_key_pem,
cw_lists, lists_enabled,
bold_reading) + license_str
bold_reading, dogwhistles) + license_str
# Footer which is only used for system accounts
profile_footer_str = ' </td>\n'

View File

@ -41,7 +41,7 @@ def html_likers_of_post(base_dir: str, nickname: str,
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
box_name: str, default_timeline: str,
bold_reading: bool,
bold_reading: bool, dogwhistles: {},
dict_name: str = 'likes') -> str:
"""Returns html for a screen showing who liked a post
"""
@ -107,7 +107,8 @@ def html_likers_of_post(base_dir: str, nickname: str,
False, False, False,
False, False, False,
cw_lists, lists_enabled,
timezone, mitm, bold_reading)
timezone, mitm, bold_reading,
dogwhistles)
# show likers beneath the post
obj = post_json_object

View File

@ -57,7 +57,8 @@ def html_moderation(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the moderation feed as html
This is what you see when selecting the "mod" timeline
"""
@ -83,7 +84,7 @@ def html_moderation(css_cache: {}, default_timeline: str,
text_mode_banner, access_keys, system_language,
max_like_count, shared_items_federated_domains,
signing_priv_key_pem, cw_lists, lists_enabled,
timezone, bold_reading)
timezone, bold_reading, dogwhistles)
def html_account_info(css_cache: {}, translate: {},

View File

@ -62,6 +62,7 @@ from utils import get_domain_from_actor
from utils import acct_dir
from utils import local_actor_url
from utils import is_unlisted_post
from content import detect_dogwhistles
from content import create_edits_html
from content import bold_reading_string
from content import limit_repeated_words
@ -1555,6 +1556,30 @@ def _substitute_onion_domains(base_dir: str, content: str) -> str:
return content
def _add_dogwhistle_warnings(summary: str, content: str,
dogwhistles: {}, translate: {}) -> {}:
"""Adds dogwhistle warnings for the given content
"""
if not dogwhistles:
return summary
content_str = str(summary) + ' ' + content
detected = detect_dogwhistles(content_str, dogwhistles)
if not detected:
return summary
for _, item in detected.items():
if not item.get('category'):
continue
whistle_str = item['category']
if translate.get(whistle_str):
whistle_str = translate[whistle_str]
if summary:
summary += ', ' + whistle_str
else:
summary = whistle_str
return summary
def individual_post_as_html(signing_priv_key_pem: str,
allow_downloads: bool,
recent_posts_cache: {}, max_recent_posts: int,
@ -1583,7 +1608,8 @@ def individual_post_as_html(signing_priv_key_pem: str,
cw_lists: {},
lists_enabled: str,
timezone: str,
mitm: bool, bold_reading: bool) -> str:
mitm: bool, bold_reading: bool,
dogwhistles: {}) -> str:
""" Shows a single post as html
"""
if not post_json_object:
@ -2142,6 +2168,10 @@ def individual_post_as_html(signing_priv_key_pem: str,
if content_str:
summary_str = get_summary_from_post(post_json_object, system_language,
languages_understood)
# add dogwhistle warnings to summary
summary_str = _add_dogwhistle_warnings(summary_str, content_str,
dogwhistles, translate)
content_all_str = str(summary_str) + ' ' + content_str
# does an emoji indicate a no boost preference?
# if so then don't show the repeat/announce icon
@ -2378,7 +2408,7 @@ def html_individual_post(recent_posts_cache: {}, max_recent_posts: int,
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, mitm: bool,
bold_reading: bool) -> str:
bold_reading: bool, dogwhistles: {}) -> str:
"""Show an individual post as html
"""
original_post_json = post_json_object
@ -2447,7 +2477,7 @@ def html_individual_post(recent_posts_cache: {}, max_recent_posts: int,
system_language, max_like_count,
False, authorized, False, False, False, False,
cw_lists, lists_enabled, timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
message_id = remove_id_ending(post_json_object['id'])
# show the previous posts
@ -2488,7 +2518,8 @@ def html_individual_post(recent_posts_cache: {}, max_recent_posts: int,
False, False, False, False,
cw_lists, lists_enabled,
timezone, mitm,
bold_reading) + post_str
bold_reading,
dogwhistles) + post_str
# show the following posts
post_filename = locate_post(base_dir, nickname, domain, message_id)
@ -2527,7 +2558,7 @@ def html_individual_post(recent_posts_cache: {}, max_recent_posts: int,
False, False, False, False,
cw_lists, lists_enabled,
timezone, False,
bold_reading)
bold_reading, dogwhistles)
css_filename = base_dir + '/epicyon-profile.css'
if os.path.isfile(base_dir + '/epicyon.css'):
css_filename = base_dir + '/epicyon.css'
@ -2555,7 +2586,8 @@ def html_post_replies(recent_posts_cache: {}, max_recent_posts: int,
max_like_count: int,
signing_priv_key_pem: str, cw_lists: {},
lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the replies to an individual post as html
"""
replies_str = ''
@ -2582,7 +2614,7 @@ def html_post_replies(recent_posts_cache: {}, max_recent_posts: int,
False, False,
cw_lists, lists_enabled,
timezone, False,
bold_reading)
bold_reading, dogwhistles)
css_filename = base_dir + '/epicyon-profile.css'
if os.path.isfile(base_dir + '/epicyon.css'):
@ -2611,7 +2643,8 @@ def html_emoji_reaction_picker(recent_posts_cache: {}, max_recent_posts: int,
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
box_name: str, page_number: int,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Returns the emoji picker screen
"""
reacted_to_post_str = \
@ -2635,7 +2668,7 @@ def html_emoji_reaction_picker(recent_posts_cache: {}, max_recent_posts: int,
max_like_count,
False, False, False, False, False, False,
cw_lists, lists_enabled, timezone, False,
bold_reading)
bold_reading, dogwhistles)
reactions_filename = base_dir + '/emoji/reactions.json'
if not os.path.isfile(reactions_filename):

View File

@ -148,7 +148,7 @@ def html_profile_after_search(recent_posts_cache: {}, max_recent_posts: int,
cw_lists: {}, lists_enabled: str,
timezone: str,
onion_domain: str, i2p_domain: str,
bold_reading: bool) -> str:
bold_reading: bool, dogwhistles: {}) -> str:
"""Show a profile page after a search for a fediverse address
"""
http = False
@ -388,7 +388,7 @@ def html_profile_after_search(recent_posts_cache: {}, max_recent_posts: int,
False, False, False,
cw_lists, lists_enabled,
timezone, False,
bold_reading)
bold_reading, dogwhistles)
i += 1
if i >= 8:
break
@ -636,7 +636,7 @@ def html_profile(signing_priv_key_pem: str,
system_language, max_like_count,
shared_items_federated_domains, None,
page_number, max_items_per_page, cw_lists,
lists_enabled)
lists_enabled, {})
domain, port = get_domain_from_actor(profile_json['id'])
if not domain:
@ -1031,7 +1031,7 @@ def html_profile(signing_priv_key_pem: str,
max_like_count,
signing_priv_key_pem,
cw_lists, lists_enabled,
timezone, bold_reading) + license_str
timezone, bold_reading, {}) + license_str
if not is_group:
if selected == 'following':
profile_str += \
@ -1104,7 +1104,8 @@ def _html_profile_posts(recent_posts_cache: {}, max_recent_posts: int,
max_like_count: int,
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Shows posts on the profile screen
These should only be public posts
"""
@ -1154,7 +1155,7 @@ def _html_profile_posts(recent_posts_cache: {}, max_recent_posts: int,
True, False, False,
cw_lists, lists_enabled,
timezone, False,
bold_reading)
bold_reading, dogwhistles)
if post_str:
profile_str += post_str + separator_str
ctr += 1

View File

@ -621,7 +621,8 @@ def html_history_search(translate: {}, base_dir: str,
signing_priv_key_pem: str,
cw_lists: {},
lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show a page containing search results for your post history
"""
if historysearch.startswith("'"):
@ -709,7 +710,8 @@ def html_history_search(translate: {}, base_dir: str,
show_individual_post_icons,
False, False, False, False,
cw_lists, lists_enabled,
timezone, False, bold_reading)
timezone, False, bold_reading,
dogwhistles)
if post_str:
history_search_form += separator_str + post_str
index += 1
@ -734,7 +736,8 @@ def html_hashtag_search(nickname: str, domain: str, port: int,
max_like_count: int,
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show a page containing search results for a hashtag
or after selecting a hashtag from the swarm
"""
@ -897,7 +900,7 @@ def html_hashtag_search(nickname: str, domain: str, port: int,
show_public_only,
store_to_sache, False, cw_lists,
lists_enabled, timezone, False,
bold_reading)
bold_reading, dogwhistles)
if post_str:
hashtag_search_form += separator_str + post_str
index += 1

View File

@ -493,7 +493,8 @@ def html_timeline(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the timeline as html
"""
enable_timing_log = False
@ -1000,7 +1001,7 @@ def html_timeline(css_cache: {}, default_timeline: str,
False, True, use_cache_only,
cw_lists, lists_enabled,
timezone, mitm,
bold_reading)
bold_reading, dogwhistles)
_log_timeline_timing(enable_timing_log,
timeline_start_time, box_name, '12')
@ -1238,7 +1239,8 @@ def html_shares(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the shares timeline as html
"""
manually_approve_followers = \
@ -1269,7 +1271,7 @@ def html_shares(css_cache: {}, default_timeline: str,
shared_items_federated_domains,
signing_priv_key_pem,
cw_lists, lists_enabled, timezone,
bold_reading)
bold_reading, dogwhistles)
def html_wanted(css_cache: {}, default_timeline: str,
@ -1298,7 +1300,8 @@ def html_wanted(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the wanted timeline as html
"""
manually_approve_followers = \
@ -1329,7 +1332,7 @@ def html_wanted(css_cache: {}, default_timeline: str,
shared_items_federated_domains,
signing_priv_key_pem,
cw_lists, lists_enabled, timezone,
bold_reading)
bold_reading, dogwhistles)
def html_inbox(css_cache: {}, default_timeline: str,
@ -1359,7 +1362,8 @@ def html_inbox(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the inbox as html
"""
manually_approve_followers = \
@ -1390,7 +1394,7 @@ def html_inbox(css_cache: {}, default_timeline: str,
shared_items_federated_domains,
signing_priv_key_pem,
cw_lists, lists_enabled, timezone,
bold_reading)
bold_reading, dogwhistles)
def html_bookmarks(css_cache: {}, default_timeline: str,
@ -1420,7 +1424,8 @@ def html_bookmarks(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the bookmarks as html
"""
manually_approve_followers = \
@ -1450,7 +1455,7 @@ def html_bookmarks(css_cache: {}, default_timeline: str,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone,
bold_reading)
bold_reading, dogwhistles)
def html_inbox_dms(css_cache: {}, default_timeline: str,
@ -1480,7 +1485,8 @@ def html_inbox_dms(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the DM timeline as html
"""
artist = is_artist(base_dir, nickname)
@ -1506,7 +1512,7 @@ def html_inbox_dms(css_cache: {}, default_timeline: str,
shared_items_federated_domains,
signing_priv_key_pem,
cw_lists, lists_enabled, timezone,
bold_reading)
bold_reading, dogwhistles)
def html_inbox_replies(css_cache: {}, default_timeline: str,
@ -1536,7 +1542,8 @@ def html_inbox_replies(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the replies timeline as html
"""
artist = is_artist(base_dir, nickname)
@ -1560,7 +1567,8 @@ def html_inbox_replies(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone, bold_reading)
cw_lists, lists_enabled, timezone, bold_reading,
dogwhistles)
def html_inbox_media(css_cache: {}, default_timeline: str,
@ -1590,7 +1598,8 @@ def html_inbox_media(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the media timeline as html
"""
artist = is_artist(base_dir, nickname)
@ -1614,7 +1623,8 @@ def html_inbox_media(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone, bold_reading)
cw_lists, lists_enabled, timezone, bold_reading,
dogwhistles)
def html_inbox_blogs(css_cache: {}, default_timeline: str,
@ -1644,7 +1654,8 @@ def html_inbox_blogs(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the blogs timeline as html
"""
artist = is_artist(base_dir, nickname)
@ -1668,7 +1679,8 @@ def html_inbox_blogs(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone, bold_reading)
cw_lists, lists_enabled, timezone, bold_reading,
dogwhistles)
def html_inbox_features(css_cache: {}, default_timeline: str,
@ -1699,7 +1711,8 @@ def html_inbox_features(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the features timeline as html
"""
return html_timeline(css_cache, default_timeline,
@ -1722,7 +1735,8 @@ def html_inbox_features(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone, bold_reading)
cw_lists, lists_enabled, timezone, bold_reading,
dogwhistles)
def html_inbox_news(css_cache: {}, default_timeline: str,
@ -1752,7 +1766,8 @@ def html_inbox_news(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the news timeline as html
"""
return html_timeline(css_cache, default_timeline,
@ -1775,7 +1790,8 @@ def html_inbox_news(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone, bold_reading)
cw_lists, lists_enabled, timezone, bold_reading,
dogwhistles)
def html_outbox(css_cache: {}, default_timeline: str,
@ -1805,7 +1821,8 @@ def html_outbox(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, bold_reading: bool) -> str:
timezone: str, bold_reading: bool,
dogwhistles: {}) -> str:
"""Show the Outbox as html
"""
manually_approve_followers = \
@ -1831,4 +1848,5 @@ def html_outbox(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone, bold_reading)
cw_lists, lists_enabled, timezone, bold_reading,
dogwhistles)