new messaging option

This commit is contained in:
euzu
2024-01-11 09:43:16 +01:00
parent 2b511bba66
commit d16fd8a6d4
12 changed files with 85 additions and 14 deletions
+3 -1
View File
@@ -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`
+13 -2
View File
@@ -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: '<telegram bot token>'
chat_ids:
- '<telegram chat id>'
rest:
url: '<api url as POST endpoint for json data>'
```
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:
+20
View File
@@ -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
+3
View File
@@ -9,10 +9,13 @@ messaging:
- error
- info
- stats
- watch
telegram:
bot_token: '<your telegram bot token>'
chat_ids:
- <your telegram chat_id>
rest:
url: http://<ip>/<maybe_path>/<notification>
video:
web_search: 'https://www.imdb.com/search/title/?title={}'
extensions:
@@ -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;
+6
View File
@@ -0,0 +1,6 @@
export enum MessageKind {
Info = "info",
Stats = "stats",
Error = "error",
Watch = "watch",
}
+1
View File
@@ -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);
+19
View File
@@ -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<MessagingConfig>, 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)
}
});
}
}
}
}
+6
View File
@@ -445,11 +445,17 @@ pub(crate) struct TelegramMessagingConfig {
pub chat_ids: Vec<String>,
}
#[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<MsgKind>,
pub telegram: Option<TelegramMessagingConfig>,
pub rest: Option<RestMessagingConfig>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+2 -2
View File
@@ -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())
}
+3 -2
View File
@@ -535,7 +535,7 @@ fn persist_playlist(playlist: &[PlaylistGroup], epg: Option<Epg>,
pub(crate) async fn exec_processing(cfg: Arc<Config>, targets: Arc<ProcessTargets>) {
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::<Vec<String>>().join("\n"));
let stats_msg = format!("{{\"stats\": {}}}", stats.iter().map(|stat| stat.to_string()).collect::<Vec<String>>().join("\n"));
// print stats
info!("{}", stats_msg);
// send stats
@@ -544,6 +544,7 @@ pub(crate) async fn exec_processing(cfg: Arc<Config>, targets: Arc<ProcessTarget
errors.iter().for_each(|err| error!("{}", err.message));
// send errors
if let Some(message) = get_errors_notify_message!(errors, 255) {
send_message(&MsgKind::Error, &cfg.messaging, message.as_str());
let error_msg = format!("{{\"errors\": \"{}\"}}",message.as_str());
send_message(&MsgKind::Error, &cfg.messaging, error_msg.as_str());
}
}
+1 -1
View File
@@ -72,7 +72,7 @@ fn handle_watch_notification(cfg: &Config, added: BTreeSet<String>, 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);
}
}