From d16fd8a6d4bafdfd306ff6b68fe2a78dc9c50360 Mon Sep 17 00:00:00 2001 From: euzu Date: Thu, 11 Jan 2024 09:43:16 +0100 Subject: [PATCH] new messaging option --- CHANGELOG.md | 4 +++- README.md | 15 ++++++++++++-- TODO.md | 20 +++++++++++++++++++ config.yml | 3 +++ .../main-config-view/main-config-view.tsx | 14 +++++++------ frontend/src/model/message-kind.ts | 6 ++++++ src/api/xtream_player_api.rs | 1 + src/messaging.rs | 19 ++++++++++++++++++ src/model/config.rs | 6 ++++++ src/model/stats.rs | 4 ++-- src/processing/playlist_processor.rs | 5 +++-- src/processing/playlist_watch.rs | 2 +- 12 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 TODO.md create mode 100644 frontend/src/model/message-kind.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 44e801184..0cd76b09c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # Changelog -# v1.1.5(2023-12-??) +# v1.1.5(2024-01-11) * Changed api-proxy user default proxy type from `reverse` to `redirect` * Added `xtream_resolve_series` and `xtream_resolve_series_delay` option for `m3u` target +* Messaging calling rest endpoint added +* Messaging added 'Watch' option as OptIn # v1.1.4(2023-12-06) * Breaking change, `config.yml` split into `config.yml` and `source.yml` diff --git a/README.md b/README.md index 2aecbb0e8..d02ddb98c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ m3u-filter is a simple application which can: - use regular expressions for matching - define filter as statements like `filter: (Group ~ "^FR.*") AND NOT(Group ~ ".*XXX.*" OR Group ~ ".*SERIES.*" OR Group ~".*MOVIES.*")` - DRY - define templates and use them, don't repeat yourself - - Send a telegram bot message when something goes wrong + - Send a telegram bot or rest message when something goes wrong - Watch changes in groups and get a message on changes ![m3u-filter-overview](https://github.com/euzu/m3u-filter/assets/33094714/9a3449ac-c646-4bb4-a5ab-320a588d35c8) @@ -66,13 +66,15 @@ With this configuration, you should create a `data` directory where you execute ### 1.4 `messaging` `messaging` is an optional configuration for receiving messages. -Currently only telegram is supported. +Currently only and rest is supported. Messaging is Opt-In, you need to set the `notify_on` message types which are - `info` - `stats` - `error` +`telegram` and `rest` configurations are optional. + ```yaml messaging: notify_on: @@ -83,6 +85,8 @@ messaging: bot_token: '' chat_ids: - '' + rest: + url: '' ``` For more information: [Telegram bots](https://core.telegram.org/bots/tutorial) @@ -426,6 +430,13 @@ watch: Changes from this groups will be printed as info on console and send to the configured messaging (f.e. telegram channel). +To get the watch notifications over messaging notify_on `watch` should be enabled. +In `config.yml` +```yaml +messaging: + notify_on: + - watch +``` ## 2. `mapping.yml` Has the root item `mappings` which has the following top level entries: diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..e636008b8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,20 @@ +# TODO +## Bugs + +- xtream api, only looks for first input configuration. + But if you have multiple xtream sources merged, + each entry should have a pointer to his input to get the right provider url. +- +- double download from web-ui possible + + +## Features +- m3u8 support missing. +- download from web-ui is not cancellable +- resume download if available +- display file size from header if available +- on web-ui page refresh download list should be fetched + +## Open topics +- xmltv api delivers original content if no epg_url is provided for input for reverse or makes redirect. +- web ui should fetch series info when expanded \ No newline at end of file diff --git a/config.yml b/config.yml index cf99a0f03..381bc970b 100644 --- a/config.yml +++ b/config.yml @@ -9,10 +9,13 @@ messaging: - error - info - stats + - watch telegram: bot_token: '' chat_ids: - + rest: + url: http://// video: web_search: 'https://www.imdb.com/search/title/?title={}' extensions: diff --git a/frontend/src/component/main-config-view/main-config-view.tsx b/frontend/src/component/main-config-view/main-config-view.tsx index 8a9c9b0fb..d9584ea26 100644 --- a/frontend/src/component/main-config-view/main-config-view.tsx +++ b/frontend/src/component/main-config-view/main-config-view.tsx @@ -12,6 +12,7 @@ import FormView, {FormFieldType} from "../form-view/from-view"; import TabSet from "../tab-set/tab-set"; import {getIconByName} from "../../icons/icons"; import {useServices} from "../../provider/service-provider"; +import {MessageKind} from "../../model/message-kind"; const isNumber = (value: string): boolean => { return !isNaN(value as any); @@ -22,6 +23,7 @@ const CONFIG_API_FIELDS = [ {name: 'port', label: 'Port', fieldType: FormFieldType.NUMBER, validator: isNumber}, {name: 'web_root', label: 'Web-Root', fieldType: FormFieldType.TEXT, validator: undefined}, ]; + const CONFIG_FIELDS = [ {name: 'threads', label: 'Threads', fieldType: FormFieldType.NUMBER, validator: isNumber}, {name: 'working_dir', label: 'Working dir', fieldType: FormFieldType.TEXT, validator: undefined}, @@ -32,33 +34,33 @@ const CONFIG_FIELDS = [ const CONFIG_MESSAGING_FIELDS = [ { name: 'notify_on', label: 'Notify on', fieldType: FormFieldType.MULTI_SELECT, options: - [{value: 'error', label: 'Error'}, {value: 'info', label: 'Info'}, {value: 'stats', label: 'Stats'}] + Object.keys(MessageKind).map((key) => ({value: (MessageKind as any)[key], label: key})) }, -] +]; const CONFIG_TELEGRAM_FIELDS = [ {name: 'bot_token', label: 'Bot Token', fieldType: FormFieldType.TEXT}, {name: 'chat_ids', label: 'Chat Ids', fieldType: FormFieldType.TAGS}, -] +]; const CONFIG_VIDEO_FIELDS = [ {name: 'web_search', label: 'Web Search Url', fieldType: FormFieldType.TEXT}, {name: 'extensions', label: 'Extensions', fieldType: FormFieldType.TEXT}, -] +]; const CONFIG_VIDEO_DOWNLOAD_FIELDS = [ {name: 'headers', label: 'Headers', fieldType: FormFieldType.MAP}, {name: 'directory', label: 'Directory', fieldType: FormFieldType.TEXT}, {name: 'organize_into_directories', label: 'Organize Into Directories', fieldType: FormFieldType.CHECK}, {name: 'episode_pattern', label: 'Episode Pattern', fieldType: FormFieldType.TEXT}, -] +]; const TABS = [ {label: 'Api', key: 'api'}, {label: 'Main', key: 'main'}, {label: 'Messaging', key: 'messaging'}, {label: 'Video', key: 'video'} -] +]; interface MainConfigViewProps { config: ServerConfig; diff --git a/frontend/src/model/message-kind.ts b/frontend/src/model/message-kind.ts new file mode 100644 index 000000000..2a0509b56 --- /dev/null +++ b/frontend/src/model/message-kind.ts @@ -0,0 +1,6 @@ +export enum MessageKind { + Info = "info", + Stats = "stats", + Error = "error", + Watch = "watch", +} \ No newline at end of file diff --git a/src/api/xtream_player_api.rs b/src/api/xtream_player_api.rs index 9b747e225..51d6ce2fe 100644 --- a/src/api/xtream_player_api.rs +++ b/src/api/xtream_player_api.rs @@ -213,6 +213,7 @@ async fn xtream_get_stream_info(app_state: &AppState, target_name: &str, stream_ if response.status().is_success() { match response.text().await { Ok(content) => { + // TODO we are not replacing direct_source, we should add an option to do this. xtream_persist_stream_info(app_state, target_name, stream_id, cluster, target_input, content.as_str()).await; return Ok(content); diff --git a/src/messaging.rs b/src/messaging.rs index 3a3b301a9..66a9d33ae 100644 --- a/src/messaging.rs +++ b/src/messaging.rs @@ -1,4 +1,5 @@ use log::{debug, error}; +use reqwest::header; use crate::model::config::{MessagingConfig}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] @@ -9,6 +10,8 @@ pub(crate) enum MsgKind { Stats, #[serde(rename = "error")] Error, + #[serde(rename = "watch")] + Watch, } fn is_enabled(kind: &MsgKind, cfg: &MessagingConfig) -> bool { @@ -28,6 +31,22 @@ pub(crate) fn send_message(kind: &MsgKind, cfg: &Option, msg: & } }; } + + if let Some(rest) = &messaging.rest { + let url = rest.url.to_owned(); + let data = msg.to_owned(); + actix_rt::spawn(async move { + let client = reqwest::Client::new(); + match client.post(&url) + .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.to_string()) + .body(data) + .send() + .await { + Ok(_) => debug!("Text message sent successfully to rest api"), + Err(e) => error!("Text message wasn't sent to rest api because of: {}", e) + } + }); + } } } } diff --git a/src/model/config.rs b/src/model/config.rs index cef0e61db..46ca2806f 100644 --- a/src/model/config.rs +++ b/src/model/config.rs @@ -445,11 +445,17 @@ pub(crate) struct TelegramMessagingConfig { pub chat_ids: Vec, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub(crate) struct RestMessagingConfig { + pub url: String, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub(crate) struct MessagingConfig { #[serde(default = "default_as_empty_list")] pub notify_on: Vec, pub telegram: Option, + pub rest: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] diff --git a/src/model/stats.rs b/src/model/stats.rs index a013cf03a..34b085ba9 100644 --- a/src/model/stats.rs +++ b/src/model/stats.rs @@ -8,7 +8,7 @@ pub(crate) struct PlaylistStats { impl ToString for PlaylistStats { fn to_string(&self) -> String { - format!("{{groups: {}, channels: {}}}", self.group_count, self.channel_count) + format!("{{\"groups\": {}, \"channels\": {}}}", self.group_count, self.channel_count) } } @@ -23,7 +23,7 @@ pub(crate) struct InputStats { impl ToString for InputStats { fn to_string(&self) -> String { - format!("{{name: {}, type: {}, errors: {}, raw: {}, processed: {}}}", + format!("{{\"name\": {}, \"type\": {}, \"errors\": {}, \"raw\": {}, \"processed\": {}}}", self.name, self.input_type.to_string(), self.error_count, self.raw_stats.to_string(), self.processed_stats.to_string()) } diff --git a/src/processing/playlist_processor.rs b/src/processing/playlist_processor.rs index a93698454..256391707 100644 --- a/src/processing/playlist_processor.rs +++ b/src/processing/playlist_processor.rs @@ -535,7 +535,7 @@ fn persist_playlist(playlist: &[PlaylistGroup], epg: Option, pub(crate) async fn exec_processing(cfg: Arc, targets: Arc) { let (stats, errors) = process_sources(cfg.to_owned(), targets.to_owned()).await; - let stats_msg = format!("Stats: {}", stats.iter().map(|stat| stat.to_string()).collect::>().join("\n")); + let stats_msg = format!("{{\"stats\": {}}}", stats.iter().map(|stat| stat.to_string()).collect::>().join("\n")); // print stats info!("{}", stats_msg); // send stats @@ -544,6 +544,7 @@ pub(crate) async fn exec_processing(cfg: Arc, targets: Arc, removed: BTr if !message.is_empty() { let msg = format!("Changes {}/{}\n{}", target_name, group_name, message.join("")); info!("{}", &msg); - send_message(&MsgKind::Info, &cfg.messaging, &msg); + send_message(&MsgKind::Watch, &cfg.messaging, &msg); } }