mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-15 22:32:10 +00:00
Tag: 1.26
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
This commit is contained in:
@@ -29,6 +29,8 @@ from flask import (
|
|||||||
send_from_directory,
|
send_from_directory,
|
||||||
)
|
)
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
import media_search_functions
|
||||||
|
|
||||||
|
|
||||||
MAIN_BOT_ADDRESS = "http://192.168.1.191:5000"
|
MAIN_BOT_ADDRESS = "http://192.168.1.191:5000"
|
||||||
MUSIC_TRACKER = "/prepped_tracks"
|
MUSIC_TRACKER = "/prepped_tracks"
|
||||||
@@ -48,10 +50,10 @@ if platform in ("linux", "linux2"):
|
|||||||
else:
|
else:
|
||||||
LOGFILE = "/home/pi/Conjurer/discord_mus_service.log"
|
LOGFILE = "/home/pi/Conjurer/discord_mus_service.log"
|
||||||
NETRC_FILE = "/home/pi/.netrc"
|
NETRC_FILE = "/home/pi/.netrc"
|
||||||
LOGSTORE = "/home/pi/MediaShare/logs/"
|
LOGSTORE = "/home/pi/MediaFolder/logs/"
|
||||||
ENCODING = "utf-8"
|
ENCODING = "utf-8"
|
||||||
MUSIC_FOLDER = "/home/pi/MediaShare/mp3/"
|
MUSIC_FOLDER = "/home/pi/MediaFolder/mp3/"
|
||||||
PRIORITY_FOLDER = "/home/pi/MediaShare/mp3/Magiczne i chuj/"
|
PRIORITY_FOLDER = "/home/pi/MediaFoldermp3/Magiczne i chuj/"
|
||||||
RADIOLOG_PATH = "/home/pi/Conjurer/radio_log.log"
|
RADIOLOG_PATH = "/home/pi/Conjurer/radio_log.log"
|
||||||
PERSISTENCE_PATH = "/home/pi/Conjurer/persistence.log"
|
PERSISTENCE_PATH = "/home/pi/Conjurer/persistence.log"
|
||||||
|
|
||||||
@@ -332,6 +334,36 @@ def remove_characters(string, character):
|
|||||||
return string.replace(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"])
|
@app.route("/stream", methods=["GET"])
|
||||||
def stream_music():
|
def stream_music():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/home/pi/Conjurer/radio_log.log {
|
||||||
|
daily
|
||||||
|
rotate 7
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
copytruncate
|
||||||
|
}
|
||||||
@@ -43,6 +43,22 @@ def queue_processing()
|
|||||||
end
|
end
|
||||||
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
|
# Randomly select a playlist with weights
|
||||||
s4 = random(id="randomizer", weights=[2, 3, 5], [s1, s2, s3])
|
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
|
# Create the main stream with random playlist and jingles
|
||||||
s = rotate(id="randomizer", weights=[10, 1], [s4, 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
|
# Set up an interactive harbor for controlling the stream
|
||||||
interactive.harbor(port = 9999)
|
interactive.harbor(port = 9999)
|
||||||
@@ -87,7 +104,7 @@ s = blank.skip(max_blank=10., s)
|
|||||||
live_enabled = interactive.bool("Going Live!", true)
|
live_enabled = interactive.bool("Going Live!", true)
|
||||||
|
|
||||||
s = add([mic,s])
|
s = add([mic,s])
|
||||||
s=switch(track_sensitive=false,
|
s=switch(track_sensitive=true,
|
||||||
[(live_enabled, mic),
|
[(live_enabled, mic),
|
||||||
({true}, s)])
|
({true}, s)])
|
||||||
|
|
||||||
@@ -102,9 +119,8 @@ set("log.level", loglevel)
|
|||||||
# Enable logging to file
|
# Enable logging to file
|
||||||
set("log.file", log_to_file)
|
set("log.file", log_to_file)
|
||||||
set("log.file.path", logpath)
|
set("log.file.path", logpath)
|
||||||
|
|
||||||
# Set up emergency fallback track
|
# 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])
|
radio = fallback(id="switcher2", track_sensitive=false, [s, emergency])
|
||||||
|
|
||||||
# Set up an interactive control for skipping tracks
|
# Set up an interactive control for skipping tracks
|
||||||
@@ -138,8 +154,7 @@ interactive.persistent("script.params")
|
|||||||
|
|
||||||
# Configure output formats and destinations
|
# Configure output formats and destinations
|
||||||
|
|
||||||
|
output.icecast(%mp3, host="radio", port=8000, password=credentials.password, icy_metadata="true", mount="mp3-stream", radio)
|
||||||
output.icecast(%mp3, host="retropie", port=8000, password=credentials.password, icy_metadata="true", mount="mp3-stream", radio)
|
|
||||||
output.pulseaudio(radio)
|
output.pulseaudio(radio)
|
||||||
#output.file.hls("/tmp/hls", [("mp3-low", %mp3(bitrate=96)), ("mp3-hi", %mp3(bitrate=160))], 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
|
# Uncomment the following lines to enable additional output formats
|
||||||
|
|||||||
Executable
+99
@@ -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()
|
||||||
Executable
+44
@@ -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()
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=One‑shot scan of /mnt/shares → JSON
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/scan_shares.py
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
Executable
+93
@@ -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()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
total_commands=24
|
total_commands=26
|
||||||
current_command=0
|
current_command=0
|
||||||
|
|
||||||
function print_progress {
|
function print_progress {
|
||||||
@@ -79,6 +79,14 @@ print_progress "cp ./conjurer/radio_commands.py ./Conjurer/"
|
|||||||
cp ./conjurer/voice_recognition_commands.py ./Conjurer/
|
cp ./conjurer/voice_recognition_commands.py ./Conjurer/
|
||||||
print_progress "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
|
cp ./conjurer/thin_client.py ./Conjurer/bot.py
|
||||||
print_progress "cp ./conjurer/thin_client.py ./Conjurer/bot.py"
|
print_progress "cp ./conjurer/thin_client.py ./Conjurer/bot.py"
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
cd ./conjurer || exit
|
cd ./conjurer || exit
|
||||||
git pull
|
git pull
|
||||||
cd ..
|
cd ..
|
||||||
|
cp ./conjurer/conjurer_musician/media_search_functions.py ./Conjurer
|
||||||
cp ./conjurer/conjurer_musician/conjurer_musician.py ./Conjurer
|
cp ./conjurer/conjurer_musician/conjurer_musician.py ./Conjurer
|
||||||
sudo systemctl restart conjurer_musician.service
|
sudo systemctl restart conjurer_musician.service
|
||||||
@@ -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 <entries 1-10> <keywords>
|
||||||
|
"""
|
||||||
|
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))
|
||||||
@@ -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', [])
|
||||||
@@ -68,6 +68,7 @@ async def on_ready():
|
|||||||
|
|
||||||
await client.load_extension("other_commands")
|
await client.load_extension("other_commands")
|
||||||
await client.load_extension("voice_recognition_commands")
|
await client.load_extension("voice_recognition_commands")
|
||||||
|
await client.load_extension("file_search_commands")
|
||||||
logger.info("Sensors: online")
|
logger.info("Sensors: online")
|
||||||
|
|
||||||
logger.info(client.cogs)
|
logger.info(client.cogs)
|
||||||
|
|||||||
Executable
+63
@@ -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()
|
||||||
Reference in New Issue
Block a user