From 185d30ca9574741c5cc3aa69c2528938e79a782a Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Fri, 27 Mar 2026 16:27:03 -0400 Subject: [PATCH 01/16] Updated Front End --- app/client/package-lock.json | 4 +- app/client/src/views/Settings.js | 89 ++++++++++++++++++++++++++++++++ test.py | 29 +++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 test.py diff --git a/app/client/package-lock.json b/app/client/package-lock.json index cace157f..9c1aa6b2 100644 --- a/app/client/package-lock.json +++ b/app/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "fireshare", - "version": "1.5.1", + "version": "1.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fireshare", - "version": "1.5.1", + "version": "1.5.2", "dependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index a62cb24e..55783f6f 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -46,6 +46,21 @@ const isValidDiscordWebhook = (url) => { const regex = /^https:\/\/discord\.com\/api\/webhooks\/\d{17,20}\/[\w-]{60,}$/ return regex.test(url) } +const isValidJson = (str) => { + try { + JSON.parse(str); + return true; + } catch (e) { + return false; + } +}; +const jsonPlaceholder = +`#Example JSON Data: +{ + "title": "Fireshare", + "body": "New Fireshare Video Uploaded!", + "type": "info" +}`; const Settings = () => { const [alert, setAlert] = React.useState({ open: false }) @@ -53,6 +68,8 @@ const Settings = () => { const [updatedConfig, setUpdatedConfig] = React.useState({}) const [updateable, setUpdateable] = React.useState(false) const [discordUrl, setDiscordUrl] = React.useState('') + const [webhookUrl, setwebhookUrl] = React.useState('') + const [webhookJson, setwebhookJson] = React.useState('')//needed? const [showSteamGridKey, setShowSteamGridKey] = React.useState(false) const [activeTab, setActiveTab] = React.useState(0) const [transcodingStatus, setTranscodingStatus] = React.useState({ @@ -65,6 +82,7 @@ const Settings = () => { const [deleteMenuRuleId, setDeleteMenuRuleId] = React.useState(null) const [editingFolder, setEditingFolder] = React.useState(null) const isDiscordUsed = discordUrl.trim() !== '' + const isWebhookUsed = webhookUrl.trim() !== '' React.useEffect(() => { async function fetch() { @@ -544,6 +562,7 @@ const Settings = () => { {/* Integrations */} {activeTab === 2 && ( +
Notifications
{ })) }} /> + + + + + + Used for API POST to Generic Webhook Endpoint{' '} + + Example + + + } + onChange={(e) => { + const url = e.target.value + setwebhookUrl(url) + setUpdatedConfig((prev) => ({ + ...prev, + integrations: { + ...prev.integrations, + discord_webhook_url: url, + }, + })) + }} + /> + { + const val = e.target.value; + setwebhookJson(val); + + // Only update the config if the JSON is actually valid + if (isValidJson(val)) { + setUpdatedConfig((prev) => ({ + ...prev, + integrations: { + ...prev.integrations, + custom_payload: JSON.parse(val), + }, + })); + } + }} + /> + + + + +
Game Tagging
{ }} /> +
RSS
Date: Fri, 27 Mar 2026 17:00:21 -0400 Subject: [PATCH 02/16] Update --- app/client/src/views/Settings.js | 94 +++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index 55783f6f..29e8b1c3 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -24,6 +24,7 @@ import SnackbarAlert from '../components/alert/SnackbarAlert' import SaveIcon from '@mui/icons-material/Save' import SensorsIcon from '@mui/icons-material/Sensors' import RssFeedIcon from '@mui/icons-material/RssFeed' +import SendIcon from '@mui/icons-material/Send'; import SportsEsportsIcon from '@mui/icons-material/SportsEsports' import CalendarMonthIcon from '@mui/icons-material/CalendarMonth' import MoreVertIcon from '@mui/icons-material/MoreVert' @@ -571,7 +572,18 @@ const Settings = () => { helperText={ discordUrl !== '' && !isValidDiscordWebhook(discordUrl) ? 'Webhook Format should look like: https://discord.com/api/webhooks/12345/fj8903k' - : ' ' + : + + Get Discord Webhook for you Server Channel - {' '} + + Docs + + } onChange={(e) => { const url = e.target.value @@ -585,10 +597,21 @@ const Settings = () => { })) }} /> + - + {/* Probably A better way to do this */} + + { error={webhookUrl !== '' && !isValidDiscordWebhook(webhookUrl)} helperText={ - Used for API POST to Generic Webhook Endpoint{' '} + Used for API POST to Generic Webhook Endpoint - {' '} { }} /> { - const val = e.target.value; - setwebhookJson(val); - - // Only update the config if the JSON is actually valid - if (isValidJson(val)) { - setUpdatedConfig((prev) => ({ - ...prev, - integrations: { - ...prev.integrations, - custom_payload: JSON.parse(val), - }, - })); + fullWidth + multiline + rows={6} + size="small" + label="Generic Webhook JSON Payload" + value={webhookJson} + placeholder={jsonPlaceholder} + error={webhookJson !== '' && !isValidJson(webhookJson)} + helperText={ + webhookJson !== '' && !isValidJson(webhookJson) + ? "Invalid JSON format" + : "Add Valid JSON, with data from the docs of your webhook provider" } - }} - /> + onChange={(e) => { + const val = e.target.value; + setwebhookJson(val); + + // Only update the config if the JSON is actually valid + if (isValidJson(val)) { + setUpdatedConfig((prev) => ({ + ...prev, + integrations: { + ...prev.integrations, + custom_payload: JSON.parse(val), + }, + })); + } + }} + /> + From da7fd39172bafdde816ea3d0698d7a5a8bd75c53 Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Thu, 2 Apr 2026 10:03:43 -0400 Subject: [PATCH 03/16] functioning webhook test --- app/client/package-lock.json | 4 +- app/client/src/views/Settings.js | 75 +++++++++++++++++++++++++++---- app/server/fireshare/api.py | 27 +++++++++++ app/server/fireshare/cli.py | 18 ++++++++ app/server/fireshare/constants.py | 1 + test.py | 2 +- 6 files changed, 115 insertions(+), 12 deletions(-) diff --git a/app/client/package-lock.json b/app/client/package-lock.json index 6e49859e..74998181 100644 --- a/app/client/package-lock.json +++ b/app/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "fireshare", - "version": "1.5.2", + "version": "1.5.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fireshare", - "version": "1.5.2", + "version": "1.5.4", "dependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index 29e8b1c3..257e102a 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -69,8 +69,8 @@ const Settings = () => { const [updatedConfig, setUpdatedConfig] = React.useState({}) const [updateable, setUpdateable] = React.useState(false) const [discordUrl, setDiscordUrl] = React.useState('') - const [webhookUrl, setwebhookUrl] = React.useState('') - const [webhookJson, setwebhookJson] = React.useState('')//needed? + const [webhookUrl, setWebhookUrl] = React.useState('') + const [webhookJson, setWebhookJson] = React.useState('')//needed? const [showSteamGridKey, setShowSteamGridKey] = React.useState(false) const [activeTab, setActiveTab] = React.useState(0) const [transcodingStatus, setTranscodingStatus] = React.useState({ @@ -85,6 +85,50 @@ const Settings = () => { const isDiscordUsed = discordUrl.trim() !== '' const isWebhookUsed = webhookUrl.trim() !== '' + const handleTestWebhook = async () => { + let payloadToTest = {}; + + try { + // Attempt to parse the text field, otherwise fall back to what's in the config + payloadToTest = webhookJson ? JSON.parse(webhookJson) : (updatedConfig.integrations?.custom_payload || {}); + } catch (e) { + setAlert({ open: true, message: 'Invalid JSON in payload field', type: 'error' }); + return; + } + + const testData = { + webhook_url: webhookUrl, + video_url: "https://example.com/test-video.mp4", + payload: payloadToTest + }; + + if (!webhookUrl) { + setAlert({ open: true, message: 'Please enter a Webhook URL first', type: 'error' }); + return; + } + + try { + const response = await fetch('/api/test-webhook', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(testData), + }); + + const result = await response.json(); + + if (response.ok) { + setAlert({ open: true, message: 'Test Webhook Sent!', type: 'success' }); + } else { + setAlert({ open: true, message: result.error || 'Failed to send test', type: 'error' }); + } + } catch (err) { + console.error("Connection failed:", err); + setAlert({ open: true, message: 'Network error connecting to server', type: 'error' }); + } + }; + React.useEffect(() => { async function fetch() { try { @@ -138,6 +182,12 @@ const Settings = () => { } }, [updatedConfig]) + React.useEffect(() => { + if (updatedConfig.integrations?.generic_webhook_url) { + setWebhookUrl(updatedConfig.integrations.generic_webhook_url) + } + }, [updatedConfig]) + const handleSave = async () => { try { await ConfigService.updateConfig(updatedConfig) @@ -616,7 +666,7 @@ const Settings = () => { size="small" label="Generic Webhook" value={webhookUrl} - error={webhookUrl !== '' && !isValidDiscordWebhook(webhookUrl)} + // error={webhookUrl !== '' && !isValidDiscordWebhook(webhookUrl)} //NEED TO VALIDATE HERE!!!!! helperText={ Used for API POST to Generic Webhook Endpoint - {' '} @@ -632,12 +682,12 @@ const Settings = () => { } onChange={(e) => { const url = e.target.value - setwebhookUrl(url) + setWebhookUrl(url) setUpdatedConfig((prev) => ({ ...prev, integrations: { ...prev.integrations, - discord_webhook_url: url, + generic_webhook_url: url, }, })) }} @@ -658,7 +708,7 @@ const Settings = () => { } onChange={(e) => { const val = e.target.value; - setwebhookJson(val); + setWebhookJson(val); // Only update the config if the JSON is actually valid if (isValidJson(val)) { @@ -675,9 +725,16 @@ const Settings = () => { diff --git a/app/server/fireshare/api.py b/app/server/fireshare/api.py index 02e8c693..28cb03e5 100644 --- a/app/server/fireshare/api.py +++ b/app/server/fireshare/api.py @@ -2807,3 +2807,30 @@ def bulk_remove_tag(): def after_request(response): response.headers.add('Accept-Ranges', 'bytes') return response + +from flask import request, jsonify, current_app +from .cli import send_generic_webhook +@api.route('/api/test-webhook', methods=['POST']) +def test_webhook(): + # 1. Get the data sent from the JavaScript frontend + data = request.get_json() + + webhook_url = data.get('webhook_url') + video_url = data.get('video_url') + payload = data.get('payload') + + if not webhook_url: + return jsonify({"error": "No Webhook URL provided"}), 400 + + # 2. Call your function from cli.py + try: + # Assuming send_generic_webhook is the function we built earlier + result = send_generic_webhook(webhook_url, video_url, payload) + + if result.get("status") == "success": + return jsonify({"message": "Webhook sent successfully!"}), 200 + else: + return jsonify({"error": result.get("message")}), 500 + + except Exception as e: + return jsonify({"error": str(e)}), 500 \ No newline at end of file diff --git a/app/server/fireshare/cli.py b/app/server/fireshare/cli.py index 993a66db..559d3445 100755 --- a/app/server/fireshare/cli.py +++ b/app/server/fireshare/cli.py @@ -148,6 +148,22 @@ def send_discord_webhook(webhook_url=None, video_url=None): print("Webhook sent successfully.") except requests.exceptions.RequestException as e: print(f"Failed to send webhook: {e}") +##### +def send_generic_webhook(webhook_url, video_url=None, custom_payload=None): + + payload = custom_payload if custom_payload is not None else {} + + if video_url and "content" not in payload: + payload["content"] = video_url + + try: + response = requests.post(webhook_url, json=payload) + response.raise_for_status() + return {"status": "success", "code": response.status_code} + + except requests.exceptions.RequestException as e: + return {"status": "error", "message": str(e)} +##### def get_public_watch_url(video_id, config, host): shareable_link_domain = config.get("ui_config", {}).get("shareable_link_domain", "") @@ -195,6 +211,7 @@ def scan_videos(root): config = json.load(config_file) video_config = config["app_config"]["video_defaults"] discord_webhook_url = config["integrations"]["discord_webhook_url"] + generic_webhook_url = config["integrations"]["generic_webhook_url"] config_file.close() if not video_links.is_dir(): @@ -360,6 +377,7 @@ def scan_video(ctx, path, tag_ids, game_id, title): config = json.load(config_file) video_config = config["app_config"]["video_defaults"] discord_webhook_url = config["integrations"]["discord_webhook_url"] + generic_webhook_url = config["integrations"]["generic_webhook_url"] config_file.close() diff --git a/app/server/fireshare/constants.py b/app/server/fireshare/constants.py index 6d6fa5ba..8aa9b2a2 100644 --- a/app/server/fireshare/constants.py +++ b/app/server/fireshare/constants.py @@ -14,6 +14,7 @@ }, "integrations": { "discord_webhook_url": "", + "generic_webhook_url": "", "steamgriddb_api_key": "", }, "rss_config": { diff --git a/test.py b/test.py index ebdcff05..c5155a30 100644 --- a/test.py +++ b/test.py @@ -2,7 +2,7 @@ import json # The target webhook URL -webhook_url = "https://webhook.site/6a20bb51-32ff-45fd-b6c6-40be0135b159" +webhook_url = "https://eager-hawk-15.webhook.cool" # The data you want to send (formatted as a dictionary) data_to_send = { From 162e5add7c813c314b5f1c8ed0af30b15c6ec51c Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Thu, 2 Apr 2026 10:10:05 -0400 Subject: [PATCH 04/16] Save json payload to backend config --- app/client/src/views/Settings.js | 17 ++++++++++++----- app/server/fireshare/constants.py | 1 + todo.txt | 11 +++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 todo.txt diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index 257e102a..e3f8d533 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -90,7 +90,7 @@ const Settings = () => { try { // Attempt to parse the text field, otherwise fall back to what's in the config - payloadToTest = webhookJson ? JSON.parse(webhookJson) : (updatedConfig.integrations?.custom_payload || {}); + payloadToTest = webhookJson ? JSON.parse(webhookJson) : (updatedConfig.integrations?.generic_webhook_payload || {}); } catch (e) { setAlert({ open: true, message: 'Invalid JSON in payload field', type: 'error' }); return; @@ -183,10 +183,17 @@ const Settings = () => { }, [updatedConfig]) React.useEffect(() => { - if (updatedConfig.integrations?.generic_webhook_url) { - setWebhookUrl(updatedConfig.integrations.generic_webhook_url) + if (updatedConfig.integrations) { + if (updatedConfig.integrations.generic_webhook_url) { + setWebhookUrl(updatedConfig.integrations.generic_webhook_url); + } + + if (updatedConfig.integrations.generic_webhook_payload) { + const jsonString = JSON.stringify(updatedConfig.integrations.generic_webhook_payload, null, 2); + setWebhookJson(jsonString); + } } - }, [updatedConfig]) + }, [updatedConfig]); const handleSave = async () => { try { @@ -716,7 +723,7 @@ const Settings = () => { ...prev, integrations: { ...prev.integrations, - custom_payload: JSON.parse(val), + generic_webhook_payload: JSON.parse(val), }, })); } diff --git a/app/server/fireshare/constants.py b/app/server/fireshare/constants.py index 8aa9b2a2..7f5778d7 100644 --- a/app/server/fireshare/constants.py +++ b/app/server/fireshare/constants.py @@ -15,6 +15,7 @@ "integrations": { "discord_webhook_url": "", "generic_webhook_url": "", + "generic_webhook_payload": {}, "steamgriddb_api_key": "", }, "rss_config": { diff --git a/todo.txt b/todo.txt new file mode 100644 index 00000000..4b41166c --- /dev/null +++ b/todo.txt @@ -0,0 +1,11 @@ +add test url for discord +add config backend to handle webhook input vars +validate webhook data on boot of container to validate it's accurate + +Integrate docker vars to generic webhook + +add save functionlality + +cleanup comments and code + +cleanup gui \ No newline at end of file From 2b3a2ca91e8fc08f7d26404a9ceed19f6f5662c5 Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Thu, 2 Apr 2026 10:56:29 -0400 Subject: [PATCH 05/16] added ENV options for discord and webhook | validation on init.py --- .env.dev | 6 ++++- app/server/fireshare/__init__.py | 46 +++++++++++++++++++++++++++++++- todo.txt | 2 +- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/.env.dev b/.env.dev index d17103bf..a7ce4162 100644 --- a/.env.dev +++ b/.env.dev @@ -9,4 +9,8 @@ export VIDEO_DIRECTORY=$(pwd)/dev_root/dev_videos/ export PROCESSED_DIRECTORY=$(pwd)/dev_root/dev_processed/ export STEAMGRIDDB_API_KEY="" export ADMIN_PASSWORD=admin -export ADMIN_USERNAME=admin \ No newline at end of file +export ADMIN_USERNAME=admin +export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1369655538228527104/XA0p42ImlJnS5zeACItW6fV5YeOVLMysqOVATreGZpMKoyIktqLe_LE7MLxSbda57QiP" +export GENERIC_WEBHOOK_URL="https://eager-hawk-15.webhook.cool" +export GENERIC_WEBHOOK_PAYLOAD='{"pp": "test"}' +#"https://eager-hawk-15.webhook.cool" \ No newline at end of file diff --git a/app/server/fireshare/__init__.py b/app/server/fireshare/__init__.py index 0628b8e4..7b1a7ea7 100644 --- a/app/server/fireshare/__init__.py +++ b/app/server/fireshare/__init__.py @@ -1,4 +1,4 @@ -import os, sys +import os, sys, re import os.path try: import ldap @@ -116,6 +116,19 @@ def create_app(init_schedule=False): app.config['TRANSCODE_GPU'] = os.getenv('TRANSCODE_GPU', '').lower() in ('true', '1', 'yes') app.config['TRANSCODE_TIMEOUT'] = int(os.getenv('TRANSCODE_TIMEOUT', '7200')) # Default: 2 hours + #Integrations + app.config['DISCORD_WEBHOOK_URL'] = os.getenv('DISCORD_WEBHOOK_URL', '') + app.config['GENERIC_WEBHOOK_URL'] = os.getenv('GENERIC_WEBHOOK_URL', '') + raw_payload = os.getenv('GENERIC_WEBHOOK_PAYLOAD') + if raw_payload: + try: + app.config['GENERIC_WEBHOOK_PAYLOAD'] = json.loads(raw_payload) + except (json.JSONDecodeError, TypeError) as e: + app.logger.error(f"FATAL: GENERIC_WEBHOOK_PAYLOAD contains invalid JSON syntax, please verify JSON format") + sys.exit(1) + else: + app.config['GENERIC_WEBHOOK_PAYLOAD'] = None + app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{app.config["DATA_DIRECTORY"]}/db.sqlite' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Configure SQLite connection for thread safety with Flask's --with-threads @@ -222,6 +235,37 @@ def load_user(user_id): from .schedule import init_schedule init_schedule(app.config['SCHEDULED_JOBS_DATABASE_URI'], app.config['MINUTES_BETWEEN_VIDEO_SCANS']) + + #Integrations Validation + if app.config.get('DISCORD_WEBHOOK_URL'): + discord_regex = r"^https:\/\/discord\.com\/api\/webhooks\/\d{17,20}\/[\w-]{60,}$" + if re.match(discord_regex, app.config['DISCORD_WEBHOOK_URL']): + app.logger.info(f"Discord Integration: URL VALID | ENABLED (URL: {app.config['DISCORD_WEBHOOK_URL'][:20]}...)") + else: + app.logger.error("Discord Webhook URL format looks invalid. Please double-check the URL.") + sys.exit(1) + + if app.config.get("GENERIC_WEBHOOK_URL") or app.config.get("GENERIC_WEBHOOK_PAYLOAD"): + # Check for missing pieces + if not app.config.get("GENERIC_WEBHOOK_URL") or not app.config.get("GENERIC_WEBHOOK_PAYLOAD"): + app.logger.error("FATAL: Incomplete Generic Webhook configuration. Both URL and PAYLOAD must be set.") + sys.exit(1) + + # Validate URL Format + url_regex = r"^https?:\/\/[^\s\/$.?#].[^\s]*$" + if re.match(url_regex, app.config['GENERIC_WEBHOOK_URL']): + app.logger.info(f"Generic Webhook: URL VALID | ENABLED ({app.config['GENERIC_WEBHOOK_URL'][:20]}...)") + else: + app.logger.error(f"FATAL: Generic Webhook URL format is invalid: {app.config['GENERIC_WEBHOOK_URL']}") + sys.exit(1) + + # Final Type Check (Since it's already been json.loads'd above) + if isinstance(app.config['GENERIC_WEBHOOK_PAYLOAD'], dict): + app.logger.info("Generic Webhook: PAYLOAD VALID | ENABLED") + else: + app.logger.error("FATAL: Generic Webhook PAYLOAD must be a JSON object (dictionary).") + sys.exit(1) + with app.app_context(): # db.create_all() diff --git a/todo.txt b/todo.txt index 4b41166c..2c106ed4 100644 --- a/todo.txt +++ b/todo.txt @@ -4,7 +4,7 @@ validate webhook data on boot of container to validate it's accurate Integrate docker vars to generic webhook -add save functionlality +!add save functionlality cleanup comments and code From f3ea22a266bae2b3ab9a48e245f3b47d3a004027 Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Thu, 2 Apr 2026 11:33:08 -0400 Subject: [PATCH 06/16] added means to add integrations to config.json from init.py --- .env.dev | 1 - app/server/fireshare/__init__.py | 28 +++++++++++++++++++++++++++- todo.txt | 6 +++--- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/.env.dev b/.env.dev index a7ce4162..fcbb3e44 100644 --- a/.env.dev +++ b/.env.dev @@ -13,4 +13,3 @@ export ADMIN_USERNAME=admin export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1369655538228527104/XA0p42ImlJnS5zeACItW6fV5YeOVLMysqOVATreGZpMKoyIktqLe_LE7MLxSbda57QiP" export GENERIC_WEBHOOK_URL="https://eager-hawk-15.webhook.cool" export GENERIC_WEBHOOK_PAYLOAD='{"pp": "test"}' -#"https://eager-hawk-15.webhook.cool" \ No newline at end of file diff --git a/app/server/fireshare/__init__.py b/app/server/fireshare/__init__.py index 7b1a7ea7..1746f31a 100644 --- a/app/server/fireshare/__init__.py +++ b/app/server/fireshare/__init__.py @@ -169,6 +169,7 @@ def create_app(init_schedule=False): 'data': Path(app.config['DATA_DIRECTORY']), 'video': Path(app.config['VIDEO_DIRECTORY']), 'processed': Path(app.config['PROCESSED_DIRECTORY']), + } app.config['PATHS'] = paths for k, path in paths.items(): @@ -189,7 +190,7 @@ def create_app(init_schedule=False): if not game_assets_dir.is_dir(): logger.info(f"Creating game_assets directory at {str(game_assets_dir.absolute())}") game_assets_dir.mkdir(parents=True, exist_ok=True) - + update_config(paths['data'] / 'config.json') db.init_app(app) @@ -266,6 +267,31 @@ def load_user(user_id): app.logger.error("FATAL: Generic Webhook PAYLOAD must be a JSON object (dictionary).") sys.exit(1) + #Allow config.json updates + from .constants import DEFAULT_CONFIG + if 'integrations' not in DEFAULT_CONFIG: + DEFAULT_CONFIG['integrations'] = {} + + DEFAULT_CONFIG['integrations']['discord_webhook_url'] = app.config.get('DISCORD_WEBHOOK_URL', '') + DEFAULT_CONFIG['integrations']['generic_webhook_url'] = app.config.get('GENERIC_WEBHOOK_URL', '') + DEFAULT_CONFIG['integrations']['generic_webhook_payload'] = app.config.get('GENERIC_WEBHOOK_PAYLOAD', {}) + + # Run the merge first to get everything else from the file + update_config(paths['data'] / 'config.json') + + # NOW force the ENV vars into the file so they definitely stick + config_path = paths['data'] / 'config.json' + with open(config_path, 'r+') as f: + data = json.load(f) + + # Force the integrations block to match our validated app.config + data['integrations']['discord_webhook_url'] = app.config.get('DISCORD_WEBHOOK_URL', '') + data['integrations']['generic_webhook_url'] = app.config.get('GENERIC_WEBHOOK_URL', '') + data['integrations']['generic_webhook_payload'] = app.config.get('GENERIC_WEBHOOK_PAYLOAD', {}) + + f.seek(0) + json.dump(data, f, indent=2) + f.truncate() with app.app_context(): # db.create_all() diff --git a/todo.txt b/todo.txt index 2c106ed4..a8d459ed 100644 --- a/todo.txt +++ b/todo.txt @@ -1,8 +1,8 @@ add test url for discord -add config backend to handle webhook input vars -validate webhook data on boot of container to validate it's accurate +!add config backend to handle webhook input vars +!validate webhook data on boot of container to validate it's accurate -Integrate docker vars to generic webhook +!Integrate docker vars to generic webhook !add save functionlality From 8b907edcec007775ffdba93243ce1cb2dfb4088d Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Thu, 2 Apr 2026 11:54:34 -0400 Subject: [PATCH 07/16] Added test dicord webhook functionality --- app/client/src/views/Settings.js | 52 +++++++++++++++++++++++++------- app/server/fireshare/api.py | 35 ++++++++++++++------- app/server/fireshare/cli.py | 6 ++-- todo.txt | 2 +- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index e3f8d533..721deeee 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -85,28 +85,52 @@ const Settings = () => { const isDiscordUsed = discordUrl.trim() !== '' const isWebhookUsed = webhookUrl.trim() !== '' +const handleTestDiscordWebhook = async () => { + const urlToTest = discordUrl || updatedConfig.integrations?.discord_webhook_url; + if (!urlToTest) { + setAlert({ open: true, message: 'Please enter a Discord Webhook URL first', type: 'error' }); + return; + } + try { + const response = await fetch('/api/test-discord-webhook', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + webhook_url: urlToTest, + video_url: "https://fireshare.test.worked" + }), + }); + const result = await response.json(); + if (response.ok) { + setAlert({ open: true, message: 'Discord Test Sent!', type: 'success' }); + } else { + setAlert({ open: true, message: result.error || 'Discord test failed', type: 'error' }); + } + } catch (err) { + console.error("Connection failed:", err); + setAlert({ open: true, message: 'Network error connecting to server', type: 'error' }); + } +}; + const handleTestWebhook = async () => { let payloadToTest = {}; - try { - // Attempt to parse the text field, otherwise fall back to what's in the config payloadToTest = webhookJson ? JSON.parse(webhookJson) : (updatedConfig.integrations?.generic_webhook_payload || {}); } catch (e) { setAlert({ open: true, message: 'Invalid JSON in payload field', type: 'error' }); return; } - const testData = { webhook_url: webhookUrl, - video_url: "https://example.com/test-video.mp4", + video_url: "https://fireshare.test.worked", payload: payloadToTest }; - if (!webhookUrl) { setAlert({ open: true, message: 'Please enter a Webhook URL first', type: 'error' }); return; } - try { const response = await fetch('/api/test-webhook', { method: 'POST', @@ -117,7 +141,6 @@ const Settings = () => { }); const result = await response.json(); - if (response.ok) { setAlert({ open: true, message: 'Test Webhook Sent!', type: 'success' }); } else { @@ -657,11 +680,18 @@ const Settings = () => { diff --git a/app/server/fireshare/api.py b/app/server/fireshare/api.py index 28cb03e5..15f855b0 100644 --- a/app/server/fireshare/api.py +++ b/app/server/fireshare/api.py @@ -20,11 +20,11 @@ from pathlib import Path import requests - from . import db, logger, util from .constants import DEFAULT_CONFIG, SUPPORTED_FILE_TYPES from .models import User, Video, VideoInfo, VideoView, GameMetadata, VideoGameLink, FolderRule, CustomTag, VideoTagLink from .steamgrid import SteamGridDBClient +from .cli import send_discord_webhook,send_generic_webhook def secure_filename(filename): clean = re.sub(r"[/\\?%*:|\"<>\x7F\x00-\x1F]", "-", filename) @@ -2808,29 +2808,42 @@ def after_request(response): response.headers.add('Accept-Ranges', 'bytes') return response -from flask import request, jsonify, current_app -from .cli import send_generic_webhook +@api.route('/api/test-discord-webhook', methods=['POST']) +def test_discord_webhook(): + data = request.get_json() + webhook_url = data.get('webhook_url') + video_url = data.get('video_url', 'https://fireshare.test.worked') + + if not webhook_url: + return jsonify({"error": "No Discord Webhook URL provided"}), 400 + try: + result = send_discord_webhook(webhook_url, video_url) + if result and isinstance(result, dict): + if result.get("status") == "success": + return jsonify({"message": "Discord Webhook sent successfully!"}), 200 + else: + return jsonify({"error": result.get("message", "Unknown discord error")}), 500 + else: + return jsonify({"error": "Webhook function did not return a valid response object"}), 500 + except Exception as e: + print(f"DEBUG ERROR: {str(e)}") + return jsonify({"error": f"Internal Server Error: {str(e)}"}), 500 + @api.route('/api/test-webhook', methods=['POST']) def test_webhook(): - # 1. Get the data sent from the JavaScript frontend data = request.get_json() - webhook_url = data.get('webhook_url') video_url = data.get('video_url') payload = data.get('payload') if not webhook_url: return jsonify({"error": "No Webhook URL provided"}), 400 - - # 2. Call your function from cli.py try: - # Assuming send_generic_webhook is the function we built earlier result = send_generic_webhook(webhook_url, video_url, payload) - if result.get("status") == "success": return jsonify({"message": "Webhook sent successfully!"}), 200 else: return jsonify({"error": result.get("message")}), 500 - except Exception as e: - return jsonify({"error": str(e)}), 500 \ No newline at end of file + return jsonify({"error": str(e)}), 500 + \ No newline at end of file diff --git a/app/server/fireshare/cli.py b/app/server/fireshare/cli.py index 559d3445..dc622c85 100755 --- a/app/server/fireshare/cli.py +++ b/app/server/fireshare/cli.py @@ -141,14 +141,15 @@ def send_discord_webhook(webhook_url=None, video_url=None): "username": "Fireshare", "avatar_url": "https://github.com/ShaneIsrael/fireshare/raw/develop/app/client/src/assets/logo_square.png", } - try: response = requests.post(webhook_url, json=payload) response.raise_for_status() print("Webhook sent successfully.") + return {"status": "success", "message": "Webhook sent successfully."} except requests.exceptions.RequestException as e: print(f"Failed to send webhook: {e}") -##### + return {"status": "error", "message": str(e)} + def send_generic_webhook(webhook_url, video_url=None, custom_payload=None): payload = custom_payload if custom_payload is not None else {} @@ -163,7 +164,6 @@ def send_generic_webhook(webhook_url, video_url=None, custom_payload=None): except requests.exceptions.RequestException as e: return {"status": "error", "message": str(e)} -##### def get_public_watch_url(video_id, config, host): shareable_link_domain = config.get("ui_config", {}).get("shareable_link_domain", "") diff --git a/todo.txt b/todo.txt index a8d459ed..d214d7e7 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,4 @@ -add test url for discord +!add test url for discord !add config backend to handle webhook input vars !validate webhook data on boot of container to validate it's accurate From 97ef030586c75076a27571b47820aaf9acc7f709 Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Thu, 2 Apr 2026 11:58:53 -0400 Subject: [PATCH 08/16] Cleaned up GUI | clean up files --- app/client/src/views/Settings.js | 10 +++------- test.py | 29 ----------------------------- todo.txt | 11 ----------- 3 files changed, 3 insertions(+), 47 deletions(-) delete mode 100644 test.py delete mode 100644 todo.txt diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index 721deeee..34f55762 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -691,14 +691,11 @@ const handleTestDiscordWebhook = async () => { } }} > - Test Webhook + Test Discord + - - {/* Probably A better way to do this */} - - { > Test Webhook - - +
Game Tagging
Date: Thu, 2 Apr 2026 12:02:52 -0400 Subject: [PATCH 09/16] removed test ENVs --- .env.dev | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.env.dev b/.env.dev index fcbb3e44..692d0c88 100644 --- a/.env.dev +++ b/.env.dev @@ -10,6 +10,4 @@ export PROCESSED_DIRECTORY=$(pwd)/dev_root/dev_processed/ export STEAMGRIDDB_API_KEY="" export ADMIN_PASSWORD=admin export ADMIN_USERNAME=admin -export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1369655538228527104/XA0p42ImlJnS5zeACItW6fV5YeOVLMysqOVATreGZpMKoyIktqLe_LE7MLxSbda57QiP" -export GENERIC_WEBHOOK_URL="https://eager-hawk-15.webhook.cool" -export GENERIC_WEBHOOK_PAYLOAD='{"pp": "test"}' + From 7e15a676f3dd2ad01ce14571b9d32957f1fdf46d Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Thu, 2 Apr 2026 12:06:57 -0400 Subject: [PATCH 10/16] Added Regex validation in settings.js to check for valid URL for generic webhook url --- app/client/src/views/Settings.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index 34f55762..c77eb065 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -47,6 +47,10 @@ const isValidDiscordWebhook = (url) => { const regex = /^https:\/\/discord\.com\/api\/webhooks\/\d{17,20}\/[\w-]{60,}$/ return regex.test(url) } +const isValidGenericWebhook = (url) => { + const regex = /^https?:\/\/[^\s\/$.?#].[^\s]*$/; + return regex.test(url) +} const isValidJson = (str) => { try { JSON.parse(str); @@ -698,9 +702,9 @@ const handleTestDiscordWebhook = async () => { Used for API POST to Generic Webhook Endpoint - {' '} From 9296d5e6c695e9e4408cbbd158ee4d4fa1a6f159 Mon Sep 17 00:00:00 2001 From: Cody Casteel Date: Thu, 2 Apr 2026 12:09:13 -0400 Subject: [PATCH 11/16] cleaned up code / removed comments --- app/client/src/views/Settings.js | 3 --- app/server/fireshare/__init__.py | 7 ------- 2 files changed, 10 deletions(-) diff --git a/app/client/src/views/Settings.js b/app/client/src/views/Settings.js index c77eb065..6b737335 100644 --- a/app/client/src/views/Settings.js +++ b/app/client/src/views/Settings.js @@ -747,8 +747,6 @@ const handleTestDiscordWebhook = async () => { onChange={(e) => { const val = e.target.value; setWebhookJson(val); - - // Only update the config if the JSON is actually valid if (isValidJson(val)) { setUpdatedConfig((prev) => ({ ...prev, @@ -763,7 +761,6 @@ const handleTestDiscordWebhook = async () => {