Implement tree thread

This commit is contained in:
Huy Ngo 2024-07-31 15:59:24 +07:00
parent 55b02ccf4e
commit 6952577957
4 changed files with 98 additions and 22 deletions

View file

@ -32,3 +32,20 @@ def get_config_dir():
# Default to ~/.config/witchie/
return join(expanduser("~"), ".config", CONFIG_DIR_NAME)
def get_data_dir():
"""Returns the path to witchie data directory"""
# On Windows, store the config in roaming appdata
if sys.platform == "win32" and "APPDATA" in os.environ:
return join(os.getenv("APPDATA"), CONFIG_DIR_NAME)
# Respect XDG_CONFIG_HOME env variable if set
# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
if "XDG_DATA_HOME" in os.environ:
config_home = expanduser(os.environ["XDG_DATA_HOME"])
return join(config_home, CONFIG_DIR_NAME)
# Default to ~/.config/witchie/
return join(expanduser("~"), ".local/share", CONFIG_DIR_NAME)

View file

@ -14,7 +14,7 @@ from witchie.output import (print_account, print_acct_list, print_instance,
print_list_accounts, print_lists,
print_notifications, print_out,
print_search_results, print_status, print_table,
print_tag_list, print_timeline, print_user_list)
print_tag_list, print_timeline, print_tree, print_user_list)
from witchie.utils import (EOF_KEY, args_get_instance, delete_tmp_status_file,
editor_input, multiline_input)
from witchie.utils.datetime import parse_datetime
@ -85,6 +85,17 @@ def status(app, user, args):
print_status(status)
def _get_replies_tree(status_id: int, descendants: list):
tree = []
for d in descendants:
if d['in_reply_to_id'] == status_id:
tree.append({
'status': from_dict(Status, d),
'replies': _get_replies_tree(d['id'], descendants)
})
return tree
def thread(app, user, args):
context_response = api.context(app, user, args.status_id)
@ -94,8 +105,25 @@ def thread(app, user, args):
post = api.fetch_status(app, user, args.status_id).json()
context = context_response.json()
statuses = chain(context["ancestors"], [post], context["descendants"])
print_timeline(from_dict(Status, s) for s in statuses)
if args.tree:
reply_to = post["in_reply_to_id"]
ancestors_map = {status['id']: status for status in context["ancestors"]}
ancestors = []
while reply_to:
ancestors.append(ancestors_map[reply_to])
reply_to = ancestors_map[reply_to]['in_reply_to_id']
ancestors = reversed(ancestors)
print_timeline([from_dict(Status, s) for s in ancestors], padding=1)
descendants = {
'status': from_dict(Status, post),
'replies': _get_replies_tree(args.status_id, context["descendants"])
}
print_tree(descendants)
else:
statuses = chain(context["ancestors"], [post], context["descendants"])
print_timeline(from_dict(Status, s) for s in statuses)
def post(app, user, args):

View file

@ -473,6 +473,11 @@ READ_COMMANDS = [
(["status_id"], {
"help": "Show thread for post.",
}),
(["-t", "--tree"], {
"action": 'store_true',
"default": False,
"help": "format the thread as tree (only makes sense for akkoma/pleroma)",
}),
json_arg,
],
require_auth=True,
@ -546,7 +551,8 @@ POST_COMMANDS = [
(["-A", "--include-mentions"], {
"action": 'store_true',
"default": False,
"help": "include mentions of accounts mentioned in the post you're replying to and the account of the author themself",
"help": "include mentions of accounts mentioned in the post you're replying to"
" and the account of the author themself (may make it slower)",
}),
(["-l", "--language"], {
"type": language,

View file

@ -277,7 +277,7 @@ def print_search_results(results):
print_out("<yellow>Nothing found</yellow>")
def print_status(status: Status, width: int = 80):
def print_status(status: Status, width: int = 80, padding: int = 0):
status_id = status.original.id
in_reply_to_id = status.in_reply_to_id
reblogged_by = status.account if status.reblog else None
@ -296,51 +296,55 @@ def print_status(status: Status, width: int = 80):
for react in status.emoji_reactions])
print_out(
"" * padding,
f"<green>{display_name}</green>" if display_name else "",
f"<blue>{username}</blue>",
" " * spacing,
f"<yellow>{time}</yellow>",
)
print_out("")
print_out("" * padding)
if status.spoiler_text:
print_out("<yellow>Subject</yellow>: ", end="")
print_out("" * padding, "<yellow>Subject</yellow>: ", end="")
print_html(status.spoiler_text)
print()
print_html(status.content, width)
print_html(status.content, width, padding)
if status.media_attachments:
print_out("\nMedia:")
print_out(f"\n{"" * padding}Media:")
for count, attachment in enumerate(status.media_attachments):
url = attachment.url
description = f'Description: {attachment.description}'
description = f'{"" * padding}Description: {attachment.description}'
print_out(f'{count+1}. <yellow>URL</yellow>: {url}')
for i, line in enumerate(wc_wrap(description, width)):
if i == 0:
line = line.replace('Description', '<yellow>Description</yellow>')
print_out(line)
print_out("" * padding + line)
if status.poll:
print_poll(status.poll)
print_out()
print_out("" * padding)
if reacts:
reacts = "" * padding + " " + reacts
print_out(
f"<yellow>Reacts:</yellow>\n{reacts}\n" if reacts else "",
"" * padding,
f"<yellow>Reacts:</yellow>\n{reacts}\n{'' * padding}" if reacts else "",
f"ID <yellow>{status_id}</yellow> ",
f"↲ In reply to <yellow>{in_reply_to_id}</yellow> " if in_reply_to_id else "",
f"↻ <blue>@{reblogged_by.acct}</blue> boosted " if reblogged_by else "",
)
def print_html(text, width=80):
def print_html(text, width=80, padding=0):
first = True
for paragraph in html_to_paragraphs(text):
if not first:
print_out("")
print_out("" * padding)
for line in paragraph:
for subline in wc_wrap(line, width):
print_out(highlight_hashtags(subline))
print_out("" * padding, highlight_hashtags(subline))
first = False
@ -370,11 +374,32 @@ def print_poll(poll: Poll):
print_out(poll_footer)
def print_timeline(items: Iterable[Status], width=100):
print_out("" * width)
def print_timeline(items: Iterable[Status], width=80, padding=0):
width -= padding
if padding:
first_paddings = "" * (padding - 1) + ""
paddings = "" * (padding - 1) + ""
else:
first_paddings = ""
paddings = ""
print_out(first_paddings + "" * width)
for item in items:
print_status(item, width)
print_out("" * width)
print_status(item, width - padding, padding)
print_out(paddings + "" * width)
def print_tree(tree, depth=0):
"""Print a thread tree"""
if depth >= 20:
print_out(" " * 20 + "(Thread goes too deep)")
if depth:
paddings = "" * (depth - 1) + ""
else:
paddings = ""
print_status(tree['status'], 80-depth, padding=depth)
print_out(paddings + "" * 80)
for reply in tree['replies']:
print_tree(reply, depth+1)
notification_msgs = {
@ -385,7 +410,7 @@ notification_msgs = {
}
def print_notification(notification: Notification, width=100):
def print_notification(notification: Notification, width=80):
account = f"{notification.account.display_name} @{notification.account.acct}"
msg = notification_msgs.get(notification.type)
if msg is None:
@ -397,7 +422,7 @@ def print_notification(notification: Notification, width=100):
print_status(notification.status, width)
def print_notifications(notifications: List[Notification], width=100):
def print_notifications(notifications: List[Notification], width=80):
for notification in notifications:
print_notification(notification)
print_out("" * width)