Save sync token, check if videos are music before saving.

Signed-off-by: Abdulkadir Furkan Şanlı <me@abdulocra.cy>
This commit is contained in:
Abdulkadir Furkan Şanlı 2024-01-21 09:42:28 +01:00
parent 7aba9580b6
commit 3d4483539b
Signed by: afk
SSH Key Fingerprint: SHA256:s1hULLl4YWdqU501MUfGe1CAG/m1pf9Cs6vFsqeTNHk
2 changed files with 66 additions and 32 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ venv
*.pickle *.pickle
*.json *.json
*.swp *.swp
sync_token

93
main.py
View File

@ -1,17 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""parkerbot: Matrix bot to generate YouTube (music) playlists from links sent to a channel.""" """parkerbot: Matrix bot to generate YouTube (music) playlists from links sent to a channel."""
import asyncio
import os import os
import pickle
import re import re
import sqlite3 import sqlite3
import asyncio
import pickle
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dotenv import load_dotenv from dotenv import load_dotenv
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 google.auth.transport.requests import Request from nio import AsyncClient, ClientConfig, RoomMessageText, SyncResponse
from nio import AsyncClient, MatrixRoom, RoomMessageText
load_dotenv() load_dotenv()
DB_PATH = os.getenv("DB_PATH") DB_PATH = os.getenv("DB_PATH")
@ -28,7 +29,8 @@ conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor() cursor = conn.cursor()
def create_tables(): def define_tables():
"""Define tables for use with program."""
with conn: with conn:
cursor.execute( cursor.execute(
"""CREATE TABLE IF NOT EXISTS messages ( """CREATE TABLE IF NOT EXISTS messages (
@ -57,6 +59,7 @@ def create_tables():
def get_authenticated_service(): def get_authenticated_service():
"""Get an authentivated YouTube service."""
credentials = None credentials = None
# The file token.pickle stores the user's access and refresh tokens. # The file token.pickle stores the user's access and refresh tokens.
if os.path.exists("token.pickle"): if os.path.exists("token.pickle"):
@ -81,11 +84,13 @@ def get_authenticated_service():
def get_monday_date(): def get_monday_date():
"""Get Monday of current week. Weeks start on Monday."""
today = datetime.now() today = datetime.now()
return today - timedelta(days=today.weekday()) return today - timedelta(days=today.weekday())
def create_playlist(youtube, title): def make_playlist(youtube, title):
"""Make a playlist with given title."""
response = ( response = (
youtube.playlists() youtube.playlists()
.insert( .insert(
@ -93,7 +98,7 @@ def create_playlist(youtube, title):
body={ body={
"snippet": { "snippet": {
"title": title, "title": title,
"description": "Weekly playlist created by ParkerBot", "description": "Weekly playlist generated by ParkerBot",
}, },
"status": {"privacyStatus": "public"}, "status": {"privacyStatus": "public"},
}, },
@ -104,7 +109,8 @@ def create_playlist(youtube, title):
return response["id"] return response["id"]
def get_or_create_playlist(youtube, monday_date): def get_or_make_playlist(youtube, monday_date):
"""Get ID of playlist for given Monday's week, make if doesn't exist."""
playlist_title = f"{PLAYLIST_TITLE} {monday_date.strftime('%Y-%m-%d')}" playlist_title = f"{PLAYLIST_TITLE} {monday_date.strftime('%Y-%m-%d')}"
# Check if playlist exists in the database # Check if playlist exists in the database
@ -115,8 +121,8 @@ def get_or_create_playlist(youtube, monday_date):
if row: if row:
return row[0] # Playlist already exists return row[0] # Playlist already exists
# If not, create a new playlist on YouTube and save it in the database # If not, make a new playlist on YouTube and save it in the database
playlist_id = create_playlist(youtube, playlist_title) playlist_id = make_playlist(youtube, playlist_title)
with conn: with conn:
cursor.execute( cursor.execute(
"INSERT INTO playlists (title, playlist_id, creation_date) VALUES (?, ?, ?)", "INSERT INTO playlists (title, playlist_id, creation_date) VALUES (?, ?, ?)",
@ -127,6 +133,7 @@ def get_or_create_playlist(youtube, monday_date):
def add_video_to_playlist(youtube, playlist_id, video_id): def add_video_to_playlist(youtube, playlist_id, video_id):
"""Add video to playlist."""
youtube.playlistItems().insert( youtube.playlistItems().insert(
part="snippet", part="snippet",
body={ body={
@ -138,32 +145,39 @@ def add_video_to_playlist(youtube, playlist_id, video_id):
).execute() ).execute()
async def message_callback(room, event): def is_music(youtube, video_id):
"""Check whether a YouTube video is music."""
video_details = youtube.videos().list(id=video_id, part="snippet").execute()
# Check if the video category is Music (typically category ID 10)
return video_details["items"][0]["snippet"]["categoryId"] == "10"
async def message_callback(client, room, event):
"""Event handler for received messages."""
youtube_link_pattern = r"(https?://(?:www\.|music\.)?youtube\.com/(?!playlist\?list=)watch\?v=[\w-]+|https?://youtu\.be/[\w-]+)" youtube_link_pattern = r"(https?://(?:www\.|music\.)?youtube\.com/(?!playlist\?list=)watch\?v=[\w-]+|https?://youtu\.be/[\w-]+)"
if event.sender != MATRIX_USER:
sender = event.sender sender = event.sender
message_body = event.body if sender != MATRIX_USER:
body = event.body
timestamp = event.server_timestamp
room_id = room.room_id room_id = room.room_id
monday_date = get_monday_date() monday_date = get_monday_date()
youtube = get_authenticated_service() youtube = get_authenticated_service()
playlist_id = get_or_create_playlist(youtube, monday_date) playlist_id = get_or_make_playlist(youtube, monday_date)
youtube_links = re.findall(youtube_link_pattern, message_body) youtube_links = re.findall(youtube_link_pattern, body)
if message_body == "!pow": if body == "!pow":
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}"
await 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={"msgtype": "m.text", "body": reply_msg},
"msgtype": "m.text",
"body": reply_msg
}
) )
if youtube_links:
timestamp = event.server_timestamp
for link in youtube_links: for link in youtube_links:
video_id = link.split("v=")[-1]
if is_music(youtube, video_id):
try: try:
cursor.execute( cursor.execute(
"INSERT INTO messages (sender, message, timestamp) VALUES (?, ?, ?)", "INSERT INTO messages (sender, message, timestamp) VALUES (?, ?, ?)",
@ -177,9 +191,6 @@ async def message_callback(room, event):
else: else:
raise e raise e
for link in youtube_links:
video_id = link.split("v=")[-1]
# Check if the link is already added to any playlist # Check if the link is already added to any playlist
cursor.execute("SELECT id FROM messages WHERE message = ?", (link,)) cursor.execute("SELECT id FROM messages WHERE message = ?", (link,))
message_row = cursor.fetchone() message_row = cursor.fetchone()
@ -204,15 +215,37 @@ async def message_callback(room, event):
print(f"Added track to playlist: {link}") print(f"Added track to playlist: {link}")
async def main(): async def sync_callback(response):
create_tables() # Save the sync token to a file or handle it as needed
global client with open("sync_token", "w") as f:
f.write(response.next_batch)
def load_sync_token():
try:
with open("sync_token", "r") as file:
return file.read().strip()
except FileNotFoundError:
return None
async def get_client():
client = AsyncClient(MATRIX_SERVER, MATRIX_USER) client = AsyncClient(MATRIX_SERVER, MATRIX_USER)
client.add_event_callback(message_callback, RoomMessageText) client.add_event_callback(
lambda room, event: message_callback(client, room, event), RoomMessageText
)
client.add_response_callback(sync_callback, SyncResponse)
print(await client.login(MATRIX_PASSWORD)) print(await client.login(MATRIX_PASSWORD))
await client.join(MATRIX_ROOM) await client.join(MATRIX_ROOM)
await client.sync_forever(timeout=10000) # milliseconds return client
async def main():
"""Get DB and Matrix client ready, and start syncing."""
define_tables()
client = await get_client()
sync_token = load_sync_token()
await client.sync_forever(30000, full_state=True, since=sync_token)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())