feat: show tagged statuses in tag page

This commit is contained in:
Ayo Ayco 2025-01-18 19:52:02 +01:00
parent 1f91f7add9
commit 3c2665630d
6 changed files with 115 additions and 45 deletions

View file

@ -1,4 +1,18 @@
from mastodon import Mastodon
from . import utils
def get_account_tagged_statuses(app, tag):
mastodon = initialize_client(app)
account = mastodon.me()
print('>>> account id', account.id)
statuses = []
try:
statuses = mastodon.account_statuses(id=account.id, tagged=tag)
except:
print('>>> failed to fetch statuses', tag)
return list(map(lambda x: utils.clean_status(x), statuses))
def initialize_client(app):
mastodon = None
@ -30,5 +44,5 @@ def initialize_client(app):
app['password'],
to_file = app['secret_file']
)
return mastodon

View file

@ -16,8 +16,10 @@
<a href="{{ thread.url }}" title="{{ thread.created_at }}">
<relative-time datetime="{{ thread.created_at }}" precision="day">{{ thread.created_at }}</relative-time>
</a>
{% if not is_tag %}
<span>&middot;</span>
<a href="{{ url_for('threads.thread', id=parent_id) + '#' + thread['id'] }}">Anchor</a>
{% endif %}
</div>
</div>
@ -51,7 +53,7 @@
{% endif %}
</div>
{% if is_thread %}
{% if is_thread and not is_tag %}
<a href="{{ url_for('threads.thread', id=thread.id) }}">Read full thread</a>
{% endif %}
</article>

View file

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ app.title }}</title>
<title>{{ app.title }} / {{ tag }}</title>
<meta name="theme-color" content="#3054bf">
{% if threads|length == 1 %}
<meta name="description" content="{{ threads[0].summary }}" />
@ -16,7 +16,7 @@
<meta name="author" content="{{ attribution.owner }}" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="{{ app.site_name }}" />
<meta property="og:title" content="{{ app.title }}" />
<meta property="og:title" content="{{ app.title }} / {{ tag }}" />
<script type="module">
import TimeAgo from 'https://esm.sh/v135/@github/relative-time-element@4.4.0'
@ -82,12 +82,6 @@
gap: 1em;
}
main.home {
& .back {
display: none
}
}
main.thread {
& .card:not(:last-of-type) .card_avatar::after {
content: " ";
@ -99,6 +93,28 @@
}
}
.pill {
border-radius: 2em;
background-color: rgba(197, 209, 222, 0.25);
margin: 0 0.5em 0.5em 0;
padding: 1em;
display: inline-block;
font-size: var(--font-size-sm);
color: var(--text-color-dark);
text-decoration: none;
&:hover {
color: var(--color-link);
}
& .pill-label {
background-color: rgba(0, 0, 0, 0.15);
border-radius: 0.5em;
padding: 0 0.25em;
margin-right: 0.25em;
}
}
.card_avatar img {
border: 2px solid rgba(197, 209, 222, 0.15);
border-radius: 50%;
@ -267,6 +283,15 @@
}
}
}
.pill {
background-color: rgba(0, 0, 0, 0.15);
color: var(--text-color-light-faded);
&:hover {
color: var(--color-brand-complement);
}
}
}
</style>
</head>
@ -275,10 +300,37 @@
<a id="top"></a>
<header>
{% include "nav.html" %}
<h1>{{ app.title }} / #{{ tag }}</h1>
<h1>{{ app.title }} / {{ tag }}</h1>
<p>{{ app.description }}</p>
</header>
<main>tag</main>
<main class={{ "thread" if threads|length==1 else "home" }}>
{% if tags is defined%}
<div class="featured-tags">
{% for tag in tags %}
<a class="pill" href="{{ tag.url }}">
<span class="pill-label">{{ tag.statuses_count }}</span>
{{ tag.name }}
</a>
{% endfor %}
</div>
{% endif %}
<div class="back">
<a href="{{url_for('threads.home')}}">Back</a>
</div>
{% for thread in threads %}
{% with is_tag=tag!=None, thread=thread, parent_id=thread.id, is_thread=threads|length > 1 %}
{% include "card.html" %}
{% endwith %}
{% if thread.descendants is defined %}
{% for descendant in thread.descendants %}
{% with is_tag=tag!=None, thread=descendant, parent_id=thread.id %}
{% include "card.html" %}
{% endwith %}
{% endfor %}
{% endif %}
{% endfor %}
<a href="#top">Top</a>
</main>
<footer>
<p>
Copyright &copy;

View file

@ -114,6 +114,7 @@
}
& .pill-label {
font-weight: bold;
background-color: rgba(0, 0, 0, 0.15);
border-radius: 0.5em;
padding: 0 0.25em;
@ -314,7 +315,7 @@
{% if tags is defined%}
<div class="featured-tags">
{% for tag in tags %}
<a class="pill" href="{{ tag.url }}">
<a class="pill" href="/tag/{{ tag.name }}">
<span class="pill-label">{{ tag.statuses_count }}</span>
{{ tag.name }}
</a>

View file

@ -1,12 +1,10 @@
from flask import Blueprint, render_template, current_app
import requests
from datetime import datetime
import markdown
import re
from .cache import cache
import asyncio
import aiohttp
from . import mastodon
from . import mastodon, utils
threads = Blueprint('threads', __name__, template_folder='templates')
@ -75,7 +73,9 @@ async def home():
async def tag(id):
attribution = get_attribution()
app = get_app_config()
return render_template('tag.html', tag=id, app=app, attribution=attribution, render_date=datetime.now())
statuses = mastodon.get_account_tagged_statuses(app, id)
return render_template('tag.html', threads=statuses, tag=id, app=app, attribution=attribution, render_date=datetime.now())
@threads.route('/<path:id>')
@ -85,7 +85,7 @@ def thread(id):
attribution = get_attribution()
app = get_app_config()
status = fetch_thread(id)
status['summary'] = clean_html(status['content']).strip()
status['summary'] = utils.clean_html(status['content']).strip()
if len(status['summary']) > 69:
status['summary'] = status['summary'][:69] + '...'
return render_template('threads.html', threads=[status], app=app, attribution=attribution, render_date=datetime.now())
@ -106,7 +106,7 @@ async def get(url, session):
try:
async with session.get(url, ssl=False) as response:
res = await response.json()
return clean_status(res)
return utils.clean_status(res)
except Exception as e:
print(f"Unable to get url {url} due to {e.__class__}")
return {}
@ -129,7 +129,7 @@ async def fetch_statuses():
def fetch_thread(id):
status = requests.get(server() + '/api/v1/statuses/' + id ).json()
status = clean_status(status)
status = utils.clean_status(status)
status['descendants'] = get_descendants(server(), status)
return status
@ -141,30 +141,6 @@ def get_descendants(server, status):
# TODO: the following condition will include a reply to a reply of the author
# - edge case: a different author replies in the thread and the author replies then replies again
if reply['account']['id'] == author_id and reply['in_reply_to_account_id'] == author_id:
descendants.append(clean_status(reply))
descendants.append(utils.clean_status(reply))
return descendants
def clean_author(account):
if 'emojis' in account and len(account['emojis']) > 0:
name = account['display_name']
for emoji in account['emojis']:
account['display_name'] = name.replace(":" + emoji['shortcode'] + ":", '<img alt="' + emoji['shortcode'] + ' emoji" class="emoji" src="'+emoji['url']+'" />')
return clean_dict(account, ['avatar', 'display_name', 'id', 'url'])
def clean_status(status):
clean = clean_dict(status, ['id', 'content', 'created_at', 'url', 'media_attachments', 'card'])
clean['account'] = clean_author(status['account'])
clean['content'] = markdown.markdown("<section markdown='block'>"+ clean['content'] +"</section>", extensions=['md_in_html'])
for emoji in status['emojis']:
clean['content'] = clean['content'].replace(":" + emoji['shortcode'] + ":", '<img alt="' + emoji['shortcode'] + ' emoji" class="emoji" src="'+emoji['url']+'" />')
return clean
def clean_dict(dict, keys):
return {k: dict[k] for k in keys}
def clean_html(raw_html):
cleaner = re.compile('<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});')
return re.sub(cleaner, '', raw_html)

25
utils.py Normal file
View file

@ -0,0 +1,25 @@
import markdown
import re
def clean_status(status):
clean = clean_dict(status, ['id', 'content', 'created_at', 'url', 'media_attachments', 'card'])
clean['account'] = clean_author(status['account'])
clean['content'] = markdown.markdown("<section markdown='block'>"+ clean['content'] +"</section>", extensions=['md_in_html'])
for emoji in status['emojis']:
clean['content'] = clean['content'].replace(":" + emoji['shortcode'] + ":", '<img alt="' + emoji['shortcode'] + ' emoji" class="emoji" src="'+emoji['url']+'" />')
return clean
def clean_dict(dict, keys):
return {k: dict[k] for k in keys}
def clean_html(raw_html):
cleaner = re.compile('<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});')
return re.sub(cleaner, '', raw_html)
def clean_author(account):
if 'emojis' in account and len(account['emojis']) > 0:
name = account['display_name']
for emoji in account['emojis']:
account['display_name'] = name.replace(":" + emoji['shortcode'] + ":", '<img alt="' + emoji['shortcode'] + ' emoji" class="emoji" src="'+emoji['url']+'" />')
return clean_dict(account, ['avatar', 'display_name', 'id', 'url'])