Merge pull request #244 from g0ldyy/master

name change
This commit is contained in:
euzu
2025-05-10 18:35:26 +02:00
committed by GitHub
76 changed files with 487 additions and 487 deletions
+1 -1
View File
@@ -21,5 +21,5 @@ yarn.lock
*.log
docker/web
docker/m3u-filter
docker/tuliprox
docker/resources
+18 -18
View File
@@ -1,13 +1,13 @@
# Changelog
# 2.2.6 (2025-04-xx)
- !BREAKING_CHANGE! all docker images have now m3u-filter under `/app`
- !BREAKING_CHANGE! all docker images have now tuliprox under `/app`
- !BREAKING CHANGE! bandwidth `throttle_kbps` attribute for `reverse_proxy.stream` in `config.yml`
is now `throttle` and supports units. Allowed units are `KB/s`,`MB/s`,`KiB/s`,`MiB/s`,`kbps`,`mbps`,`Mibps`.
Default unit is `kbps`.
- !BREAKING_CHANGE! `log` config `active_clients` renamed to `log_active_user`
- !BREAKING_CHANGE! user has now the attribute `ui_enabled` to disable/enable web_ui for user.
You need to migrate the user db if you have used `use_user_db:true`.
Set it to `false` run old m3u-filter version, then update m3u-filter and set `use_user_db:true`and start.
Set it to `false` run old tuliprox version, then update tuliprox and set `use_user_db:true`and start.
- !BREAKING_CHANGE! `web_ui config` restructured and added `user_ui_enabled` attribute
```yaml
web_ui:
@@ -16,7 +16,7 @@ web_ui:
path:
auth:
enabled: true
issuer: m3u_filter
issuer: tuliprox
secret: ef9ab256a8c0abe5de92c2e05ca92baa810472ab702ff1674e9248308ceeec92
userfile: user.txt
```
@@ -70,7 +70,7 @@ url: 'https://localhost.com/epg.xml'
```
```yaml
# multi local file epg
url: ['file:///${env:M3U_FILTER_HOME}/epg.xml', 'file:///${env:M3U_FILTER_HOME}/epg2.xml']
url: ['file:///${env:TULIPROX_HOME}/epg.xml', 'file:///${env:TULIPROX_HOME}/epg2.xml']
```
```yaml
# multi url epg
@@ -175,9 +175,9 @@ ipcheck:
- New custom streams `user_connections_exhausted` and `provider_connections_exhausted`added.
```yaml
custom_stream_response:
channel_unavailable: /home/m3u-filter/resources/channel_unavailable.ts
user_connections_exhausted: /home/m3u-filter/resources/user_connections_exhausted.ts
provider_connections_exhausted: /home/m3u-filter/resources/provider_connections_exhausted.ts
channel_unavailable: /home/tuliprox/resources/channel_unavailable.ts
user_connections_exhausted: /home/tuliprox/resources/user_connections_exhausted.ts
provider_connections_exhausted: /home/tuliprox/resources/provider_connections_exhausted.ts
```
- input alias definition for same provider with same content but different credentials
```yaml
@@ -204,7 +204,7 @@ There are 2 batch input types `xtream_batch` and `m3u_batch`.
- sources:
- inputs:
- type: xtream_batch
url: 'file:///home/m3u-filter/config/my_provider_batch.csv'
url: 'file:///home/tuliprox/config/my_provider_batch.csv'
targets:
- name: test
```
@@ -220,7 +220,7 @@ my_provider_2;user2;password2;http://my_provider_2.com:8080;1;0
- sources:
- inputs:
- type: m3u_batch
url: 'file:///home/m3u-filter/config/my_provider_batch.csv'
url: 'file:///home/tuliprox/config/my_provider_batch.csv'
targets:
- name: test
```
@@ -318,14 +318,14 @@ server:
host: 192.169.1.9
port: '8901'
timezone: Europe/Paris
message: Welcome to m3u-filter
message: Welcome to tuliprox
- name: external
protocol: https
host: m3ufilter.mydomain.tv
host: tuliprox.mydomain.tv
port: '443'
timezone: Europe/Paris
message: Welcome to m3u-filter
path: m3uflt
message: Welcome to tuliprox
path: tuliprox
```
- Added Active clients count (for reverse proxy mode users) which is now displayed in `/status` and can be logged with setting
`active_clients: true` under `log`section in `config.yml`
@@ -430,7 +430,7 @@ reverse_proxy:
# 2.0.10 (2024-12-03)
- added Target Output Option `m3u_include_type_in_url`, default false. This adds `live`, `movie`, `series` to the url of the stream in reverse proxy mode.
- added Target Output Option `m3u_mask_redirect_url`, default false. The urls are pointed to m3u-filter in redirect mode. In stream request a redirect response is send. Usefully if you want to track calls in redirect mode.
- added Target Output Option `m3u_mask_redirect_url`, default false. The urls are pointed to tuliprox in redirect mode. In stream request a redirect response is send. Usefully if you want to track calls in redirect mode.
- fixed xtream api redirect url problem.
# 2.0.9 (2024-12-01)
@@ -510,8 +510,8 @@ reverse_proxy:
- `secret` secret for jwt token
- `userfile` optional userfile with generated userfile in format "username: password" per file, default name is user.txt in config path
* Password generation argument --genpwd to generate passwords for userfile.
* Added env var `M3U_FILTER_LOG` for log level
* Log Level has now module support like `m3u_filter::util=error,m3u_filter::filter=debug,m3u_filter=debug`
* Added env var `TULIPROX_LOG` for log level
* Log Level has now module support like `tuliprox::util=error,tuliprox::filter=debug,tuliprox=debug`
* Multiple Xtream Sources merging into one target is now supported
# v1.1.8(2024-03-06)
@@ -602,7 +602,7 @@ server:
https_port:
rtmp_port:
timezone: Europe/Paris
message: Welcome to m3u-filter
message: Welcome to tuliprox
user:
- target: pl1
credentials:
@@ -614,7 +614,7 @@ user:
# Changelog
# v1.0.0(2023-04-27)
* Added target argument for command line. `m3u-filter -t <target_name> -t <target_name>`. Target names should be provided in the config.
* Added target argument for command line. `tuliprox -t <target_name> -t <target_name>`. Target names should be provided in the config.
* Added filter to mapper definition.
* Refactored filter parsing.
* Fixed sort after mapping group names.
Generated
+1 -1
View File
@@ -1576,7 +1576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "m3u-filter"
name = "tuliprox"
version = "2.2.5"
dependencies = [
"axum",
+1 -1
View File
@@ -1,5 +1,5 @@
[package]
name = "m3u-filter"
name = "tuliprox"
version = "2.2.5"
edition = "2021"
+76 -76
View File
@@ -1,6 +1,6 @@
# <img src="https://github.com/user-attachments/assets/8ef9ea79-62ff-4298-978f-22326c5c3d02" height="24" /> **m3u-filter** - A Powerful Playlist Processing & Proxy Tool
# <img src="https://github.com/user-attachments/assets/8ef9ea79-62ff-4298-978f-22326c5c3d02" height="24" /> **tuliprox** - A Powerful Playlist Processing & Proxy Tool
**m3u-filter** is a flexible and efficient tool built in Rust 🦀,
**tuliprox** is a flexible and efficient tool built in Rust 🦀,
designed for processing, filtering, and serving media playlists with minimal resource usage.
<p align="center">
@@ -34,7 +34,7 @@ Define complex filters using expressive logic, e.g.:
## Command line Arguments
```
Usage: m3u-filter [OPTIONS]
Usage: tuliprox [OPTIONS]
Options:
-p, --config-path <CONFIG_PATH> The config directory
@@ -56,7 +56,7 @@ Options:
For running in cli mode, you need to define a `config.yml` file which can be inside config directory next to the executable or provided with the
`-c` cli argument.
For running specific targets use the `-t` argument like `m3u-filter -t <target_name> -t <other_target_name>`.
For running specific targets use the `-t` argument like `tuliprox -t <target_name> -t <other_target_name>`.
Target names should be provided in the config. The -t option overrides `enabled` attributes of `input` and `target` elements.
This means, even disabled inputs and targets are processed when the given target name as cli argument matches a target.
@@ -85,7 +85,7 @@ Default is `0`.
If you process the same provider multiple times each thread uses a connection. Keep in mind that you hit the provider max-connection.
### 1.2. `api`
`api` contains the `server-mode` settings. To run `m3u-filter` in `server-mode` you need to start it with the `-s`cli argument.
`api` contains the `server-mode` settings. To run `tuliprox` in `server-mode` you need to start it with the `-s`cli argument.
-`api: {host: localhost, port: 8901, web_root: ./web}`
### 1.3. `working_dir`
@@ -238,7 +238,7 @@ LRU-Cache is for resources. If it is `enabled`, the resources/images are persist
In an LRU cache, the least recently used items are evicted to make room for new items if the cache `size`is exceeded.
#### 1.6.3 `resource_rewrite_disabled`
If you have m3u-filter behind a reverse proxy and dont want rewritten resource urls inside responses, you can disable the resource_url rewrite.
If you have tuliprox behind a reverse proxy and dont want rewritten resource urls inside responses, you can disable the resource_url rewrite.
Default value is false.
If you set it `true` `cache` is disabled! Because the cache cant work without rewritten urls.
@@ -268,7 +268,7 @@ if set to true, an update is started when the application starts.
- `sanitize_sensitive_info` default true
- `active_clients` default false, if set to true reverse proxy client count is printed as info log.
- `log_level` can be set to `trace`, `debug`, `info`, `warn` and `error`.
You can also set module based level like `hyper_util::client::legacy::connect=error,m3u_filter=debug`
You can also set module based level like `hyper_util::client::legacy::connect=error,tuliprox=debug`
`log_level` priority CLI-Argument, Env-Var, Config, Default(`info`).
@@ -291,13 +291,13 @@ web_ui_enabled: true
web_auth:
enabled: true
secret: very.secret.secret
issuer: m3u_filter
issuer: tuliprox
userfile: user.txt
```
- `web_auth` can be deactivated if `enabled` is set to `false`. If not set default is `true`.
- `secret` is used for jwt token generation.
- `userfile` is the file where the ui users are stored. if the filename is not absolute `m3u-filter` will look into the `config_dir`. if `userfile`is not given the default value is `user.txt`
- `userfile` is the file where the ui users are stored. if the filename is not absolute `tuliprox` will look into the `config_dir`. if `userfile`is not given the default value is `user.txt`
You can generate a secret for jwt token for example with `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
@@ -310,12 +310,12 @@ nobody: $argon2id$v=$argon2id$v=19$m=19456,t=2,p=1$Y2FROE83ZDQ1c2VaYmJ4VU9YdHpuZ
The password can be generated with
```shell
./m3u-filter --genpwd`
./tuliprox --genpwd`
```
or with docker
```shell
docker container exec -it m3u-filter ./m3u-filter --genpwd
docker container exec -it tuliprox ./tuliprox --genpwd
```
The encrypted pasword needs to be added manually into the users file.
@@ -364,9 +364,9 @@ and add it to the `config.yml`.
```yaml
custom_stream_response:
channel_unavailable: /home/m3u-filter/channel_unavailable.ts
user_connections_exhausted: /home/m3u-filter/user_connections_exhausted.ts
provider_connections_exhausted: /home/m3u-filter/provider_connections_exhausted.ts
channel_unavailable: /home/tuliprox/channel_unavailable.ts
user_connections_exhausted: /home/tuliprox/user_connections_exhausted.ts
provider_connections_exhausted: /home/tuliprox/provider_connections_exhausted.ts
```
### 1.15 `user_config_dir`
@@ -419,7 +419,7 @@ HdHomeRun device config has the following attributes:
- `firmware_version`: _optional_
- `device_type`: _optional_
- `device_udn`: _optional_
- `port`: _optional_, if not given the m3u-filter-server port is incremented for each device.
- `port`: _optional_, if not given the tuliprox-server port is incremented for each device.
## 2. `source.yml`
@@ -543,7 +543,7 @@ There are 2 batch input types `xtream_batch` and `m3u_batch`.
- sources:
- inputs:
- type: xtream_batch
url: 'file:///home/m3u-filter/config/my_provider_batch.csv'
url: 'file:///home/tuliprox/config/my_provider_batch.csv'
targets:
- name: test
```
@@ -559,7 +559,7 @@ my_provider_2;user2;password2;http://my_provider_2.com:8080;1;0
- sources:
- inputs:
- type: m3u_batch
url: 'file:///home/m3u-filter/config/my_provider_batch.csv'
url: 'file:///home/tuliprox/config/my_provider_batch.csv'
targets:
- name: test
```
@@ -1131,7 +1131,7 @@ mappings:
## 3. Api-Proxy Config
If you use m3u-filter to deliver playlists, we require a configuration to provide the necessary server information, rewrite URLs in reverse proxy mode, and define users who can access the API.
If you use tuliprox to deliver playlists, we require a configuration to provide the necessary server information, rewrite URLs in reverse proxy mode, and define users who can access the API.
For this purpose, we use the `api-proxy.yml` configuration.
@@ -1147,14 +1147,14 @@ server:
host: 192.169.1.9
port: '8901'
timezone: Europe/Paris
message: Welcome to m3u-filter
message: Welcome to tuliprox
- name: external
protocol: https
host: m3ufilter.mydomain.tv
host: tuliprox.mydomain.tv
port: '443'
timezone: Europe/Paris
message: Welcome to m3u-filter
path: m3uflt
message: Welcome to tuliprox
path: tuliprox
```
User definitions are made for the targets. Each target can have multiple users. Usernames and tokens must be unique.
@@ -1215,11 +1215,11 @@ If you use the endpoints through rest calls, you can use, for the sake of simpli
When you define credentials for a `target`, ensure that this target has
`output` format `xtream`or `m3u`.
The `proxy` property can be `reverse`or `redirect`. `reverse` means the streams are going through m3u-filter, `redirect` means the streams are comming from your provider.
The `proxy` property can be `reverse`or `redirect`. `reverse` means the streams are going through tuliprox, `redirect` means the streams are comming from your provider.
If you use `https` you need a ssl terminator. `m3u-filter` does not support https traffic.
If you use `https` you need a ssl terminator. `tuliprox` does not support https traffic.
If you use a ssl-terminator or proxy in front of m3u-filter you can set a `path` to make the configuration of your proxy simpler.
If you use a ssl-terminator or proxy in front of tuliprox you can set a `path` to make the configuration of your proxy simpler.
For example you use `nginx` as your reverse proxy.
`api-proxy.yml`
@@ -1230,14 +1230,14 @@ server:
host: 192.169.1.9
port: '8901'
timezone: Europe/Paris
message: Welcome to m3u-filter
message: Welcome to tuliprox
- name: external
protocol: https
host: m3ufilter.mydomain.tv
host: tuliprox.mydomain.tv
port: '443'
timezone: Europe/Paris
message: Welcome to m3u-filter
path: m3uflt
message: Welcome to tuliprox
path: tuliprox
user:
- target: xc_m3u
credentials:
@@ -1253,8 +1253,8 @@ user:
Now you can do `nginx` configuration like
```config
location /m3uflt {
rewrite ^/m3uflt/(.*)$ /$1 break;
location /tuliprox {
rewrite ^/tuliprox/(.*)$ /$1 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
@@ -1273,14 +1273,14 @@ server:
host: 192.168.0.3
port: 80
timezone: Europe/Paris
message: Welcome to m3u-filter
message: Welcome to tuliprox
- name: external
protocol: https
host: my_external_domain.com
port: 443
timezone: Europe/Paris
message: Welcome to m3u-filter
path: /m3uflt
message: Welcome to tuliprox
path: /tuliprox
- target: pl1
credentials:
- {username: x3452, password: ztrhgrGZ, token: 4342sd, proxy: reverse, server: external, epg_timeshift: -2:30}
@@ -1297,16 +1297,16 @@ Following log levels are supported:
Use the `-l` or `--log-level` cli-argument to specify the log-level.
The log level can be set through environment variable `M3U_FILTER_LOG`,
The log level can be set through environment variable `TULIPROX_LOG`,
or config.
Precedence is cli-argument, env-var, config, default(`info`).
Log Level has module support like `m3u_filter::util=error,m3u_filter::filter=debug,m3u_filter=debug`
Log Level has module support like `tuliprox::util=error,tuliprox::filter=debug,tuliprox=debug`
## 6. Web-UI
The WebUI is for configuration the m3u-filter config.
The WebUI is for configuration the tuliprox config.
If you enable authentication, users can log in with their accounts (you can disable login per user),
and configure their playlist.
@@ -1317,13 +1317,13 @@ and configure their playlist.
Change into the root directory and run:
```shell
docker build --rm -f docker/Dockerfile -t m3u-filter .
docker build --rm -f docker/Dockerfile -t tuliprox .
```
This will build the complete project and create a docker image.
To start the container, you can use the `docker-compose.yml`
But you need to change `image: ghcr.io/euzu/m3u-filter:latest` to `image: m3u-filter`
But you need to change `image: ghcr.io/euzu/tuliprox:latest` to `image: tuliprox`
### Manual build static binary for docker
@@ -1358,41 +1358,41 @@ Targets are
```shell
# Build for a specific architecture
docker build --rm -f docker/Dockerfile -t m3u-filter --target scratch-final --build-arg RUST_TARGET=x86_64-unknown-linux-musl .
docker build --rm -f docker/Dockerfile -t tuliprox --target scratch-final --build-arg RUST_TARGET=x86_64-unknown-linux-musl .
docker build --rm -f docker/Dockerfile -t m3u-filter --target scratch-final --build-arg RUST_TARGET=aarch64-unknown-linux-musl .
docker build --rm -f docker/Dockerfile -t tuliprox --target scratch-final --build-arg RUST_TARGET=aarch64-unknown-linux-musl .
docker build --rm -f docker/Dockerfile -t m3u-filter --target scratch-final --build-arg RUST_TARGET=armv7-unknown-linux-musleabihf .
docker build --rm -f docker/Dockerfile -t tuliprox --target scratch-final --build-arg RUST_TARGET=armv7-unknown-linux-musleabihf .
docker build --rm -f docker/Dockerfile -t m3u-filter --target scratch-final --build-arg RUST_TARGET=x86_64-apple-darwin .
docker build --rm -f docker/Dockerfile -t tuliprox --target scratch-final --build-arg RUST_TARGET=x86_64-apple-darwin .
```
##### docker-compose.yml
```docker
version: '3'
services:
m3u-filter:
container_name: m3u-filter
image: m3u-filter
tuliprox:
container_name: tuliprox
image: tuliprox
user: "133:144"
working_dir: /app
volumes:
- /opt/m3u-filter/config:/app/config
- /opt/m3u-filter/data:/app/data
- /opt/m3u-filter/backup:/app/backup
- /opt/m3u-filter/downloads:/app/downloads
- /opt/tuliprox/config:/app/config
- /opt/tuliprox/data:/app/data
- /opt/tuliprox/backup:/app/backup
- /opt/tuliprox/downloads:/app/downloads
environment:
- TZ=Europe/Paris
ports:
- "8901:8901"
restart: unless-stopped
```
This example is for the local image, the official can be found under `ghcr.io/euzu/m3u-filter:latest`
This example is for the local image, the official can be found under `ghcr.io/euzu/tuliprox:latest`
If you want to use m3u-filter with docker-compose, there is a `--healthcheck` argument for healthchecks
If you want to use tuliprox with docker-compose, there is a `--healthcheck` argument for healthchecks
```docker
healthcheck:
test: ["CMD", "/app/m3u-filter", "-p", "/app/config" "--healthcheck"]
test: ["CMD", "/app/tuliprox", "-p", "/app/config" "--healthcheck"]
interval: 30s
timeout: 10s
retries: 3
@@ -1406,29 +1406,29 @@ To get it started in a Alpine 3.19 LXC
apk update
apk add nano git yarn bash cargo perl-local-lib perl-module-build make
cd /opt
git clone https://github.com/euzu/m3u-filter.git
cd /opt/m3u-filter/bin
git clone https://github.com/euzu/tuliprox.git
cd /opt/tuliprox/bin
./build_lin.sh
ln -s /opt/m3u-filter/target/release/m3u-filter /bin/m3u-filter
cd /opt/m3u-filter/frontend
ln -s /opt/tuliprox/target/release/tuliprox /bin/tuliprox
cd /opt/tuliprox/frontend
yarn
yarn build
ln -s /opt/m3u-filter/frontend/build /web
ln -s /opt/m3u-filter/config /config
ln -s /opt/tuliprox/frontend/build /web
ln -s /opt/tuliprox/config /config
mkdir /data
mkdir /backup
```
**Creating a service, create /etc/init.d/m3u-filter**
**Creating a service, create /etc/init.d/tuliprox**
```shell
#!/sbin/openrc-run
name=m3u-filter
command="/bin/m3u-filter"
name=tuliprox
command="/bin/tuliprox"
command_args="-p /config -s"
command_user="root"
command_background="yes"
output_log="/var/log/m3u-filter/m3u-filter.log"
error_log="/var/log/m3u-filter/m3u-filter.log"
output_log="/var/log/tuliprox/tuliprox.log"
error_log="/var/log/tuliprox/tuliprox.log"
supervisor="supervise-daemon"
depend() {
@@ -1437,13 +1437,13 @@ depend() {
start_pre() {
checkpath --directory --owner $command_user:$command_user --mode 0775 \
/run/m3u-filter /var/log/m3u-filter
/run/tuliprox /var/log/tuliprox
}
```
**then add it to boot**
```shell
rc-update add m3u-filter default
rc-update add tuliprox default
```
@@ -1476,10 +1476,10 @@ env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build --release --target armv
```
# Different Scenarios
## Using `m3u-filter` with a m3u provider.
## Using `tuliprox` with a m3u provider.
todo.
## Using `m3u-filter` with a xtream provider.
## Using `tuliprox` with a xtream provider.
You have a provider who supports the xtream api.
@@ -1490,7 +1490,7 @@ The provider gives you:
- epg_url: `http://fantastic.provider.xyz:8080/xmltv.php?username=tvjunkie&password=junkie.secret`
To use `m3u-filter` you need to create the configuration.
To use `tuliprox` you need to create the configuration.
The configuration consist of 4 files.
- config.yml
- source.yml
@@ -1507,7 +1507,7 @@ working_dir: ./data
update_on_boot: true
```
This configuration starts `m3u-filter`and listens on the 8901 port. The downloaded playlists are stored inside the `data`-folder in the current working directory.
This configuration starts `tuliprox`and listens on the 8901 port. The downloaded playlists are stored inside the `data`-folder in the current working directory.
The property `update_on_boot` is optional and can be helpful in the beginning until you have found a working configuration. I prefer to set it to false.
Now we have to define the sources we want to import. We do this inside `source.yml`
@@ -1549,13 +1549,13 @@ server:
host: 192.168.1.41
port: '8901'
timezone: Europe/Berlin
message: Welcome to m3u-filter
message: Welcome to tuliprox
- name: external
protocol: https
host: tvjunkie.dyndns.org
port: '443'
timezone: Europe/Berlin
message: Welcome to m3u-filter
message: Welcome to tuliprox
user:
- target: all_channels
credentials:
@@ -1568,11 +1568,11 @@ user:
proxy: redirect
server: external
```
We have defined 2 server configurations. The `default` configuration is intended for use in the local network, the IP address is that of the computer on which `m3u-filter` is running. The `external` configuration is optional and is only required for access from outside your local network. External access requires port forwarding on your router and an SSL terminator proxy such as nginx and a dyndns provider configured from your router if you do not have a static IP address (this is outside the scope of this manual).
We have defined 2 server configurations. The `default` configuration is intended for use in the local network, the IP address is that of the computer on which `tuliprox` is running. The `external` configuration is optional and is only required for access from outside your local network. External access requires port forwarding on your router and an SSL terminator proxy such as nginx and a dyndns provider configured from your router if you do not have a static IP address (this is outside the scope of this manual).
The next section of the `api-proxy.yml` contains the user definition. We can define users for each `target` from the `source.yml`.
This means that each `user` can only access one `target` from `source.yml`. We have named our target `all_channels` in `source.yml` and used this name for the user definition. We have defined 2 users, one for local access and one for external access.
We have set the proxy type to `redirect`, which means that the client will be redirected to the original provider URL when opening a stream. If you set the proxy type to `reverse`, the stream will be streamed from the provider through `m3u-filter`. Based on the hardware you are running `m3u-filter` on, you can opt for the proxy type `reverse`. But you should start with `redirect` first until everything works well.
We have set the proxy type to `redirect`, which means that the client will be redirected to the original provider URL when opening a stream. If you set the proxy type to `reverse`, the stream will be streamed from the provider through `tuliprox`. Based on the hardware you are running `tuliprox` on, you can opt for the proxy type `reverse`. But you should start with `redirect` first until everything works well.
If no server is specified for a user, the default one is taken.
@@ -1583,7 +1583,7 @@ All this information are now defined in `api-proxy.yml`.
- username: `xt`
- password: `xt.secret`
Start `m3u-filter`, fire up your IPTV-Application, enter credentials and watch.
Start `tuliprox`, fire up your IPTV-Application, enter credentials and watch.
# It works well, but I don't need all the channels, how can I filter?
@@ -1614,7 +1614,7 @@ Ok now let's start.
First: We have a lot of channel groups we dont need.
`m3u-filter` excludes or includes groups or channels based on filter. Usable fields for filter are `Group`, `Name` and `Title`.
`tuliprox` excludes or includes groups or channels based on filter. Usable fields for filter are `Group`, `Name` and `Title`.
The simplest filter is:
`<Field> ~ <Regular Expression>`. For example `Group ~ ".*"`. This means include all categories.
+4 -4
View File
@@ -13,8 +13,8 @@ declare -A ARCHITECTURES=(
)
declare -A BUILDS=(
[LINUX]="m3u-filter:scratch-final m3u-filter-alpine:alpine-final"
[AARCH64]="m3u-filter-aarch64:scratch-final"
[LINUX]="tuliprox:scratch-final tuliprox-alpine:alpine-final"
[AARCH64]="tuliprox-aarch64:scratch-final"
)
cd "$FRONTEND_DIR" && rm -rf build && yarn && yarn build
@@ -47,7 +47,7 @@ for PLATFORM in "${!ARCHITECTURES[@]}"; do
cargo clean || true
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build --release --target "$ARCHITECTURE"
BINARY_PATH="${WORKING_DIR}/target/${ARCHITECTURE}/release/m3u-filter"
BINARY_PATH="${WORKING_DIR}/target/${ARCHITECTURE}/release/tuliprox"
if [ ! -f "$BINARY_PATH" ]; then
echo "🧨 Error: Binary $BINARY_PATH does not exist."
exit 1
@@ -89,7 +89,7 @@ done
# Clean up
echo "🗑 Cleaning up Docker context..."
rm -rf "${DOCKER_DIR}/web"
rm -f "${DOCKER_DIR}/m3u-filter"
rm -f "${DOCKER_DIR}/tuliprox"
rm -rf "${DOCKER_DIR}/resources"
echo "🎉 Docker images for version ${VERSION} have been successfully built, tagged, and pushed."
+6 -6
View File
@@ -61,13 +61,13 @@ cargo clean
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build --release --target "$TARGET"
# Check if the binary exists
if [ ! -f "${WORKING_DIR}/target/${TARGET}/release/m3u-filter" ]; then
echo "Error: Static binary '${WORKING_DIR}/target/${TARGET}/release/m3u-filter' does not exist."
if [ ! -f "${WORKING_DIR}/target/${TARGET}/release/tuliprox" ]; then
echo "Error: Static binary '${WORKING_DIR}/target/${TARGET}/release/tuliprox' does not exist."
exit 1
fi
# Prepare Docker build context
cp "${WORKING_DIR}/target/${TARGET}/release/m3u-filter" "${DOCKER_DIR}/"
cp "${WORKING_DIR}/target/${TARGET}/release/tuliprox" "${DOCKER_DIR}/"
rm -rf "${DOCKER_DIR}/web"
cp -r "${WORKING_DIR}/frontend/build" "${DOCKER_DIR}/web"
cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/"
@@ -75,9 +75,9 @@ cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/"
cd "${DOCKER_DIR}"
echo "Building Docker images for version ${VERSION}"
if [ "$beta_image" = true ]; then
SCRATCH_IMAGE_NAME=m3u-filter-aarch64-beta
SCRATCH_IMAGE_NAME=tuliprox-aarch64-beta
else
SCRATCH_IMAGE_NAME=m3u-filter-aarch64
SCRATCH_IMAGE_NAME=tuliprox-aarch64
fi
# Build scratch image and tag as "latest"
@@ -94,7 +94,7 @@ docker push ghcr.io/euzu/${SCRATCH_IMAGE_NAME}:latest
# Clean up
echo "Cleaning up build artifacts..."
rm -rf "${DOCKER_DIR}/web"
rm -f "${DOCKER_DIR}/m3u-filter"
rm -f "${DOCKER_DIR}/tuliprox"
rm -rf "${DOCKER_DIR}/resources"
echo "Docker images ghcr.io/euzu/${SCRATCH_IMAGE_NAME}:${VERSION} have been successfully built, tagged, and pushed."
+5 -5
View File
@@ -38,20 +38,20 @@ cargo clean
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build --release --target "$TARGET"
# Check if the binary exists
if [ ! -f "${WORKING_DIR}/target/${TARGET}/release/m3u-filter" ]; then
echo "Error: Static binary '${WORKING_DIR}/target/${TARGET}/release/m3u-filter' does not exist."
if [ ! -f "${WORKING_DIR}/target/${TARGET}/release/tuliprox" ]; then
echo "Error: Static binary '${WORKING_DIR}/target/${TARGET}/release/tuliprox' does not exist."
exit 1
fi
# Prepare Docker build context
cp "${WORKING_DIR}/target/${TARGET}/release/m3u-filter" "${DOCKER_DIR}/"
cp "${WORKING_DIR}/target/${TARGET}/release/tuliprox" "${DOCKER_DIR}/"
rm -rf "${DOCKER_DIR}/web"
cp -r "${WORKING_DIR}/frontend/build" "${DOCKER_DIR}/web"
cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/"
cd "${DOCKER_DIR}"
echo "Building Docker images for version ${VERSION}"
BETA_IMAGE_NAME=m3u-filter-beta
BETA_IMAGE_NAME=tuliprox-beta
# Build alpine image and tag as "latest"
docker build -f Dockerfile-manual -t ghcr.io/euzu/${BETA_IMAGE_NAME}:"${VERSION}" --target alpine-final .
@@ -67,7 +67,7 @@ docker push ghcr.io/euzu/${BETA_IMAGE_NAME}:latest
# Clean up
echo "Cleaning up build artifacts..."
rm -rf "${DOCKER_DIR}/web"
rm -f "${DOCKER_DIR}/m3u-filter"
rm -f "${DOCKER_DIR}/tuliprox"
rm -rf "${DOCKER_DIR}/resources"
echo "Docker images ghcr.io/euzu/${BETA_IMAGE_NAME}${VERSION} have been successfully built, tagged, and pushed."
+7 -7
View File
@@ -39,11 +39,11 @@ declare -A ARCHITECTURES=(
)
declare -A DIRS=(
[LINUX]=m3u-filter_${VERSION}_linux_x86_64
[WINDOWS]=m3u-filter_${VERSION}_windows_x86_64
[ARM7]=m3u-filter_${VERSION}_armv7
[AARCH64]=m3u-filter_${VERSION}_aarch64_x86_64
[DARWIN]=m3u-filter_${VERSION}_apple-darwin_x86_64
[LINUX]=tuliprox_${VERSION}_linux_x86_64
[WINDOWS]=tuliprox_${VERSION}_windows_x86_64
[ARM7]=tuliprox_${VERSION}_armv7
[AARCH64]=tuliprox_${VERSION}_aarch64_x86_64
[DARWIN]=tuliprox_${VERSION}_apple-darwin_x86_64
)
# Special case mapping for binary extensions (e.g., Windows needs .exe)
@@ -73,9 +73,9 @@ for PLATFORM in "${!ARCHITECTURES[@]}"; do
ARC=${DIR}.tgz
# Handle platform-specific binary file names
if [[ -n "${BIN_EXTENSIONS[$PLATFORM]}" ]]; then
BIN="${ARCHITECTURE}/release/m3u-filter${BIN_EXTENSIONS[$PLATFORM]}"
BIN="${ARCHITECTURE}/release/tuliprox${BIN_EXTENSIONS[$PLATFORM]}"
else
BIN="${ARCHITECTURE}/release/m3u-filter"
BIN="${ARCHITECTURE}/release/tuliprox"
fi
rustup target add "$ARCHITECTURE"
+4 -4
View File
@@ -4,15 +4,15 @@ server:
host: 192.168.9.3
port: 80
timezone: Europe/Paris
message: Welcome to m3u-filter
message: Welcome to tuliprox
- name: extern
protocol: https
host: m3u-filter.mydns.com
host: tuliprox.mydns.com
port: 443
rtmp_port:
timezone: Europe/Paris
message: Welcome to m3u-filter
path: m3uflt # optional, only needed for easier reverse proxy configuration see README.md.
message: Welcome to tuliprox
path: tuliprox # optional, only needed for easier reverse proxy configuration see README.md.
user:
- target: pl1
credentials:
+8 -8
View File
@@ -1,12 +1,12 @@
api: { host: 127.0.0.1, port: 8901, web_root: /home/m3u-filter/web }
api: { host: 127.0.0.1, port: 8901, web_root: /home/tuliprox/web }
threads: 0
working_dir: /home/m3u-filter/data
backup_dir: /home/m3u-filter/.backup
working_dir: /home/tuliprox/data
backup_dir: /home/tuliprox/.backup
update_on_boot: false
custom_stream_response:
channel_unavailable: /home/m3u-filter/resources/channel_unavailable.ts
user_connections_exhausted: /home/m3u-filter/resources/user_connections_exhausted.ts
provider_connections_exhausted: /home/m3u-filter/resources/provider_connections_exhausted.ts
channel_unavailable: /home/tuliprox/resources/channel_unavailable.ts
user_connections_exhausted: /home/tuliprox/resources/user_connections_exhausted.ts
provider_connections_exhausted: /home/tuliprox/resources/provider_connections_exhausted.ts
connect_timeout_secs: 10
@@ -22,7 +22,7 @@ reverse_proxy:
cache:
size: 500MB
enabled: true
dir: /home/m3u-filter/cache
dir: /home/tuliprox/cache
log:
sanitize_sensitive_info: false
active_clients: true
@@ -33,7 +33,7 @@ web_ui:
path:
auth:
enabled: true
issuer: m3u_filter
issuer: tuliprox
secret: ef9ab256a8c0abe5de92c2e05ca92baa810472ab702ff1674e9248308ceeec92
userfile: user.txt
+4 -4
View File
@@ -70,10 +70,10 @@ ARG RUST_TARGET
WORKDIR /app
COPY --from=rust-build /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=rust-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=rust-build /src/target/${RUST_TARGET}/release/m3u-filter ./m3u-filter
COPY --from=rust-build /src/target/${RUST_TARGET}/release/tuliprox ./tuliprox
COPY --from=node-build /app/build ./web
COPY --from=resource-build /src/resources ./resources
ENTRYPOINT ["/app/m3u-filter"]
ENTRYPOINT ["/app/tuliprox"]
CMD ["-s", "-p", "/app/config"]
# Final container (Alpine)
@@ -84,8 +84,8 @@ ARG RUST_TARGET
RUN apk add --no-cache bash curl ca-certificates tini
WORKDIR /app
COPY --from=rust-build /src/target/${RUST_TARGET}/release/m3u-filter ./m3u-filter
COPY --from=rust-build /src/target/${RUST_TARGET}/release/tuliprox ./tuliprox
COPY --from=node-build /app/build ./web
COPY --from=resource-build /src/resources ./resources
ENTRYPOINT ["/sbin/tini", "--", "/app/m3u-filter"]
ENTRYPOINT ["/sbin/tini", "--", "/app/tuliprox"]
CMD ["-s", "-p", "/app/config"]
+4 -4
View File
@@ -7,11 +7,11 @@ WORKDIR /app
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY ./m3u-filter ./
COPY ./tuliprox ./
COPY ./web ./web
COPY ./resources ./resources
CMD ["/app/m3u-filter", "-s", "-p", "/app/config"]
CMD ["/app/tuliprox", "-s", "-p", "/app/config"]
# Alpine Final container
FROM alpine:latest AS alpine-final
@@ -20,11 +20,11 @@ RUN apk add --no-cache bash curl strace tcpdump bind-tools nano ca-certificates
WORKDIR /app
COPY ./m3u-filter ./
COPY ./tuliprox ./
COPY ./web ./web
COPY ./resources ./resources
# config should be mounted as volume
# COPY ./config ./config
ENTRYPOINT ["/sbin/tini", "--", "/app/m3u-filter"]
ENTRYPOINT ["/sbin/tini", "--", "/app/tuliprox"]
CMD ["-s", "-p", "/app/config"]
+8 -8
View File
@@ -8,26 +8,26 @@ Change into the root directory and run:
```shell
# Build for a specific architecture
docker build --rm -f docker/Dockerfile -t m3u-filter --target scratch-final --build-arg RUST_TARGET=x86_64-unknown-linux-musl .
docker build --rm -f docker/Dockerfile -t tuliprox --target scratch-final --build-arg RUST_TARGET=x86_64-unknown-linux-musl .
docker build --rm -f docker/Dockerfile -t m3u-filter --target scratch-final --build-arg RUST_TARGET=aarch64-unknown-linux-musl .
docker build --rm -f docker/Dockerfile -t tuliprox --target scratch-final --build-arg RUST_TARGET=aarch64-unknown-linux-musl .
docker build --rm -f docker/Dockerfile -t m3u-filter --target scratch-final --build-arg RUST_TARGET=armv7-unknown-linux-musleabihf .
docker build --rm -f docker/Dockerfile -t tuliprox --target scratch-final --build-arg RUST_TARGET=armv7-unknown-linux-musleabihf .
docker build --rm -f docker/Dockerfile -t m3u-filter --target scratch-final --build-arg RUST_TARGET=x86_64-apple-darwin .
docker build --rm -f docker/Dockerfile -t tuliprox --target scratch-final --build-arg RUST_TARGET=x86_64-apple-darwin .
```
Both targets have the path prefix: `/app`
This will build the complete project and create a docker image.
To start the container, you can use the `docker-compose.yml`
But you need to change `image: ghcr.io/euzu/m3u-filter:latest` to `image: m3u-filter`
But you need to change `image: ghcr.io/euzu/tuliprox:latest` to `image: tuliprox`
# Manual docker image
You want to build the binary and web folder manually and create a docker image.
To dockerize m3u-filter, you need to compile a static build.
To dockerize tuliprox, you need to compile a static build.
The static build can created with `bin\build_lin_static.sh`.
Description of static binary compiling is in the main `README.md`
@@ -36,7 +36,7 @@ Then you need to compile the frontend with `yarn build`
Change into the `docker` directory and copy all the needed files (look at the Dockerfile) into the current directory.
To create a docker image type:
`docker -f Dockerfile-manual build -t m3u-filter .`
`docker -f Dockerfile-manual build -t tuliprox .`
To start the container, you can use the `docker-compose.yml`
But you need to change `image: ghcr.io/euzu/m3u-filter:latest` to `image: m3u-filter`
But you need to change `image: ghcr.io/euzu/tuliprox:latest` to `image: tuliprox`
+3 -3
View File
@@ -1,8 +1,8 @@
version: '3'
services:
m3u-filter:
container_name: m3u-filter
image: ghcr.io/euzu/m3u-filter:latest
tuliprox:
container_name: tuliprox
image: ghcr.io/euzu/tuliprox:latest
working_dir: /app
volumes:
- ./data:/app/data
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "m3u-filter",
"description": "m3u-filter",
"name": "tuliprox",
"description": "tuliprox",
"author": "euzu",
"version": "2.2.5",
"private": true,
+2 -2
View File
@@ -186,7 +186,7 @@
"THROTTLE": "Supported units are KB/s,MB/s,KiB/s,MiB/s,kbps,mbps,Mibps"
},
"WEB_UI": {
"PATH": "Configuration of a path prefix, allowing it to be accessed behind a reverse proxy under a custom subpath. like https://yourdomain.com/m3uflt"
"PATH": "Configuration of a path prefix, allowing it to be accessed behind a reverse proxy under a custom subpath. like https://yourdomain.com/tuliprox"
}
}
},
@@ -197,7 +197,7 @@
"FINISH_SETUP": "Finish Setup"
},
"MSG": {
"WELCOME": "Welcome to m3u-filter setup. This wizard will guide you through the initial setup process."
"WELCOME": "Welcome to tuliprox setup. This wizard will guide you through the initial setup process."
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"app_title": "m3u-filter",
"app_title": "tuliprox",
"app_logo": null,
"api": {
"apiUrl": "/api/v1/",
+1 -1
View File
@@ -9,7 +9,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!-- link rel="manifest" href="%PUBLIC_URL%/assets/manifest.json" / -->
<title>m3u-filter</title>
<title>tuliprox</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
+2 -2
View File
@@ -1,6 +1,6 @@
{
"short_name": "m3u-filter",
"name": "m3u-filter",
"short_name": "tuliprox",
"name": "tuliprox",
"icons": [
{
"src": "assets/favicon.ico",
+1 -1
View File
@@ -13,7 +13,7 @@ interface AppUserProps {
export default function AppUser(props: AppUserProps) {
const services = useServices();
const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'm3u-filter', [services]);
const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'tuliprox', [services]);
const appLogo = useMemo(() => {
let logo = services.config().getUiConfig().app_logo;
if (logo) {
+1 -1
View File
@@ -22,7 +22,7 @@ export default function App(props: AppProps) {
const [serverConfig, setServerConfig] = useState<ServerConfig>(undefined);
const serverConfigLoaded = useRef(false);
const [preferencesVisible, setPreferencesVisible] = useState<boolean>(true);
const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'm3u-filter', [services]);
const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'tuliprox', [services]);
const translate = useTranslator();
const appLogo = useMemo(() => {
let logo = services.config().getUiConfig().app_logo;
+1 -1
View File
@@ -11,7 +11,7 @@ export default function Login(): JSX.Element {
const usernameRef = useRef<HTMLInputElement>(undefined);
const passwordRef = useRef<HTMLInputElement>(undefined);
const services = useServices();
const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'm3u-filter', [services]);
const appTitle = useMemo(() => services.config().getUiConfig().app_title ?? 'tuliprox', [services]);
const appLogo = useMemo(() => {
let logo = services.config().getUiConfig().app_logo;
if (logo) {
+1 -1
View File
@@ -10,7 +10,7 @@ export interface UiConfig {
export const DefaultUiConfig: UiConfig = {
languages: ["en", "de", "fr", "tr", "es", "it", "ru"],
app_title: "m3u-filter",
app_title: "tuliprox",
api: {
apiUrl: "/api/v1/",
authUrl: "/auth/"
+1 -1
View File
@@ -518,7 +518,7 @@ fn is_throttled_stream(item_type: PlaylistItemType, throttle_kbps: usize) -> boo
throttle_kbps > 0 && matches!(item_type, PlaylistItemType::Video | PlaylistItemType::Series | PlaylistItemType::SeriesInfo)
}
const SESSION_COOKIE_NAME: &str = "m3uflt_session=";
const SESSION_COOKIE_NAME: &str = "tuliprox_session=";
// fn create_delete_session_cookie() -> String {
// format!("{SESSION_COOKIE_NAME}; Max-Age=0; Path=/; HttpOnly")
+1 -1
View File
@@ -12,7 +12,7 @@ use std::ops::Deref;
use std::sync::Arc;
use std::{fs};
use axum::response::IntoResponse;
use crate::m3u_filter_error::to_io_error;
use crate::tuliprox_error::to_io_error;
use crate::utils::request::create_client;
async fn download_file(active: Arc<RwLock<Option<FileDownload>>>, client: &reqwest::Client) -> Result<(), String> {
+1 -1
View File
@@ -166,7 +166,7 @@ async fn create_device(app_state: &Arc<HdHomerunAppState>) -> Option<Device> {
Some(Device {
friendly_name: device.friendly_name.to_string(),
manufacturer: device.manufacturer.to_string(),
//manufacturer_url: "https://github.com/euzu/m3u-filter".to_string(),
//manufacturer_url: "https://github.com/euzu/tuliprox".to_string(),
model_number: device.model_number.to_string(),
model_name: device.model_name.to_string(),
firmware_name: device.firmware_name.to_string(),
+3 -3
View File
@@ -6,7 +6,7 @@ use crate::api::model::config::{ServerConfig, ServerInputConfig, ServerSourceCon
use crate::api::model::request::{PlaylistRequest, PlaylistRequestType};
use crate::auth::access_token::create_access_token;
use crate::auth::authenticator::validator_admin;
use crate::m3u_filter_error::M3uFilterError;
use crate::tuliprox_error::TuliProxError;
use crate::model::StatusCheck;
use crate::model::XtreamPlaylistItem;
use crate::model::{validate_targets, Config, ConfigDto, ConfigInput, ConfigInputOptions, ConfigSource, ConfigTarget, InputType};
@@ -23,7 +23,7 @@ use serde_json::json;
use std::collections::{BTreeMap, HashSet};
use std::sync::Arc;
fn intern_save_config_api_proxy(backup_dir: &str, api_proxy: &ApiProxyConfig, file_path: &str) -> Option<M3uFilterError> {
fn intern_save_config_api_proxy(backup_dir: &str, api_proxy: &ApiProxyConfig, file_path: &str) -> Option<TuliProxError> {
match config_reader::save_api_proxy(file_path, backup_dir, api_proxy) {
Ok(()) => {}
Err(err) => {
@@ -34,7 +34,7 @@ fn intern_save_config_api_proxy(backup_dir: &str, api_proxy: &ApiProxyConfig, fi
None
}
fn intern_save_config_main(file_path: &str, backup_dir: &str, cfg: &ConfigDto) -> Option<M3uFilterError> {
fn intern_save_config_main(file_path: &str, backup_dir: &str, cfg: &ConfigDto) -> Option<TuliProxError> {
match config_reader::save_main_config(file_path, backup_dir, cfg) {
Ok(()) => {}
Err(err) => {
+6 -6
View File
@@ -10,9 +10,9 @@ use crate::api::model::request::UserApiRequest;
use crate::api::model::streams::provider_stream::{create_custom_video_stream_response, CustomVideoStreamType}
;
use crate::api::model::xtream::XtreamAuthorizationResponse;
use crate::m3u_filter_error::create_m3u_filter_error_result;
use crate::m3u_filter_error::info_err;
use crate::m3u_filter_error::{str_to_io_error, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::create_tuliprox_error_result;
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{str_to_io_error, TuliProxError, TuliProxErrorKind};
use crate::model::{ProxyType, ProxyUserCredentials, UserConnectionPermission};
use crate::model::TargetType;
use crate::model::{Config, ConfigInput, ConfigTarget};
@@ -80,15 +80,15 @@ impl TryFrom<XtreamCluster> for XtreamApiStreamContext {
}
impl FromStr for XtreamApiStreamContext {
type Err = M3uFilterError;
type Err = TuliProxError;
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
fn from_str(s: &str) -> Result<Self, TuliProxError> {
match s.to_lowercase().as_str() {
Self::LIVE => Ok(Self::Live),
Self::MOVIE => Ok(Self::Movie),
Self::SERIES => Ok(Self::Series),
Self::TIMESHIFT => Ok(Self::Timeshift),
_ => create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown CounterModifier: {}", s)
_ => create_tuliprox_error_result!(TuliProxErrorKind::Info, "Unknown CounterModifier: {}", s)
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ use crate::api::api_utils::get_username_from_auth_header;
use crate::model::WebAuthConfig;
use crate::api::model::app_state::AppState;
use crate::auth::auth_bearer::AuthBearer;
use crate::m3u_filter_error::to_io_error;
use crate::tuliprox_error::to_io_error;
const ROLE_ADMIN: &str = "ADMIN";
const ROLE_USER: &str = "USER";
+1 -1
View File
@@ -1,6 +1,6 @@
use rand::{Rng};
use rand::distr::Alphanumeric;
use crate::m3u_filter_error::str_to_io_error;
use crate::tuliprox_error::str_to_io_error;
fn generate_salt(length: usize) -> String {
let salt: String = rand::rng()
+22 -22
View File
@@ -6,11 +6,11 @@ use log::{debug, error, log_enabled, trace, Level};
use pest::iterators::Pair;
use pest::Parser;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::ItemField;
use crate::model::{PlaylistItem, PlaylistItemType};
use crate::tools::directed_graph::DirectedGraph;
use crate::m3u_filter_error::{create_m3u_filter_error_result, info_err};
use crate::tuliprox_error::{create_tuliprox_error_result, info_err};
use crate::utils::CONSTANTS;
use crate::utils::exit;
@@ -234,7 +234,7 @@ impl std::fmt::Display for Filter {
}
}
fn get_parser_item_field(expr: &Pair<Rule>) -> Result<ItemField, M3uFilterError> {
fn get_parser_item_field(expr: &Pair<Rule>) -> Result<ItemField, TuliProxError> {
if expr.as_rule() == Rule::field {
let field_text = expr.as_str();
for item in all::<ItemField>() {
@@ -243,13 +243,13 @@ fn get_parser_item_field(expr: &Pair<Rule>) -> Result<ItemField, M3uFilterError>
}
}
}
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "unknown field: {}", expr.as_str())
create_tuliprox_error_result!(TuliProxErrorKind::Info, "unknown field: {}", expr.as_str())
}
fn get_parser_regexp(
expr: &Pair<Rule>,
templates: &Vec<PatternTemplate>,
) -> Result<RegexWithCaptures, M3uFilterError> {
) -> Result<RegexWithCaptures, TuliProxError> {
if expr.as_rule() == Rule::regexp {
let mut parsed_text = String::from(expr.as_str());
parsed_text.pop();
@@ -257,7 +257,7 @@ fn get_parser_regexp(
let regstr = apply_templates_to_pattern(&parsed_text, templates);
let re = regex::Regex::new(regstr.as_str());
if re.is_err() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "cant parse regex: {}", regstr);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "cant parse regex: {}", regstr);
}
let regexp = re.unwrap();
let captures = regexp
@@ -275,13 +275,13 @@ fn get_parser_regexp(
captures,
});
}
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "unknown field: {}", expr.as_str())
create_tuliprox_error_result!(TuliProxErrorKind::Info, "unknown field: {}", expr.as_str())
}
fn get_parser_field_comparison(
expr: Pair<Rule>,
templates: &Vec<PatternTemplate>,
) -> Result<Filter, M3uFilterError> {
) -> Result<Filter, TuliProxError> {
let mut expr_inner = expr.into_inner();
match get_parser_item_field(&expr_inner.next().unwrap()) {
Ok(field) => match get_parser_regexp(&expr_inner.next().unwrap(), templates) {
@@ -311,11 +311,11 @@ fn get_filter_item_type(text_item_type: &str) -> Option<PlaylistItemType> {
}
}
fn get_parser_type_comparison(expr: Pair<Rule>) -> Result<Filter, M3uFilterError> {
fn get_parser_type_comparison(expr: Pair<Rule>) -> Result<Filter, TuliProxError> {
let expr_inner = expr.into_inner();
let text_item_type = expr_inner.as_str();
let item_type = get_filter_item_type(text_item_type);
item_type.map_or_else(|| create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "cant parse item type: {text_item_type}"), |itype| Ok(Filter::TypeComparison(ItemField::Type, itype)))
item_type.map_or_else(|| create_tuliprox_error_result!(TuliProxErrorKind::Info, "cant parse item type: {text_item_type}"), |itype| Ok(Filter::TypeComparison(ItemField::Type, itype)))
}
macro_rules! handle_expr {
@@ -396,12 +396,12 @@ fn get_parser_expression(
stmts.pop().unwrap()
}
fn get_parser_binary_op(expr: &Pair<Rule>) -> Result<BinaryOperator, M3uFilterError> {
fn get_parser_binary_op(expr: &Pair<Rule>) -> Result<BinaryOperator, TuliProxError> {
match expr.as_rule() {
Rule::and => Ok(BinaryOperator::And),
Rule::or => Ok(BinaryOperator::Or),
_ => create_m3u_filter_error_result!(
M3uFilterErrorKind::Info,
_ => create_tuliprox_error_result!(
TuliProxErrorKind::Info,
"Unknown binary operator {}",
expr.as_str()
),
@@ -411,7 +411,7 @@ fn get_parser_binary_op(expr: &Pair<Rule>) -> Result<BinaryOperator, M3uFilterEr
pub fn get_filter(
filter_text: &str,
templates: Option<&Vec<PatternTemplate>>,
) -> Result<Filter, M3uFilterError> {
) -> Result<Filter, TuliProxError> {
let empty_list = Vec::with_capacity(0);
let template_list: &Vec<PatternTemplate> = templates.unwrap_or(&empty_list);
let source = apply_templates_to_pattern(filter_text, template_list);
@@ -471,8 +471,8 @@ pub fn get_filter(
result.map_or_else(
|| {
create_m3u_filter_error_result!(
M3uFilterErrorKind::Info,
create_tuliprox_error_result!(
TuliProxErrorKind::Info,
"Unable to parse filter: {}",
&filter_text
)
@@ -480,13 +480,13 @@ pub fn get_filter(
Ok,
)
}
Err(err) => create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "{}", err),
Err(err) => create_tuliprox_error_result!(TuliProxErrorKind::Info, "{}", err),
}
}
fn build_dependency_graph(
templates: &Vec<PatternTemplate>,
) -> Result<DirectedGraph<String>, M3uFilterError> {
) -> Result<DirectedGraph<String>, TuliProxError> {
let mut graph = DirectedGraph::<String>::new();
for template in templates {
graph.add_node(&template.name);
@@ -508,8 +508,8 @@ fn build_dependency_graph(
);
}
if !cycles.is_empty() {
return create_m3u_filter_error_result!(
M3uFilterErrorKind::Info,
return create_tuliprox_error_result!(
TuliProxErrorKind::Info,
"Cyclic dependencies in templates detected!"
);
}
@@ -518,7 +518,7 @@ fn build_dependency_graph(
pub fn prepare_templates(
templates: &Vec<PatternTemplate>,
) -> Result<Vec<PatternTemplate>, M3uFilterError> {
) -> Result<Vec<PatternTemplate>, TuliProxError> {
let graph = build_dependency_graph(templates)?;
let mut template_values = HashMap::<String, String>::new();
let mut template_map: HashMap<String, PatternTemplate> = templates
@@ -535,7 +535,7 @@ pub fn prepare_templates(
if let Some(depends_on) = dependencies.get(&template_name) {
let mut templ_value = template_values.get(&template_name).unwrap().to_string();
for dep_templ_name in depends_on {
let dep_value = template_values.get(dep_templ_name).ok_or_else(|| M3uFilterError::new(M3uFilterErrorKind::Info, format!("Failed to load template {dep_templ_name}")))?;
let dep_value = template_values.get(dep_templ_name).ok_or_else(|| TuliProxError::new(TuliProxErrorKind::Info, format!("Failed to load template {dep_templ_name}")))?;
templ_value =
templ_value.replace(format!("!{dep_templ_name}!").as_str(), dep_value);
}
+23 -23
View File
@@ -9,7 +9,7 @@ macro_rules! get_errors_notify_message {
} else {
let text = $errors
.iter()
.filter(|&err| err.kind == M3uFilterErrorKind::Notify)
.filter(|&err| err.kind == TuliProxErrorKind::Notify)
.map(|err| err.message.as_str())
.collect::<Vec<&str>>()
.join("\r\n");
@@ -27,7 +27,7 @@ pub use get_errors_notify_message;
#[macro_export]
macro_rules! notify_err {
($text:expr) => {
M3uFilterError::new(M3uFilterErrorKind::Notify, $text)
TuliProxError::new(TuliProxErrorKind::Notify, $text)
};
}
@@ -36,30 +36,30 @@ pub use notify_err;
#[macro_export]
macro_rules! info_err {
($text:expr) => {
M3uFilterError::new(M3uFilterErrorKind::Info, $text)
TuliProxError::new(TuliProxErrorKind::Info, $text)
};
}
pub use info_err;
#[macro_export]
macro_rules! create_m3u_filter_error {
macro_rules! create_tuliprox_error {
($kind: expr, $($arg:tt)*) => {
M3uFilterError::new($kind, format!($($arg)*))
TuliProxError::new($kind, format!($($arg)*))
}
}
pub use create_m3u_filter_error;
pub use create_tuliprox_error;
#[macro_export]
macro_rules! create_m3u_filter_error_result {
macro_rules! create_tuliprox_error_result {
($kind: expr, $($arg:tt)*) => {
Err(M3uFilterError::new($kind, format!($($arg)*)))
Err(TuliProxError::new($kind, format!($($arg)*)))
}
}
pub use create_m3u_filter_error_result;
pub use create_tuliprox_error_result;
#[macro_export]
macro_rules! handle_m3u_filter_error_result_list {
macro_rules! handle_tuliprox_error_result_list {
($kind:expr, $result: expr) => {
let errors = $result
.filter_map(|result| {
@@ -71,51 +71,51 @@ macro_rules! handle_m3u_filter_error_result_list {
})
.collect::<Vec<String>>();
if !&errors.is_empty() {
return Err(M3uFilterError::new($kind, errors.join("\n")));
return Err(TuliProxError::new($kind, errors.join("\n")));
}
}
}
pub use handle_m3u_filter_error_result_list;
pub use handle_tuliprox_error_result_list;
#[macro_export]
macro_rules! handle_m3u_filter_error_result {
macro_rules! handle_tuliprox_error_result {
($kind:expr, $result: expr) => {
if let Err(err) = $result {
return Err(M3uFilterError::new($kind, err.to_string()));
return Err(TuliProxError::new($kind, err.to_string()));
}
}
}
pub use handle_m3u_filter_error_result;
pub use handle_tuliprox_error_result;
#[derive(Debug, PartialEq, Eq)]
pub enum M3uFilterErrorKind {
pub enum TuliProxErrorKind {
// do not send with messaging
Info,
Notify, // send with messaging
}
#[derive(Debug)]
pub struct M3uFilterError {
pub kind: M3uFilterErrorKind,
pub struct TuliProxError {
pub kind: TuliProxErrorKind,
pub message: String,
}
impl M3uFilterError {
pub const fn new(kind: M3uFilterErrorKind, message: String) -> Self {
impl TuliProxError {
pub const fn new(kind: TuliProxErrorKind, message: String) -> Self {
Self { kind, message }
}
}
impl Display for M3uFilterError {
impl Display for TuliProxError {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "M3uFilter error: {}", self.message)
write!(f, "TuliProx error: {}", self.message)
}
}
impl Error for M3uFilterError {}
impl Error for TuliProxError {}
pub fn to_io_error<E>(err: E) -> std::io::Error
where
+2 -2
View File
@@ -32,7 +32,7 @@ const LOG_ERROR_LEVEL_MOD: &[&str] = &[
#[derive(Parser)]
#[command(name = "m3u-filter")]
#[command(name = "tuliprox")]
#[command(author = "euzu <euzu@proton.me>")]
#[command(version)]
#[command(about = "Extended M3U playlist filter", long_about = None)]
@@ -103,7 +103,7 @@ fn main() {
let config_path: String = args.config_path.unwrap_or_else(file_utils::get_default_config_path);
let config_file: String = args.config_file.unwrap_or_else(|| file_utils::get_default_config_file_path(&config_path));
let env_log_level = std::env::var("M3U_FILTER_LOG");
let env_log_level = std::env::var("TULIPROX_LOG");
init_logger(args.log_level.as_ref(), env_log_level.ok(), config_file.as_str());
if args.healthcheck {
+13 -13
View File
@@ -1,5 +1,5 @@
use crate::api::model::app_state::AppState;
use crate::m3u_filter_error::{create_m3u_filter_error_result, info_err, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliProxError, TuliProxErrorKind};
use crate::model::{Config, ClusterFlags};
use crate::repository::user_repository::{backup_api_user_db_file, get_api_user_db_path, load_api_user, merge_api_user};
use crate::utils::default_as_true;
@@ -75,9 +75,9 @@ impl Display for ProxyType {
}
impl FromStr for ProxyType {
type Err = M3uFilterError;
type Err = TuliProxError;
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
fn from_str(s: &str) -> Result<Self, TuliProxError> {
if s == Self::REDIRECT {
return Ok(Self::Redirect);
}
@@ -95,7 +95,7 @@ impl FromStr for ProxyType {
}
}
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown ProxyType: {}", s)
create_tuliprox_error_result!(TuliProxErrorKind::Info, "Unknown ProxyType: {}", s)
}
}
@@ -170,16 +170,16 @@ impl Display for ProxyUserStatus {
}
impl FromStr for ProxyUserStatus {
type Err = M3uFilterError;
type Err = TuliProxError;
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
fn from_str(s: &str) -> Result<Self, TuliProxError> {
match s {
Self::EXPIRED => Ok(Self::Expired),
Self::BANNED => Ok(Self::Banned),
Self::TRIAL => Ok(Self::Trial),
Self::DISABLED => Ok(Self::Disabled),
Self::PENDING => Ok(Self::Pending),
_ => create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown ProxyType: {}", s)
_ => create_tuliprox_error_result!(TuliProxErrorKind::Info, "Unknown ProxyType: {}", s)
}
}
}
@@ -238,12 +238,12 @@ impl ProxyUserCredentials {
}
}
pub fn validate(&self) -> Result<(), M3uFilterError> {
pub fn validate(&self) -> Result<(), TuliProxError> {
if self.username.is_empty() {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Username required".to_string()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, "Username required".to_string()));
}
if self.password.is_empty() {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Password required".to_string()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, "Password required".to_string()));
}
Ok(())
}
@@ -323,7 +323,7 @@ pub struct ApiProxyServerInfo {
impl ApiProxyServerInfo {
pub fn prepare(&mut self ) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self ) -> Result<(), TuliProxError> {
self.name = self.name.trim().to_string();
if self.name.is_empty() {
return Err(info_err!("Server info name is empty ".to_string()));
@@ -352,7 +352,7 @@ impl ApiProxyServerInfo {
self.timezone = "UTC".to_string();
}
if self.message.is_empty() {
self.message = "Welcome to m3u-filter".to_string();
self.message = "Welcome to tuliprox".to_string();
}
if let Some(path) = &self.path {
if path.trim().is_empty() {
@@ -505,7 +505,7 @@ impl ApiProxyConfig {
}
}
pub fn prepare(&mut self, cfg: &Config) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, cfg: &Config) -> Result<(), TuliProxError> {
let mut errors = Vec::new();
if self.server.is_empty() {
errors.push("No server info defined".to_string());
+33 -33
View File
@@ -11,14 +11,14 @@ use path_clean::PathClean;
use rand::Rng;
use crate::foundation::filter::{prepare_templates, PatternTemplate};
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::{ApiProxyConfig, ApiProxyServerInfo, ProxyUserCredentials, Mappings};
use crate::utils::{default_as_default, default_connect_timeout_secs, default_grace_period_millis, default_grace_period_timeout_secs};
use crate::utils::file_lock_manager::FileLockManager;
use crate::utils::file_utils;
use crate::utils::{parse_size_base_2, parse_to_kbps};
use crate::utils::exit;
use crate::m3u_filter_error::{ create_m3u_filter_error_result};
use crate::tuliprox_error::{ create_tuliprox_error_result};
use crate::model::{ConfigInput, ConfigInputOptions, IpCheckConfig, ProxyConfig, ConfigSource, ConfigTarget, HdHomeRunConfig, LogConfig, MessagingConfig, ProcessTargets, TargetOutput, VideoConfig, WebUiConfig};
const STREAM_QUEUE_SIZE: usize = 1024; // mpsc channel holding messages. with 8192byte chunks and 2Mbit/s approx 8MB
@@ -244,12 +244,12 @@ pub struct StreamConfig {
}
impl StreamConfig {
fn prepare(&mut self) -> Result<(), M3uFilterError> {
fn prepare(&mut self) -> Result<(), TuliProxError> {
if let Some(buffer) = self.buffer.as_mut() {
buffer.prepare();
}
if let Some(throttle) = &self.throttle {
self.throttle_kbps = parse_to_kbps(throttle).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err))?;
self.throttle_kbps = parse_to_kbps(throttle).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err))?;
}
if self.grace_period_millis > 0 {
@@ -257,7 +257,7 @@ impl StreamConfig {
let triple_ms = self.grace_period_millis * 3;
self.grace_period_timeout_secs = std::cmp::max(1, triple_ms.div_ceil(1000));
} else if self.grace_period_millis / 1000 > self.grace_period_timeout_secs {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, format!("Grace time period timeout {} sec should be more than grace time period {} ms", self.grace_period_timeout_secs, self.grace_period_millis)));
return Err(TuliProxError::new(TuliProxErrorKind::Info, format!("Grace time period timeout {} sec should be more than grace time period {} ms", self.grace_period_timeout_secs, self.grace_period_millis)));
}
}
@@ -274,12 +274,12 @@ pub struct RateLimitConfig {
}
impl RateLimitConfig {
fn prepare(&self) -> Result<(), M3uFilterError> {
fn prepare(&self) -> Result<(), TuliProxError> {
if self.period_millis == 0 {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Rate limiter period can't be 0".to_string()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, "Rate limiter period can't be 0".to_string()));
}
if self.burst_size == 0 {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Rate limiter bust can't be 0".to_string()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, "Rate limiter bust can't be 0".to_string()));
}
Ok(())
}
@@ -300,7 +300,7 @@ pub struct ReverseProxyConfig {
}
impl ReverseProxyConfig {
fn prepare(&mut self, working_dir: &str) -> Result<(), M3uFilterError> {
fn prepare(&mut self, working_dir: &str) -> Result<(), TuliProxError> {
if let Some(stream) = self.stream.as_mut() {
stream.prepare()?;
}
@@ -398,26 +398,26 @@ pub struct Config {
}
impl Config {
pub async fn set_api_proxy(&mut self, api_proxy: Option<ApiProxyConfig>) -> Result<(), M3uFilterError> {
pub async fn set_api_proxy(&mut self, api_proxy: Option<ApiProxyConfig>) -> Result<(), TuliProxError> {
self.t_api_proxy = Arc::new(RwLock::new(api_proxy));
self.check_target_user().await
}
async fn check_username(&self, output_username: Option<&str>, target_name: &str) -> Result<(), M3uFilterError> {
async fn check_username(&self, output_username: Option<&str>, target_name: &str) -> Result<(), TuliProxError> {
if let Some(username) = output_username {
if let Some((_, config_target)) = self.get_target_for_username(username).await {
if config_target.name != target_name {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "User:{username} does not belong to target: {}", target_name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "User:{username} does not belong to target: {}", target_name);
}
} else {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "User: {username} does not exist");
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "User: {username} does not exist");
}
Ok(())
} else {
Ok(())
}
}
async fn check_target_user(&mut self) -> Result<(), M3uFilterError> {
async fn check_target_user(&mut self) -> Result<(), TuliProxError> {
let check_homerun = self.hdhomerun.as_ref().is_some_and(|h| h.enabled);
for source in &self.sources {
for target in &source.targets {
@@ -566,26 +566,26 @@ impl Config {
}
}
fn check_unique_input_names(&mut self) -> Result<(), M3uFilterError> {
fn check_unique_input_names(&mut self) -> Result<(), TuliProxError> {
let mut seen_names = HashSet::new();
for source in &mut self.sources {
for input in &source.inputs {
let input_name = input.name.trim().to_string();
if input_name.is_empty() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "input name required");
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "input name required");
}
if seen_names.contains(input_name.as_str()) {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "input names should be unique: {}", input_name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "input names should be unique: {}", input_name);
}
seen_names.insert(input_name);
if let Some(aliases) = &input.aliases {
for alias in aliases {
let input_name = alias.name.trim().to_string();
if input_name.is_empty() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "input name required");
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "input name required");
}
if seen_names.contains(input_name.as_str()) {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "input names should be unique: {}", input_name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "input names should be unique: {}", input_name);
}
seen_names.insert(input_name);
}
@@ -595,7 +595,7 @@ impl Config {
Ok(())
}
fn check_unique_target_names(&mut self) -> Result<HashSet<String>, M3uFilterError> {
fn check_unique_target_names(&mut self) -> Result<HashSet<String>, TuliProxError> {
let mut seen_names = HashSet::new();
let default_target_name = default_as_default();
for source in &self.sources {
@@ -603,11 +603,11 @@ impl Config {
// check target name is unique
let target_name = target.name.trim().to_string();
if target_name.is_empty() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "target name required");
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "target name required");
}
if !default_target_name.eq_ignore_ascii_case(target_name.as_str()) {
if seen_names.contains(target_name.as_str()) {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "target names should be unique: {}", target_name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "target names should be unique: {}", target_name);
}
seen_names.insert(target_name);
}
@@ -616,13 +616,13 @@ impl Config {
Ok(seen_names)
}
fn check_scheduled_targets(&mut self, target_names: &HashSet<String>) -> Result<(), M3uFilterError> {
fn check_scheduled_targets(&mut self, target_names: &HashSet<String>) -> Result<(), TuliProxError> {
if let Some(schedules) = &self.schedules {
for schedule in schedules {
if let Some(targets) = &schedule.targets {
for target_name in targets {
if !target_names.contains(target_name) {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown target name in scheduler: {}", target_name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "Unknown target name in scheduler: {}", target_name);
}
}
}
@@ -634,12 +634,12 @@ impl Config {
/**
* if `include_computed` set to true for `app_state`
*/
pub fn prepare(&mut self, include_computed: bool) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, include_computed: bool) -> Result<(), TuliProxError> {
let work_dir = &self.working_dir;
self.working_dir = file_utils::get_working_path(work_dir);
if include_computed {
self.t_access_token_secret = generate_secret();
self.t_encrypt_secret = <&[u8] as TryInto<[u8; 16]>>::try_into(&generate_secret()[0..16]).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))?;
self.t_encrypt_secret = <&[u8] as TryInto<[u8; 16]>>::try_into(&generate_secret()[0..16]).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))?;
self.prepare_custom_stream_response();
}
self.prepare_directories();
@@ -678,7 +678,7 @@ impl Config {
set_directory(&mut self.user_config_dir, "user_config", &self.working_dir);
}
fn prepare_hdhomerun(&mut self) -> Result<(), M3uFilterError> {
fn prepare_hdhomerun(&mut self) -> Result<(), TuliProxError> {
if let Some(hdhomerun) = self.hdhomerun.as_mut() {
if hdhomerun.enabled {
hdhomerun.prepare(self.api.port)?;
@@ -687,7 +687,7 @@ impl Config {
Ok(())
}
fn prepare_sources(&mut self, include_computed: bool) -> Result<(), M3uFilterError> {
fn prepare_sources(&mut self, include_computed: bool) -> Result<(), TuliProxError> {
// prepare sources and set id's
let mut source_index: u16 = 1;
let mut target_index: u16 = 1;
@@ -706,7 +706,7 @@ impl Config {
Ok(())
}
fn prepare_templates(&mut self) -> Result<(), M3uFilterError> {
fn prepare_templates(&mut self) -> Result<(), TuliProxError> {
if let Some(templates) = &mut self.templates {
match prepare_templates(templates) {
Ok(tmplts) => {
@@ -720,14 +720,14 @@ impl Config {
Ok(())
}
fn prepare_web(&mut self) -> Result<(), M3uFilterError> {
fn prepare_web(&mut self) -> Result<(), TuliProxError> {
if let Some(web_ui_config) = self.web_ui.as_mut() {
web_ui_config.prepare(&self.t_config_path)?;
}
Ok(())
}
fn prepare_video_config(&mut self) -> Result<(), M3uFilterError> {
fn prepare_video_config(&mut self) -> Result<(), TuliProxError> {
match &mut self.video {
None => {
self.video = Some(VideoConfig {
@@ -793,7 +793,7 @@ impl Config {
/// * `target_args` the program parameters given with `-target` parameter.
/// * `sources` configured sources in config file
///
pub fn validate_targets(target_args: Option<&Vec<String>>, sources: &Vec<ConfigSource>) -> Result<ProcessTargets, M3uFilterError> {
pub fn validate_targets(target_args: Option<&Vec<String>>, sources: &Vec<ConfigSource>) -> Result<ProcessTargets, TuliProxError> {
let mut enabled = true;
let mut inputs: Vec<u16> = vec![];
let mut targets: Vec<u16> = vec![];
@@ -820,7 +820,7 @@ pub fn validate_targets(target_args: Option<&Vec<String>>, sources: &Vec<ConfigS
let missing_targets: Vec<String> = check_targets.iter().filter(|&(_, v)| *v == 0).map(|(k, _)| k.to_string()).collect();
if !missing_targets.is_empty() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "No target found for {}", missing_targets.join(", "));
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "No target found for {}", missing_targets.join(", "));
}
// let processing_targets: Vec<String> = check_targets.iter().filter(|&(_, v)| *v != 0).map(|(k, _)| k.to_string()).collect();
// info!("Processing targets {}", processing_targets.join(", "));
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{create_m3u_filter_error_result, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error_result, TuliProxError, TuliProxErrorKind};
use regex::Regex;
use std::borrow::BorrowMut;
use std::collections::HashMap;
@@ -35,7 +35,7 @@ impl VideoConfig {
/// # Panics
///
/// Will panic if default `RegEx` gets invalid
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
if self.extensions.is_empty() {
self.extensions = ["mkv", "avi", "mp4", "mpeg", "divx", "mov"]
.iter()
@@ -57,7 +57,7 @@ impl VideoConfig {
downl.t_re_episode_pattern = Some(pattern);
}
Err(err) => {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "cant parse regex: {episode_pattern} {err}");
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "cant parse regex: {episode_pattern} {err}");
}
}
}
+6 -6
View File
@@ -1,6 +1,6 @@
use log::warn;
use regex::Regex;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind, create_m3u_filter_error_result};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind, create_tuliprox_error_result};
use crate::utils::CONSTANTS;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -57,7 +57,7 @@ impl EpgSmartMatchConfig {
/// let config = EpgSmartMatchConfig::new().unwrap();
/// assert!(config.enabled);
/// ```
pub fn new() -> Result<Self, M3uFilterError> {
pub fn new() -> Result<Self, TuliProxError> {
let mut this = Self { enabled: true, ..Self::default() };
this.prepare()?;
Ok(this)
@@ -71,8 +71,8 @@ impl EpgSmartMatchConfig {
///
/// # Returns
///
/// `Ok(())` if preparation succeeds, or an `M3uFilterError` if regex compilation fails.
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
/// `Ok(())` if preparation succeeds, or an `TuliProxError` if regex compilation fails.
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
if !self.enabled {
return Ok(());
}
@@ -99,7 +99,7 @@ impl EpgSmartMatchConfig {
Some(regstr) => {
let re = regex::Regex::new(regstr.as_str());
if re.is_err() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "cant parse regex: {}", regstr);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "cant parse regex: {}", regstr);
}
Some(re.unwrap())
}
@@ -148,7 +148,7 @@ pub struct EpgConfig {
}
impl EpgConfig {
pub fn prepare(&mut self, include_computed: bool) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, include_computed: bool) -> Result<(), TuliProxError> {
if include_computed {
self.t_urls = self.url.as_ref().map_or_else(Vec::new, |epg_url| {
match epg_url {
+13 -13
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{create_m3u_filter_error_result, handle_m3u_filter_error_result_list, info_err, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliProxError, TuliProxErrorKind};
use crate::model::EpgConfig;
use crate::utils::config_reader::csv_read_inputs;
use crate::utils::default_as_true;
@@ -82,9 +82,9 @@ impl Display for InputType {
}
impl FromStr for InputType {
type Err = M3uFilterError;
type Err = TuliProxError;
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
fn from_str(s: &str) -> Result<Self, TuliProxError> {
if s.eq(Self::M3U) {
Ok(Self::M3u)
} else if s.eq(Self::XTREAM) {
@@ -94,7 +94,7 @@ impl FromStr for InputType {
} else if s.eq(Self::XTREAM_BATCH) {
Ok(Self::XtreamBatch)
} else {
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown InputType: {}", s)
create_tuliprox_error_result!(TuliProxErrorKind::Info, "Unknown InputType: {}", s)
}
}
}
@@ -131,15 +131,15 @@ impl Display for InputFetchMethod {
}
impl FromStr for InputFetchMethod {
type Err = M3uFilterError;
type Err = TuliProxError;
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
fn from_str(s: &str) -> Result<Self, TuliProxError> {
if s.eq(Self::GET_METHOD) {
Ok(Self::GET)
} else if s.eq(Self::POST_METHOD) {
Ok(Self::POST)
} else {
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown Fetch Method: {}", s)
create_tuliprox_error_result!(TuliProxErrorKind::Info, "Unknown Fetch Method: {}", s)
}
}
}
@@ -213,7 +213,7 @@ pub struct ConfigInputAlias {
impl ConfigInputAlias {
pub fn prepare(&mut self, index: u16, input_type: &InputType) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, index: u16, input_type: &InputType) -> Result<(), TuliProxError> {
self.id = index;
self.name = self.name.trim().to_string();
if self.name.is_empty() {
@@ -275,7 +275,7 @@ pub struct ConfigInput {
impl ConfigInput {
#[allow(clippy::cast_possible_truncation)]
pub fn prepare(&mut self, index: u16, include_computed: bool) -> Result<u16, M3uFilterError> {
pub fn prepare(&mut self, index: u16, include_computed: bool) -> Result<u16, TuliProxError> {
self.id = index;
self.check_url()?;
self.prepare_batch()?;
@@ -318,12 +318,12 @@ impl ConfigInput {
if let Some(aliases) = self.aliases.as_mut() {
let input_type = &self.input_type;
handle_m3u_filter_error_result_list!(M3uFilterErrorKind::Info, aliases.iter_mut().enumerate().map(|(idx, i)| i.prepare(index+1+(idx as u16), input_type)));
handle_tuliprox_error_result_list!(TuliProxErrorKind::Info, aliases.iter_mut().enumerate().map(|(idx, i)| i.prepare(index+1+(idx as u16), input_type)));
}
Ok(index + self.aliases.as_ref().map_or(0, std::vec::Vec::len) as u16)
}
fn check_url(&mut self) -> Result<(), M3uFilterError> {
fn check_url(&mut self) -> Result<(), TuliProxError> {
self.url = self.url.trim().to_string();
if self.url.is_empty() {
return Err(info_err!("url for input is mandatory".to_string()));
@@ -331,7 +331,7 @@ impl ConfigInput {
Ok(())
}
fn prepare_batch(&mut self) -> Result<(), M3uFilterError> {
fn prepare_batch(&mut self) -> Result<(), TuliProxError> {
if self.input_type == InputType::M3uBatch || self.input_type == InputType::XtreamBatch {
let input_type = if self.input_type == InputType::M3uBatch {
InputType::M3u
@@ -364,7 +364,7 @@ impl ConfigInput {
}
}
Err(err) => {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, err.to_string()));
}
}
self.input_type = input_type;
+6 -6
View File
@@ -1,5 +1,5 @@
use regex::Regex;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
@@ -31,7 +31,7 @@ pub struct IpCheckConfig {
}
impl IpCheckConfig {
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
if let Some(url) = &self.url {
let url = url.trim();
if url.is_empty() {
@@ -58,22 +58,22 @@ impl IpCheckConfig {
}
if self.url.is_none() && self.url_ipv4.is_none() && self.url_ipv6.is_none() {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "No url provided!".to_owned()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, "No url provided!".to_owned()));
}
// TODO allow or do not allow ?
// if self.url.is_some() && (self.url_ipv4.is_some() || self.url_ipv6.is_some()) {
// return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "url in combination with ipv4 and/or ipv6 url not allowed!".to_owned()));
// return Err(TuliProxError::new(TuliProxErrorKind::Info, "url in combination with ipv4 and/or ipv6 url not allowed!".to_owned()));
// }
if let Some(p4) = &self.pattern_ipv4 {
self.t_pattern_ipv4 = Some(
Regex::new(p4).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, format!("Invalid IPv4 regex: {p4} {err}")))?,
Regex::new(p4).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, format!("Invalid IPv4 regex: {p4} {err}")))?,
);
}
if let Some(p6) = &self.pattern_ipv6 {
self.t_pattern_ipv6 = Some(
Regex::new(p6).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, format!("Invalid IPv6 regex: {p6} {err}")))?,
Regex::new(p6).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, format!("Invalid IPv6 regex: {p6} {err}")))?,
);
}
Ok(())
+5 -5
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
@@ -9,24 +9,24 @@ pub struct ProxyConfig {
}
impl ProxyConfig {
pub(crate) fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub(crate) fn prepare(&mut self) -> Result<(), TuliProxError> {
if self.username.is_some() || self.password.is_some() {
if let (Some(username), Some(password)) = (self.username.as_ref(), self.password.as_ref()) {
let uname = username.trim();
let pwd = password.trim();
if uname.is_empty() || pwd.is_empty() {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Proxy credentials missing".to_string()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, "Proxy credentials missing".to_string()));
}
self.username = Some(uname.to_string());
self.password = Some(pwd.to_string());
} else {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Proxy credentials missing".to_string()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, "Proxy credentials missing".to_string()));
}
}
self.url = self.url.trim().to_string();
if self.url.is_empty() {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Proxy url missing".to_string()));
return Err(TuliProxError::new(TuliProxErrorKind::Info, "Proxy url missing".to_string()));
}
Ok(())
}
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind, create_m3u_filter_error_result};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind, create_tuliprox_error_result};
use crate::model::ItemField;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -12,13 +12,13 @@ pub struct ConfigRename {
}
impl ConfigRename {
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
match regex::Regex::new(&self.pattern) {
Ok(pattern) => {
self.re = Some(pattern);
Ok(())
}
Err(err) => create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "cant parse regex: {} {err}", &self.pattern),
Err(err) => create_tuliprox_error_result!(TuliProxErrorKind::Info, "cant parse regex: {} {err}", &self.pattern),
}
}
}
+8 -8
View File
@@ -1,5 +1,5 @@
use regex::Regex;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind, create_m3u_filter_error, handle_m3u_filter_error_result_list};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind, create_tuliprox_error, handle_tuliprox_error_result_list};
use crate::model::{ItemField};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -10,12 +10,12 @@ pub enum SortOrder {
Desc,
}
fn compile_regex_vec(patterns: Option<&Vec<String>>) -> Result<Option<Vec<Regex>>, M3uFilterError> {
fn compile_regex_vec(patterns: Option<&Vec<String>>) -> Result<Option<Vec<Regex>>, TuliProxError> {
patterns.as_ref()
.map(|seq| {
seq.iter()
.map(|s| Regex::new(s).map_err(|err| {
create_m3u_filter_error!(M3uFilterErrorKind::Info, "cant parse regex: {s} {err}")
create_tuliprox_error!(TuliProxErrorKind::Info, "cant parse regex: {s} {err}")
}))
.collect::<Result<Vec<_>, _>>()
})
@@ -34,7 +34,7 @@ pub struct ConfigSortGroup {
impl ConfigSortGroup {
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
// Compile sequence patterns, if any
self.t_sequence = compile_regex_vec(self.sequence.as_ref())?;
Ok(())
@@ -58,11 +58,11 @@ pub struct ConfigSortChannel {
}
impl ConfigSortChannel {
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
// Compile group_pattern
self.t_re_group_pattern = Some(
Regex::new(&self.group_pattern).map_err(|err| {
create_m3u_filter_error!(M3uFilterErrorKind::Info, "cant parse regex: {} {err}", &self.group_pattern)
create_tuliprox_error!(TuliProxErrorKind::Info, "cant parse regex: {} {err}", &self.group_pattern)
})?
);
@@ -84,12 +84,12 @@ pub struct ConfigSort {
}
impl ConfigSort {
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
if let Some(group) = self.groups.as_mut() {
group.prepare()?;
}
if let Some(channels) = self.channels.as_mut() {
handle_m3u_filter_error_result_list!(M3uFilterErrorKind::Info, channels.iter_mut().map(ConfigSortChannel::prepare));
handle_tuliprox_error_result_list!(TuliProxErrorKind::Info, channels.iter_mut().map(ConfigSortChannel::prepare));
}
Ok(())
}
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind, handle_m3u_filter_error_result_list};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind, handle_tuliprox_error_result_list};
use crate::model::ConfigInput;
use crate::model::config_target::ConfigTarget;
@@ -11,8 +11,8 @@ pub struct ConfigSource {
impl ConfigSource {
#[allow(clippy::cast_possible_truncation)]
pub fn prepare(&mut self, index: u16, include_computed: bool) -> Result<u16, M3uFilterError> {
handle_m3u_filter_error_result_list!(M3uFilterErrorKind::Info, self.inputs.iter_mut().enumerate().map(|(idx, i)| i.prepare(index+(idx as u16), include_computed)));
pub fn prepare(&mut self, index: u16, include_computed: bool) -> Result<u16, TuliProxError> {
handle_tuliprox_error_result_list!(TuliProxErrorKind::Info, self.inputs.iter_mut().enumerate().map(|(idx, i)| i.prepare(index+(idx as u16), include_computed)));
Ok(index + (self.inputs.len() as u16))
}
+15 -15
View File
@@ -1,5 +1,5 @@
use crate::foundation::filter::{get_filter, Filter, MockValueProcessor, PatternTemplate, ValueProvider};
use crate::m3u_filter_error::{create_m3u_filter_error_result, handle_m3u_filter_error_result, handle_m3u_filter_error_result_list, info_err, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error_result, handle_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliProxError, TuliProxErrorKind};
use crate::model::cluster_flags::ClusterFlags;
use crate::model::config_rename::ConfigRename;
use crate::model::config_sort::ConfigSort;
@@ -190,7 +190,7 @@ pub struct ConfigTarget {
}
impl ConfigTarget {
pub fn prepare(&mut self, id: u16, templates: Option<&Vec<PatternTemplate>>) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, id: u16, templates: Option<&Vec<PatternTemplate>>) -> Result<(), TuliProxError> {
self.id = id;
if self.output.is_empty() {
return Err(info_err!(format!("Missing output format for {}", self.name)));
@@ -208,7 +208,7 @@ impl ConfigTarget {
TargetOutput::Xtream(_) => {
xtream_cnt += 1;
if default_as_default().eq_ignore_ascii_case(&self.name) {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "unique target name is required for xtream type output: {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "unique target name is required for xtream type output: {}", self.name);
}
}
TargetOutput::M3u(m3u_output) => {
@@ -219,7 +219,7 @@ impl ConfigTarget {
strm_cnt += 1;
strm_output.directory = strm_output.directory.trim().to_string();
if strm_output.directory.trim().is_empty() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "directory is required for strm type: {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "directory is required for strm type: {}", self.name);
}
if let Some(username) = &mut strm_output.username {
*username = username.trim().to_string();
@@ -234,19 +234,19 @@ impl ConfigTarget {
hdhr_cnt += 1;
hdhomerun_output.username = hdhomerun_output.username.trim().to_string();
if hdhomerun_output.username.is_empty() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Username is required for HdHomeRun type: {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "Username is required for HdHomeRun type: {}", self.name);
}
hdhomerun_output.device = hdhomerun_output.device.trim().to_string();
if hdhomerun_output.device.is_empty() {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Device is required for HdHomeRun type: {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "Device is required for HdHomeRun type: {}", self.name);
}
if let Some(use_output) = hdhomerun_output.use_output.as_ref() {
match &use_output {
TargetType::M3u => { hdhomerun_needs_m3u = true; }
TargetType::Xtream => { hdhomerun_needs_xtream = true; }
_ => return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "HdHomeRun output option `use_output` only accepts `m3u` or `xtream` for target: {}", self.name),
_ => return create_tuliprox_error_result!(TuliProxErrorKind::Info, "HdHomeRun output option `use_output` only accepts `m3u` or `xtream` for target: {}", self.name),
}
}
}
@@ -254,22 +254,22 @@ impl ConfigTarget {
}
if m3u_cnt > 1 || strm_cnt > 1 || xtream_cnt > 1 || hdhr_cnt > 1 {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Multiple output formats with same type : {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "Multiple output formats with same type : {}", self.name);
}
if strm_cnt > 0 && strm_needs_xtream && xtream_cnt == 0 {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "strm output with a username is only permitted when used in combination with xtream output: {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "strm output with a username is only permitted when used in combination with xtream output: {}", self.name);
}
if hdhr_cnt > 0 {
if xtream_cnt == 0 && m3u_cnt == 0 {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "HdHomeRun output is only permitted when used in combination with xtream or m3u output: {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "HdHomeRun output is only permitted when used in combination with xtream or m3u output: {}", self.name);
}
if hdhomerun_needs_m3u && m3u_cnt == 0 {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "HdHomeRun output has `use_output=m3u` but no `m3u` output defined: {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "HdHomeRun output has `use_output=m3u` but no `m3u` output defined: {}", self.name);
}
if hdhomerun_needs_xtream && xtream_cnt == 0 {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "HdHomeRun output has `use_output=xtream` but no `xtream` output defined: {}", self.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "HdHomeRun output has `use_output=xtream` but no `xtream` output defined: {}", self.name);
}
}
@@ -278,7 +278,7 @@ impl ConfigTarget {
match regexps {
Ok(watch_re) => self.t_watch_re = Some(watch_re),
Err(err) => {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Invalid watch regular expression: {}", err);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "Invalid watch regular expression: {}", err);
}
}
}
@@ -288,10 +288,10 @@ impl ConfigTarget {
// debug!("Filter: {}", fltr);
self.t_filter = Some(fltr);
if let Some(renames) = self.rename.as_mut() {
handle_m3u_filter_error_result_list!(M3uFilterErrorKind::Info, renames.iter_mut().map(ConfigRename::prepare));
handle_tuliprox_error_result_list!(TuliProxErrorKind::Info, renames.iter_mut().map(ConfigRename::prepare));
}
if let Some(sort) = self.sort.as_mut() {
handle_m3u_filter_error_result!(M3uFilterErrorKind::Info, sort.prepare());
handle_tuliprox_error_result!(TuliProxErrorKind::Info, sort.prepare());
}
Ok(())
}
+4 -4
View File
@@ -3,7 +3,7 @@ use std::fs::File;
use std::io::BufRead;
use std::path::PathBuf;
use crate::auth::user::UserCredential;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind, create_m3u_filter_error_result};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind, create_tuliprox_error_result};
use crate::utils::file_utils;
use crate::utils::file_utils::file_reader;
@@ -21,7 +21,7 @@ pub struct WebAuthConfig {
}
impl WebAuthConfig {
pub fn prepare(&mut self, config_path: &str) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, config_path: &str) -> Result<(), TuliProxError> {
let userfile_name = self.userfile.as_ref().map_or_else(|| file_utils::get_default_user_file_path(config_path), std::borrow::ToOwned::to_owned);
self.userfile = Some(userfile_name.clone());
@@ -29,7 +29,7 @@ impl WebAuthConfig {
if !file_utils::path_exists(&userfile_path) {
userfile_path = PathBuf::from(config_path).join(&userfile_name);
if !file_utils::path_exists(&userfile_path) {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Could not find userfile {}", &userfile_name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "Could not find userfile {}", &userfile_name);
}
}
@@ -50,7 +50,7 @@ impl WebAuthConfig {
self.t_users = Some(users);
} else {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Could not read userfile {:?}", &userfile_path);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "Could not read userfile {:?}", &userfile_path);
}
Ok(())
}
+3 -3
View File
@@ -1,5 +1,5 @@
use crate::utils::default_as_true;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::WebAuthConfig;
const RESERVED_PATHS: &[&str] = &[
@@ -24,7 +24,7 @@ pub struct WebUiConfig {
}
impl WebUiConfig {
pub fn prepare(&mut self, config_path: &str) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, config_path: &str) -> Result<(), TuliProxError> {
if !self.enabled {
self.auth = None;
}
@@ -36,7 +36,7 @@ impl WebUiConfig {
} else {
let web_path = web_path.trim().trim_start_matches('/').trim_end_matches('/').to_string();
if RESERVED_PATHS.contains(&web_path.to_lowercase().as_str()) {
return Err(M3uFilterError::new(M3uFilterErrorKind::Info, format!("web ui path is a reserved path. Do not use {RESERVED_PATHS:?}")));
return Err(TuliProxError::new(TuliProxErrorKind::Info, format!("web ui path is a reserved path. Do not use {RESERVED_PATHS:?}")));
}
self.path = Some(web_path);
}
+5 -5
View File
@@ -1,7 +1,7 @@
use std::collections::HashSet;
use log::warn;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind, create_m3u_filter_error_result};
fn default_friendly_name() -> String { String::from("M3uFilterTV") }
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind, create_tuliprox_error_result};
fn default_friendly_name() -> String { String::from("TuliProxTV") }
fn default_manufacturer() -> String { String::from("Silicondust") }
fn default_model_name() -> String { String::from("HDTC-2US") }
fn default_firmware_name() -> String { String::from("hdhomeruntc_atsc") }
@@ -72,7 +72,7 @@ pub struct HdHomeRunConfig {
}
impl HdHomeRunConfig {
pub fn prepare(&mut self, api_port: u16) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, api_port: u16) -> Result<(), TuliProxError> {
let mut names = HashSet::new();
let mut ports = HashSet::new();
ports.insert(api_port);
@@ -80,11 +80,11 @@ impl HdHomeRunConfig {
device.prepare(device_num);
if names.contains(&device.name) {
names.insert(&device.name);
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "HdHomeRun duplicate device name {}", device.name);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "HdHomeRun duplicate device name {}", device.name);
}
if device.port > 0 && ports.contains(&device.port) {
ports.insert(device.port);
return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "HdHomeRun duplicate port {}", device.port);
return create_tuliprox_error_result!(TuliProxErrorKind::Info, "HdHomeRun duplicate port {}", device.port);
}
}
let mut current_port = api_port + 1;
+16 -16
View File
@@ -9,8 +9,8 @@ use std::sync::atomic::AtomicU32;
use std::sync::Arc;
use crate::foundation::filter::{apply_templates_to_pattern, get_filter, prepare_templates, Filter, PatternTemplate, RegexWithCaptures, ValueProcessor};
use crate::m3u_filter_error::{create_m3u_filter_error_result, handle_m3u_filter_error_result, info_err};
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error_result, handle_tuliprox_error_result, info_err};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::valid_property;
use crate::model::{ItemField};
use crate::model::{FieldGetAccessor, FieldSetAccessor, PlaylistItem};
@@ -74,9 +74,9 @@ impl Display for CounterModifier {
}
impl FromStr for CounterModifier {
type Err = M3uFilterError;
type Err = TuliProxError;
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
fn from_str(s: &str) -> Result<Self, TuliProxError> {
if s.eq("assign") {
Ok(Self::Assign)
} else if s.eq("suffix") {
@@ -84,7 +84,7 @@ impl FromStr for CounterModifier {
} else if s.eq("prefix") {
Ok(Self::Prefix)
} else {
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown CounterModifier: {}", s)
create_tuliprox_error_result!(TuliProxErrorKind::Info, "Unknown CounterModifier: {}", s)
}
}
}
@@ -146,9 +146,9 @@ impl Display for TransformModifier {
}
impl FromStr for TransformModifier {
type Err = M3uFilterError;
type Err = TuliProxError;
fn from_str(s: &str) -> Result<Self, M3uFilterError> {
fn from_str(s: &str) -> Result<Self, TuliProxError> {
if s.eq("lowercase") {
Ok(Self::Lowercase)
} else if s.eq("uppercase") {
@@ -156,7 +156,7 @@ impl FromStr for TransformModifier {
} else if s.eq("capitalize") {
Ok(Self::Capitalize)
} else {
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown TransformModifier: {}", s)
create_tuliprox_error_result!(TuliProxErrorKind::Info, "Unknown TransformModifier: {}", s)
}
}
}
@@ -171,7 +171,7 @@ pub struct MapperTransform {
}
impl MapperTransform {
pub fn prepare(&mut self, templates: Option<&Vec<PatternTemplate>>) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, templates: Option<&Vec<PatternTemplate>>) -> Result<(), TuliProxError> {
match &self.pattern {
None => self.t_pattern = None,
Some(pattern) => {
@@ -184,7 +184,7 @@ impl MapperTransform {
}
match Regex::new(&new_pattern) {
Ok(pattern) => self.t_pattern = Some(pattern),
Err(err) => return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "cant parse regex: {new_pattern} {err}"),
Err(err) => return create_tuliprox_error_result!(TuliProxErrorKind::Info, "cant parse regex: {new_pattern} {err}"),
}
}
}
@@ -218,7 +218,7 @@ impl Mapper {
/// # Panics
///
/// Will panic if default `RegEx` gets invalid
pub fn prepare(&mut self, templates: Option<&Vec<PatternTemplate>>, tags: Option<&Vec<MappingTag>>) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self, templates: Option<&Vec<PatternTemplate>>, tags: Option<&Vec<MappingTag>>) -> Result<(), TuliProxError> {
for key in self.attributes.keys() {
if !valid_property!(key.as_str(), MAPPER_ATTRIBUTE_FIELDS) {
return Err(info_err!(format!("Invalid mapper attribute field {key}")));
@@ -453,9 +453,9 @@ pub struct Mapping {
impl Mapping {
pub fn prepare(&mut self, templates: Option<&Vec<PatternTemplate>>,
tags: Option<&Vec<MappingTag>>) -> Result<(), M3uFilterError> {
tags: Option<&Vec<MappingTag>>) -> Result<(), TuliProxError> {
for mapper in &mut self.mapper {
handle_m3u_filter_error_result!(M3uFilterErrorKind::Info, mapper.prepare(templates, tags));
handle_tuliprox_error_result!(TuliProxErrorKind::Info, mapper.prepare(templates, tags));
}
if let Some(counter_def_list) = &self.counter {
@@ -493,7 +493,7 @@ pub struct MappingDefinition {
}
impl MappingDefinition {
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
if let Some(templates) = &mut self.templates {
match prepare_templates(templates) {
Ok(tmplts) => {
@@ -505,7 +505,7 @@ impl MappingDefinition {
for mapping in &mut self.mapping {
let template_list = self.templates.as_ref();
let tag_list = self.tags.as_ref();
handle_m3u_filter_error_result!(M3uFilterErrorKind::Info, mapping.prepare(template_list, tag_list));
handle_tuliprox_error_result!(TuliProxErrorKind::Info, mapping.prepare(template_list, tag_list));
}
Ok(())
}
@@ -517,7 +517,7 @@ pub struct Mappings {
}
impl Mappings {
pub fn prepare(&mut self) -> Result<(), M3uFilterError> {
pub fn prepare(&mut self) -> Result<(), TuliProxError> {
self.mappings.prepare()
}
+1 -1
View File
@@ -8,7 +8,7 @@ macro_rules! include_modules {
extern crate pest_derive;
pub mod api;
pub mod auth;
pub mod m3u_filter_error;
pub mod tuliprox_error;
pub mod messaging;
pub mod model;
pub mod processing;
+2 -2
View File
@@ -450,7 +450,7 @@ mod tests {
/// ```
/// parse_normalize().unwrap();
/// ```
fn parse_normalize() -> Result<(), M3uFilterError> {
fn parse_normalize() -> Result<(), TuliProxError> {
let epg_normalize = EpgSmartMatchConfig::new()?;
let normalized = normalize_channel_name("Love Nature", &epg_normalize);
assert_eq!(normalized, "lovenature".to_string());
@@ -500,7 +500,7 @@ mod tests {
assert_eq!("odisea.bg", normalize_channel_name("BG | ODISEA ᵁᴴᴰ ³⁸⁴⁰ᴾ", &epg_smart_cfg));
}
use crate::m3u_filter_error::M3uFilterError;
use crate::tuliprox_error::TuliProxError;
use rphonetic::{Encoder, Metaphone};
#[test]
+8 -8
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{create_m3u_filter_error_result, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error_result, TuliProxError, TuliProxErrorKind};
use crate::model::ConfigInput;
use crate::model::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, PlaylistItemType, XtreamCluster};
use crate::model::{XtreamCategory, XtreamSeriesInfo, XtreamSeriesInfoEpisode, XtreamStream};
@@ -8,20 +8,20 @@ use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
fn map_to_xtream_category(categories: &Value) -> Result<Vec<XtreamCategory>, M3uFilterError> {
fn map_to_xtream_category(categories: &Value) -> Result<Vec<XtreamCategory>, TuliProxError> {
match serde_json::from_value::<Vec<XtreamCategory>>(categories.to_owned()) {
Ok(xtream_categories) => Ok(xtream_categories),
Err(err) => {
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to process categories {}", &err)
create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed to process categories {}", &err)
}
}
}
fn map_to_xtream_streams(xtream_cluster: XtreamCluster, streams: &Value) -> Result<Vec<XtreamStream>, M3uFilterError> {
fn map_to_xtream_streams(xtream_cluster: XtreamCluster, streams: &Value) -> Result<Vec<XtreamStream>, TuliProxError> {
match serde_json::from_value::<Vec<XtreamStream>>(streams.to_owned()) {
Ok(stream_list) => Ok(stream_list),
Err(err) => {
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to map to xtream streams {:?}: {}", xtream_cluster, &err)
create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed to map to xtream streams {:?}: {}", xtream_cluster, &err)
}
}
}
@@ -36,7 +36,7 @@ fn create_xtream_series_episode_url(url: &str, username: &str, password: &str, e
}
}
pub fn parse_xtream_series_info(info: &Value, group_title: &str, series_name: &str, input: &ConfigInput) -> Result<Option<Vec<(XtreamSeriesInfoEpisode, PlaylistItem)>>, M3uFilterError> {
pub fn parse_xtream_series_info(info: &Value, group_title: &str, series_name: &str, input: &ConfigInput) -> Result<Option<Vec<(XtreamSeriesInfoEpisode, PlaylistItem)>>, TuliProxError> {
let url = input.url.as_str();
let username = input.username.as_ref().map_or("", |v| v);
let password = input.password.as_ref().map_or("", |v| v);
@@ -70,7 +70,7 @@ pub fn parse_xtream_series_info(info: &Value, group_title: &str, series_name: &s
Ok(None)
}
Err(err) => {
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to process series info for {series_name} {err}")
create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed to process series info for {series_name} {err}")
}
}
}
@@ -110,7 +110,7 @@ pub fn create_xtream_url(xtream_cluster: XtreamCluster, url: &str, username: &st
pub fn parse_xtream(input: &ConfigInput,
xtream_cluster: XtreamCluster,
categories: &Value,
streams: &Value) -> Result<Option<Vec<PlaylistGroup>>, M3uFilterError> {
streams: &Value) -> Result<Option<Vec<PlaylistGroup>>, TuliProxError> {
match map_to_xtream_category(categories) {
Ok(xtream_categories) => {
let input_name = Arc::new(input.name.to_string());
+5 -5
View File
@@ -10,7 +10,7 @@ use std::thread;
use tokio::sync::Mutex;
use crate::foundation::filter::{get_field_value, set_field_value, MockValueProcessor, ValueProvider};
use crate::m3u_filter_error::{get_errors_notify_message, notify_err, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{get_errors_notify_message, notify_err, TuliProxError, TuliProxErrorKind};
use crate::messaging::{send_message, MsgKind};
use crate::model::{ConfigTarget, InputType, ItemField, ProcessTargets, ProcessingOrder};
use crate::model::{CounterModifier, Mapping, MappingValueProcessor};
@@ -245,7 +245,7 @@ fn is_target_enabled(target: &ConfigTarget, user_targets: &ProcessTargets) -> bo
(!user_targets.enabled && target.enabled) || (user_targets.enabled && user_targets.has_target(target.id))
}
async fn process_source(client: Arc<reqwest::Client>, cfg: Arc<Config>, source_idx: usize, user_targets: Arc<ProcessTargets>) -> (Vec<InputStats>, Vec<TargetStats>, Vec<M3uFilterError>) {
async fn process_source(client: Arc<reqwest::Client>, cfg: Arc<Config>, source_idx: usize, user_targets: Arc<ProcessTargets>) -> (Vec<InputStats>, Vec<TargetStats>, Vec<TuliProxError>) {
let source = cfg.sources.get(source_idx).unwrap();
let mut errors = vec![];
let mut input_stats = HashMap::<String, InputStats>::new();
@@ -330,14 +330,14 @@ fn create_input_stat(group_count: usize, channel_count: usize, error_count: usiz
}
}
async fn process_sources(client: Arc<reqwest::Client>, config: Arc<Config>, user_targets: Arc<ProcessTargets>) -> (Vec<SourceStats>, Vec<M3uFilterError>) {
async fn process_sources(client: Arc<reqwest::Client>, config: Arc<Config>, user_targets: Arc<ProcessTargets>) -> (Vec<SourceStats>, Vec<TuliProxError>) {
let mut handle_list = vec![];
let thread_num = config.threads;
let process_parallel = thread_num > 1 && config.sources.len() > 1;
if process_parallel && log_enabled!(Level::Debug) {
debug!("Using {thread_num} threads");
}
let errors = Arc::new(Mutex::<Vec<M3uFilterError>>::new(vec![]));
let errors = Arc::new(Mutex::<Vec<TuliProxError>>::new(vec![]));
let stats = Arc::new(Mutex::<Vec<SourceStats>>::new(vec![]));
for (index, _) in config.sources.iter().enumerate() {
// We're using the file lock this way on purpose
@@ -447,7 +447,7 @@ async fn process_playlist_for_target(client: Arc<reqwest::Client>,
target: &ConfigTarget,
cfg: &Config,
stats: &mut HashMap<String, InputStats>,
errors: &mut Vec<M3uFilterError>) -> Result<(), Vec<M3uFilterError>> {
errors: &mut Vec<TuliProxError>) -> Result<(), Vec<TuliProxError>> {
let pipe = get_processing_pipe(target);
debug_if_enabled!("Processing order is {}", &target.processing_order);
+4 -4
View File
@@ -1,5 +1,5 @@
use crate::m3u_filter_error::{info_err, notify_err};
use crate::m3u_filter_error::{str_to_io_error, to_io_error, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{info_err, notify_err};
use crate::tuliprox_error::{str_to_io_error, to_io_error, TuliProxError, TuliProxErrorKind};
use crate::model::{Config, ConfigInput};
use crate::model::{FetchedPlaylist, PlaylistEntry, PlaylistItem, PlaylistItemType, XtreamCluster};
use crate::repository::storage::get_input_storage_path;
@@ -15,7 +15,7 @@ use crate::repository::xtream_repository::xtream_get_record_file_path;
use crate::utils::file_utils::append_or_crate_file;
use crate::utils::xtream;
pub(in crate::processing) async fn playlist_resolve_download_playlist_item(client: Arc<reqwest::Client>, pli: &PlaylistItem, input: &ConfigInput, errors: &mut Vec<M3uFilterError>, resolve_delay: u16, cluster: XtreamCluster) -> Option<String> {
pub(in crate::processing) async fn playlist_resolve_download_playlist_item(client: Arc<reqwest::Client>, pli: &PlaylistItem, input: &ConfigInput, errors: &mut Vec<TuliProxError>, resolve_delay: u16, cluster: XtreamCluster) -> Option<String> {
let mut result = None;
let provider_id = pli.get_provider_id()?;
if let Some(info_url) = xtream::get_xtream_player_api_info_url(input, cluster, provider_id) {
@@ -83,7 +83,7 @@ pub(in crate::processing) fn should_update_info(pli: &mut PlaylistItem, processe
|| *old_timestamp.unwrap() != last_modified.unwrap(), provider_id, last_modified.unwrap_or(0))
}
pub(in crate::processing) async fn read_processed_info_ids<V, F>(cfg: &Config, errors: &mut Vec<M3uFilterError>, fpl: &FetchedPlaylist<'_>,
pub(in crate::processing) async fn read_processed_info_ids<V, F>(cfg: &Config, errors: &mut Vec<TuliProxError>, fpl: &FetchedPlaylist<'_>,
item_type: PlaylistItemType, extract_ts: F) -> HashMap<u32, u64>
where
F: Fn(&V) -> u64,
+6 -6
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::{Config, ConfigTarget, InputType};
use crate::model::{FetchedPlaylist, PlaylistGroup, PlaylistItem, PlaylistItemType, XtreamCluster};
use crate::processing::processor::playlist::ProcessingPipe;
@@ -7,7 +7,7 @@ use crate::processing::processor::xtream::{create_resolve_episode_wal_files, cre
use crate::repository::storage::get_input_storage_path;
use crate::repository::xtream_repository::{xtream_get_info_file_paths, xtream_update_input_info_file, xtream_update_input_series_episodes_record_from_wal_file, xtream_update_input_series_record_from_wal_file};
use crate::repository::IndexedDocumentReader;
use crate::m3u_filter_error::{notify_err, info_err};
use crate::tuliprox_error::{notify_err, info_err};
use crate::processing::processor::{handle_error, handle_error_and_return, create_resolve_options_function_for_xtream_target};
use std::collections::HashMap;
use std::fs::File;
@@ -21,7 +21,7 @@ use crate::utils::file_utils::file_writer;
create_resolve_options_function_for_xtream_target!(series);
async fn read_processed_series_info_ids(cfg: &Config, errors: &mut Vec<M3uFilterError>, fpl: &FetchedPlaylist<'_>) -> HashMap<u32, u64> {
async fn read_processed_series_info_ids(cfg: &Config, errors: &mut Vec<TuliProxError>, fpl: &FetchedPlaylist<'_>) -> HashMap<u32, u64> {
read_processed_info_ids(cfg, errors, fpl, PlaylistItemType::SeriesInfo, |ts: &u64| *ts).await
}
@@ -54,7 +54,7 @@ fn should_update_series_info(pli: &mut PlaylistItem, processed_provider_ids: &Ha
should_update_info(pli, processed_provider_ids, crate::model::XC_TAG_SERIES_INFO_LAST_MODIFIED)
}
async fn playlist_resolve_series_info(client: Arc<reqwest::Client>, cfg: &Config, errors: &mut Vec<M3uFilterError>,
async fn playlist_resolve_series_info(client: Arc<reqwest::Client>, cfg: &Config, errors: &mut Vec<TuliProxError>,
fpl: &mut FetchedPlaylist<'_>, resolve_delay: u16) -> bool {
let mut processed_info_ids = read_processed_series_info_ids(cfg, errors, fpl).await;
// we cant write to the indexed-document directly because of the write lock and time-consuming operation.
@@ -132,7 +132,7 @@ async fn playlist_resolve_series_info(client: Arc<reqwest::Client>, cfg: &Config
async fn process_series_info(
cfg: &Config,
fpl: &mut FetchedPlaylist<'_>,
errors: &mut Vec<M3uFilterError>,
errors: &mut Vec<TuliProxError>,
) -> Vec<PlaylistGroup> {
let mut result: Vec<PlaylistGroup> = vec![];
let input = fpl.input;
@@ -214,7 +214,7 @@ async fn process_series_info(
pub async fn playlist_resolve_series(client: Arc<reqwest::Client>, cfg: &Config, target: &ConfigTarget,
errors: &mut Vec<M3uFilterError>,
errors: &mut Vec<TuliProxError>,
pipe: &ProcessingPipe,
provider_fpl: &mut FetchedPlaylist<'_>,
processed_fpl: &mut FetchedPlaylist<'_>,
+4 -4
View File
@@ -1,9 +1,9 @@
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::{Config, ConfigTarget, InputType};
use crate::model::{FetchedPlaylist, PlaylistItem, PlaylistItemType, XtreamCluster};
use crate::processing::processor::xtream::{create_resolve_info_wal_files, playlist_resolve_download_playlist_item, read_processed_info_ids, should_update_info, write_info_content_to_wal_file};
use crate::repository::xtream_repository::{xtream_update_input_info_file, xtream_update_input_vod_record_from_wal_file, InputVodInfoRecord};
use crate::m3u_filter_error::{notify_err};
use crate::tuliprox_error::{notify_err};
use crate::processing::processor::{handle_error, handle_error_and_return, create_resolve_options_function_for_xtream_target};
use crate::utils::{get_u32_from_serde_value, get_u64_from_serde_value};
use serde_json::{Map, Value};
@@ -17,7 +17,7 @@ use crate::utils::file_utils::file_writer;
create_resolve_options_function_for_xtream_target!(vod);
async fn read_processed_vod_info_ids(cfg: &Config, errors: &mut Vec<M3uFilterError>, fpl: &FetchedPlaylist<'_>) -> HashMap<u32, u64> {
async fn read_processed_vod_info_ids(cfg: &Config, errors: &mut Vec<TuliProxError>, fpl: &FetchedPlaylist<'_>) -> HashMap<u32, u64> {
read_processed_info_ids(cfg, errors, fpl, PlaylistItemType::Video, |record: &InputVodInfoRecord| record.ts).await
}
@@ -60,7 +60,7 @@ fn should_update_vod_info(pli: &mut PlaylistItem, processed_provider_ids: &HashM
should_update_info(pli, processed_provider_ids, crate::model::XC_TAG_VOD_INFO_ADDED)
}
pub async fn playlist_resolve_vod(client: Arc<reqwest::Client>, cfg: &Config, target: &ConfigTarget, errors: &mut Vec<M3uFilterError>, fpl: &mut FetchedPlaylist<'_>) {
pub async fn playlist_resolve_vod(client: Arc<reqwest::Client>, cfg: &Config, target: &ConfigTarget, errors: &mut Vec<TuliProxError>, fpl: &mut FetchedPlaylist<'_>) {
let (resolve_movies, resolve_delay) = get_resolve_vod_options(target, fpl);
if !resolve_movies { return; }
+1 -1
View File
@@ -3,7 +3,7 @@ use std::io::{self, BufReader, Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;
use std::mem::size_of;
use std::path::Path;
use crate::m3u_filter_error::{str_to_io_error, to_io_error};
use crate::tuliprox_error::{str_to_io_error, to_io_error};
use crate::utils::file_utils::{file_reader, file_writer, open_read_write_file, rename_or_copy};
use log::error;
use ruzstd::decoding::StreamingDecoder;
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{notify_err, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{notify_err, TuliProxError, TuliProxErrorKind};
use crate::model::{Config, ConfigTarget, TargetOutput};
use crate::model::Epg;
use crate::repository::m3u_repository::m3u_get_epg_file_path;
@@ -9,7 +9,7 @@ use std::fs::File;
use std::io::{Cursor, Write};
use std::path::Path;
fn epg_write_file(target: &ConfigTarget, epg: &Epg, path: &Path) -> Result<(), M3uFilterError> {
fn epg_write_file(target: &ConfigTarget, epg: &Epg, path: &Path) -> Result<(), TuliProxError> {
let mut writer = Writer::new(Cursor::new(vec![]));
match epg.write_to(&mut writer) {
Ok(()) => {
@@ -35,7 +35,7 @@ fn epg_write_file(target: &ConfigTarget, epg: &Epg, path: &Path) -> Result<(), M
Ok(())
}
pub fn epg_write(target: &ConfigTarget, cfg: &Config, target_path: &Path, epg: Option<&Epg>, output: &TargetOutput) -> Result<(), M3uFilterError> {
pub fn epg_write(target: &ConfigTarget, cfg: &Config, target_path: &Path, epg: Option<&Epg>, output: &TargetOutput) -> Result<(), TuliProxError> {
if let Some(epg_data) = epg {
match output {
TargetOutput::Xtream(_) => {
+1 -1
View File
@@ -9,7 +9,7 @@ use crate::utils::file_utils;
use log::error;
use serde::{Deserialize, Serialize};
use tempfile::NamedTempFile;
use crate::m3u_filter_error::{str_to_io_error, to_io_error};
use crate::tuliprox_error::{str_to_io_error, to_io_error};
use crate::utils::{bincode_deserialize, bincode_serialize};
use crate::utils::file_utils::{create_new_file_for_read_write, file_reader, file_writer, open_read_write_file, open_readonly_file, rename_or_copy};
+6 -6
View File
@@ -1,5 +1,5 @@
use crate::m3u_filter_error::{create_m3u_filter_error_result, info_err};
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error_result, info_err};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::{ApiProxyServerInfo, ProxyUserCredentials};
use crate::model::{ClusterFlags, Config, ConfigTarget, StrmTargetOutput};
use crate::model::{
@@ -404,12 +404,12 @@ fn extract_item_info(pli: &mut PlaylistItem) -> StrmItemInfo {
}
}
async fn prepare_strm_output_directory(path: &Path) -> Result<(), M3uFilterError> {
async fn prepare_strm_output_directory(path: &Path) -> Result<(), TuliProxError> {
// Ensure the directory exists
if let Err(e) = tokio::fs::create_dir_all(path).await {
error!("Failed to create directory {path:?}: {e}");
return create_m3u_filter_error_result!(
M3uFilterErrorKind::Notify,
return create_tuliprox_error_result!(
TuliProxErrorKind::Notify,
"Error creating STRM directory: {e}"
);
}
@@ -575,7 +575,7 @@ pub async fn kodi_write_strm_playlist(
target_output: &StrmTargetOutput,
cfg: &Config,
new_playlist: &mut [PlaylistGroup],
) -> Result<(), M3uFilterError> {
) -> Result<(), TuliProxError> {
if new_playlist.is_empty() {
return Ok(());
}
+4 -4
View File
@@ -1,5 +1,5 @@
use crate::m3u_filter_error::info_err;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::{ProxyType, ProxyUserCredentials};
use crate::model::{Config, ConfigTarget, ConfigTargetOptions, TargetType};
use crate::model::{M3uPlaylistItem, PlaylistItemType, XtreamCluster};
@@ -31,7 +31,7 @@ impl M3uPlaylistIterator {
cfg: &Config,
target: &ConfigTarget,
user: &ProxyUserCredentials,
) -> Result<Self, M3uFilterError> {
) -> Result<Self, TuliProxError> {
let m3u_output = target.get_m3u_output().ok_or_else(|| info_err!(format!("Unexpected failure, missing m3u target output for target {}", target.name)))?;
let target_path = ensure_target_storage_path(cfg, target.name.as_str())?;
let (m3u_path, idx_path) = m3u_get_file_paths(&target_path);
@@ -138,7 +138,7 @@ impl M3uPlaylistM3uTextIterator {
cfg: &Config,
target: &ConfigTarget,
user: &ProxyUserCredentials,
) -> Result<Self, M3uFilterError> {
) -> Result<Self, TuliProxError> {
Ok(Self {
inner: M3uPlaylistIterator::new(cfg, target, user).await?,
started: false,
+5 -5
View File
@@ -1,5 +1,5 @@
use crate::m3u_filter_error::{create_m3u_filter_error,info_err};
use crate::m3u_filter_error::{str_to_io_error, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error,info_err};
use crate::tuliprox_error::{str_to_io_error, TuliProxError, TuliProxErrorKind};
use crate::model::ProxyUserCredentials;
use crate::model::{Config, ConfigTarget, M3uTargetOutput};
use crate::model::{M3uPlaylistItem, PlaylistGroup, PlaylistItem, PlaylistItemType};
@@ -18,7 +18,7 @@ use crate::repository::storage_const;
macro_rules! cant_write_result {
($path:expr, $err:expr) => {
create_m3u_filter_error!(M3uFilterErrorKind::Notify, "failed to write m3u playlist: {} - {}", $path.to_str().unwrap() ,$err)
create_tuliprox_error!(TuliProxErrorKind::Notify, "failed to write m3u playlist: {} - {}", $path.to_str().unwrap() ,$err)
}
}
@@ -53,7 +53,7 @@ fn persist_m3u_playlist_as_text(cfg: &Config, target: &ConfigTarget, target_outp
}
}
pub async fn m3u_write_playlist( cfg: &Config, target: &ConfigTarget, target_output: &M3uTargetOutput, target_path: &Path, new_playlist: &[PlaylistGroup]) -> Result<(), M3uFilterError> {
pub async fn m3u_write_playlist( cfg: &Config, target: &ConfigTarget, target_output: &M3uTargetOutput, target_path: &Path, new_playlist: &[PlaylistGroup]) -> Result<(), TuliProxError> {
if !new_playlist.is_empty() {
let (m3u_path, idx_path) = m3u_get_file_paths(target_path);
let m3u_playlist = new_playlist.iter()
@@ -85,7 +85,7 @@ pub async fn m3u_load_rewrite_playlist(
cfg: &Config,
target: &ConfigTarget,
user: &ProxyUserCredentials,
) -> Result<M3uPlaylistM3uTextIterator, M3uFilterError> {
) -> Result<M3uPlaylistM3uTextIterator, TuliProxError> {
M3uPlaylistM3uTextIterator::new(cfg, target, user).await
}
+3 -3
View File
@@ -1,5 +1,5 @@
use crate::m3u_filter_error::info_err;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::{Config, ConfigTarget, TargetOutput};
use crate::model::{PlaylistGroup, PlaylistItemType};
use crate::model::Epg;
@@ -14,7 +14,7 @@ use crate::utils::request::{is_dash_url, is_hls_url};
use std::path::Path;
pub async fn persist_playlist(playlist: &mut [PlaylistGroup], epg: Option<&Epg>,
target: &ConfigTarget, cfg: &Config) -> Result<(), Vec<M3uFilterError>> {
target: &ConfigTarget, cfg: &Config) -> Result<(), Vec<TuliProxError>> {
let mut errors = vec![];
let target_path = match ensure_target_storage_path(cfg, &target.name) {
Ok(path) => path,
+3 -3
View File
@@ -1,9 +1,9 @@
use std::path::{Path, PathBuf};
use std::fmt::Write;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::{Config};
use crate::model::UUIDType;
use crate::m3u_filter_error::{notify_err};
use crate::tuliprox_error::{notify_err};
use crate::repository::storage_const;
use crate::utils::file_utils;
@@ -54,7 +54,7 @@ pub(in crate::repository) fn get_target_id_mapping_file(target_path: &Path) -> P
target_path.join(PathBuf::from(storage_const::FILE_ID_MAPPING))
}
pub fn ensure_target_storage_path(cfg: &Config, target_name: &str) -> Result<PathBuf, M3uFilterError> {
pub fn ensure_target_storage_path(cfg: &Config, target_name: &str) -> Result<PathBuf, TuliProxError> {
if let Some(path) = get_target_storage_path(cfg, target_name) {
if std::fs::create_dir_all(&path).is_err() {
let msg = format!("Failed to save target data, can't create directory {path:?}");
+4 -4
View File
@@ -1,7 +1,7 @@
use std::collections::HashSet;
use log::error;
use crate::m3u_filter_error::info_err;
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::{ProxyUserCredentials};
use crate::model::{Config, ConfigTarget, TargetType};
use crate::model::{XtreamCluster, XtreamPlaylistItem};
@@ -27,7 +27,7 @@ impl XtreamPlaylistIterator {
target: &ConfigTarget,
category_id: Option<u32>,
user: &ProxyUserCredentials,
) -> Result<Self, M3uFilterError> {
) -> Result<Self, TuliProxError> {
let xtream_output = target.get_xtream_output().ok_or_else(|| info_err!(format!("Unexpected: xtream output required for target {}", target.name)))?;
if let Some(storage_path) = xtream_get_storage_path(config, target.name.as_str()) {
let (xtream_path, idx_path) = xtream_get_file_paths(&storage_path, cluster);
@@ -90,7 +90,7 @@ pub async fn new(
target: &ConfigTarget,
category_id: Option<u32>,
user: &ProxyUserCredentials,
) -> Result<Self, M3uFilterError> {
) -> Result<Self, TuliProxError> {
Ok(Self {
inner: XtreamPlaylistIterator::new(cluster, config, target, category_id, user).await?
})
+13 -13
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{create_m3u_filter_error, create_m3u_filter_error_result, info_err, notify_err, str_to_io_error, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error, create_tuliprox_error_result, info_err, notify_err, str_to_io_error, TuliProxError, TuliProxErrorKind};
use crate::model::{ProxyUserCredentials};
use crate::model::{Config, ConfigInput, ConfigTarget, XtreamTargetOutput};
use crate::model::{PlaylistEntry, PlaylistGroup, PlaylistItem, PlaylistItemType, XtreamCluster, XtreamPlaylistItem};
@@ -31,8 +31,8 @@ use std::sync::Arc;
macro_rules! cant_write_result {
($path:expr, $err:expr) => {
create_m3u_filter_error!(
M3uFilterErrorKind::Notify,
create_tuliprox_error!(
TuliProxErrorKind::Notify,
"failed to write xtream playlist: {} - {}",
$path.to_str().unwrap(),
$err
@@ -60,7 +60,7 @@ fn get_collection_path(path: &Path, collection: &str) -> PathBuf {
path.join(format!("{collection}.json"))
}
fn ensure_xtream_storage_path(cfg: &Config, target_name: &str) -> Result<PathBuf, M3uFilterError> {
fn ensure_xtream_storage_path(cfg: &Config, target_name: &str) -> Result<PathBuf, TuliProxError> {
if let Some(path) = xtream_get_storage_path(cfg, target_name) {
if std::fs::create_dir_all(&path).is_err() {
let msg = format!(
@@ -104,7 +104,7 @@ fn write_playlists_to_file(
cfg: &Config,
storage_path: &Path,
collections: Vec<(XtreamCluster, &[&mut PlaylistItem])>,
) -> Result<(), M3uFilterError> {
) -> Result<(), TuliProxError> {
for (cluster, playlist) in collections {
let (xtream_path, idx_path) = xtream_get_file_paths(storage_path, cluster);
{
@@ -200,7 +200,7 @@ pub async fn xtream_write_playlist(
target: &ConfigTarget,
cfg: &Config,
playlist: &mut [PlaylistGroup],
) -> Result<(), M3uFilterError> {
) -> Result<(), TuliProxError> {
let path = ensure_xtream_storage_path(cfg, target.name.as_str())?;
let mut errors = Vec::new();
let mut cat_live_col = Vec::with_capacity(1_000);
@@ -280,8 +280,8 @@ pub async fn xtream_write_playlist(
}
if !errors.is_empty() {
return create_m3u_filter_error_result!(
M3uFilterErrorKind::Notify,
return create_tuliprox_error_result!(
TuliProxErrorKind::Notify,
"{}",
errors.join("\n")
);
@@ -385,7 +385,7 @@ pub async fn xtream_load_rewrite_playlist(
target: &ConfigTarget,
category_id: Option<u32>,
user: &ProxyUserCredentials,
) -> Result<XtreamPlaylistJsonIterator, M3uFilterError> {
) -> Result<XtreamPlaylistJsonIterator, TuliProxError> {
XtreamPlaylistJsonIterator::new(cluster, config, target, category_id, user).await
}
@@ -728,7 +728,7 @@ pub async fn xtream_update_input_info_file(
input: &ConfigInput,
wal_path: &Path,
cluster: XtreamCluster,
) -> Result<(), M3uFilterError> {
) -> Result<(), TuliProxError> {
match get_input_storage_path(&input.name, &cfg.working_dir).map(|storage_path| xtream_get_info_file_paths(&storage_path, cluster)) {
Ok(Some((info_path, idx_path))) => {
{
@@ -771,7 +771,7 @@ pub async fn xtream_update_input_vod_record_from_wal_file(
cfg: &Config,
input: &ConfigInput,
wal_path: &Path,
) -> Result<(), M3uFilterError> {
) -> Result<(), TuliProxError> {
let record_path = get_input_storage_path(&input.name, &cfg.working_dir).map(|storage_path| xtream_get_record_file_path(&storage_path, PlaylistItemType::Video))
.map_err(|err| notify_err!(format!("Error accessing storage path: {err}")))
.and_then(|opt| opt.ok_or_else(|| notify_err!(format!("Error accessing storage path for input: {}", &input.name))))?;
@@ -811,7 +811,7 @@ pub async fn xtream_update_input_series_record_from_wal_file(
cfg: &Config,
input: &ConfigInput,
wal_path: &Path,
) -> Result<(), M3uFilterError> {
) -> Result<(), TuliProxError> {
let record_path = get_input_storage_path(&input.name, &cfg.working_dir).map(|storage_path| xtream_get_record_file_path(&storage_path, PlaylistItemType::SeriesInfo))
.map_err(|err| notify_err!(format!("Error accessing storage path: {err}")))
.and_then(|opt| opt.ok_or_else(|| notify_err!(format!("Error accessing storage path for input: {}", &input.name))))?;
@@ -845,7 +845,7 @@ pub async fn xtream_update_input_series_episodes_record_from_wal_file(
cfg: &Config,
input: &ConfigInput,
wal_path: &Path,
) -> Result<(), M3uFilterError> {
) -> Result<(), TuliProxError> {
let record_path = get_input_storage_path(&input.name, &cfg.working_dir).map(|storage_path| xtream_get_record_file_path(&storage_path, PlaylistItemType::Series))
.map_err(|err| notify_err!(format!("Error accessing storage path: {err}")))
.and_then(|opt| opt.ok_or_else(|| notify_err!(format!("Error accessing storage path for input: {}", &input.name))))?;
+1 -1
View File
@@ -1,5 +1,5 @@
use std::io;
use crate::m3u_filter_error::to_io_error;
use crate::tuliprox_error::to_io_error;
#[inline]
pub fn bincode_serialize<T>(value: &T) -> io::Result<Vec<u8>>
+11 -11
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use base64::{engine::general_purpose, Engine as _};
use openssl::symm::{Cipher, Crypter, Mode};
use rand::Rng;
@@ -32,13 +32,13 @@ pub fn deobfuscate_text(secret: &[u8], text: &str) -> Result<String, String> {
}
}
pub fn encrypt_text(secret: &[u8; 16], text: &str) -> Result<String, M3uFilterError> {
pub fn encrypt_text(secret: &[u8; 16], text: &str) -> Result<String, TuliProxError> {
let iv: [u8; 16] = rand::rng().random(); // Random IV (AES-CBC 16 Bytes)
let cipher = Cipher::aes_128_cbc();
let mut crypter = Crypter::new(cipher, Mode::Encrypt, secret, Some(&iv)).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))?;
let mut crypter = Crypter::new(cipher, Mode::Encrypt, secret, Some(&iv)).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))?;
let mut ciphertext = vec![0; text.len() + cipher.block_size()];
let mut count = crypter.update(text.as_bytes(), &mut ciphertext).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))?;
count += crypter.finalize(&mut ciphertext[count..]).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))?;
let mut count = crypter.update(text.as_bytes(), &mut ciphertext).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))?;
count += crypter.finalize(&mut ciphertext[count..]).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))?;
ciphertext.truncate(count);
// IV + Ciphertext
@@ -47,17 +47,17 @@ pub fn encrypt_text(secret: &[u8; 16], text: &str) -> Result<String, M3uFilterEr
Ok(general_purpose::URL_SAFE_NO_PAD.encode(out))
}
pub fn decrypt_text(secret: &[u8; 16], encrypted_text: &str) -> Result<String, M3uFilterError> {
let data = general_purpose::URL_SAFE_NO_PAD.decode(encrypted_text).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))?;
pub fn decrypt_text(secret: &[u8; 16], encrypted_text: &str) -> Result<String, TuliProxError> {
let data = general_purpose::URL_SAFE_NO_PAD.decode(encrypted_text).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))?;
let (iv, ciphertext) = data.split_at(16); // first 16 bytes IV
let cipher = Cipher::aes_128_cbc();
let mut crypter = Crypter::new(cipher, Mode::Decrypt, secret, Some(iv)).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))?;
let mut crypter = Crypter::new(cipher, Mode::Decrypt, secret, Some(iv)).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))?;
let mut decrypted = vec![0; ciphertext.len() + cipher.block_size()];
let mut count = crypter.update(ciphertext, &mut decrypted).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))?;
count += crypter.finalize(&mut decrypted[count..]).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))?;
let mut count = crypter.update(ciphertext, &mut decrypted).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))?;
count += crypter.finalize(&mut decrypted[count..]).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))?;
decrypted.truncate(count);
String::from_utf8(decrypted).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err.to_string()))
String::from_utf8(decrypted).map_err(|err| TuliProxError::new(TuliProxErrorKind::Info, err.to_string()))
}
#[cfg(test)]
+12 -12
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::{create_m3u_filter_error, create_m3u_filter_error_result, handle_m3u_filter_error_result, info_err, str_to_io_error, to_io_error, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{create_tuliprox_error, create_tuliprox_error_result, handle_tuliprox_error_result, info_err, str_to_io_error, to_io_error, TuliProxError, TuliProxErrorKind};
use crate::model::ApiProxyConfig;
use crate::model::{Config, ConfigDto, ConfigInput, ConfigInputAlias, InputType};
use crate::model::Mappings;
@@ -41,7 +41,7 @@ pub fn config_file_reader(file: File, resolve_env: bool) -> impl Read
}
}
pub fn read_mappings(args_mapping: Option<String>, cfg: &mut Config, resolve_env: bool) -> Result<Option<String>, M3uFilterError> {
pub fn read_mappings(args_mapping: Option<String>, cfg: &mut Config, resolve_env: bool) -> Result<Option<String>, TuliProxError> {
let mappings_file: String = args_mapping.unwrap_or_else(|| file_utils::get_default_mappings_path(cfg.t_config_path.as_str()));
match read_mapping(mappings_file.as_str(), resolve_env) {
@@ -61,7 +61,7 @@ pub fn read_mappings(args_mapping: Option<String>, cfg: &mut Config, resolve_env
}
}
pub async fn read_api_proxy_config(args_api_proxy_config: Option<String>, cfg: &mut Config) -> Result<Option<String>, M3uFilterError> {
pub async fn read_api_proxy_config(args_api_proxy_config: Option<String>, cfg: &mut Config) -> Result<Option<String>, TuliProxError> {
let api_proxy_config_file: String = args_api_proxy_config.unwrap_or_else(|| file_utils::get_default_api_proxy_config_path(cfg.t_config_path.as_str()));
api_proxy_config_file.clone_into(&mut cfg.t_api_proxy_file_path);
let api_proxy_config = read_api_proxy(cfg, api_proxy_config_file.as_str(), true);
@@ -77,7 +77,7 @@ pub async fn read_api_proxy_config(args_api_proxy_config: Option<String>, cfg: &
}
}
pub fn read_config(config_path: &str, config_file: &str, sources_file: &str, include_computed: bool) -> Result<Config, M3uFilterError> {
pub fn read_config(config_path: &str, config_file: &str, sources_file: &str, include_computed: bool) -> Result<Config, TuliProxError> {
let files = vec![std::path::PathBuf::from(config_file), std::path::PathBuf::from(sources_file)];
match multi_file_reader::MultiFileReader::new(&files) {
Ok(reader) => {
@@ -92,21 +92,21 @@ pub fn read_config(config_path: &str, config_file: &str, sources_file: &str, inc
}
}
Err(e) => {
create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "cant read config file: {}", e)
create_tuliprox_error_result!(TuliProxErrorKind::Info, "cant read config file: {}", e)
}
}
}
Err(err) => create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "{}", err)
Err(err) => create_tuliprox_error_result!(TuliProxErrorKind::Info, "{}", err)
}
}
pub fn read_mapping(mapping_file: &str, resolve_var: bool) -> Result<Option<Mappings>, M3uFilterError> {
pub fn read_mapping(mapping_file: &str, resolve_var: bool) -> Result<Option<Mappings>, TuliProxError> {
let mapping_file = std::path::PathBuf::from(mapping_file);
if let Ok(file) = file_utils::open_file(&mapping_file) {
let mapping: Result<Mappings, _> = serde_yaml::from_reader(config_file_reader(file, resolve_var));
return match mapping {
Ok(mut result) => {
handle_m3u_filter_error_result!(M3uFilterErrorKind::Info, result.prepare());
handle_tuliprox_error_result!(TuliProxErrorKind::Info, result.prepare());
Ok(Some(result))
}
Err(err) => {
@@ -140,7 +140,7 @@ pub fn read_api_proxy(config: &Config, api_proxy_file: &str, resolve_env: bool)
})
}
fn write_config_file<T>(file_path: &str, backup_dir: &str, config: &T, default_name: &str) -> Result<(), M3uFilterError>
fn write_config_file<T>(file_path: &str, backup_dir: &str, config: &T, default_name: &str) -> Result<(), TuliProxError>
where
T: ?Sized + Serialize,
{
@@ -157,14 +157,14 @@ where
File::create(&path)
.and_then(|f| serde_yaml::to_writer(f, &config).map_err(to_io_error))
.map_err(|err| create_m3u_filter_error!(M3uFilterErrorKind::Info, "Could not write file {}: {}", &path.to_str().unwrap_or("?"), err))
.map_err(|err| create_tuliprox_error!(TuliProxErrorKind::Info, "Could not write file {}: {}", &path.to_str().unwrap_or("?"), err))
}
pub fn save_api_proxy(file_path: &str, backup_dir: &str, config: &ApiProxyConfig) -> Result<(), M3uFilterError> {
pub fn save_api_proxy(file_path: &str, backup_dir: &str, config: &ApiProxyConfig) -> Result<(), TuliProxError> {
write_config_file(file_path, backup_dir, config, "api-proxy.yml")
}
pub fn save_main_config(file_path: &str, backup_dir: &str, config: &ConfigDto) -> Result<(), M3uFilterError> {
pub fn save_main_config(file_path: &str, backup_dir: &str, config: &ConfigDto) -> Result<(), TuliProxError> {
write_config_file(file_path, backup_dir, config, "config.yml")
}
+1 -1
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use std::{fmt, io};
use std::path::{Path, PathBuf};
use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::m3u_filter_error::str_to_io_error;
use crate::tuliprox_error::str_to_io_error;
#[derive(Clone)]
pub struct FileLockManager {
+1 -1
View File
@@ -4,7 +4,7 @@ use std::fs::{File, OpenOptions};
use std::io::{BufReader, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use crate::m3u_filter_error::str_to_io_error;
use crate::tuliprox_error::str_to_io_error;
use crate::utils::debug_if_enabled;
use log::{debug, error};
use path_clean::PathClean;
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::m3u_filter_error::M3uFilterError;
use crate::tuliprox_error::TuliProxError;
use crate::model::{Config, ConfigInput};
use crate::model::TVGuide;
use crate::repository::storage::short_hash;
@@ -10,7 +10,7 @@ use std::path::PathBuf;
use std::sync::Arc;
async fn download_epg_file(url: &str, client: &Arc<reqwest::Client>, input: &ConfigInput, working_dir: &str) -> Result<PathBuf, M3uFilterError> {
async fn download_epg_file(url: &str, client: &Arc<reqwest::Client>, input: &ConfigInput, working_dir: &str) -> Result<PathBuf, TuliProxError> {
debug!("Getting epg file path for url: {url}");
let file_prefix = short_hash(url);
let persist_file_path = prepare_file_path(input.persist.as_deref(), working_dir, "")
@@ -19,7 +19,7 @@ async fn download_epg_file(url: &str, client: &Arc<reqwest::Client>, input: &Con
request::get_input_text_content_as_file(Arc::clone(client), input, working_dir, url, persist_file_path).await
}
pub async fn get_xmltv(client: Arc<reqwest::Client>, _cfg: &Config, input: &ConfigInput, working_dir: &str) -> (Option<TVGuide>, Vec<M3uFilterError>) {
pub async fn get_xmltv(client: Arc<reqwest::Client>, _cfg: &Config, input: &ConfigInput, working_dir: &str) -> (Option<TVGuide>, Vec<TuliProxError>) {
match &input.epg {
None => (None, vec![]),
Some(epg_config) => {
+8 -8
View File
@@ -1,25 +1,25 @@
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::{TuliProxError, TuliProxErrorKind};
use crate::model::IpCheckConfig;
use regex::Regex;
use reqwest::Client;
use std::sync::Arc;
async fn fetch_ip(client: &Arc<Client>, url: &str, regex: Option<&Regex>) -> Result<String, M3uFilterError> {
async fn fetch_ip(client: &Arc<Client>, url: &str, regex: Option<&Regex>) -> Result<String, TuliProxError> {
let response = client.get(url).send().await
.map_err(|e| M3uFilterError::new(M3uFilterErrorKind::Info, format!("Failed to request {url}: {e}")))?;
.map_err(|e| TuliProxError::new(TuliProxErrorKind::Info, format!("Failed to request {url}: {e}")))?;
let text = response.text().await
.map_err(|e| M3uFilterError::new(M3uFilterErrorKind::Info, format!("Failed to read response: {e}")))?;
.map_err(|e| TuliProxError::new(TuliProxErrorKind::Info, format!("Failed to read response: {e}")))?;
if let Some(re) = regex {
return if let Some(caps) = re.captures(&text) {
if let Some(m) = caps.get(1) {
Ok(m.as_str().to_string())
} else {
Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Regex matched but no group found".to_string()))
Err(TuliProxError::new(TuliProxErrorKind::Info, "Regex matched but no group found".to_string()))
}
} else {
Err(M3uFilterError::new(M3uFilterErrorKind::Info, "Regex did not match".to_string()))
Err(TuliProxError::new(TuliProxErrorKind::Info, "Regex did not match".to_string()))
};
}
@@ -54,7 +54,7 @@ async fn fetch_combined_ips(client: &Arc<Client>, config: &IpCheckConfig, url: &
}
/// Fetch both IPv4 and IPv6 addresses, using separate or combined URL(s)
pub async fn get_ips(client: &Arc<Client>, config: &IpCheckConfig) -> Result<(Option<String>, Option<String>), M3uFilterError> {
pub async fn get_ips(client: &Arc<Client>, config: &IpCheckConfig) -> Result<(Option<String>, Option<String>), TuliProxError> {
match (&config.url_ipv4, &config.url_ipv6, &config.url) {
// Both dedicated URLs provided
(Some(url_v4), Some(url_v6), _) => {
@@ -82,6 +82,6 @@ pub async fn get_ips(client: &Arc<Client>, config: &IpCheckConfig) -> Result<(Op
}
// No URLs given
_ => Err(M3uFilterError::new(M3uFilterErrorKind::Info, "No valid IP-check URLs provided".to_owned())),
_ => Err(TuliProxError::new(TuliProxErrorKind::Info, "No valid IP-check URLs provided".to_owned())),
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
use std::sync::Arc;
use crate::m3u_filter_error::M3uFilterError;
use crate::tuliprox_error::TuliProxError;
use crate::model::{Config, ConfigInput};
use crate::model::PlaylistGroup;
use crate::processing::parser::m3u;
use crate::utils::file_utils::prepare_file_path;
use crate::utils::request;
pub async fn get_m3u_playlist(client: Arc<reqwest::Client>, cfg: &Config, input: &ConfigInput, working_dir: &str) -> (Vec<PlaylistGroup>, Vec<M3uFilterError>) {
pub async fn get_m3u_playlist(client: Arc<reqwest::Client>, cfg: &Config, input: &ConfigInput, working_dir: &str) -> (Vec<PlaylistGroup>, Vec<TuliProxError>) {
let url = input.url.clone();
let persist_file_path = prepare_file_path(input.persist.as_deref(), working_dir, "");
match request::get_input_text_content(client, input, working_dir, &url, persist_file_path).await {
+14 -14
View File
@@ -14,8 +14,8 @@ use reqwest::header::CONTENT_ENCODING;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use url::Url;
use crate::m3u_filter_error::create_m3u_filter_error_result;
use crate::m3u_filter_error::{str_to_io_error, M3uFilterError, M3uFilterErrorKind};
use crate::tuliprox_error::create_tuliprox_error_result;
use crate::tuliprox_error::{str_to_io_error, TuliProxError, TuliProxErrorKind};
use crate::model::format_elapsed_time;
use crate::model::{ConfigInput, ProxyConfig, InputFetchMethod};
use crate::repository::storage::{get_input_storage_path, short_hash};
@@ -29,14 +29,14 @@ pub const fn bytes_to_megabytes(bytes: u64) -> u64 {
bytes / 1_048_576
}
pub async fn get_input_text_content_as_file(client: Arc<reqwest::Client>, input: &ConfigInput, working_dir: &str, url_str: &str, persist_filepath: Option<PathBuf>) -> Result<PathBuf, M3uFilterError> {
pub async fn get_input_text_content_as_file(client: Arc<reqwest::Client>, input: &ConfigInput, working_dir: &str, url_str: &str, persist_filepath: Option<PathBuf>) -> Result<PathBuf, TuliProxError> {
debug_if_enabled!("getting input text content working_dir: {}, url: {}", working_dir, sanitize_sensitive_info(url_str));
if url_str.parse::<url::Url>().is_ok() {
match download_text_content_as_file(client, input, url_str, working_dir, persist_filepath).await {
Ok(content) => Ok(content),
Err(e) => {
error!("cant download input url: {} => {}", sanitize_sensitive_info(url_str), sanitize_sensitive_info(e.to_string().as_str()));
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to download")
create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed to download")
}
}
} else {
@@ -49,7 +49,7 @@ pub async fn get_input_text_content_as_file(client: Arc<reqwest::Client>, input:
Ok(_) => {}
Err(e) => {
error!("cant persist to: {} => {}", to_file.to_str().unwrap_or("?"), e);
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to persist: {} => {}", to_file.to_str().unwrap_or("?"), e);
return create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed to persist: {} => {}", to_file.to_str().unwrap_or("?"), e);
}
}
}
@@ -57,7 +57,7 @@ pub async fn get_input_text_content_as_file(client: Arc<reqwest::Client>, input:
if filepath.exists() {
Some(filepath)
} else {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed: file does not exists {filepath:?}");
return create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed: file does not exists {filepath:?}");
}
} else {
None
@@ -69,13 +69,13 @@ pub async fn get_input_text_content_as_file(client: Arc<reqwest::Client>, input:
result.map_or_else(|| {
let msg = format!("cant read input url: {}", sanitize_sensitive_info(url_str));
error!("{msg}");
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "{msg}")
create_tuliprox_error_result!(TuliProxErrorKind::Notify, "{msg}")
}, Ok)
}
}
pub async fn get_input_text_content(client: Arc<reqwest::Client>, input: &ConfigInput, working_dir: &str, url_str: &str, persist_filepath: Option<PathBuf>) -> Result<String, M3uFilterError> {
pub async fn get_input_text_content(client: Arc<reqwest::Client>, input: &ConfigInput, working_dir: &str, url_str: &str, persist_filepath: Option<PathBuf>) -> Result<String, TuliProxError> {
debug_if_enabled!("getting input text content working_dir: {}, url: {}", working_dir, sanitize_sensitive_info(url_str));
if url_str.parse::<url::Url>().is_ok() {
@@ -83,7 +83,7 @@ pub async fn get_input_text_content(client: Arc<reqwest::Client>, input: &Config
Ok((content, _response_url)) => Ok(content),
Err(e) => {
error!("cant download input url: {} => {}", sanitize_sensitive_info(url_str), sanitize_sensitive_info(e.to_string().as_str()));
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to download")
create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed to download")
}
}
} else {
@@ -96,7 +96,7 @@ pub async fn get_input_text_content(client: Arc<reqwest::Client>, input: &Config
Ok(_) => {}
Err(e) => {
error!("cant persist to: {} => {}", to_file.to_str().unwrap_or("?"), e);
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to persist: {} => {}", to_file.to_str().unwrap_or("?"), e);
return create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed to persist: {} => {}", to_file.to_str().unwrap_or("?"), e);
}
}
}
@@ -104,7 +104,7 @@ pub async fn get_input_text_content(client: Arc<reqwest::Client>, input: &Config
match get_local_file_content(&filepath) {
Ok(content) => Some(content),
Err(err) => {
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed : {}", err);
return create_tuliprox_error_result!(TuliProxErrorKind::Notify, "Failed : {}", err);
}
}
} else {
@@ -116,7 +116,7 @@ pub async fn get_input_text_content(client: Arc<reqwest::Client>, input: &Config
result.map_or_else(|| {
let msg = format!("cant read input url: {}", sanitize_sensitive_info(url_str));
error!("{msg}");
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "{msg}")
create_tuliprox_error_result!(TuliProxErrorKind::Notify, "{msg}")
}, Ok)
}
}
@@ -360,10 +360,10 @@ async fn download_json_content(client: Arc<reqwest::Client>, input: &ConfigInput
}
}
pub async fn get_input_json_content(client: Arc<reqwest::Client>, input: &ConfigInput, url: &str, persist_filepath: Option<PathBuf>) -> Result<serde_json::Value, M3uFilterError> {
pub async fn get_input_json_content(client: Arc<reqwest::Client>, input: &ConfigInput, url: &str, persist_filepath: Option<PathBuf>) -> Result<serde_json::Value, TuliProxError> {
match download_json_content(client, input, url, persist_filepath).await {
Ok(content) => Ok(content),
Err(e) => create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "cant download input url: {} => {}", sanitize_sensitive_info(url), sanitize_sensitive_info(e.to_string().as_str()))
Err(e) => create_tuliprox_error_result!(TuliProxErrorKind::Notify, "cant download input url: {} => {}", sanitize_sensitive_info(url), sanitize_sensitive_info(e.to_string().as_str()))
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
use std::sync::Arc;
use crate::m3u_filter_error::{str_to_io_error, M3uFilterError};
use crate::tuliprox_error::{str_to_io_error, TuliProxError};
use crate::model::{Config, ConfigInput, ConfigTarget};
use crate::model::{PlaylistEntry, PlaylistGroup, XtreamCluster, XtreamPlaylistItem};
use crate::processing::parser::xtream;
@@ -129,7 +129,7 @@ const ACTIONS: [(XtreamCluster, &str, &str); 3] = [
(XtreamCluster::Video, crate::model::XC_ACTION_GET_VOD_CATEGORIES, crate::model::XC_ACTION_GET_VOD_STREAMS),
(XtreamCluster::Series, crate::model::XC_ACTION_GET_SERIES_CATEGORIES, crate::model::XC_ACTION_GET_SERIES)];
pub async fn get_xtream_playlist(client: Arc<reqwest::Client>, input: &ConfigInput, working_dir: &str) -> (Vec<PlaylistGroup>, Vec<M3uFilterError>) {
pub async fn get_xtream_playlist(client: Arc<reqwest::Client>, input: &ConfigInput, working_dir: &str) -> (Vec<PlaylistGroup>, Vec<TuliProxError>) {
let username = input.username.as_ref().map_or("", |v| v);
let password = input.password.as_ref().map_or("", |v| v);