1 Commits

Author SHA1 Message Date
afk f98f83d5df revert 512275a8b8
revert Switch to synchronous Python.

Signed-off-by: Abdulkadir Furkan Şanlı <me@abdulocra.cy>
2026-04-20 11:49:08 +02:00
+32 -41
View File
@@ -2,6 +2,7 @@
"""ParkerBot""" """ParkerBot"""
import argparse import argparse
import asyncio
import datetime import datetime
import html import html
import os import os
@@ -14,7 +15,7 @@ from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build from googleapiclient.discovery import build
from googleapiclient import errors from googleapiclient import errors
from nio import HttpClient, RoomMessageText, SyncResponse, UploadResponse from nio import AsyncClient, RoomMessageText, SyncResponse, UploadResponse
DATA_DIR = os.getenv("DATA_DIR", "./") DATA_DIR = os.getenv("DATA_DIR", "./")
DB_PATH = os.path.join(DATA_DIR, "parkerbot.sqlite3") DB_PATH = os.path.join(DATA_DIR, "parkerbot.sqlite3")
@@ -204,14 +205,14 @@ def get_video_info(youtube, video_id):
return False, "[Error fetching title]", "" return False, "[Error fetching title]", ""
def send_intro_message(client, sender, room_id): async def send_intro_message(client, sender, room_id):
"""Sends introduction message in reply to sender, in room with room_id.""" """Sends introduction message in reply to sender, in room with room_id."""
intro_message = ( intro_message = (
f"Hi {sender}, I'm ParkerBot! I generate YouTube playlists from links " f"Hi {sender}, I'm ParkerBot! I generate YouTube playlists from links "
"sent to this channel. You can find my source code here: " "sent to this channel. You can find my source code here: "
"https://git.abdulocra.cy/abdulocracy/parkerbot" "https://git.abdulocra.cy/abdulocracy/parkerbot"
) )
client.room_send( await client.room_send(
room_id=room_id, room_id=room_id,
message_type="m.room.message", message_type="m.room.message",
content={"msgtype": "m.text", "body": intro_message}, content={"msgtype": "m.text", "body": intro_message},
@@ -219,11 +220,11 @@ def send_intro_message(client, sender, room_id):
# TODO: Figure out how to properly send GIF, this is broken as shit. # TODO: Figure out how to properly send GIF, this is broken as shit.
with open("./parker.gif", "rb") as gif_file: with open("./parker.gif", "rb") as gif_file:
response = client.upload(gif_file, content_type="image/gif") response = await client.upload(gif_file, content_type="image/gif")
if isinstance(response, UploadResponse): if isinstance(response, UploadResponse):
print("Image was uploaded successfully to server. ") print("Image was uploaded successfully to server. ")
gif_uri = response.content_uri gif_uri = response.content_uri
client.room_send( await client.room_send(
room_id=room_id, room_id=room_id,
message_type="m.room.message", message_type="m.room.message",
content={ content={
@@ -237,29 +238,29 @@ def send_intro_message(client, sender, room_id):
print(f"Failed to upload image. Failure response: {response}") print(f"Failed to upload image. Failure response: {response}")
def send_playlist_of_week(client, sender, room_id, playlist_id): async def send_playlist_of_week(client, sender, room_id, playlist_id):
"""Sends playlist of the week in reply to sender, in room with room_id.""" """Sends playlist of the week in reply to sender, in room with room_id."""
playlist_link = f"https://www.youtube.com/playlist?list={playlist_id}" playlist_link = f"https://www.youtube.com/playlist?list={playlist_id}"
reply_msg = f"{sender}, here's the playlist of the week: {playlist_link}" reply_msg = f"{sender}, here's the playlist of the week: {playlist_link}"
client.room_send( await client.room_send(
room_id=room_id, room_id=room_id,
message_type="m.room.message", message_type="m.room.message",
content={"msgtype": "m.text", "body": reply_msg}, content={"msgtype": "m.text", "body": reply_msg},
) )
def send_playlist_of_all(client, sender, room_id, playlist_id): async def send_playlist_of_all(client, sender, room_id, playlist_id):
"""Sends playlist of all time in reply to sender, in room with room_id.""" """Sends playlist of all time in reply to sender, in room with room_id."""
playlist_link = f"https://www.youtube.com/playlist?list={playlist_id}" playlist_link = f"https://www.youtube.com/playlist?list={playlist_id}"
reply_msg = f"{sender}, here's the playlist of all time: {playlist_link}" reply_msg = f"{sender}, here's the playlist of all time: {playlist_link}"
client.room_send( await client.room_send(
room_id=room_id, room_id=room_id,
message_type="m.room.message", message_type="m.room.message",
content={"msgtype": "m.text", "body": reply_msg}, content={"msgtype": "m.text", "body": reply_msg},
) )
def message_callback(conn, cursor, youtube, client, room, event): async def message_callback(conn, cursor, youtube, client, room, event):
"""Event handler for received messages.""" """Event handler for received messages."""
sender = event.sender sender = event.sender
if sender != MATRIX_USER: if sender != MATRIX_USER:
@@ -273,8 +274,7 @@ def message_callback(conn, cursor, youtube, client, room, event):
) )
timestamp_sec = datetime.datetime.fromtimestamp( timestamp_sec = datetime.datetime.fromtimestamp(
event.server_timestamp / 1000, event.server_timestamp / 1000, datetime.UTC # millisec to sec
datetime.UTC, # millisec to sec
) )
current_time = datetime.datetime.now(datetime.UTC) current_time = datetime.datetime.now(datetime.UTC)
@@ -282,15 +282,15 @@ def message_callback(conn, cursor, youtube, client, room, event):
recent = abs(current_time - timestamp_sec) < datetime.timedelta(minutes=5) recent = abs(current_time - timestamp_sec) < datetime.timedelta(minutes=5)
if body == "!parkerbot" and recent: if body == "!parkerbot" and recent:
send_intro_message(client, sender, room.room_id) await send_intro_message(client, sender, room.room_id)
return return
if body == "!week" and recent: if body == "!week" and recent:
send_playlist_of_week(client, sender, room.room_id, playlist_id) await send_playlist_of_week(client, sender, room.room_id, playlist_id)
return return
if body == "!all" and recent: if body == "!all" and recent:
send_playlist_of_all(client, sender, room.room_id, all_playlist_id) await send_playlist_of_all(client, sender, room.room_id, all_playlist_id)
return return
youtube_link_pattern = ( youtube_link_pattern = (
@@ -314,18 +314,16 @@ def message_callback(conn, cursor, youtube, client, room, event):
# Escape HTML characters to prevent broken rendering in Matrix # Escape HTML characters to prevent broken rendering in Matrix
escaped_text = html.escape(plain_text) escaped_text = html.escape(plain_text)
html_text = ( html_text = f"<em><span data-mx-color='#808080'>{escaped_text}</span></em>"
f"<em><span data-mx-color='#808080'>{escaped_text}</span></em>"
)
client.room_send( await client.room_send(
room_id=room.room_id, room_id=room.room_id,
message_type="m.room.message", message_type="m.room.message",
content={ content={
"msgtype": "m.text", "msgtype": "m.text",
"body": plain_text, "body": plain_text,
"format": "org.matrix.custom.html", "format": "org.matrix.custom.html",
"formatted_body": html_text, "formatted_body": html_text
}, },
) )
@@ -382,7 +380,7 @@ def record_message(conn, cursor, sender, link, timestamp):
return cursor.fetchone()[0] return cursor.fetchone()[0]
def sync_callback(response): async def sync_callback(response):
"""Saves Matrix sync token.""" """Saves Matrix sync token."""
with open(TOKEN_PATH, "w", encoding="utf-8") as f: with open(TOKEN_PATH, "w", encoding="utf-8") as f:
f.write(response.next_batch) f.write(response.next_batch)
@@ -397,9 +395,9 @@ def load_sync_token():
return None return None
def get_client(conn, cursor, youtube): async def get_client(conn, cursor, youtube):
"""Returns configured and logged in Matrix client.""" """Returns configured and logged in Matrix client."""
client = HttpClient(MATRIX_SERVER, MATRIX_USER) client = AsyncClient(MATRIX_SERVER, MATRIX_USER)
client.add_event_callback( client.add_event_callback(
lambda room, event: message_callback( lambda room, event: message_callback(
conn, cursor, youtube, client, room, event conn, cursor, youtube, client, room, event
@@ -407,23 +405,23 @@ def get_client(conn, cursor, youtube):
RoomMessageText, RoomMessageText,
) )
client.add_response_callback(sync_callback, SyncResponse) client.add_response_callback(sync_callback, SyncResponse)
print(client.login(MATRIX_PASSWORD)) print(await client.login(MATRIX_PASSWORD))
return client return client
def backwards_sync(conn, cursor, youtube, client, room, start_token): async def backwards_sync(conn, cursor, youtube, client, room, start_token):
"""Fetch and process historical messages from a given room.""" """Fetch and process historical messages from a given room."""
print("Starting to process channel log...") print("Starting to process channel log...")
from_token = start_token from_token = start_token
room_id = room.room_id room_id = room.room_id
while True: while True:
# Fetch room messages # Fetch room messages
response = client.room_messages(room_id, from_token, direction="b") response = await client.room_messages(room_id, from_token, direction="b")
# Process each message # Process each message
for event in response.chunk: for event in response.chunk:
if isinstance(event, RoomMessageText): if isinstance(event, RoomMessageText):
message_callback(conn, cursor, youtube, client, room, event) await message_callback(conn, cursor, youtube, client, room, event)
# Break if there are no more messages to fetch # Break if there are no more messages to fetch
if not response.end or response.end == from_token: if not response.end or response.end == from_token:
@@ -433,30 +431,23 @@ def backwards_sync(conn, cursor, youtube, client, room, start_token):
from_token = response.end from_token = response.end
def main(): async def main():
"""Get DB and Matrix client ready, and start syncing.""" """Get DB and Matrix client ready, and start syncing."""
args = parse_arguments() args = parse_arguments()
conn, cursor = connect_db() conn, cursor = connect_db()
define_tables(conn, cursor) define_tables(conn, cursor)
youtube = get_authenticated_service() youtube = get_authenticated_service()
client = get_client(conn, cursor, youtube) client = await get_client(conn, cursor, youtube)
sync_token = load_sync_token() sync_token = load_sync_token()
# This is incredibly dumb and most probably will exceed your YouTube API quota. # This is incredibly dumb and most probably will exceed your YouTube API quota.
if args.backwards_sync: if args.backwards_sync:
init_sync = client.sync(30000) init_sync = await client.sync(30000)
room = client.room_resolve_alias(MATRIX_ROOM) room = await client.room_resolve_alias(MATRIX_ROOM)
backwards_sync(conn, cursor, youtube, client, room, init_sync.next_batch) await backwards_sync(conn, cursor, youtube, client, room, init_sync.next_batch)
print("Started syncing...") await client.sync_forever(30000, full_state=True, since=sync_token)
while True:
try:
client.sync(timeout=30000, full_state=True, since=sync_token)
sync_token = load_sync_token()
except Exception as e:
print(f"Sync error: {e}")
time.sleep(5)
if __name__ == "__main__": if __name__ == "__main__":
main() asyncio.run(main())