From 2aeb3d286f79673bebf95d2dc8f134ee4156f541 Mon Sep 17 00:00:00 2001 From: Polish Hammer Date: Thu, 30 Oct 2025 16:59:21 +0100 Subject: [PATCH] Tag: 1.26 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intermediate commits (oldest → newest): - Generated scripts - new function - more work needed - Search client - serverside - added server side search - M - fix - fix deploy - zmiana nazwy - Params of radio - Watcher --- conjurer_musician/conjurer_musician.py | 38 +++- conjurer_musician/media_search_functions.py | 52 +++++ .../radio_conjurer.etc.logrotate | 9 + conjurer_musician/radio_conjurer.liq | 27 ++- conjurer_musician/revoke_shares.py | 99 +++++++++ conjurer_musician/scan_shares.py | 44 ++++ conjurer_musician/scan_shares.service | 6 + conjurer_musician/scan_shares.timer | 11 + conjurer_musician/script.params | 195 ++++++++++++++++++ conjurer_musician/search_and_share.py | 93 +++++++++ deploy.sh | 12 +- deploy_music.sh | 3 +- file_search_commands.py | 71 +++++++ file_search_functions.py | 31 +++ thin_client.py | 1 + watch_script_params.py | 63 ++++++ 16 files changed, 743 insertions(+), 12 deletions(-) create mode 100644 conjurer_musician/media_search_functions.py create mode 100644 conjurer_musician/radio_conjurer.etc.logrotate create mode 100755 conjurer_musician/revoke_shares.py create mode 100755 conjurer_musician/scan_shares.py create mode 100644 conjurer_musician/scan_shares.service create mode 100644 conjurer_musician/scan_shares.timer create mode 100644 conjurer_musician/script.params create mode 100755 conjurer_musician/search_and_share.py create mode 100644 file_search_commands.py create mode 100644 file_search_functions.py create mode 100755 watch_script_params.py diff --git a/conjurer_musician/conjurer_musician.py b/conjurer_musician/conjurer_musician.py index 16e18c4..548e90e 100644 --- a/conjurer_musician/conjurer_musician.py +++ b/conjurer_musician/conjurer_musician.py @@ -29,6 +29,8 @@ from flask import ( send_from_directory, ) from waitress import serve +import media_search_functions + MAIN_BOT_ADDRESS = "http://192.168.1.191:5000" MUSIC_TRACKER = "/prepped_tracks" @@ -48,10 +50,10 @@ if platform in ("linux", "linux2"): else: LOGFILE = "/home/pi/Conjurer/discord_mus_service.log" NETRC_FILE = "/home/pi/.netrc" - LOGSTORE = "/home/pi/MediaShare/logs/" + LOGSTORE = "/home/pi/MediaFolder/logs/" ENCODING = "utf-8" - MUSIC_FOLDER = "/home/pi/MediaShare/mp3/" - PRIORITY_FOLDER = "/home/pi/MediaShare/mp3/Magiczne i chuj/" + MUSIC_FOLDER = "/home/pi/MediaFolder/mp3/" + PRIORITY_FOLDER = "/home/pi/MediaFoldermp3/Magiczne i chuj/" RADIOLOG_PATH = "/home/pi/Conjurer/radio_log.log" PERSISTENCE_PATH = "/home/pi/Conjurer/persistence.log" @@ -332,6 +334,36 @@ def remove_characters(string, character): return string.replace(character, "") +@app.route('/get_share_list', methods=['POST']) +def get_share_list(): + data = request.get_json() + entries = data.get('entries') + keywords = data.get('keywords') + # Validate entries + if not isinstance(entries, int) or not (1 <= entries <= 10): + return jsonify({'error': '"entries" must be an integer between 1 and 10.'}), 400 + # Validate keywords list + if not isinstance(keywords, list) or not all(isinstance(k, str) for k in keywords): + return jsonify({'error': '"keywords" must be a list of strings.'}), 400 + # Call external search + files = media_search_functions.find_matches(entries, keywords) + # Return each file path as a JSON list + return jsonify({'files': files}), 200 + +@app.route('/get_share_links', methods=['POST']) +def get_share_links(): + data = request.get_json() + file_paths = data.get('file_paths') + # Validate file_paths list + if not isinstance(file_paths, list) or not all(isinstance(p, str) for p in file_paths): + return jsonify({'error': '"file_paths" must be a list of strings.'}), 400 + # Call external publish + links = media_search_functions.publish(file_paths) + # Return list of published links + return jsonify({'links': links}), 200 + + + @app.route("/stream", methods=["GET"]) def stream_music(): """ diff --git a/conjurer_musician/media_search_functions.py b/conjurer_musician/media_search_functions.py new file mode 100644 index 0000000..87a2712 --- /dev/null +++ b/conjurer_musician/media_search_functions.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +import argparse +import json +import os +import sys +import uuid +from pathlib import Path + +# CONFIGURATION +JSON_DB = '/var/log/share_scan.json' +SHARE_DIR = Path('/var/www/html/share') +BASE_URL = 'https://czernobog.pl/share' + +# Ensure share directory exists +SHARE_DIR.mkdir(parents=True, exist_ok=True) + +def load_db(): + with open(JSON_DB) as f: + return json.load(f)['entries'] + +ENTRIES = load_db() + +def relevancy(path, keywords): + score = 0 + low = path.lower() + for kw in keywords: + if kw.lower() in low: + score += low.count(kw.lower()) + return score + +def find_matches(count, keywords): + scored = [] + for e in ENTRIES: + score = relevancy(e['path'], keywords) + if score > 0: + scored.append((score, e['path'])) + scored.sort(reverse=True, key=lambda x: x[0]) + result = [p for _, p in scored] + return result[:count] + +def publish(paths): + urls = [] + for path in paths: + token = uuid.uuid4().hex + link = SHARE_DIR / token + try: + os.symlink(path, link) + except FileExistsError: + pass + urls.append(f"{BASE_URL}/{token}") + return urls + diff --git a/conjurer_musician/radio_conjurer.etc.logrotate b/conjurer_musician/radio_conjurer.etc.logrotate new file mode 100644 index 0000000..ea879af --- /dev/null +++ b/conjurer_musician/radio_conjurer.etc.logrotate @@ -0,0 +1,9 @@ +/home/pi/Conjurer/radio_log.log { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + copytruncate +} diff --git a/conjurer_musician/radio_conjurer.liq b/conjurer_musician/radio_conjurer.liq index d536607..b28c947 100644 --- a/conjurer_musician/radio_conjurer.liq +++ b/conjurer_musician/radio_conjurer.liq @@ -43,6 +43,22 @@ def queue_processing() end end +# Configure output formats and destinations +def now_playing(m) + # raw lookup ("" if missing) + raw_title = m["title"] + raw_artist = m["artist"] + + # default if empty + title = if raw_title == "" then "Unknown" else raw_title end + artist = if raw_artist == "" then "Unknown" else raw_artist end + log.critical("Now playing #{artist} - #{title}") + # write it out + system("echo '#{artist} - #{title}' > /tmp/now_playing.txt") + m +end + + # Randomly select a playlist with weights s4 = random(id="randomizer", weights=[2, 3, 5], [s1, s2, s3]) @@ -52,7 +68,8 @@ jingles = (playlist(reload_mode="watch", "/home/pi/Conjurer/jingles.playlist")) # Create the main stream with random playlist and jingles s = rotate(id="randomizer", weights=[10, 1], [s4, jingles]) -s = crossfade(fade_out=2., fade_in=2., duration=4., fallback(id="switcher", track_sensitive=false, [requests_queue, s])) +s = crossfade(fade_out=2., fade_in=2., duration=4., fallback(id="switcher", track_sensitive=true, [requests_queue, s])) +s = metadata.map(now_playing, s) # Set up an interactive harbor for controlling the stream interactive.harbor(port = 9999) @@ -87,7 +104,7 @@ s = blank.skip(max_blank=10., s) live_enabled = interactive.bool("Going Live!", true) s = add([mic,s]) -s=switch(track_sensitive=false, +s=switch(track_sensitive=true, [(live_enabled, mic), ({true}, s)]) @@ -102,9 +119,8 @@ set("log.level", loglevel) # Enable logging to file set("log.file", log_to_file) set("log.file.path", logpath) - # Set up emergency fallback track -emergency = single("/home/pi/RetroPie/mp3/Youtube/Dr. Peacock - Trip to Ireland [GvrvQTUbUcA].mp3") +emergency = single("/home/pi/MediaFolder/mp3/Youtube/Dr. Peacock - Trip to Ireland [GvrvQTUbUcA].mp3") radio = fallback(id="switcher2", track_sensitive=false, [s, emergency]) # Set up an interactive control for skipping tracks @@ -138,8 +154,7 @@ interactive.persistent("script.params") # Configure output formats and destinations - -output.icecast(%mp3, host="retropie", port=8000, password=credentials.password, icy_metadata="true", mount="mp3-stream", radio) +output.icecast(%mp3, host="radio", port=8000, password=credentials.password, icy_metadata="true", mount="mp3-stream", radio) output.pulseaudio(radio) #output.file.hls("/tmp/hls", [("mp3-low", %mp3(bitrate=96)), ("mp3-hi", %mp3(bitrate=160))], radio) # Uncomment the following lines to enable additional output formats diff --git a/conjurer_musician/revoke_shares.py b/conjurer_musician/revoke_shares.py new file mode 100755 index 0000000..a3f19f3 --- /dev/null +++ b/conjurer_musician/revoke_shares.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +import os +import re +import time +import json +import argparse +import sys +from pathlib import Path + +# — CONFIGURATION — adjust as needed — +APACHE_LOG = '/var/log/apache2/share_access.log' # <- point to your share-specific log! +SHARE_DIR = Path('/var/www/html/share') +METADATA_FILE = SHARE_DIR / '.downloads.json' +STATE_FILE = SHARE_DIR / '.logpos' +# Regex to match lines like: "GET /share/abcdef1234... HTTP/1.1" +TOKEN_REGEX = re.compile(r'"\s*GET\s+/share/([0-9a-f]{32})\s+HTTP/') + +def debug(msg, args): + if args.debug: + print(f"[DEBUG] {msg}", file=sys.stderr) + +def load_metadata(args): + if METADATA_FILE.exists(): + data = json.loads(METADATA_FILE.read_text()) + debug(f"Loaded metadata: {data}", args) + return data + return {} + +def save_metadata(md, args): + METADATA_FILE.write_text(json.dumps(md)) + debug(f"Saved metadata: {md}", args) + +def load_state(args): + if STATE_FILE.exists(): + pos = int(STATE_FILE.read_text()) + debug(f"Loaded last log position: {pos}", args) + return pos + return 0 + +def save_state(pos, args): + STATE_FILE.write_text(str(pos)) + debug(f"Saved new log position: {pos}", args) + +def scan_log(since_pos, args): + try: + with open(APACHE_LOG, 'r') as f: + f.seek(since_pos) + lines = f.readlines() + newpos = f.tell() + debug(f"Read {len(lines)} new lines", args) + return lines, newpos + except Exception as e: + print(f"Error reading log: {e}", file=sys.stderr) + return [], since_pos + +def record_downloads(lines, metadata, args): + now = time.time() + for line in lines: + m = TOKEN_REGEX.search(line) + if m: + tok = m.group(1) + if tok not in metadata: + metadata[tok] = now + debug(f"Recorded download of token {tok} at {now}", args) + return metadata + +def revoke_old(metadata, args): + now = time.time() + to_remove = [tok for tok, ts in metadata.items() if now - ts >= 3600] + for tok in to_remove: + link = SHARE_DIR / tok + try: + link.unlink() + debug(f"Unlinked token {tok}", args) + except FileNotFoundError: + debug(f"Link {link} not found when revoking", args) + metadata.pop(tok, None) + return bool(to_remove) + +def main(): + p = argparse.ArgumentParser(description="Revoke share links 2min after download") + p.add_argument('--debug', action='store_true', help='print debug info to stderr') + args = p.parse_args() + + SHARE_DIR.mkdir(parents=True, exist_ok=True) + metadata = load_metadata(args) + state = load_state(args) + + lines, newpos = scan_log(state, args) + metadata = record_downloads(lines, metadata, args) + save_metadata(metadata, args) # always persist new downloads + + if revoke_old(metadata, args): + save_metadata(metadata, args) # persist removals + + save_state(newpos, args) + +if __name__ == '__main__': + main() diff --git a/conjurer_musician/scan_shares.py b/conjurer_musician/scan_shares.py new file mode 100755 index 0000000..dd78636 --- /dev/null +++ b/conjurer_musician/scan_shares.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +import os +import json +import time +from pathlib import Path + +# Configuration +SCAN_PATH = '/mnt/shares' +OUTPUT_FILE = '/var/log/share_scan.json' + +def scan_directory(root): + """Recursively walk `root` and collect metadata.""" + entries = [] + for dirpath, dirs, files in os.walk(root): + for name in dirs + files: + full = os.path.join(dirpath, name) + try: + stat = os.stat(full) + entries.append({ + 'path': full, + 'is_dir': os.path.isdir(full), + 'size_bytes': stat.st_size, + 'mtime': time.strftime('%Y-%m-%dT%H:%M:%S%z', + time.localtime(stat.st_mtime)), + }) + except Exception as e: + # skip items we can't stat + continue + return entries + +def main(): + data = { + 'scanned_at': time.strftime('%Y-%m-%dT%H:%M:%S%z', time.localtime()), + 'root': SCAN_PATH, + 'entries': scan_directory(SCAN_PATH), + } + # Write atomically + temp = OUTPUT_FILE + '.tmp' + with open(temp, 'w') as f: + json.dump(data, f, indent=2) + os.replace(temp, OUTPUT_FILE) + +if __name__ == '__main__': + main() diff --git a/conjurer_musician/scan_shares.service b/conjurer_musician/scan_shares.service new file mode 100644 index 0000000..861c100 --- /dev/null +++ b/conjurer_musician/scan_shares.service @@ -0,0 +1,6 @@ +[Unit] +Description=One‑shot scan of /mnt/shares → JSON + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/scan_shares.py diff --git a/conjurer_musician/scan_shares.timer b/conjurer_musician/scan_shares.timer new file mode 100644 index 0000000..3196c32 --- /dev/null +++ b/conjurer_musician/scan_shares.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Hourly timer for share‑scanner + +[Timer] +# delay 2min after boot, then every hour +OnBootSec=20min +OnUnitActiveSec=24h +Unit=scan_shares.service + +[Install] +WantedBy=timers.target diff --git a/conjurer_musician/script.params b/conjurer_musician/script.params new file mode 100644 index 0000000..f8837f0 --- /dev/null +++ b/conjurer_musician/script.params @@ -0,0 +1,195 @@ +{ + "string": [], + "bool": [ [ "Skip track", false ], [ "Going Live!", false ] ], + "int": [], + "float": [ + [ + "mic_volume", + 0.0 + ], + [ + "compress_gain6", + 0.1 + ], + [ + "compress_ratio6", + 7.9 + ], + [ + "compress_threshold6", + -15.8 + ], + [ + "compress_release6", + 30.0 + ], + [ + "compress_attack6", + 30.0 + ], + [ + "compress_frequency6", + 18610.0 + ], + [ + "compress_gain5", + 5.4 + ], + [ + "compress_ratio5", + 4.8 + ], + [ + "compress_threshold5", + -13.6 + ], + [ + "compress_release5", + 170.0 + ], + [ + "compress_attack5", + 160.0 + ], + [ + "compress_frequency5", + 13950.0 + ], + [ + "compress_gain4", + 6.5 + ], + [ + "compress_ratio4", + 4.0 + ], + [ + "compress_threshold4", + -12.2 + ], + [ + "compress_release4", + 180.0 + ], + [ + "compress_attack4", + 200.0 + ], + [ + "compress_frequency4", + 3890.0 + ], + [ + "compress_gain3", + 8.2 + ], + [ + "compress_ratio3", + 3.5 + ], + [ + "compress_threshold3", + -11.7 + ], + [ + "compress_release3", + 180.0 + ], + [ + "compress_attack3", + 250.0 + ], + [ + "compress_frequency3", + 2270.0 + ], + [ + "compress_gain2", + 7.4 + ], + [ + "compress_ratio2", + 3.9 + ], + [ + "compress_threshold2", + -11.5 + ], + [ + "compress_release2", + 190.0 + ], + [ + "compress_attack2", + 250.0 + ], + [ + "compress_frequency2", + 1920.0 + ], + [ + "compress_gain1", + 11.1 + ], + [ + "compress_ratio1", + 3.4 + ], + [ + "compress_threshold1", + -12.7 + ], + [ + "compress_release1", + 170.0 + ], + [ + "compress_attack1", + 220.0 + ], + [ + "compress_frequency1", + 650.0 + ], + [ + "compress_gain0", + 16.0 + ], + [ + "compress_ratio0", + 3.2 + ], + [ + "compress_threshold0", + -15.3 + ], + [ + "compress_release0", + 200.0 + ], + [ + "compress_attack0", + 90.0 + ], + [ + "compress_frequency0", + 110.0 + ], + [ + "compress_wet", + 1.0 + ], + [ + "main_volume", + 1.8 + ], + [ + "g", + 9.5 + ], + [ + "f", + 64.2 + ] + ] +} \ No newline at end of file diff --git a/conjurer_musician/search_and_share.py b/conjurer_musician/search_and_share.py new file mode 100755 index 0000000..c41160d --- /dev/null +++ b/conjurer_musician/search_and_share.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +import argparse +import json +import os +import sys +import uuid +from pathlib import Path + +# CONFIGURATION +JSON_DB = '/var/log/share_scan.json' +SHARE_DIR = Path('/var/www/html/share') +BASE_URL = 'https://czernobog.pl/share' + +# Ensure share directory exists +SHARE_DIR.mkdir(parents=True, exist_ok=True) + +def load_db(): + with open(JSON_DB) as f: + return json.load(f)['entries'] + +def relevancy(path, keywords): + score = 0 + low = path.lower() + for kw in keywords: + if kw.lower() in low: + score += low.count(kw.lower()) + return score + +def find_matches(entries, keywords): + scored = [] + for e in entries: + score = relevancy(e['path'], keywords) + if score > 0: + scored.append((score, e['path'])) + scored.sort(reverse=True, key=lambda x: x[0]) + return [p for _, p in scored] + +def interactive_select(candidates): + print("Search results:") + for idx, p in enumerate(candidates, 1): + print(f" {idx:3d}. {p}") + sel = input("\nSelect files (e.g. 1,3-5): ").strip() + nums = set() + for part in sel.split(','): + if '-' in part: + a,b = part.split('-',1) + nums.update(range(int(a), int(b)+1)) + else: + nums.add(int(part)) + return [candidates[i-1] for i in sorted(nums) if 1 <= i <= len(candidates)] + +def publish(paths): + urls = [] + for path in paths: + token = uuid.uuid4().hex + link = SHARE_DIR / token + try: + os.symlink(path, link) + except FileExistsError: + pass + urls.append(f"{BASE_URL}/{token}") + return urls + +def main(): + parser = argparse.ArgumentParser(description="Search and share files from JSON DB") + parser.add_argument('keywords', nargs='*', + help='Search keywords (interactive if omitted)') + parser.add_argument('-n','--count', type=int, default=10, + help='How many top results to share') + args = parser.parse_args() + + if not args.keywords: + # Interactive: ask for query + q = input("Enter search keywords (space-separated): ").strip().split() + args.keywords = q + + entries = load_db() + matches = find_matches(entries, args.keywords) + top_n = matches[:args.count] + + if sys.stdin.isatty(): + # interactive selection + sel = interactive_select(top_n) + else: + # non-interactive: share all top_n + sel = top_n + + urls = publish(sel) + for u in urls: + print(u) + +if __name__=='__main__': + main() diff --git a/deploy.sh b/deploy.sh index ff0e8d9..0651f6a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash -total_commands=24 +total_commands=26 current_command=0 function print_progress { @@ -79,8 +79,16 @@ print_progress "cp ./conjurer/radio_commands.py ./Conjurer/" cp ./conjurer/voice_recognition_commands.py ./Conjurer/ print_progress "cp ./conjurer/voice_recognition_commands.py ./Conjurer/" +cp ./conjurer/file_search_functions.py ./Conjurer/ + +print_progress "cp ./conjurer/file_search_functions.py ./Conjurer/" + +cp ./conjurer/file_search_commands.py ./Conjurer +print_progress "cp ./conjurer/file_search_functions.py ./Conjurer/" + + cp ./conjurer/thin_client.py ./Conjurer/bot.py print_progress "cp ./conjurer/thin_client.py ./Conjurer/bot.py" sudo systemctl restart conjurer.service -print_progress "sudo systemctl restart conjurer.service" \ No newline at end of file +print_progress "sudo systemctl restart conjurer.service" diff --git a/deploy_music.sh b/deploy_music.sh index 7914d31..6253a7b 100755 --- a/deploy_music.sh +++ b/deploy_music.sh @@ -2,5 +2,6 @@ cd ./conjurer || exit git pull cd .. +cp ./conjurer/conjurer_musician/media_search_functions.py ./Conjurer cp ./conjurer/conjurer_musician/conjurer_musician.py ./Conjurer -sudo systemctl restart conjurer_musician.service \ No newline at end of file +sudo systemctl restart conjurer_musician.service diff --git a/file_search_commands.py b/file_search_commands.py new file mode 100644 index 0000000..1e20850 --- /dev/null +++ b/file_search_commands.py @@ -0,0 +1,71 @@ +import os +import discord +from discord.ext import commands +import file_search_functions + +class FileSelectView(discord.ui.View): + def __init__(self, files): + super().__init__(timeout=60) + options = [discord.SelectOption(label=os.path.basename(f)[:100], description=f, value=f) for f in files] + self.select = discord.ui.Select( + placeholder="Select files...", + min_values=1, + max_values=len(options), + options=options + ) + self.select.callback = self.select_callback + self.add_item(self.select) + self.selected = [] + + async def select_callback(self, interaction: discord.Interaction): + # Store selected file paths + self.selected = self.select.values + await interaction.response.defer() + + @discord.ui.button(label="Accept", style=discord.ButtonStyle.green) + async def accept_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + # Handle acceptance + if not self.selected: + await interaction.response.send_message("No files selected.", ephemeral=True) + return + # Publish selected files + links = file_search_functions.publish(self.selected) + # Display the returned links + await interaction.response.edit_message(content="Published Links:\n" + "\n".join(links), view=None) + + @discord.ui.button(label="Cancel", style=discord.ButtonStyle.red) + async def cancel_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + # Handle cancellation + await interaction.response.edit_message(content="Operation cancelled.", view=None) + +class FileSearchCog(commands.Cog): + """Cog providing a file search and publish command.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.has_any_role("Jarl", "Thane") + @commands.command(name="tajna_biblioteka_inkwizycji") + async def findfiles(self, ctx: commands.Context, entries: int, *, keywords: str): + """ + Search for files matching keywords and publish selected ones. + + Usage: !findfiles + """ + if entries < 1 or entries > 10: + await ctx.send("❌ Entries must be between 1 and 10.") + return + keyword_list = keywords.split() + files = file_search_functions.find_matches(entries, keyword_list) + if not files: + await ctx.send("🔍 No matching files found.") + return + view = FileSelectView(files) + file_list = "\n".join(f"{i+1}. {f}" for i, f in enumerate(files)) + await ctx.send( + f"🔍 Found files (select and click Accept or Cancel):\n{file_list}", + view=view + ) + +async def setup(bot: commands.Bot): + await bot.add_cog(FileSearchCog(bot)) diff --git a/file_search_functions.py b/file_search_functions.py new file mode 100644 index 0000000..b064447 --- /dev/null +++ b/file_search_functions.py @@ -0,0 +1,31 @@ +import requests + +# Base URL for Flask service +BASE_URL = 'http://192.168.1.15:5000' + + +def find_matches(entries, keywords): + """ + Call the get_share_list endpoint. + entries: int (1-10) + keywords: list of strings + Returns: list of file paths + """ + payload = {'entries': entries, 'keywords': keywords} + response = requests.post(f"{BASE_URL}/get_share_list", json=payload) + response.raise_for_status() + data = response.json() + return data.get('files', []) + + +def publish(file_paths): + """ + Call the get_share_links endpoint. + file_paths: list of file path strings + Returns: list of published URLs + """ + payload = {'file_paths': file_paths} + response = requests.post(f"{BASE_URL}/get_share_links", json=payload) + response.raise_for_status() + data = response.json() + return data.get('links', []) diff --git a/thin_client.py b/thin_client.py index efac6cb..38c4fa5 100755 --- a/thin_client.py +++ b/thin_client.py @@ -68,6 +68,7 @@ async def on_ready(): await client.load_extension("other_commands") await client.load_extension("voice_recognition_commands") + await client.load_extension("file_search_commands") logger.info("Sensors: online") logger.info(client.cogs) diff --git a/watch_script_params.py b/watch_script_params.py new file mode 100755 index 0000000..5d2e20e --- /dev/null +++ b/watch_script_params.py @@ -0,0 +1,63 @@ +import os +import time +import shutil +from datetime import datetime, date +import hashlib +import subprocess + +# File paths +SOURCE_FILE = "/home/pi/Conjurer/script.params" +BACKUP_DIR = "/home/pi/Conjurer" +GIT_REPO_DIR = "/home/pi/conjurer/conjurer_musician" +LAST_HASH_FILE = "/home/pi/Conjurer/.last_hash" +LAST_GIT_COMMIT_FILE = "/home/pi/Conjurer/.last_git_commit" + +def compute_file_hash(filepath): + with open(filepath, 'rb') as f: + return hashlib.sha256(f.read()).hexdigest() + +def backup_file(): + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + backup_path = os.path.join(BACKUP_DIR, f"{timestamp}_script.params") + shutil.copy2(SOURCE_FILE, backup_path) + +def commit_to_git(): + try: + subprocess.run(["cp", SOURCE_FILE, os.path.join(GIT_REPO_DIR, "script.params")], check=True) + subprocess.run(["git", "-C", GIT_REPO_DIR, "add", "script.params"], check=True) + subprocess.run(["git", "-C", GIT_REPO_DIR, "commit", "-m", f"Daily update: {datetime.now()}"], check=True) + subprocess.run(["git", "-C", GIT_REPO_DIR, "push"], check=True) + except subprocess.CalledProcessError as e: + print(f"Git operation failed: {e}") + +def main(): + if not os.path.exists(SOURCE_FILE): + return + + current_hash = compute_file_hash(SOURCE_FILE) + + # Detect change + last_hash = None + if os.path.exists(LAST_HASH_FILE): + with open(LAST_HASH_FILE, 'r') as f: + last_hash = f.read().strip() + + if current_hash != last_hash: + backup_file() + with open(LAST_HASH_FILE, 'w') as f: + f.write(current_hash) + + # Daily git commit + today = str(date.today()) + last_commit_date = "" + if os.path.exists(LAST_GIT_COMMIT_FILE): + with open(LAST_GIT_COMMIT_FILE, 'r') as f: + last_commit_date = f.read().strip() + + if today != last_commit_date: + commit_to_git() + with open(LAST_GIT_COMMIT_FILE, 'w') as f: + f.write(today) + +if __name__ == "__main__": + main()