feat: use public APIs and remove mastodon.py dependency

This commit is contained in:
Ayo Ayco 2025-08-07 14:38:17 +02:00
parent 08403d912b
commit 5a1b8eaee3
5 changed files with 83 additions and 153 deletions

View file

@ -5,9 +5,7 @@
"title": "Thoughts",
"description": "Hand-picked public posts from my social feed",
"server": "https://social.ayco.io",
"user": "user@mastodon.social",
"password": "ultraelectromagneticpassword",
"secret_file": "threads-masto-client.secret"
"user_id": "0123456789"
}
},
"ATTRIBUTION": {

View file

@ -1,91 +0,0 @@
from mastodon import Mastodon
from . import utils
session_id = None
account_id = None
def is_public(status):
print(status)
return status['visibility'] == 'public'
def get_account_tagged_statuses(app, tag):
global account_id
mastodon = initialize_client(app)
statuses = []
try:
statuses = mastodon.account_statuses(
id=account_id,
tagged=tag,
exclude_reblogs=True
)
except:
message = f'>>> failed to fetch statuses for ${tag}'
raise Exception(message)
# filter out not public
filtered = filter(is_public, statuses)
return list(map(lambda x: utils.clean_status(x), filtered))
def initialize_client(app):
global session_id
global account_id
mastodon = None
secret = None
try:
secret_file = open(app['secret_file'], 'r')
secret = secret_file.read()
except OSError as e:
message = '>>> No secret found.'
print(message)
# todo, check if access_token exist in secret_file
if secret == None:
#...if token does not exist, create app:
Mastodon.create_app(
app['site_name'],
api_base_url = app['server'],
to_file = app['secret_file']
)
try:
mastodon = Mastodon(client_id=app['secret_file'])
print('>>> Persisted new token!')
except:
message = '>>> Failed to create masto client token'
raise Exception(message)
else:
#... otherwise, reuse
try:
mastodon = Mastodon(access_token=app['secret_file'])
print('>>> Reused persisted token!')
except:
message = '>>> Persisted token did not work'
raise Exception(message)
if session_id == None:
try:
session_id = mastodon.log_in(
app['user'],
app['password'],
to_file = app['secret_file']
)
print('>>> Logged in: ', session_id)
except:
message = '>>> Failed to get mastodon session'
raise Exception(message)
else:
print('>>> Reused session: ', session_id)
if account_id == None:
try:
account = mastodon.me()
account_id = account.id
print('>>> Set account ID: ', account_id)
except:
message = '>>> Failed to get mastodon account'
raise Exception(message)
else:
print('>>> Reused account ID:', account_id)
return mastodon

View file

@ -2,6 +2,4 @@ flask[async]
requests
markdown
Flask-Caching
aiohttp
mastodon-py
aiohttp

View file

@ -6,13 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ app.title }} / {{ tag }}</title>
<meta name="theme-color" content="#3054bf">
{% if threads|length == 1 %}
<meta name="description" content="{{ threads[0].summary }}" />
<meta property="og:description" content="{{ threads[0].summary }}" />
{% else %}
<meta name="description" content="{{ app.description }}" />
<meta property="og:description" content="{{ app.description }}" />
{% endif %}
<meta name="author" content="{{ attribution.owner }}" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="{{ app.site_name }}" />

View file

@ -4,7 +4,7 @@ from datetime import datetime
from .cache import cache
import asyncio
import aiohttp
from . import mastodon, utils
from . import utils
threads = Blueprint('threads', __name__, template_folder='templates', static_folder='static')
@ -45,12 +45,44 @@ thread_ids = [
###########################################################
### config
def server():
return current_app.config['APPS']['threads']['server']
def get_attribution():
return current_app.config['ATTRIBUTION']
def get_app_config():
return current_app.config['APPS']['threads']
def get_user_id():
return current_app.config['APPS']['threads']['user_id']
### featured tags
def get_account_tagged_statuses(tag_name):
print(tag_name)
id = get_user_id()
ser = server()
url = f'{ser}/api/v1/accounts/{id}/statuses?exclude_replies=true&tagged={tag_name}'
response = requests.get(url)
statuses = response.json()
statuses = [utils.clean_status(s) for s in statuses]
return statuses
def get_tags_url():
id = get_user_id()
ser = server()
url = f'{ser}/api/v1/accounts/{id}/featured_tags'
return url
def get_featured_tags():
url = get_tags_url()
response = requests.get(url)
tags = response.json()
return tags
### middleware
@threads.before_request
def middleware():
# check current year and put ange as attribution
@ -61,53 +93,7 @@ def middleware():
if year != attribution['year']:
attribution['current_year'] = year
@threads.route('/')
@cache.cached(timeout=300)
async def home():
statuses = await fetch_statuses()
attribution = get_attribution()
app = get_app_config()
tags = []
masto = mastodon.initialize_client(app)
# List featured hashtags
tags = masto.featured_tags()
return render_template('_home.html', threads=statuses, tags=tags, app=app, attribution=attribution, render_date=datetime.now())
@threads.route('/tag/<path:id>')
@cache.cached(timeout=300)
async def tag(id):
attribution = get_attribution()
app = get_app_config()
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>')
@cache.cached(timeout=300)
def thread(id):
attribution = get_attribution()
app = get_app_config()
status = fetch_thread(id)
status['summary'] = utils.clean_html(status['content']).strip()
if len(status['summary']) > 69:
status['summary'] = status['summary'][:69] + '...'
return render_template('_home.html', threads=[status], app=app, attribution=attribution, render_date=datetime.now())
@threads.route('/api')
@cache.cached(timeout=300)
async def api():
return await fetch_statuses();
@threads.route('/api/<path:id>')
@cache.cached(timeout=300)
def api_thread(id):
return fetch_thread(id)
### statuses
async def get(url, session):
try:
async with session.get(url, ssl=False) as response:
@ -120,9 +106,6 @@ async def get(url, session):
def get_status_url(ser, id):
return f'{ser}/api/v1/statuses/{id}'
def server():
return current_app.config['APPS']['threads']['server']
async def fetch_statuses():
statuses = []
urls = [get_status_url(server(), id) for id in thread_ids]
@ -149,3 +132,50 @@ def get_descendants(server, status):
if reply['account']['id'] == author_id and reply['in_reply_to_account_id'] == author_id:
descendants.append(utils.clean_status(reply))
return descendants
### routes
@threads.route('/')
@cache.cached(timeout=300)
async def home():
statuses = await fetch_statuses()
attribution = get_attribution()
app = get_app_config()
tags = []
# List featured hashtags
tags = get_featured_tags()
return render_template('_home.html', threads=statuses, tags=tags, app=app, attribution=attribution, render_date=datetime.now())
@threads.route('/tag/<path:id>')
@cache.cached(timeout=300)
async def tag(id):
attribution = get_attribution()
app = get_app_config()
statuses = get_account_tagged_statuses(id)
return render_template('_tag.html', threads=statuses, tag=id, app=app, attribution=attribution, render_date=datetime.now())
@threads.route('/<path:id>')
@cache.cached(timeout=300)
def thread(id):
attribution = get_attribution()
app = get_app_config()
status = fetch_thread(id)
status['summary'] = utils.clean_html(status['content']).strip()
if len(status['summary']) > 69:
status['summary'] = status['summary'][:69] + '...'
return render_template('_home.html', threads=[status], app=app, attribution=attribution, render_date=datetime.now())
@threads.route('/api')
@cache.cached(timeout=300)
async def api():
return await fetch_statuses();
@threads.route('/api/<path:id>')
@cache.cached(timeout=300)
def api_thread(id):
return fetch_thread(id)