johnpaulbin's picture
Update app.py
3a12425 verified
raw
history blame
3.97 kB
from flask import Flask, render_template, Response
from sonatoki.ilo import Ilo
from sonatoki.Configs import PrefConfig
from atproto import FirehoseSubscribeReposClient, parse_subscribe_repos_message
from atproto import CAR, models
import json
import re
import emoji
import queue
import threading
from werkzeug.serving import run_simple
app = Flask(__name__)
message_queue = queue.Queue()
# Your existing code for Ilo and JSONExtra remains the same
ilo = Ilo(**PrefConfig)
class JSONExtra(json.JSONEncoder):
def default(self, obj):
try:
return json.JSONEncoder.default(self, obj)
except:
return repr(obj)
def clean_text(text: str) -> str:
text = emoji.replace_emoji(text, replace='')
text = re.sub(r'https?://\S+', '', text)
text = re.sub(r'[^A-Za-z\s]', '', text)
text = text.strip()
return text
def process_firehose():
client = FirehoseSubscribeReposClient()
def on_message_handler(message):
commit = parse_subscribe_repos_message(message)
if not isinstance(commit, models.ComAtprotoSyncSubscribeRepos.Commit):
return
car = CAR.from_bytes(commit.blocks)
for op in commit.ops:
if op.action in ["create"] and op.cid:
raw = car.blocks.get(op.cid)
cooked = models.get_or_create(raw, strict=False)
if not cooked:
continue
if cooked.py_type == "app.bsky.feed.post":
cleaned_text = clean_text(raw.get("text", ""))
if not cleaned_text or len(cleaned_text.split()) < 3:
continue
if not ilo.is_toki_pona(cleaned_text.lower()):
continue
url = f'https://bsky.app/profile/{commit.repo}/post/{op.path.split("/")[1]}'
message_queue.put({'text': raw.get("text", ""), 'url': url})
client.start(on_message_handler)
def generate_sse():
while True:
message = message_queue.get()
yield f"data: {json.dumps(message)}\n\n"
@app.route('/')
def index():
return """<!DOCTYPE html>
<html>
<head>
<title>Toki Pona Live Stream</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.message {
background: white;
padding: 15px;
margin: 10px 0;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
a {
color: #0066cc;
text-decoration: none;
}
h1 {
text-align: center;
}
</style>
</head>
<body>
<h1>Toki Pona Live Stream</h1>
<div id="messages"></div>
<script>
const evtSource = new EventSource("/stream");
const messages = document.getElementById('messages');
evtSource.onmessage = function(event) {
const data = JSON.parse(event.data);
const messageDiv = document.createElement('div');
messageDiv.className = 'message';
messageDiv.innerHTML = `
<p>${data.text}</p>
<a href="${data.url}" target="_blank">View on Bluesky</a>
`;
messages.insertBefore(messageDiv, messages.firstChild);
if (messages.children.length > 50) {
messages.removeChild(messages.lastChild);
}
};
</script>
</body>
</html>"""
@app.route('/stream')
def stream():
return Response(generate_sse(), mimetype='text/event-stream')
if __name__ == '__main__':
# Start the firehose processing in a separate thread
threading.Thread(target=process_firehose, daemon=True).start()
# Use run_simple instead of app.run
run_simple('localhost', 7860, app, use_reloader=True, use_debugger=True)