mirror of https://git.skyjake.fi/gemini/bubble.git
605 lines
25 KiB
Python
605 lines
25 KiB
Python
import math
|
|
import pytz
|
|
from utils import *
|
|
from model import Notification, User, Subspace, FOLLOW_USER, FOLLOW_SUBSPACE, \
|
|
MUTE_USER, MUTE_SUBSPACE
|
|
|
|
|
|
def modify_reactions(db, user, user_flag, notif_type):
|
|
notif = user.notif
|
|
if user.flags & user_flag:
|
|
notif |= notif_type
|
|
else:
|
|
notif &= ~notif_type
|
|
db.update_user(user, flags=user.flags ^ user_flag, notif=notif)
|
|
|
|
|
|
def make_settings_page(session):
|
|
req = session.req
|
|
db = session.db
|
|
user = session.user
|
|
page = ''
|
|
|
|
if not user:
|
|
return 60, 'Login required'
|
|
|
|
if req.path == session.path + 'settings/avatar':
|
|
AVATARS = [
|
|
'Human',
|
|
('👤', 'silhouette of person'),
|
|
('🙂', 'smily face'),
|
|
('😎', 'smiling face with sunglasses'),
|
|
('🤠', 'cowboy face'),
|
|
('😈', 'smiling face with horns'),
|
|
('👻', 'ghost'),
|
|
('💀', 'skull'),
|
|
('❤️', 'red heart'),
|
|
|
|
'Animals',
|
|
('😺', 'cat face'),
|
|
('🐶', 'dog face'),
|
|
('🐸', 'frog face'),
|
|
('🐵', 'monkey face'),
|
|
('🐷', 'pig face'),
|
|
('🐻', 'bear face'),
|
|
('🐦', 'bird'),
|
|
('🐧', 'penguin'),
|
|
('🐝', 'bee'),
|
|
('🦋', 'butterfly'),
|
|
('🦀', 'crab'),
|
|
('🐐', 'goat'),
|
|
('🦥', 'sloth'),
|
|
('🦎', 'lizard'),
|
|
('🦂', 'scorpion'),
|
|
('🐉', 'dragon'),
|
|
|
|
'Nature',
|
|
('🍀', 'four leaf clover'),
|
|
('🌻', 'sunflower'),
|
|
('🌝', 'full moon with face'),
|
|
('🌙', 'crescent moon'),
|
|
('☀️', 'sun'),
|
|
('🌧️', 'cloud with rain'),
|
|
('🌲', 'evergreen tree'),
|
|
('🍄', 'mushroom'),
|
|
('🌊', 'wave'),
|
|
('🔥', 'fire'),
|
|
('❄', 'snowflake'),
|
|
|
|
'Space',
|
|
('🪐', 'planet'),
|
|
('🛰️', 'satellite'),
|
|
('🚀', 'rocket'),
|
|
('🛸', 'UFO'),
|
|
('👽', 'alien'),
|
|
('👾', 'alien monster'),
|
|
('🔭', 'telescope'),
|
|
|
|
'Tech',
|
|
('🤖', 'robot face'),
|
|
('🖥️', 'desktop computer'),
|
|
('📱', 'mobile phone'),
|
|
('📷', 'camera'),
|
|
('📻', 'radio'),
|
|
('🎧', 'headphones'),
|
|
('🕹️', 'joystick'),
|
|
('🎮', 'video game controller'),
|
|
('📡', 'satellite antenna'),
|
|
('♊️', 'Gemini'),
|
|
|
|
'Food',
|
|
('☕️', 'hot beverage'),
|
|
('🍵', 'teacup without handle'),
|
|
('🍺', 'beer mug'),
|
|
('🍎', 'red apple'),
|
|
('🍭', 'lollipop'),
|
|
('🧇', 'waffle'),
|
|
|
|
'Hobbies',
|
|
('🎵', 'musical note'),
|
|
('🧩', 'puzzle piece'),
|
|
('🎲', 'game die'),
|
|
('🎱', 'billiards'),
|
|
('⚽️', 'soccer ball'),
|
|
('🏈', 'American football'),
|
|
('🧶', 'yarn'),
|
|
('🏕️', 'camping'),
|
|
('⛄️', 'snowman'),
|
|
|
|
'Travel',
|
|
('🛞', 'wheel'),
|
|
('🚲', 'bicycle'),
|
|
('🏍️', 'motorcycle'),
|
|
('🚗', 'car'),
|
|
('⛵️', 'sailboat'),
|
|
('✈️', 'airplane'),
|
|
('🚂', 'steam locomotive'),
|
|
('⛱️', 'beach umbrella'),
|
|
|
|
'Miscellaneous',
|
|
('👺', 'goblin'),
|
|
('☯️', 'yin yang'),
|
|
]
|
|
if not is_empty_query(req):
|
|
try:
|
|
idx = int(req.query)
|
|
if not isinstance(AVATARS[idx], tuple):
|
|
return 51, 'Not found'
|
|
db.update_user(session.user, avatar=AVATARS[idx][0])
|
|
return 30, '/settings/profile'
|
|
except Exception as x:
|
|
import traceback; traceback.print_tb(x.__traceback__)
|
|
print(x)
|
|
return 51, 'Invalid avatar index'
|
|
page = f'Select avatar for u/{session.user.name}:\n\n'
|
|
for idx, avatar in enumerate(AVATARS):
|
|
if isinstance(avatar, str):
|
|
page += '### ' + avatar + '\n'
|
|
else:
|
|
avatar, name = avatar
|
|
page += f'=> ?{idx} {avatar} {name}\n'
|
|
|
|
note = session.bubble.cfg.get('avatar.note', '')
|
|
if note:
|
|
page += '\n' + note
|
|
return page
|
|
|
|
elif req.path == session.path + 'settings/feed-title':
|
|
if req.query is None:
|
|
return 10, f'Enter title for the u/{user.name} Gemini and Tinylog feeds:'
|
|
user_sub = db.get_subspace(owner=user)
|
|
print(user_sub.id)
|
|
db.update_subspace(user_sub, info=clean_query(req))
|
|
return 30, '/settings/profile'
|
|
|
|
elif req.path == session.path + 'settings/info':
|
|
if req.query == None:
|
|
return 10, 'Enter profile description:'
|
|
db.update_user(session.user, info=clean_query(req))
|
|
return 30, '/settings/profile'
|
|
|
|
elif req.path == session.path + 'settings/email':
|
|
if req.query == None:
|
|
return 10, 'Enter email to send notifications to:'
|
|
email = clean_query(req)
|
|
if email and not '@' in email:
|
|
return 50, 'Invalid email address'
|
|
db.update_user(session.user, email=email)
|
|
return 30, '/settings/notif'
|
|
|
|
elif req.path == session.path + 'settings/email-inter':
|
|
prompt = 'Interval for sending emails: (Examples: "30", "15 mins", "4h")'
|
|
if req.query == None:
|
|
return 10, prompt
|
|
found = re.search(r'^(\d+)\s*(\w*)$', clean_query(req))
|
|
if not found:
|
|
return 10, prompt
|
|
value = int(found[1])
|
|
unit = found[2][0] if found[2] else 'm'
|
|
if unit.lower() == 'h':
|
|
value *= 60
|
|
elif unit.lower() != 'm':
|
|
return 10, prompt
|
|
db.update_user(session.user, email_inter=value)
|
|
return 30, '/settings/notif'
|
|
|
|
elif req.path == session.path + 'settings/email-range':
|
|
prompt = f'{session.tz.localize(datetime.datetime.now()).tzname()} hour range (inclusive) when emails are not sent: (Examples: "0-6", "21-3")'
|
|
pattern = re.compile(r'(\d+)-(\d+)')
|
|
if req.query == None:
|
|
return 10, prompt
|
|
found = pattern.search(clean_query(req))
|
|
if not found:
|
|
return 10, prompt
|
|
begin, end = int(found[1]), int(found[2])
|
|
begin = max(0, begin)
|
|
begin = min(23, begin)
|
|
end = max(0, end)
|
|
end = min(23, end)
|
|
# Convert from user's time zone to UTC. The actual date doesn't matter,
|
|
# we are just using the hours.
|
|
dt_begin = session.tz.localize(datetime.datetime(2023, 6, 19, begin, 0, 0))
|
|
dt_end = session.tz.localize(datetime.datetime(2023, 6, 19, end, 0, 0))
|
|
begin = dt_begin.astimezone(pytz.utc).hour
|
|
end = dt_end.astimezone(pytz.utc).hour
|
|
db.update_user(session.user, email_range=f"{begin}-{end}")
|
|
return 30, '/settings/notif'
|
|
|
|
elif req.path == session.path + 'settings/url':
|
|
if req.query == None:
|
|
return 10, 'Featured link: (URL followed by label, separate with space)'
|
|
try:
|
|
link = form_link(parse_link_segment_query(req))
|
|
except:
|
|
link = ''
|
|
db.update_user(session.user, url=link)
|
|
return 30, '/settings/profile'
|
|
|
|
elif req.path == session.path + 'settings/profile':
|
|
user_sub = db.get_subspace(owner=user)
|
|
|
|
page += f'# {session.user.name}: Settings\n'
|
|
page += '=> /settings ⚙️ Go back\n'
|
|
|
|
page += '\n## Profile\n\n'
|
|
page += f'=> /settings/avatar Avatar: {session.user.avatar}\n'
|
|
page += f'=> /settings/feed-title Feed title: {user_sub.info if user_sub.info else user.name}\n'
|
|
|
|
page += '\n### Description\n'
|
|
page += (session.user.info if session.user.info else '(no description)') + '\n'
|
|
page += f'=> /settings/info Edit\n'
|
|
|
|
page += '\n### Featured Link\n'
|
|
page += (f'=> {session.user.url}' if session.user.url else '(no featured link)') + '\n'
|
|
page += f'=> /settings/url Edit\n'
|
|
return page
|
|
|
|
elif req.path == session.path + 'settings/sort-feed':
|
|
db.update_user(session.user,
|
|
sort_post=(User.SORT_POST_HOTNESS
|
|
if session.user.sort_post == User.SORT_POST_RECENT else
|
|
User.SORT_POST_RECENT))
|
|
return 30, '/settings'
|
|
|
|
elif req.path == session.path + 'settings/sort-cmt':
|
|
db.update_user(session.user,
|
|
sort_cmt=(User.SORT_COMMENT_OLDEST
|
|
if session.user.sort_cmt == User.SORT_COMMENT_NEWEST else
|
|
User.SORT_COMMENT_NEWEST))
|
|
return 30, '/settings'
|
|
|
|
elif req.path == session.path + 'settings/ascii':
|
|
db.update_user(session.user, flags=(session.user.flags ^ User.ASCII_ICONS_FLAG))
|
|
return 30, './display'
|
|
|
|
elif req.path == session.path + 'settings/short-preview':
|
|
db.update_user(session.user, flags=(session.user.flags ^ User.SHORT_PREVIEW_FLAG))
|
|
return 30, './display'
|
|
|
|
elif req.path == session.path + 'settings/notif':
|
|
notif = session.user.notif
|
|
|
|
if req.query is None:
|
|
NOTIFS = {
|
|
Notification.MENTION: f'@{session.user.name} is mentioned',
|
|
Notification.COMMENT: 'Comment on your post',
|
|
Notification.LIKE: 'Your post is liked',
|
|
Notification.THANKS: 'You receive thanks',
|
|
Notification.REACTION: 'Reaction on your post',
|
|
Notification.ISSUE_CLOSED: 'Your issue is closed',
|
|
Notification.ADDED_AS_MODERATOR: 'Added as moderator',
|
|
Notification.REMOVED_AS_MODERATOR: 'Removed as moderator',
|
|
|
|
Notification.COMMENT_ON_COMMENTED: 'Reply in discussion',
|
|
Notification.COMMENT_ON_FOLLOWED_POST: 'Comment in followed post',
|
|
Notification.POST_BY_FOLLOWED_USER: 'Post by followed user',
|
|
Notification.COMMENT_BY_FOLLOWED_USER: 'Comment by followed user',
|
|
Notification.POST_IN_FOLLOWED_SUBSPACE: 'Post in followed subspace',
|
|
Notification.COMMENT_IN_FOLLOWED_SUBSPACE: 'Comment in followed subspace',
|
|
Notification.NEW_POLL: 'New poll is posted',
|
|
}
|
|
|
|
if not session.is_likes_enabled():
|
|
del NOTIFS[Notification.LIKE]
|
|
if not session.is_reactions_enabled():
|
|
del NOTIFS[Notification.REACTION]
|
|
if not session.is_thanks_enabled():
|
|
del NOTIFS[Notification.THANKS]
|
|
|
|
page += f'# {session.user.name}: Settings\n'
|
|
page += '=> /settings ⚙️ Go back\n'
|
|
|
|
page += '\n## Notifications\n\n'
|
|
page += '### Involving You\n'
|
|
for nt in NOTIFS:
|
|
if nt == Notification.COMMENT_ON_COMMENTED:
|
|
page += '\n### Followed\n'
|
|
page += f'=> /settings/notif?{nt} {session.CHECKS[nonzero(session.user.notif & nt)]} {NOTIFS[nt]}\n'
|
|
page += '\n=> /settings/notif?all Enable all\n'
|
|
page += '=> /settings/notif?none Disable all\n'
|
|
|
|
page += '\n### Email\n'
|
|
page += f'=> /settings/email 📧 Send to: {session.user.email if session.user.email else "(not set)"}\n'
|
|
page += f'\n=> /settings/email-inter Interval: {session.user.email_inter} minutes\n'
|
|
#email_range = "(not set)" if not session.user.email_range else session.user.email_range
|
|
|
|
# Convert the range to local hours.
|
|
if session.user.email_range:
|
|
begin, end = map(int, session.user.email_range.split('-'))
|
|
# The date doesn't matter, we are just using the hours.
|
|
dt_begin = datetime.datetime(2023, 6, 19, begin, 0, 0, tzinfo=UTC)
|
|
dt_end = datetime.datetime(2023, 6, 19, end, 0, 0, tzinfo=UTC)
|
|
dt_begin = dt_begin.astimezone(session.tz)
|
|
dt_end = dt_end.astimezone(session.tz)
|
|
email_range = f"{dt_begin.hour}-{dt_end.hour}"
|
|
else:
|
|
email_range = "(not set)"
|
|
|
|
page += f'=> /settings/email-range "Do not disturb" range: {email_range}\n'
|
|
|
|
return page
|
|
|
|
q = clean_query(req)
|
|
if q == 'all':
|
|
notif = 0xffff
|
|
elif q == 'none':
|
|
notif = 0
|
|
else:
|
|
notif ^= max(0, int(clean_query(req)))
|
|
db.update_user(session.user, notif=notif)
|
|
return 30, '/settings/notif'
|
|
|
|
elif req.path == session.path + 'settings/omit-all':
|
|
user_space = db.get_subspace(owner=session.user.id)
|
|
db.update_subspace(user_space, flags=user_space.flags ^ Subspace.OMIT_FROM_ALL_FLAG)
|
|
return 30, '/settings'
|
|
|
|
elif req.path == session.path + 'settings/omit-feed':
|
|
user_space = db.get_subspace(owner=session.user.id)
|
|
db.update_subspace(user_space, flags=user_space.flags ^ Subspace.OMIT_FROM_FEED_BY_DEFAULT)
|
|
return 30, '/settings'
|
|
|
|
elif req.path == session.path + 'settings/likes':
|
|
modify_reactions(db, user, User.HIDE_LIKES_FLAG, Notification.LIKE)
|
|
return 30, '/settings/feedback'
|
|
|
|
elif req.path == session.path + 'settings/thanks':
|
|
modify_reactions(db, user, User.HIDE_THANKS_FLAG, Notification.THANKS)
|
|
return 30, '/settings/feedback'
|
|
|
|
elif req.path == session.path + 'settings/reactions':
|
|
modify_reactions(db, user, User.HIDE_REACTIONS_FLAG, Notification.REACTION)
|
|
return 30, '/settings/feedback'
|
|
|
|
elif req.path == session.path + 'settings/feedback':
|
|
page += f'# {session.user.name}: Settings\n'
|
|
page += '=> /settings ⚙️ Go back\n\n'
|
|
|
|
page += '## Feedback\n\n'
|
|
|
|
if session.bubble.thanks_enabled:
|
|
page += f'=> /settings/thanks {session.CHECKS[is_zero(user.flags & User.HIDE_THANKS_FLAG)]} Enable 🙏 Thanks\n'
|
|
page += 'Thanks are a way of showing appreciation privately. The recipient just receives a Thanks notification.\n'
|
|
|
|
if session.bubble.likes_enabled:
|
|
page += f'=> /settings/likes {session.CHECKS[is_zero(user.flags & User.HIDE_LIKES_FLAG)]} Enable 👍 Likes\n'
|
|
page += 'Likes are public and the names of likers are visible to everyone. Like counts are shown in feeds. '
|
|
page += 'When disabled, you will not see likes on any post nor will you be notified of likes. Others may still like your posts.\n'
|
|
|
|
if len(session.bubble.user_reactions):
|
|
page += f'=> /settings/reactions {session.CHECKS[is_zero(user.flags & User.HIDE_REACTIONS_FLAG)]} Enable 😃 Reactions\n'
|
|
page += 'Reactions are anonymous and the per-reaction totals are shown on post pages. You are notified of new reactions on your posts. The administrator chooses which Emoji can be used.\n'
|
|
page += f'\nAvailable reactions: {session.bubble.user_reactions}\n'
|
|
|
|
return page
|
|
|
|
elif req.path == session.path + 'settings/homefeed':
|
|
db.update_user(session.user, flags=session.user.flags ^ User.HOME_FOLLOWED_FEED_FLAG)
|
|
return 30, '/settings'
|
|
|
|
elif req.path == session.path + 'settings/follow':
|
|
page += f'# {session.user.name}: Settings\n'
|
|
page += '=> /settings ⚙️ Go back\n\n'
|
|
|
|
def make_follow_mute_list(user, is_follow):
|
|
follows = db.get_follows(user) if is_follow else db.get_mutes(user)
|
|
type_user = FOLLOW_USER if is_follow else MUTE_USER
|
|
type_subspace = FOLLOW_SUBSPACE if is_follow else MUTE_SUBSPACE
|
|
fusers = []
|
|
fsubs = []
|
|
fposts = []
|
|
label = 'followed' if is_follow else 'muted'
|
|
page = ''
|
|
|
|
for type, target in follows:
|
|
if type == type_user:
|
|
fu = db.get_user(id=target)
|
|
fusers.append(f'=> /u/{fu.name} {fu.avatar} {fu.name}\n')
|
|
elif type == type_subspace:
|
|
fs = db.get_subspace(id=target)
|
|
fsubs.append(f'=> /{fs.title()} {fs.title()}\n')
|
|
else:
|
|
fp = db.get_post(id=target)
|
|
fposts.append(session.gemini_feed_entry(fp))
|
|
|
|
page += '\n### Users\n'
|
|
if not fusers:
|
|
page += f'No {label} users.\n'
|
|
for u in sorted(fusers, key=str.lower):
|
|
page += u
|
|
|
|
page += '\n### Subspaces\n'
|
|
if not fsubs:
|
|
page += f'No {label} subspaces.\n'
|
|
for s in sorted(fsubs, key=str.lower):
|
|
page += s
|
|
|
|
page += '\n### Posts\n'
|
|
if not fposts:
|
|
page += f'No {label} posts.\n'
|
|
for p in sorted(fposts, reverse=True):
|
|
page += p
|
|
|
|
return page
|
|
|
|
page += '## Followed\n' + make_follow_mute_list(session.user, True)
|
|
page += '\n## Muted\n' + make_follow_mute_list(session.user, False)
|
|
|
|
return page
|
|
|
|
elif req.path.startswith(session.path + 'settings/rename/'):
|
|
found = re.search(r'/([0-9a-zA-Z]{10})$', req.path)
|
|
if not found:
|
|
return 59, 'Bad request'
|
|
token = found[1]
|
|
if not db.verify_token(user, token):
|
|
return 61, 'Not authorized'
|
|
prompt = f'New name for user account "{session.user.name}"? (Warning: Links to /u/{session.user.name} will break!)'
|
|
if is_empty_query(req):
|
|
return 10, prompt
|
|
new_name = clean_query(req)
|
|
if not is_valid_name(new_name):
|
|
return prompt
|
|
try:
|
|
db.update_user(session.user, name=new_name)
|
|
except:
|
|
return 10, prompt
|
|
return 30, '/settings'
|
|
|
|
elif req.path.startswith(session.path + 'settings/delete-account/'):
|
|
if user.role == User.ADMIN:
|
|
return 61, 'Admin users cannot be deleted'
|
|
found = re.search(r'/([0-9a-zA-Z]{10})$', req.path)
|
|
if not found:
|
|
return 59, 'Bad request'
|
|
token = found[1]
|
|
if not db.verify_token(user, token):
|
|
return 61, 'Not authorized'
|
|
if is_empty_query(req):
|
|
return 10, 'Really delete your user account? All posts and comments will be deleted. (Enter DELETE to confirm.)'
|
|
|
|
if req.query == 'DELETE':
|
|
db.destroy_user(user)
|
|
|
|
page = '# User Deleted\n'
|
|
page += f'The user account "{user.name}" has now been deleted.\n\nAll posts and comments made with that account have been removed. You should now deactivate your client certificate.\n'
|
|
page += '\n=> / Back to front page\n'
|
|
return page
|
|
|
|
return 30, '/settings'
|
|
|
|
elif req.path == session.path + 'settings/password':
|
|
if not user:
|
|
return 60, 'Login required'
|
|
PROMPT = "Enter certificate password: (Valid for one hour.)"
|
|
if req.query is None:
|
|
return 11, PROMPT
|
|
else:
|
|
pwd = urlparse.unquote(req.query)
|
|
if pwd == '' or len(pwd) >= 4:
|
|
db.update_user(user, password=pwd)
|
|
else:
|
|
return 11, "That is too short. " + PROMPT
|
|
return 30, '/settings/certs'
|
|
|
|
elif req.path == session.path + 'settings/recovery':
|
|
if req.query is None:
|
|
return 10, 'Enter certificate recovery URL:'
|
|
url = clean_query(req)
|
|
if not url.startswith('gemini://'):
|
|
url = 'gemini://' + url
|
|
db.update_user(session.user, recovery=url)
|
|
return 30, '/settings/certs'
|
|
|
|
elif req.path.startswith(session.path + 'settings/remove-cert/'):
|
|
if not session.user:
|
|
return 60, 'Login required'
|
|
m = re.search(r'/remove-cert/([0-9a-zA-Z]{10})/(\w+)$', req.path)
|
|
if not m:
|
|
return 59, 'Bad request'
|
|
token = m[1]
|
|
fp = m[2]
|
|
if not session.db.verify_token(session.user, token):
|
|
return 61, 'Not authorized'
|
|
if req.query is None:
|
|
return 10, f'Really remove certificate {fp[:10].upper()}? (Enter YES to confirm.)'
|
|
if req.query == 'YES':
|
|
db.remove_certificate(session.user, fp)
|
|
return 30, '/settings/certs'
|
|
|
|
elif req.path == session.path + 'settings/certs':
|
|
page += f'# {session.user.name}: Settings\n'
|
|
page += '=> /settings ⚙️ Go back\n\n'
|
|
|
|
page += '## Certificates\n'
|
|
|
|
exp_mins = math.ceil(user.password_expiry() / 60) if user.ts_password else 0
|
|
page += '\n=> /settings/password 🔑 Certificate password: '
|
|
if exp_mins > 0 and user.password:
|
|
page += f"(valid for {exp_mins} minute{plural_s(exp_mins)})"
|
|
else:
|
|
page += '(not set)'
|
|
page += '\nEnables registering additional certificates to this account.\n'
|
|
|
|
page += f'\n=> /settings/recovery 🛟 Recovery URL: {user.recovery if user.recovery else "(not set)"}\n'
|
|
page += "A PEM certificate at this URL can be registered to your account when needed. Set this URL to point at a file on a host you control. You should keep the URL always configured, but only serve the file temporarily when you want to recover access to your account.\n"
|
|
|
|
page += f'\n### Registered to {user.name}'
|
|
certs = db.get_certificates(user)
|
|
for fp, subject, expiry in certs:
|
|
page += f'\n{fp[:10].upper()} · Expires {expiry.strftime("%Y-%m-%d") if expiry else "?"} · Subject: {subject}\n'
|
|
if fp != req.identity.fp_cert:
|
|
page += f'=> /settings/remove-cert/{db.get_token(user)}/{fp} ❌ Remove\n'
|
|
else:
|
|
page += '\n'
|
|
return page
|
|
|
|
elif req.path == session.path + 'settings/timezone':
|
|
if is_empty_query(req):
|
|
page = 'Select a time zone:\n\n'
|
|
for tz in pytz.all_timezones:
|
|
page += f'=> ?{tz} {tz}\n'
|
|
return page
|
|
tz = clean_query(req)
|
|
if not tz in pytz.all_timezones:
|
|
return 50, 'Invalid time zone'
|
|
db.update_user(session.user, timezone=tz)
|
|
return 30, '/settings/display'
|
|
|
|
elif req.path == session.path + 'settings/display':
|
|
page += f'# {session.user.name}: Settings\n'
|
|
page += '=> /settings ⚙️ Go back\n\n'
|
|
|
|
ICON_MODE = [ 'Unicode/Emoji' , 'ASCII' ]
|
|
|
|
page += '## Display\n\n'
|
|
page += f'=> /settings/short-preview {session.CHECKS[nonzero(session.user.flags & User.SHORT_PREVIEW_FLAG)]} Short post previews\n'
|
|
page += f'\n=> /settings/timezone Time zone: {user.timezone}\n'
|
|
page += f'=> /settings/ascii Display icons as: {ICON_MODE[nonzero(session.user.flags & User.ASCII_ICONS_FLAG)]}\n'
|
|
return page
|
|
|
|
elif req.path == session.path + 'settings' or \
|
|
req.path == session.path + 'settings/':
|
|
page = f'# {session.user.name}: Settings\n'
|
|
page += f'\n=> /dashboard Back to Dashboard\n'
|
|
page += f'=> / Back to front page\n\n'
|
|
|
|
SORT_POST = { 'r': '🕑 Most recent', 'h': '🔥 Hotness' }
|
|
SORT_COMMENT = { 'o': '⬇ Oldest first', 'n': '⬆ Newest first' }
|
|
HOME_FEED = [ 'All Posts', 'Followed' ]
|
|
user_space = db.get_subspace(owner=session.user.id)
|
|
|
|
page += f'=> /settings/homefeed Home feed: {HOME_FEED[nonzero(session.user.flags & User.HOME_FOLLOWED_FEED_FLAG)]}\n'
|
|
page += f'=> /settings/sort-feed Sort posts: {SORT_POST[session.user.sort_post]}\n'
|
|
page += f'=> /settings/sort-cmt Sort comments: {SORT_COMMENT[session.user.sort_cmt]}\n'
|
|
|
|
|
|
page += f'\n=> /settings/omit-all {session.CHECKS[nonzero(user_space.flags & Subspace.OMIT_FROM_ALL_FLAG)]} ' + \
|
|
f'Omit u/{session.user.name} from All Posts\n'
|
|
page += f'=> /settings/omit-feed {session.CHECKS[nonzero(user_space.flags & Subspace.OMIT_FROM_FEED_BY_DEFAULT)]} ' + \
|
|
'Omit posts from Gemini/Atom feed by default\n'
|
|
page += 'Individual posts can be included or excluded from Gemini/Atom feeds using the composer.\n'
|
|
|
|
page += '\n=> /settings/profile ⚙️ Profile\n'
|
|
page += '=> /settings/display ⚙️ Display\n'
|
|
page += '=> /settings/notif ⚙️ Notifications\n'
|
|
page += '=> /settings/feedback ⚙️ Feedback\n'
|
|
page += '=> /settings/follow ⚙️ Followed and muted\n'
|
|
page += '=> /settings/certs ⚙️ Certificates\n'
|
|
|
|
page += '\n## Account\n'
|
|
|
|
page += f'\n=> /export/{session.user.name}.gpub 📤 Export data archive\n'
|
|
page += 'Download a ZIP archive containing all posts and comments you have made. You can extract the contents and serve them as a static Gemini capsule. The archive has Gempub metadata so it can also be viewed in a Gempub reader. \n'
|
|
|
|
page += f'\n=> /settings/rename/{session.get_token()} Rename user\n'
|
|
page += f'Renaming the account will break links pointing to u/{session.user.name}.\n'
|
|
|
|
page += f'\n=> /settings/delete-account/{session.get_token()} ⚠️ Delete user {session.user.name}\n'
|
|
page += 'All of your posts and comments will be deleted.\n'
|
|
|
|
page += f'\n💬 Bubble v{session.bubble.version} by @jk@skyjake.fi\n'
|
|
return page
|
|
|
|
return 51, 'Not found'
|