From d912d9bc1b195aee0b921287aac68642542b046e Mon Sep 17 00:00:00 2001 From: migatu Date: Fri, 12 Apr 2024 23:20:51 +0200 Subject: [PATCH] Main bot (#1) undefined --- bot.py | 281 ++++++++++++++++++------------------ bot_classes.py | 0 bot_commands.py | 0 bot_functions.py | 0 communication_subroutine.py | 76 ++++++++-- 5 files changed, 201 insertions(+), 156 deletions(-) create mode 100644 bot_classes.py create mode 100644 bot_commands.py create mode 100644 bot_functions.py diff --git a/bot.py b/bot.py index 859f03c..590ddbc 100644 --- a/bot.py +++ b/bot.py @@ -46,7 +46,12 @@ from spotify_dl import youtube as youtube_download from communication_subroutine import comm_subroutine, IN_COMM_Q, OUT_COMM_Q +# *=========================== Classes definition +# The above code is defining a TypedDict called `Music_Config` in Python. This TypedDict has three +# keys: "ctx" which is an optional string, "queue" which is a list of strings, and "requester" which +# is a list of strings. This TypedDict is used to define the structure of a dictionary that should +# have these specific keys and value types. Music_Config = TypedDict( "Music_Config", { @@ -58,6 +63,10 @@ Music_Config = TypedDict( class QueryControl: + """ + This class `QueryControl` is used to manage queries with information about the author, UUID, + content, logger, context, and replies. + """ def __init__(self, query_author, query_uuid, query_content, uplogger, ctx) -> None: self.author = query_author self.uuid = query_uuid @@ -70,6 +79,95 @@ class QueryControl: ) self.replies = [] +class MusicFileList(object): + """ + The `MusicFileList` class manages a list of music files, with the ability to refresh the list from a + file service or local directory and update the list with new items. + """ + + def __init__(self, uplogger) -> None: + self.music_file_list = [] + self.file_service_active = False + self.logger = uplogger + self.logger.info("Created Playlist organizer class") + + def refresh_file_list(self): + """ + The `refresh_file_list` function attempts to connect to a file service to retrieve a list of music + files, and if unsuccessful, it populates the list by scanning a local directory for .mp3 files. + """ + try: + self.logger.info("Attempt to connect to file service") + response = requests.get(f"{FILE_SERVICE_ADDRESS}{GET_MP3}", timeout=360) + self.music_file_list = response.json()["music_file_list"] + self.file_service_active = True + except requests.exceptions.RequestException as e: + for mp3_item in Path.glob(Path(MUSIC_FOLDER), "**/*.mp3"): + temp_music_file = mp3_item.as_posix() + if platform == "win32": + temp_music_file = temp_music_file.replace("/", "\\") + self.music_file_list.append(temp_music_file) + self.file_service_active = False + self.logger.error(e.strerror) + self.logger.error("Service Unavailable") + + def get_file_list(self): + """ + The `get_file_list` function returns the list of music files stored in the object. + :return: The `music_file_list` attribute is being returned. + """ + return self.music_file_list + + def update_file_list(self, item): + """ + The `update_file_list` function appends an item to a music file list and sends a POST request with + the item data to a file service address. + + :param item: The `item` parameter in the `update_file_list` method is the file that you want to add + to the `music_file_list` attribute of the class instance. It is then sent as a JSON payload in a + POST request to a file service address along with the endpoint `SEND_MP3` + """ + self.music_file_list.append(item) + post_data = {"item": str(item)} + requests.post(f"{FILE_SERVICE_ADDRESS}{SEND_MP3}", json=post_data, timeout=360) + +class DoSearchView(discord.ui.View): + def __init__(self, _uuid, result_list, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.uuid = _uuid + self.result_list = result_list + + @discord.ui.button( + label="Nastepna strona", style=discord.ButtonStyle.primary, row=0 + ) + async def next_callback(self, interaction, button): + # emoji = await ctx.guild.fetch_emoji(id) + # button.emoji=1102174413887111189 + logger.info("Pressed") + await interaction.response.edit_message( + content="You clicked the button!" + ) # Send a message when the button is clicked + + @discord.ui.button(label="Wyszukaj wiecej", style=discord.ButtonStyle.danger, row=0) + async def reset_callback(self, interaction, button): + # emoji = await ctx.guild.fetch_emoji(id) + # button.emoji=1102174413887111189 + logger.info("Pressed") + await interaction.response.edit_message( + content="You clicked the button!" + ) # Send a message when the button is clicked + + @discord.ui.button( + label="Poprzednia Strona", style=discord.ButtonStyle.primary, row=0 + ) + async def perv_callback(self, interaction, button): + # emoji = await ctx.guild.fetch_emoji(id) + # button.emoji=1102174413887111189 + logger.info("Pressed") + await interaction.response.edit_message( + content="You clicked the button!" + ) # Send a message when the button is clicked + # *=========================================== Predefines formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") @@ -193,60 +291,6 @@ logger.info("Playlist generation") # *=========================================== Load Data files - -class MusicFileList(object): - """ - The `MusicFileList` class manages a list of music files, with the ability to refresh the list from a - file service or local directory and update the list with new items. - """ - - def __init__(self, uplogger) -> None: - self.music_file_list = [] - self.file_service_active = False - self.logger = uplogger - self.logger.info("Created Playlist organizer class") - - def refresh_file_list(self): - """ - The `refresh_file_list` function attempts to connect to a file service to retrieve a list of music - files, and if unsuccessful, it populates the list by scanning a local directory for .mp3 files. - """ - try: - self.logger.info("Attempt to connect to file service") - response = requests.get(f"{FILE_SERVICE_ADDRESS}{GET_MP3}", timeout=360) - self.music_file_list = response.json()["music_file_list"] - self.file_service_active = True - except requests.exceptions.RequestException as e: - for mp3_item in Path.glob(Path(MUSIC_FOLDER), "**/*.mp3"): - temp_music_file = mp3_item.as_posix() - if platform == "win32": - temp_music_file = temp_music_file.replace("/", "\\") - self.music_file_list.append(temp_music_file) - self.file_service_active = False - self.logger.error(e.strerror) - self.logger.error("Service Unavailable") - - def get_file_list(self): - """ - The `get_file_list` function returns the list of music files stored in the object. - :return: The `music_file_list` attribute is being returned. - """ - return self.music_file_list - - def update_file_list(self, item): - """ - The `update_file_list` function appends an item to a music file list and sends a POST request with - the item data to a file service address. - - :param item: The `item` parameter in the `update_file_list` method is the file that you want to add - to the `music_file_list` attribute of the class instance. It is then sent as a JSON payload in a - POST request to a file service address along with the endpoint `SEND_MP3` - """ - self.music_file_list.append(item) - post_data = {"item": str(item)} - requests.post(f"{FILE_SERVICE_ADDRESS}{SEND_MP3}", json=post_data, timeout=360) - - music_file_list = [] music_file_list = MusicFileList(logger) music_file_list.refresh_file_list() @@ -283,6 +327,8 @@ for key in word_reactions: word_reactions[key][2] = datetime.now() logger.info("Done") + + # *=========================================== Create Client logger.info("Creating discord bot") client = commands.Bot(intents=intents, command_prefix="$") @@ -342,8 +388,8 @@ async def on_message(message): if isinstance(message.channel, discord.DMChannel): channel = client.get_channel(1064888712565100614) await channel.send("Słyszałem ja żem że: " + message.content) - else: - channel = message.channel + return + channel = message.channel await client.process_commands(message) message.content = message.content.lower() @@ -682,10 +728,40 @@ async def check_music(): @tasks.loop(seconds=3) # trunk-ignore(pylint/R0915) async def check_data_q(): - channel = client.get_channel(1062047367337095268) try: data = IN_COMM_Q.get(block=False) - await channel.send(f"O. A tak będzie wyglądało coś ciekawego w przyszłości: {data}") + entries = [] + if data.stop: + searcher = data.author + ctx = data.ctx + query = data.content + l_p = 1 + for doi in data.entries: + logger.info(doi) + desc = data.entries[doi] + title = desc['Title'][0] + entries.append(f"{l_p}. {title} pod linkiem https://www.sci-hub.se/{doi} i jest to {desc['type']}\n") + l_p+=1 + message = '*Z podłogi wysuwa się winda na książki*' + message += f' Dzielny poszukiwaczu @{searcher} w odpowiedzi na twoje zapytanie {query}' + message += 'nasza biblioteka służy Ci odpowiedzią. Przy dźwięku fanfar winda się otwiera a w środku ' + if len(entries)<1: + message += "niestety nie ma nic" + elif len(entries)>=1 and len(entries)<5: + message += " znajduje się coś:\n" + for item in entries: + message+=item + else: + message+= " znajduje się cholernie dużo:\n" + for item in entries: + message+=item + if len(message) > 1500: + await ctx.send(message) + message = "" + + await ctx.send(message) + #Kept for sentimental reasons + #await ctx.send(f"O. A tak będzie wyglądało coś ciekawego w przyszłości: {data}") except Empty: pass @@ -1047,8 +1123,10 @@ async def get_file(ctx, source, link): dir_path = None item_type, item_id = spotify.parse_spotify_url(link) if item_id: - file_list = spotify.fetch_tracks(spotify_ctrl, item_type, link) - directory_name = spotify.get_item_name(spotify_ctrl, item_type, item_id) + coro = asyncio.to_thread(spotify.fetch_tracks, args=(spotify_ctrl, item_type, link)) + file_list = await coro + coro = asyncio.to_thread(spotify.get_item_name, args=(spotify_ctrl, item_type, item_id)) + directory_name = await coro url_data = {"urls": []} url_dict = {} url_dict["save_path"] = Path( @@ -2065,44 +2143,6 @@ async def parametry_muzyki_mego_ludu( logger.info(exce) -class DoSearchView(discord.ui.View): - def __init__(self, uuid, result_list, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self.uuid = uuid - self.result_list = result_list - - @discord.ui.button( - label="Nastepna strona", style=discord.ButtonStyle.primary, row=0 - ) - async def next_callback(self, interaction, button): - # emoji = await ctx.guild.fetch_emoji(id) - # button.emoji=1102174413887111189 - logger.info("Pressed") - await interaction.response.edit_message( - content="You clicked the button!" - ) # Send a message when the button is clicked - - @discord.ui.button(label="Wyszukaj wiecej", style=discord.ButtonStyle.danger, row=0) - async def reset_callback(self, interaction, button): - # emoji = await ctx.guild.fetch_emoji(id) - # button.emoji=1102174413887111189 - logger.info("Pressed") - await interaction.response.edit_message( - content="You clicked the button!" - ) # Send a message when the button is clicked - - @discord.ui.button( - label="Poprzednia Strona", style=discord.ButtonStyle.primary, row=0 - ) - async def perv_callback(self, interaction, button): - # emoji = await ctx.guild.fetch_emoji(id) - # button.emoji=1102174413887111189 - logger.info("Pressed") - await interaction.response.edit_message( - content="You clicked the button!" - ) # Send a message when the button is clicked - - @client.hybrid_command( name="wyszukaj_linki_do_dokumentow", description="Szuka linkow doi w bazie crossref i podaje linki do scihuba", @@ -2111,8 +2151,8 @@ class DoSearchView(discord.ui.View): async def wyszukaj_linki_do_dokumentow(ctx): query = ctx.message.content query_uuid = uuid.uuid4() - # query_uuid = "test" - # 3 wyslij Librariana + #TODO: TESTING ONLY!! + #query_uuid = uuid.UUID('{12345678-1234-5678-1234-567812345678}') json_query = {"UUID": str(query_uuid), "query": str(query)[30:], "page": 1} coroutine = asyncio.to_thread( requests.post, @@ -2145,46 +2185,6 @@ async def wyszukaj_linki_do_dokumentow(ctx): await ctx.send(f"No dobra poszło. Zapamiętaj proszę {query_uuid} - to identyfikator twojego zapytania. Jesteś {queue_size} w kolejce."+ " Zapytania obsługuje algorytm zasilany czterema chomikami zapierdalającymi w kołowrotku - więc wyniki najwcześniej za kilka godzi - ale mogą być też dni.") -''' -async def wyswietl_wyniki_wyszukiwania(ctx): - - # 4 zwroc 4 wyniki od librariana - # 5 przyciski przy każdym wierszu pozwalają sprawdzić czy plik istnieje na scihubie - jeśli tak podaje link do sciagniecia - # 6 ostatni wiersz to dalej, wróć, skasuj, zaciagnij kolejne wyniki (jeśli jest ich więcej niż 1000) - result_view = DoSearchView( - uuid=str(query_uuid), result_list=query_response.json()["data"] - ) - resulttab = [] - resultlink = [] - hit, fetched, total, _ = query_response.json()["data"][0] - answer = query_response.json()["data"][1] - - for item in answer: - resulttab.append(str(item["title"])) - item_link = "https://sci-hub.se/" + str(item["DOI"]) - resultlink.append(item_link) - logger.info(len(resulttab)) - for i in range(0, min(4, len(resulttab))): - logger.info(i) - logger.info(resulttab[i]) - logger.info(resultlink[i]) - url = resultlink[i] - logger.info(url) - temp_label = str(resulttab[i]) - temp_label = temp_label.strip("[']") - result_view.add_item( - discord.ui.Button(label=str(temp_label[0:79]), row=i + 1, url=url) - ) - - labelka = ( - "Wyniki wyszukiwania dla: " - + str(query)[30:] - + f"\nWszystkich {total} sprawdzonych: {fetched} znalezionych: {hit} " - ) # ile znaleziono artykułów w ogóle, ile zostało po refine - await ctx.send( - labelka, view=result_view - ) # Send a message with our View class that contains the button -''' @client.hybrid_command( name="krecimy_pornola", @@ -2223,7 +2223,6 @@ async def krecimy_pornola(ctx): # *================================== Run if __name__ == "__main__": - # TODO: wpakowac kondzia w wątki odpalic asynchronicznie rownolegle z flaskiem do sterowania nim logger.info("Starting discord bot") threads = [] logger.info("Starting discord bot: Creating threads") diff --git a/bot_classes.py b/bot_classes.py new file mode 100644 index 0000000..e69de29 diff --git a/bot_commands.py b/bot_commands.py new file mode 100644 index 0000000..e69de29 diff --git a/bot_functions.py b/bot_functions.py new file mode 100644 index 0000000..e69de29 diff --git a/communication_subroutine.py b/communication_subroutine.py index 7e24705..4b53099 100644 --- a/communication_subroutine.py +++ b/communication_subroutine.py @@ -1,6 +1,7 @@ import logging import threading import json +import time from queue import Queue, Empty from flask import Flask, jsonify, request from waitress import serve @@ -9,28 +10,34 @@ HOST_ADDRESS = "192.168.1.191" PORT_ADDRESS = 5000 OUT_COMM_Q = Queue() IN_COMM_Q = Queue() -internal_q = Queue() +awaiting_q = [] +incoming_q = Queue() app = Flask(__name__) @app.route("/conjurer", methods=["POST"]) def answer_external_command(): + """ + The function `answer_external_command` logs the request data, loads the data as JSON, logs the + record, and then puts the record into an incoming queue before returning a success message. + :return: The function `answer_external_command()` is returning a JSON response with the message + "SUCCESS". + """ + app.logger.info(request) record = json.loads(request.data) app.logger.info(record) app.logger.info("DATA RECEIVED") - - - while True: - try: - q_data = internal_q.get(block=False) - except Empty: - break - if q_data.uuid in record: - pass - + incoming_q.put(record) return jsonify("SUCCESS") @app.route("/conjurer", methods=["GET"]) def check_alive(): + """ + The function "check_alive" returns a JSON response with the message "ALIVE" when the "/conjurer" + route is accessed via a GET request. + :return: The code snippet is a Flask route that listens for GET requests to the "/conjurer" + endpoint. When a GET request is made to this endpoint, the function check_alive() is called, which + returns a JSON response with the message "ALIVE". + """ return jsonify("ALIVE") def flask_debug(_logger): @@ -52,19 +59,58 @@ def waitress_run(_logger): serve(app, host=HOST_ADDRESS, port=PORT_ADDRESS) def scan_queue(_logger): + """ + The function `scan_queue` reads data from a queue, logs it, and appends it to another queue. + + :param _logger: The `_logger` parameter is typically an instance of a logging object that is used to + record and store log messages. It is commonly used to track the flow of the program, record errors, + and provide information for debugging purposes. In this code snippet, the `_logger` object is used + to log the + """ while True: data = OUT_COMM_Q.get() _logger.info(data) - internal_q.put(data) - query_list.append(data) + awaiting_q.append(data) + +def scan_incoming(_logger): + """ + The `scan_incoming` function continuously checks for incoming data, processes it, and logs when data + is found. + + :param _logger: The `_logger` parameter in the `scan_incoming` function is a logger object that is + used to log messages or information during the execution of the function. It is typically used for + debugging, monitoring, or tracking the flow of the program. In this case, the `_logger` is being + used + """ + while True: + try: + answer = incoming_q.get(block=False) + _logger.info("DATA FOUND") + for record in awaiting_q: + if record.uuid in answer.keys(): + record.stop = True + record.entries = answer[record.uuid] + IN_COMM_Q.put(record) + except Empty: + time.sleep(1) + def comm_subroutine(logger): + """ + The `comm_subroutine` function starts multiple threads to run different tasks concurrently. + + :param logger: The `logger` parameter in the `comm_subroutine` function is an instance of a logger + object that is used for logging messages at various levels (e.g., debug, info, warning, error). In + the provided code snippet, the logger is used to log messages at the "info" level + """ #logger.setLevel(logging.DEBUG) logger.info("Started") threads = [] - threads.append(threading.Thread(target=flask_debug, args=(logger,))) - #threads.append(threading.Thread(target=waitress_run, args=(logger,))) + #threads.append(threading.Thread(target=flask_debug, args=(logger,))) + threads.append(threading.Thread(target=waitress_run, args=(logger,))) threads.append(threading.Thread(target=scan_queue,args=(logger,))) + threads.append(threading.Thread(target=scan_incoming,args=(logger,))) + for worker in threads: worker.start()