2017-04-19 14:47:30 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2019-09-05 22:35:29 +02:00
|
|
|
import os
|
2017-05-08 09:09:20 +02:00
|
|
|
import re
|
2019-09-05 22:35:29 +02:00
|
|
|
import sys
|
2017-04-19 14:47:30 +02:00
|
|
|
|
2018-06-07 10:27:11 +02:00
|
|
|
from datetime import datetime
|
2019-02-14 16:53:58 +01:00
|
|
|
from textwrap import wrap
|
|
|
|
from wcwidth import wcswidth
|
2018-06-07 10:27:11 +02:00
|
|
|
|
2019-02-14 16:53:58 +01:00
|
|
|
from toot.utils import format_content, get_text, parse_html
|
|
|
|
from toot.wcstring import wc_wrap
|
2019-02-14 15:47:40 +01:00
|
|
|
|
2017-12-29 14:26:40 +01:00
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
START_CODES = {
|
2017-12-30 13:14:37 +01:00
|
|
|
'red': '\033[31m',
|
|
|
|
'green': '\033[32m',
|
|
|
|
'yellow': '\033[33m',
|
|
|
|
'blue': '\033[34m',
|
2017-05-08 09:09:20 +02:00
|
|
|
'magenta': '\033[35m',
|
2017-12-30 13:14:37 +01:00
|
|
|
'cyan': '\033[36m',
|
2017-05-08 09:09:20 +02:00
|
|
|
}
|
2017-04-19 14:47:30 +02:00
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
END_CODE = '\033[0m'
|
2017-04-19 14:47:30 +02:00
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
START_PATTERN = "<(" + "|".join(START_CODES.keys()) + ")>"
|
2017-04-19 14:47:30 +02:00
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
END_PATTERN = "</(" + "|".join(START_CODES.keys()) + ")>"
|
2017-04-19 14:47:30 +02:00
|
|
|
|
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
def start_code(match):
|
|
|
|
name = match.group(1)
|
|
|
|
return START_CODES[name]
|
2017-04-19 14:47:30 +02:00
|
|
|
|
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
def colorize(text):
|
|
|
|
text = re.sub(START_PATTERN, start_code, text)
|
|
|
|
text = re.sub(END_PATTERN, END_CODE, text)
|
2017-04-19 14:47:30 +02:00
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
return text
|
2017-04-19 14:47:30 +02:00
|
|
|
|
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
def strip_tags(text):
|
|
|
|
text = re.sub(START_PATTERN, '', text)
|
|
|
|
text = re.sub(END_PATTERN, '', text)
|
2017-04-19 14:47:30 +02:00
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
return text
|
2017-04-19 14:47:30 +02:00
|
|
|
|
|
|
|
|
2019-09-05 22:35:29 +02:00
|
|
|
def use_ansi_color():
|
|
|
|
"""Returns True if ANSI color codes should be used."""
|
|
|
|
|
|
|
|
# Windows doesn't support color unless ansicon is installed
|
|
|
|
# See: http://adoxa.altervista.org/ansicon/
|
|
|
|
if sys.platform == 'win32' and 'ANSICON' not in os.environ:
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Don't show color if stdout is not a tty, e.g. if output is piped on
|
|
|
|
if not sys.stdout.isatty():
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Don't show color if explicitly specified in options
|
|
|
|
if "--no-color" in sys.argv:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
USE_ANSI_COLOR = use_ansi_color()
|
|
|
|
|
2018-06-15 09:02:19 +02:00
|
|
|
QUIET = "--quiet" in sys.argv
|
2017-04-19 14:47:30 +02:00
|
|
|
|
2017-05-08 09:09:20 +02:00
|
|
|
|
|
|
|
def print_out(*args, **kwargs):
|
2018-06-15 09:02:19 +02:00
|
|
|
if not QUIET:
|
|
|
|
args = [colorize(a) if USE_ANSI_COLOR else strip_tags(a) for a in args]
|
|
|
|
print(*args, **kwargs)
|
2017-05-08 09:09:20 +02:00
|
|
|
|
|
|
|
|
|
|
|
def print_err(*args, **kwargs):
|
|
|
|
args = ["<red>{}</red>".format(a) for a in args]
|
|
|
|
args = [colorize(a) if USE_ANSI_COLOR else strip_tags(a) for a in args]
|
|
|
|
print(*args, file=sys.stderr, **kwargs)
|
2017-12-29 14:26:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
def print_instance(instance):
|
|
|
|
print_out("<green>{}</green>".format(instance['title']))
|
|
|
|
print_out("<blue>{}</blue>".format(instance['uri']))
|
|
|
|
print_out("running Mastodon {}".format(instance['version']))
|
|
|
|
print_out("")
|
|
|
|
|
|
|
|
description = instance['description'].strip()
|
|
|
|
if not description:
|
|
|
|
return
|
|
|
|
|
|
|
|
lines = [line.strip() for line in format_content(description) if line.strip()]
|
|
|
|
for line in lines:
|
|
|
|
for l in wrap(line.strip()):
|
2018-06-15 09:02:19 +02:00
|
|
|
print_out(l)
|
|
|
|
print_out()
|
2017-12-29 14:42:51 +01:00
|
|
|
|
|
|
|
|
|
|
|
def print_account(account):
|
|
|
|
print_out("<green>@{}</green> {}".format(account['acct'], account['display_name']))
|
|
|
|
|
|
|
|
note = get_text(account['note'])
|
|
|
|
|
|
|
|
if note:
|
|
|
|
print_out("")
|
|
|
|
print_out("\n".join(wrap(note)))
|
|
|
|
|
|
|
|
print_out("")
|
|
|
|
print_out("ID: <green>{}</green>".format(account['id']))
|
|
|
|
print_out("Since: <green>{}</green>".format(account['created_at'][:19].replace('T', ' @ ')))
|
|
|
|
print_out("")
|
|
|
|
print_out("Followers: <yellow>{}</yellow>".format(account['followers_count']))
|
|
|
|
print_out("Following: <yellow>{}</yellow>".format(account['following_count']))
|
|
|
|
print_out("Statuses: <yellow>{}</yellow>".format(account['statuses_count']))
|
|
|
|
print_out("")
|
|
|
|
print_out(account['url'])
|
|
|
|
|
|
|
|
|
2019-02-14 16:53:58 +01:00
|
|
|
HASHTAG_PATTERN = re.compile(r'(?<!\w)(#\w+)\b')
|
|
|
|
|
|
|
|
|
|
|
|
def highlight_hashtags(line):
|
|
|
|
return re.sub(HASHTAG_PATTERN, '<cyan>\\1</cyan>', line)
|
|
|
|
|
|
|
|
|
2017-12-29 14:42:51 +01:00
|
|
|
def print_search_results(results):
|
|
|
|
accounts = results['accounts']
|
|
|
|
hashtags = results['hashtags']
|
|
|
|
|
|
|
|
if accounts:
|
|
|
|
print_out("\nAccounts:")
|
|
|
|
for account in accounts:
|
|
|
|
print_out("* <green>@{}</green> {}".format(
|
|
|
|
account['acct'],
|
|
|
|
account['display_name']
|
|
|
|
))
|
|
|
|
|
|
|
|
if hashtags:
|
|
|
|
print_out("\nHashtags:")
|
2019-09-22 13:04:58 +02:00
|
|
|
print_out(", ".join(["<green>#{}</green>".format(t["name"]) for t in hashtags]))
|
2017-12-29 14:42:51 +01:00
|
|
|
|
|
|
|
if not accounts and not hashtags:
|
|
|
|
print_out("<yellow>Nothing found</yellow>")
|
2018-06-07 10:27:11 +02:00
|
|
|
|
|
|
|
|
2019-02-14 16:53:58 +01:00
|
|
|
def print_status(status, width):
|
|
|
|
reblog = status['reblog']
|
|
|
|
content = reblog['content'] if reblog else status['content']
|
|
|
|
media_attachments = reblog['media_attachments'] if reblog else status['media_attachments']
|
|
|
|
in_reply_to = status['in_reply_to_id']
|
2018-06-07 10:27:11 +02:00
|
|
|
|
2019-02-14 16:53:58 +01:00
|
|
|
time = status['created_at']
|
|
|
|
time = datetime.strptime(time, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
|
|
time = time.strftime('%Y-%m-%d %H:%M%z')
|
2019-01-19 18:38:17 +01:00
|
|
|
|
2019-02-15 13:26:22 +01:00
|
|
|
username = "@" + status['account']['acct']
|
2019-02-14 16:53:58 +01:00
|
|
|
spacing = width - wcswidth(username) - wcswidth(time)
|
2019-01-19 18:38:17 +01:00
|
|
|
|
2019-02-14 16:53:58 +01:00
|
|
|
display_name = status['account']['display_name']
|
|
|
|
if display_name:
|
|
|
|
spacing -= wcswidth(display_name) + 1
|
2018-06-07 10:27:11 +02:00
|
|
|
|
2019-02-14 16:53:58 +01:00
|
|
|
print_out("{}{}{}{}".format(
|
|
|
|
"<green>{}</green> ".format(display_name) if display_name else "",
|
|
|
|
"<blue>{}</blue>".format(username),
|
|
|
|
" " * spacing,
|
|
|
|
"<yellow>{}</yellow>".format(time),
|
|
|
|
))
|
2018-06-07 10:27:11 +02:00
|
|
|
|
2019-02-14 16:53:58 +01:00
|
|
|
for paragraph in parse_html(content):
|
|
|
|
print_out("")
|
|
|
|
for line in paragraph:
|
|
|
|
for subline in wc_wrap(line, width):
|
|
|
|
print_out(highlight_hashtags(subline))
|
|
|
|
|
|
|
|
if media_attachments:
|
|
|
|
print_out("\nMedia:")
|
|
|
|
for attachment in media_attachments:
|
|
|
|
url = attachment['text_url'] or attachment['url']
|
|
|
|
for line in wc_wrap(url, width):
|
|
|
|
print_out(line)
|
|
|
|
|
|
|
|
print_out("\n{}{}{}".format(
|
|
|
|
"ID <yellow>{}</yellow> ".format(status['id']),
|
|
|
|
"↲ In reply to <yellow>{}</yellow> ".format(in_reply_to) if in_reply_to else "",
|
2019-02-15 13:26:22 +01:00
|
|
|
"↻ Reblogged <blue>@{}</blue> ".format(reblog['account']['acct']) if reblog else "",
|
2019-02-14 16:53:58 +01:00
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
def print_timeline(items, width=100):
|
|
|
|
print_out("─" * width)
|
2018-06-07 10:27:11 +02:00
|
|
|
for item in items:
|
2019-02-14 16:53:58 +01:00
|
|
|
print_status(item, width)
|
|
|
|
print_out("─" * width)
|
2019-04-16 14:09:54 +02:00
|
|
|
|
|
|
|
|
|
|
|
notification_msgs = {
|
|
|
|
"follow": "{account} now follows you",
|
|
|
|
"mention": "{account} mentioned you in",
|
|
|
|
"reblog": "{account} reblogged your status",
|
|
|
|
"favourite": "{account} favourited your status",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def print_notification(notification, width=100):
|
|
|
|
account = "{display_name} @{acct}".format(**notification["account"])
|
|
|
|
msg = notification_msgs.get(notification["type"])
|
|
|
|
if msg is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
print_out("─" * width)
|
|
|
|
print_out(msg.format(account=account))
|
|
|
|
status = notification.get("status")
|
|
|
|
if status is not None:
|
|
|
|
print_status(status, width)
|
|
|
|
|
|
|
|
|
|
|
|
def print_notifications(notifications, width=100):
|
|
|
|
for notification in notifications:
|
|
|
|
print_notification(notification)
|
|
|
|
print_out("─" * width)
|