mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-15 00:50:49 -08:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a13bdb1aa0 | ||
|
|
627b09a723 | ||
|
|
aecec5c75b | ||
|
|
49b298ed52 | ||
|
|
9a90fa196b | ||
|
|
4ac059e873 | ||
|
|
8b39a28e32 | ||
|
|
066cc89b74 | ||
|
|
db16758d9f | ||
|
|
78e17b2ba0 | ||
|
|
c5326eb8d9 | ||
|
|
4a2d95e75e | ||
|
|
3a92ba69df | ||
|
|
cf59f4822e | ||
|
|
1cea6d0179 | ||
|
|
4bc1edcc4e | ||
|
|
0c546af99c | ||
|
|
1b49e186c8 | ||
|
|
fe831f9658 | ||
|
|
72f0e2e5b9 | ||
|
|
8530da23ef | ||
|
|
1e01b6e54a | ||
|
|
aa6ba9018d | ||
|
|
354ba6256a | ||
|
|
eae31420f9 | ||
|
|
01432a0fec | ||
|
|
c158d3fb99 | ||
|
|
877bc043a0 | ||
|
|
4968f8030a | ||
|
|
c5c7644d0d | ||
|
|
ff2a5d635a | ||
|
|
8626d1991c | ||
|
|
75d15a100d | ||
|
|
25d9895c52 | ||
|
|
f1b796d72b | ||
|
|
3f63198563 | ||
|
|
8d61463156 | ||
|
|
2daa51d384 | ||
|
|
43a0d77e1b | ||
|
|
eaedf3268d | ||
|
|
ade0465ea4 | ||
|
|
5e82db4ea8 | ||
|
|
a10e56cb6f | ||
|
|
fbd95e1966 | ||
|
|
d37a441ccf | ||
|
|
cbc1ceccbb | ||
|
|
249a207cad | ||
|
|
c8a42c4920 | ||
|
|
de8b6b7f2f | ||
|
|
54e0942233 | ||
|
|
8ea0c121c2 | ||
|
|
eddaad64e7 | ||
|
|
43be7a52cf | ||
|
|
b689760a25 | ||
|
|
e53246b79b | ||
|
|
b0fc94cdc5 | ||
|
|
449f6c1e59 | ||
|
|
ab4734b79d | ||
|
|
93d0f6a1a5 | ||
|
|
19c75c48b2 | ||
|
|
5341b0a844 | ||
|
|
24e7e6a16b | ||
|
|
4b310e60b8 | ||
|
|
4d50cffd86 | ||
|
|
f6fedf0500 | ||
|
|
7b431450fe | ||
|
|
66b247330b | ||
|
|
c6b8cfc294 | ||
|
|
6895426d67 | ||
|
|
cc69dc35f6 | ||
|
|
ed81f37ae4 | ||
|
|
c6858b00c4 | ||
|
|
a44034a5d4 | ||
|
|
f768518721 | ||
|
|
97f5bb9cb3 | ||
|
|
b09fdbf69b | ||
|
|
071c46cad9 | ||
|
|
5d32503ff9 | ||
|
|
e67532c496 | ||
|
|
819012897d |
6
.envrc
6
.envrc
@@ -1 +1,5 @@
|
|||||||
use flake
|
VIU_APP_NAME="viu-dev"
|
||||||
|
export VIU_APP_NAME
|
||||||
|
if command -v nix >/dev/null;then
|
||||||
|
use flake
|
||||||
|
fi
|
||||||
|
|||||||
15
.github/FUNDING.yml
vendored
15
.github/FUNDING.yml
vendored
@@ -1,15 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: benexl # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: benexl # Replace with a single Ko-fi username
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
||||||
polar: # Replace with a single Polar username
|
|
||||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
|
||||||
thanks_dev: # Replace with a single thanks.dev username
|
|
||||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
||||||
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@@ -42,6 +42,12 @@ jobs:
|
|||||||
# IMPORTANT: this permission is mandatory for trusted publishing
|
# IMPORTANT: this permission is mandatory for trusted publishing
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
|
# Dedicated environments with protections for publishing are strongly recommended.
|
||||||
|
environment:
|
||||||
|
name: pypi
|
||||||
|
# OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
|
||||||
|
# url: https://pypi.org/p/YOURPROJECT
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Retrieve release distributions
|
- name: Retrieve release distributions
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
|
|||||||
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10", "3.11"] # List the Python versions you want to test
|
python-version: ["3.11", "3.12"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -22,6 +22,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dbus-python build dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install libdbus-1-dev libglib2.0-dev
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@v3
|
uses: astral-sh/setup-uv@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
1
.repomixignore
Normal file
1
.repomixignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
**/generated/**/*
|
||||||
@@ -6,7 +6,7 @@ First off, thank you for considering contributing to Viu! We welcome any help, w
|
|||||||
|
|
||||||
There are many ways to contribute to the Viu project:
|
There are many ways to contribute to the Viu project:
|
||||||
|
|
||||||
* **Reporting Bugs:** If you find a bug, please create an issue in our [issue tracker](https://github.com/Benexl/Viu/issues).
|
* **Reporting Bugs:** If you find a bug, please create an issue in our [issue tracker](https://github.com/viu-media/Viu/issues).
|
||||||
* **Suggesting Enhancements:** Have an idea for a new feature or an improvement to an existing one? We'd love to hear it.
|
* **Suggesting Enhancements:** Have an idea for a new feature or an improvement to an existing one? We'd love to hear it.
|
||||||
* **Writing Code:** Help us fix bugs or implement new features.
|
* **Writing Code:** Help us fix bugs or implement new features.
|
||||||
* **Improving Documentation:** Enhance our README, add examples, or clarify our contribution guidelines.
|
* **Improving Documentation:** Enhance our README, add examples, or clarify our contribution guidelines.
|
||||||
@@ -16,7 +16,7 @@ There are many ways to contribute to the Viu project:
|
|||||||
|
|
||||||
We follow the standard GitHub Fork & Pull Request workflow.
|
We follow the standard GitHub Fork & Pull Request workflow.
|
||||||
|
|
||||||
1. **Create an Issue:** Before starting work on a new feature or a significant bug fix, please [create an issue](https://github.com/Benexl/Viu/issues/new/choose) to discuss your idea. This allows us to give feedback and prevent duplicate work. For small bugs or documentation typos, you can skip this step.
|
1. **Create an Issue:** Before starting work on a new feature or a significant bug fix, please [create an issue](https://github.com/viu-media/Viu/issues/new/choose) to discuss your idea. This allows us to give feedback and prevent duplicate work. For small bugs or documentation typos, you can skip this step.
|
||||||
|
|
||||||
2. **Fork the Repository:** Create your own fork of the Viu repository.
|
2. **Fork the Repository:** Create your own fork of the Viu repository.
|
||||||
|
|
||||||
|
|||||||
85
README.md
85
README.md
@@ -8,12 +8,12 @@
|
|||||||
</p>
|
</p>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
[](https://pypi.org/project/viu_cli/)
|
[](https://pypi.org/project/viu-media/)
|
||||||
[](https://pypi.org/project/viu_cli/)
|
[](https://pypi.org/project/viu-media/)
|
||||||
[](https://github.com/Benexl/Viu/actions)
|
[](https://github.com/viu-media/Viu/actions)
|
||||||
[](https://discord.gg/HBEmAwvbHV)
|
[](https://discord.gg/HBEmAwvbHV)
|
||||||
[](https://github.com/Benexl/Viu/issues)
|
[](https://github.com/viu-media/Viu/issues)
|
||||||
[](https://github.com/Benexl/Viu/blob/master/LICENSE)
|
[](https://github.com/viu-media/Viu/blob/master/LICENSE)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -23,45 +23,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||

|
[viu-showcase.webm](https://github.com/user-attachments/assets/5da0ec87-7780-4310-9ca2-33fae7cadd5f)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>Rofi</summary>
|
||||||
<b>Screenshots</b>
|
|
||||||
</summary>
|
|
||||||
<b>Fzf:</b>
|
|
||||||
<img width="1346" height="710" alt="250815_13h29m15s_screenshot" src="https://github.com/user-attachments/assets/d8fb8473-a0fe-47b1-b112-5cd8bec51937" />
|
|
||||||
<img width="1346" height="710" alt="250815_13h29m43s_screenshot" src="https://github.com/user-attachments/assets/16a2555d-f81e-4044-9e65-e61205dfe899" />
|
|
||||||
<img width="1346" height="710" alt="250815_13h30m09s_screenshot" src="https://github.com/user-attachments/assets/f521670a-c04f-4f5e-a62a-6c849fbf49bd" />
|
|
||||||
<img width="1346" height="710" alt="250815_13h30m33s_screenshot" src="https://github.com/user-attachments/assets/27fd2ef9-ec1f-4677-b816-038eaaca1391" />
|
|
||||||
<img width="1346" height="710" alt="250815_13h31m07s_screenshot" src="https://github.com/user-attachments/assets/6a64aa99-507e-449a-9e4a-9daa4fe496a3" />
|
|
||||||
<img width="1346" height="710" alt="250815_13h31m44s_screenshot" src="https://github.com/user-attachments/assets/a2896d1f-0e23-4ff3-b0c6-121d21a9f99a" />
|
|
||||||
|
|
||||||
<b>Rofi:</b>
|
|
||||||
<img width="1366" height="729" alt="250815_13h23m12s_screenshot" src="https://github.com/user-attachments/assets/6d18d950-11e5-41fc-a7fe-1f9eaa481e46" />
|
|
||||||
<img width="1366" height="765" alt="250815_13h24m09s_screenshot" src="https://github.com/user-attachments/assets/af852fee-17bf-4f24-ada9-7cf0e6f3451c" />
|
|
||||||
<img width="1366" height="768" alt="250815_13h24m57s_screenshot" src="https://github.com/user-attachments/assets/d3b4e2ab-10bd-40ae-88ed-0720b57957c1" />
|
|
||||||
<img width="1366" height="735" alt="250815_13h26m47s_screenshot" src="https://github.com/user-attachments/assets/64682b09-c88e-4d4c-ae26-a3aa34dd08a1" />
|
|
||||||
<img width="1366" height="768" alt="250815_13h28m05s_screenshot" src="https://github.com/user-attachments/assets/d6cd6931-0113-462c-86bb-abe6f3e12d68" />
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
[viu-showcase-rofi.webm](https://github.com/user-attachments/assets/01f197d9-5ac9-45e6-a00b-8e8cd5ab459c)
|
||||||
<summary>
|
|
||||||
<b>Riced Preview Examples</b>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
**Anilist Results Menu (FZF):**
|
|
||||||

|
|
||||||
|
|
||||||
**Episodes Menu with Preview (FZF):**
|
|
||||||

|
|
||||||
|
|
||||||
**No Image Preview Mode:**
|
|
||||||

|
|
||||||
|
|
||||||
**Desktop Notifications + Episodes Menu:**
|
|
||||||

|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -98,13 +65,13 @@ The best way to install Viu is with [**uv**](https://github.com/astral-sh/uv), a
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install with all optional features for the full experience
|
# Install with all optional features for the full experience
|
||||||
uv tool install "viu_cli[standard]"
|
uv tool install "viu-media[standard]"
|
||||||
|
|
||||||
# Or, pick and choose the extras you need:
|
# Or, pick and choose the extras you need:
|
||||||
uv tool install viu_cli # Core functionality only
|
uv tool install viu-media # Core functionality only
|
||||||
uv tool install "viu_cli[download]" # For advanced downloading with yt-dlp
|
uv tool install "viu-media[download]" # For advanced downloading with yt-dlp
|
||||||
uv tool install "viu_cli[discord]" # For Discord Rich Presence
|
uv tool install "viu-media[discord]" # For Discord Rich Presence
|
||||||
uv tool install "viu_cli[notifications]" # For desktop notifications
|
uv tool install "viu-media[notifications]" # For desktop notifications
|
||||||
```
|
```
|
||||||
|
|
||||||
### Other Installation Methods
|
### Other Installation Methods
|
||||||
@@ -113,28 +80,42 @@ uv tool install "viu_cli[notifications]" # For desktop notifications
|
|||||||
<summary><b>Platform-Specific and Alternative Installers</b></summary>
|
<summary><b>Platform-Specific and Alternative Installers</b></summary>
|
||||||
|
|
||||||
#### Nix / NixOS
|
#### Nix / NixOS
|
||||||
|
##### Ephemeral / One-Off Run (No Installation)
|
||||||
```bash
|
```bash
|
||||||
nix profile install github:Benexl/viu
|
nix run github:viu-media/viu
|
||||||
|
```
|
||||||
|
##### Imperative Installation
|
||||||
|
```bash
|
||||||
|
nix profile install github:viu-media/viu
|
||||||
|
```
|
||||||
|
##### Declarative Installation
|
||||||
|
###### in your flake.nix
|
||||||
|
```nix
|
||||||
|
viu.url = "github:viu-media/viu";
|
||||||
|
```
|
||||||
|
###### in your system or home-manager packages
|
||||||
|
```nix
|
||||||
|
inputs.viu.packages.${pkgs.system}.default
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Arch Linux (AUR)
|
#### Arch Linux (AUR)
|
||||||
Use an AUR helper like `yay` or `paru`.
|
Use an AUR helper like `yay` or `paru`.
|
||||||
```bash
|
```bash
|
||||||
# Stable version (recommended)
|
# Stable version (recommended)
|
||||||
yay -S viu
|
yay -S viu-media
|
||||||
|
|
||||||
# Git version (latest commit)
|
# Git version (latest commit)
|
||||||
yay -S viu-git
|
yay -S viu-media-git
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using pipx (for isolated environments)
|
#### Using pipx (for isolated environments)
|
||||||
```bash
|
```bash
|
||||||
pipx install "viu_cli[standard]"
|
pipx install "viu-media[standard]"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using pip
|
#### Using pip
|
||||||
```bash
|
```bash
|
||||||
pip install "viu_cli[standard]"
|
pip install "viu-media[standard]"
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -143,7 +124,7 @@ uv tool install "viu_cli[notifications]" # For desktop notifications
|
|||||||
|
|
||||||
Requires [Git](https://git-scm.com/), [Python 3.10+](https://www.python.org/), and [uv](https://astral.sh/blog/uv).
|
Requires [Git](https://git-scm.com/), [Python 3.10+](https://www.python.org/), and [uv](https://astral.sh/blog/uv).
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Benexl/Viu.git --depth 1
|
git clone https://github.com/viu-media/Viu.git --depth 1
|
||||||
cd Viu
|
cd Viu
|
||||||
uv tool install .
|
uv tool install .
|
||||||
viu --version
|
viu --version
|
||||||
|
|||||||
64
dev/generate_anilist_media_tags.py
Executable file
64
dev/generate_anilist_media_tags.py
Executable file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env -S uv run --script
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
from viu_media.core.utils.graphql import execute_graphql
|
||||||
|
from pathlib import Path
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
DEV_DIR = Path(__file__).resolve().parent
|
||||||
|
media_tags_type_py = (
|
||||||
|
DEV_DIR.parent / "viu_media" / "libs" / "media_api" / "_media_tags.py"
|
||||||
|
)
|
||||||
|
media_tags_gql = DEV_DIR / "graphql" / "anilist" / "media_tags.gql"
|
||||||
|
generated_tags_json = DEV_DIR / "generated" / "anilist" / "tags.json"
|
||||||
|
|
||||||
|
media_tags_response = execute_graphql(
|
||||||
|
"https://graphql.anilist.co", httpx.Client(), media_tags_gql, {}
|
||||||
|
)
|
||||||
|
media_tags_response.raise_for_status()
|
||||||
|
|
||||||
|
template = """\
|
||||||
|
# DO NOT EDIT THIS FILE !!! ( 。 •̀ ᴖ •́ 。)
|
||||||
|
# ITS AUTOMATICALLY GENERATED BY RUNNING ./dev/generate_anilist_media_tags.py
|
||||||
|
# FROM THE PROJECT ROOT
|
||||||
|
# SO RUN THAT INSTEAD TO UPDATE THE FILE WITH THE LATEST MEDIA TAGS :)
|
||||||
|
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class MediaTag(Enum):\
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 4 spaces
|
||||||
|
tab = " "
|
||||||
|
tags = defaultdict(list)
|
||||||
|
for tag in media_tags_response.json()["data"]["MediaTagCollection"]:
|
||||||
|
tags[tag["category"]].append(
|
||||||
|
{
|
||||||
|
"name": tag["name"],
|
||||||
|
"description": tag["description"],
|
||||||
|
"is_adult": tag["isAdult"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# save copy of data used to generate the class
|
||||||
|
json.dump(tags, generated_tags_json.open("w", encoding="utf-8"), indent=2)
|
||||||
|
|
||||||
|
for key, value in tags.items():
|
||||||
|
template = f"{template}\n{tab}#\n{tab}# {key.upper()}\n{tab}#\n"
|
||||||
|
for tag in value:
|
||||||
|
name = tag["name"]
|
||||||
|
_tag_name = name.replace("-", "_").replace(" ", "_").upper()
|
||||||
|
if _tag_name.startswith(("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")):
|
||||||
|
_tag_name = f"_{_tag_name}"
|
||||||
|
|
||||||
|
tag_name = ""
|
||||||
|
# sanitize invalid characters for attribute names
|
||||||
|
for char in _tag_name:
|
||||||
|
if char.isidentifier() or char.isdigit():
|
||||||
|
tag_name += char
|
||||||
|
|
||||||
|
desc = tag["description"].replace("\n", "")
|
||||||
|
is_adult = tag["is_adult"]
|
||||||
|
template = f'{template}\n{tab}# {desc} (is_adult: {is_adult})\n{tab}{tag_name} = "{name}"\n'
|
||||||
|
|
||||||
|
media_tags_type_py.write_text(template, "utf-8")
|
||||||
File diff suppressed because it is too large
Load Diff
8
dev/graphql/anilist/media_tags.gql
Normal file
8
dev/graphql/anilist/media_tags.gql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
query {
|
||||||
|
MediaTagCollection {
|
||||||
|
name
|
||||||
|
description
|
||||||
|
category
|
||||||
|
isAdult
|
||||||
|
}
|
||||||
|
}
|
||||||
0
dev/make_release
Normal file → Executable file
0
dev/make_release
Normal file → Executable file
8
flake.lock
generated
8
flake.lock
generated
@@ -20,17 +20,17 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1753345091,
|
"lastModified": 1756386758,
|
||||||
"narHash": "sha256-CdX2Rtvp5I8HGu9swBmYuq+ILwRxpXdJwlpg8jvN4tU=",
|
"narHash": "sha256-1wxxznpW2CKvI9VdniaUnTT2Os6rdRJcRUf65ZK9OtE=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3ff0e34b1383648053bba8ed03f201d3466f90c9",
|
"rev": "dfb2f12e899db4876308eba6d93455ab7da304cd",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3ff0e34b1383648053bba8ed03f201d3466f90c9",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
17
flake.nix
17
flake.nix
@@ -2,8 +2,7 @@
|
|||||||
description = "Viu Project Flake";
|
description = "Viu Project Flake";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
# The nixpkgs unstable latest commit breaks the plyer python package
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/3ff0e34b1383648053bba8ed03f201d3466f90c9";
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,21 +16,21 @@
|
|||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
inherit (pkgs) lib python3Packages;
|
inherit (pkgs) lib python312Packages;
|
||||||
|
|
||||||
version = "3.1.0";
|
version = "3.1.0";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.default = python3Packages.buildPythonApplication {
|
packages.default = python312Packages.buildPythonApplication {
|
||||||
pname = "viu";
|
pname = "viu";
|
||||||
inherit version;
|
inherit version;
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
|
|
||||||
src = self;
|
src = self;
|
||||||
|
|
||||||
build-system = with python3Packages; [ hatchling ];
|
build-system = with python312Packages; [ hatchling ];
|
||||||
|
|
||||||
dependencies = with python3Packages; [
|
dependencies = with python312Packages; [
|
||||||
click
|
click
|
||||||
inquirerpy
|
inquirerpy
|
||||||
requests
|
requests
|
||||||
@@ -67,12 +66,10 @@
|
|||||||
# Needs to be adapted for the nix derivation build
|
# Needs to be adapted for the nix derivation build
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
|
|
||||||
pythonImportsCheck = [ "viu" ];
|
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
description = "Your browser anime experience from the terminal";
|
description = "Your browser anime experience from the terminal";
|
||||||
homepage = "https://github.com/Benexl/Viu";
|
homepage = "https://github.com/viu-media/Viu";
|
||||||
changelog = "https://github.com/Benexl/Viu/releases/tag/v${version}";
|
changelog = "https://github.com/viu-media/Viu/releases/tag/v${version}";
|
||||||
mainProgram = "viu";
|
mainProgram = "viu";
|
||||||
license = lib.licenses.unlicense;
|
license = lib.licenses.unlicense;
|
||||||
maintainers = with lib.maintainers; [ theobori ];
|
maintainers = with lib.maintainers; [ theobori ];
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "viu_cli"
|
name = "viu-media"
|
||||||
version = "3.1.0"
|
version = "3.2.8"
|
||||||
description = "A browser anime site experience from the terminal"
|
description = "A browser anime site experience from the terminal"
|
||||||
license = "UNLICENSE"
|
license = "UNLICENSE"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click>=8.1.7",
|
"click>=8.1.7",
|
||||||
"httpx>=0.28.1",
|
"httpx>=0.28.1",
|
||||||
@@ -14,14 +14,16 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
viu = 'viu_cli:Cli'
|
viu = 'viu_media:Cli'
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
standard = [
|
standard = [
|
||||||
"thefuzz>=0.22.1",
|
"thefuzz>=0.22.1",
|
||||||
"yt-dlp>=2025.7.21",
|
"yt-dlp>=2025.7.21",
|
||||||
"pycryptodomex>=3.23.0",
|
"pycryptodomex>=3.23.0",
|
||||||
"dbus-python>=1.4.0",
|
"pypiwin32; sys_platform == 'win32'", # For Windows-specific functionality
|
||||||
|
"pyobjc; sys_platform == 'darwin'", # For macOS-specific functionality
|
||||||
|
"dbus-python; sys_platform == 'linux'", # For Linux-specific functionality (e.g., notifications),
|
||||||
"plyer>=2.1.0",
|
"plyer>=2.1.0",
|
||||||
"lxml>=6.0.0"
|
"lxml>=6.0.0"
|
||||||
]
|
]
|
||||||
@@ -47,8 +49,8 @@ torrents = [
|
|||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.uv]
|
[dependency-groups]
|
||||||
dev-dependencies = [
|
dev = [
|
||||||
"pre-commit>=4.0.1",
|
"pre-commit>=4.0.1",
|
||||||
"pyinstaller>=6.11.1",
|
"pyinstaller>=6.11.1",
|
||||||
"pyright>=1.1.384",
|
"pyright>=1.1.384",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"venvPath": ".",
|
"venvPath": ".",
|
||||||
"venv": ".venv",
|
"venv": ".venv",
|
||||||
"pythonVersion": "3.10"
|
"pythonVersion": "3.11"
|
||||||
}
|
}
|
||||||
|
|||||||
2
tox.ini
2
tox.ini
@@ -1,7 +1,7 @@
|
|||||||
[tox]
|
[tox]
|
||||||
requires =
|
requires =
|
||||||
tox>=4
|
tox>=4
|
||||||
env_list = lint, pyright, py{310,311}
|
env_list = lint, pyright, py{311,312}
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
description = run unit tests
|
description = run unit tests
|
||||||
|
|||||||
2
fa → viu
Normal file → Executable file
2
fa → viu
Normal file → Executable file
@@ -3,4 +3,4 @@ provider_type=$1
|
|||||||
provider_name=$2
|
provider_name=$2
|
||||||
[ -z "$provider_type" ] && echo "Please specify provider type" && exit
|
[ -z "$provider_type" ] && echo "Please specify provider type" && exit
|
||||||
[ -z "$provider_name" ] && echo "Please specify provider type" && exit
|
[ -z "$provider_name" ] && echo "Please specify provider type" && exit
|
||||||
uv run python -m viu_cli.libs.provider.${provider_type}.${provider_name}.provider
|
uv run python -m viu_media.libs.provider.${provider_type}.${provider_name}.provider
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
"""Update command for Viu CLI."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
import click
|
|
||||||
from rich import print
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.markdown import Markdown
|
|
||||||
|
|
||||||
from ..utils.update import check_for_updates, update_app
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from ...core.config import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
@click.command(
|
|
||||||
help="Update Viu to the latest version",
|
|
||||||
short_help="Update Viu",
|
|
||||||
epilog="""
|
|
||||||
\b
|
|
||||||
\b\bExamples:
|
|
||||||
# Check for updates and update if available
|
|
||||||
viu update
|
|
||||||
\b
|
|
||||||
# Force update even if already up to date
|
|
||||||
viu update --force
|
|
||||||
\b
|
|
||||||
# Only check for updates without updating
|
|
||||||
viu update --check-only
|
|
||||||
\b
|
|
||||||
# Show release notes for the latest version
|
|
||||||
viu update --release-notes
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--force",
|
|
||||||
"-f",
|
|
||||||
is_flag=True,
|
|
||||||
help="Force update even if already up to date",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--check-only",
|
|
||||||
"-c",
|
|
||||||
is_flag=True,
|
|
||||||
help="Only check for updates without updating",
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
"--release-notes",
|
|
||||||
"-r",
|
|
||||||
is_flag=True,
|
|
||||||
help="Show release notes for the latest version",
|
|
||||||
)
|
|
||||||
@click.pass_context
|
|
||||||
@click.pass_obj
|
|
||||||
def update(
|
|
||||||
config: "AppConfig",
|
|
||||||
ctx: click.Context,
|
|
||||||
force: bool,
|
|
||||||
check_only: bool,
|
|
||||||
release_notes: bool,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Update Viu to the latest version.
|
|
||||||
|
|
||||||
This command checks for available updates and optionally updates
|
|
||||||
the application to the latest version from the configured sources
|
|
||||||
(pip, uv, pipx, git, or nix depending on installation method).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: The application configuration object
|
|
||||||
ctx: The click context containing CLI options
|
|
||||||
force: Whether to force update even if already up to date
|
|
||||||
check_only: Whether to only check for updates without updating
|
|
||||||
release_notes: Whether to show release notes for the latest version
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if release_notes:
|
|
||||||
print("[cyan]Fetching latest release notes...[/]")
|
|
||||||
is_latest, release_json = check_for_updates()
|
|
||||||
|
|
||||||
if not release_json:
|
|
||||||
print(
|
|
||||||
"[yellow]Could not fetch release information. Please check your internet connection.[/]"
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
version = release_json.get("tag_name", "unknown")
|
|
||||||
release_name = release_json.get("name", version)
|
|
||||||
release_body = release_json.get("body", "No release notes available.")
|
|
||||||
published_at = release_json.get("published_at", "unknown")
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
|
|
||||||
print(f"[bold cyan]Release: {release_name}[/]")
|
|
||||||
print(f"[dim]Version: {version}[/]")
|
|
||||||
print(f"[dim]Published: {published_at}[/]")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# Display release notes as markdown if available
|
|
||||||
if release_body.strip():
|
|
||||||
markdown = Markdown(release_body)
|
|
||||||
console.print(markdown)
|
|
||||||
else:
|
|
||||||
print("[dim]No release notes available for this version.[/]")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
elif check_only:
|
|
||||||
print("[cyan]Checking for updates...[/]")
|
|
||||||
is_latest, release_json = check_for_updates()
|
|
||||||
|
|
||||||
if not release_json:
|
|
||||||
print(
|
|
||||||
"[yellow]Could not check for updates. Please check your internet connection.[/]"
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if is_latest:
|
|
||||||
print("[green]Viu is up to date![/]")
|
|
||||||
print(
|
|
||||||
f"[dim]Current version: {release_json.get('tag_name', 'unknown')}[/]"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
latest_version = release_json.get("tag_name", "unknown")
|
|
||||||
print(f"[yellow]Update available: {latest_version}[/]")
|
|
||||||
print("[dim]Run 'viu update' to update[/]")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
print("[cyan]Checking for updates and updating if necessary...[/]")
|
|
||||||
success, release_json = update_app(force=force)
|
|
||||||
|
|
||||||
if not release_json:
|
|
||||||
print(
|
|
||||||
"[red]Could not check for updates. Please check your internet connection.[/]"
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
latest_version = release_json.get("tag_name", "unknown")
|
|
||||||
print(f"[green]Successfully updated to version {latest_version}![/]")
|
|
||||||
else:
|
|
||||||
if force:
|
|
||||||
print(
|
|
||||||
"[red]Update failed. Please check the error messages above.[/]"
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
# If not forced and update failed, it might be because already up to date
|
|
||||||
# The update_app function already prints appropriate messages
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\n[yellow]Update cancelled by user.[/]")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[red]An error occurred during update: {e}[/]")
|
|
||||||
# Get trace option from parent context
|
|
||||||
trace = ctx.parent.params.get("trace", False) if ctx.parent else False
|
|
||||||
if trace:
|
|
||||||
raise
|
|
||||||
sys.exit(1)
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
from .generate import generate_config_ini_from_app_model
|
|
||||||
from .loader import ConfigLoader
|
|
||||||
|
|
||||||
__all__ = ["ConfigLoader", "generate_config_ini_from_app_model"]
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
from typing import Dict, Optional, Union
|
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
|
||||||
|
|
||||||
from ...libs.media_api.params import MediaSearchParams, UserMediaListSearchParams
|
|
||||||
from ...libs.media_api.types import MediaItem, PageInfo
|
|
||||||
from ...libs.provider.anime.types import Anime, SearchResults, Server
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: is internal directive a good name
|
|
||||||
class InternalDirective(Enum):
|
|
||||||
MAIN = "MAIN"
|
|
||||||
|
|
||||||
BACK = "BACK"
|
|
||||||
|
|
||||||
BACKX2 = "BACKX2"
|
|
||||||
|
|
||||||
BACKX3 = "BACKX3"
|
|
||||||
|
|
||||||
BACKX4 = "BACKX4"
|
|
||||||
|
|
||||||
EXIT = "EXIT"
|
|
||||||
|
|
||||||
CONFIG_EDIT = "CONFIG_EDIT"
|
|
||||||
|
|
||||||
RELOAD = "RELOAD"
|
|
||||||
|
|
||||||
|
|
||||||
class MenuName(Enum):
|
|
||||||
MAIN = "MAIN"
|
|
||||||
AUTH = "AUTH"
|
|
||||||
EPISODES = "EPISODES"
|
|
||||||
RESULTS = "RESULTS"
|
|
||||||
SERVERS = "SERVERS"
|
|
||||||
WATCH_HISTORY = "WATCH_HISTORY"
|
|
||||||
PROVIDER_SEARCH = "PROVIDER_SEARCH"
|
|
||||||
PLAYER_CONTROLS = "PLAYER_CONTROLS"
|
|
||||||
USER_MEDIA_LIST = "USER_MEDIA_LIST"
|
|
||||||
SESSION_MANAGEMENT = "SESSION_MANAGEMENT"
|
|
||||||
MEDIA_ACTIONS = "MEDIA_ACTIONS"
|
|
||||||
DOWNLOADS = "DOWNLOADS"
|
|
||||||
DYNAMIC_SEARCH = "DYNAMIC_SEARCH"
|
|
||||||
MEDIA_REVIEW = "MEDIA_REVIEW"
|
|
||||||
MEDIA_CHARACTERS = "MEDIA_CHARACTERS"
|
|
||||||
MEDIA_AIRING_SCHEDULE = "MEDIA_AIRING_SCHEDULE"
|
|
||||||
PLAY_DOWNLOADS = "PLAY_DOWNLOADS"
|
|
||||||
DOWNLOADS_PLAYER_CONTROLS = "DOWNLOADS_PLAYER_CONTROLS"
|
|
||||||
DOWNLOAD_EPISODES = "DOWNLOAD_EPISODES"
|
|
||||||
|
|
||||||
|
|
||||||
class StateModel(BaseModel):
|
|
||||||
model_config = ConfigDict(frozen=True)
|
|
||||||
|
|
||||||
|
|
||||||
class MediaApiState(StateModel):
|
|
||||||
search_result: Optional[Dict[int, MediaItem]] = None
|
|
||||||
search_params: Optional[Union[MediaSearchParams, UserMediaListSearchParams]] = None
|
|
||||||
page_info: Optional[PageInfo] = None
|
|
||||||
media_id: Optional[int] = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def media_item(self) -> Optional[MediaItem]:
|
|
||||||
if self.search_result and self.media_id:
|
|
||||||
return self.search_result[self.media_id]
|
|
||||||
|
|
||||||
|
|
||||||
class ProviderState(StateModel):
|
|
||||||
search_results: Optional[SearchResults] = None
|
|
||||||
anime: Optional[Anime] = None
|
|
||||||
episode: Optional[str] = None
|
|
||||||
servers: Optional[Dict[str, Server]] = None
|
|
||||||
server_name: Optional[str] = None
|
|
||||||
start_time: Optional[str] = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def server(self) -> Optional[Server]:
|
|
||||||
if self.servers and self.server_name:
|
|
||||||
return self.servers[self.server_name]
|
|
||||||
|
|
||||||
|
|
||||||
class State(StateModel):
|
|
||||||
menu_name: MenuName
|
|
||||||
provider: ProviderState = Field(default_factory=ProviderState)
|
|
||||||
media_api: MediaApiState = Field(default_factory=MediaApiState)
|
|
||||||
@@ -1,873 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from enum import Enum
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
|
||||||
|
|
||||||
|
|
||||||
# ENUMS
|
|
||||||
class MediaStatus(Enum):
|
|
||||||
FINISHED = "FINISHED"
|
|
||||||
RELEASING = "RELEASING"
|
|
||||||
NOT_YET_RELEASED = "NOT_YET_RELEASED"
|
|
||||||
CANCELLED = "CANCELLED"
|
|
||||||
HIATUS = "HIATUS"
|
|
||||||
|
|
||||||
|
|
||||||
class MediaType(Enum):
|
|
||||||
ANIME = "ANIME"
|
|
||||||
MANGA = "MANGA"
|
|
||||||
|
|
||||||
|
|
||||||
class UserMediaListStatus(Enum):
|
|
||||||
PLANNING = "planning"
|
|
||||||
WATCHING = "watching"
|
|
||||||
COMPLETED = "completed"
|
|
||||||
DROPPED = "dropped"
|
|
||||||
PAUSED = "paused"
|
|
||||||
REPEATING = "repeating"
|
|
||||||
|
|
||||||
|
|
||||||
class MediaGenre(Enum):
|
|
||||||
ACTION = "Action"
|
|
||||||
ADVENTURE = "Adventure"
|
|
||||||
COMEDY = "Comedy"
|
|
||||||
DRAMA = "Drama"
|
|
||||||
ECCHI = "Ecchi"
|
|
||||||
FANTASY = "Fantasy"
|
|
||||||
HORROR = "Horror"
|
|
||||||
MAHOU_SHOUJO = "Mahou Shoujo"
|
|
||||||
MECHA = "Mecha"
|
|
||||||
MUSIC = "Music"
|
|
||||||
MYSTERY = "Mystery"
|
|
||||||
PSYCHOLOGICAL = "Psychological"
|
|
||||||
ROMANCE = "Romance"
|
|
||||||
SCI_FI = "Sci-Fi"
|
|
||||||
SLICE_OF_LIFE = "Slice of Life"
|
|
||||||
SPORTS = "Sports"
|
|
||||||
SUPERNATURAL = "Supernatural"
|
|
||||||
THRILLER = "Thriller"
|
|
||||||
HENTAI = "Hentai"
|
|
||||||
|
|
||||||
|
|
||||||
class MediaFormat(Enum):
|
|
||||||
TV = "TV"
|
|
||||||
TV_SHORT = "TV_SHORT"
|
|
||||||
MOVIE = "MOVIE"
|
|
||||||
MANGA = "MANGA"
|
|
||||||
SPECIAL = "SPECIAL"
|
|
||||||
OVA = "OVA"
|
|
||||||
ONA = "ONA"
|
|
||||||
MUSIC = "MUSIC"
|
|
||||||
NOVEL = "NOVEL"
|
|
||||||
ONE_SHOT = "ONE_SHOT"
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationType(Enum):
|
|
||||||
AIRING = "AIRING"
|
|
||||||
RELATED_MEDIA_ADDITION = "RELATED_MEDIA_ADDITION"
|
|
||||||
MEDIA_DATA_CHANGE = "MEDIA_DATA_CHANGE"
|
|
||||||
# ... add other types as needed
|
|
||||||
|
|
||||||
|
|
||||||
# MODELS
|
|
||||||
class BaseMediaApiModel(BaseModel):
|
|
||||||
model_config = ConfigDict(frozen=True)
|
|
||||||
|
|
||||||
|
|
||||||
class MediaImage(BaseMediaApiModel):
|
|
||||||
"""A generic representation of media imagery URLs."""
|
|
||||||
|
|
||||||
large: str
|
|
||||||
medium: Optional[str] = None
|
|
||||||
extra_large: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class MediaTitle(BaseMediaApiModel):
|
|
||||||
"""A generic representation of media titles."""
|
|
||||||
|
|
||||||
english: str
|
|
||||||
romaji: Optional[str] = None
|
|
||||||
native: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class MediaTrailer(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a media trailer."""
|
|
||||||
|
|
||||||
id: str
|
|
||||||
site: str # e.g., "youtube"
|
|
||||||
thumbnail_url: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class AiringSchedule(BaseMediaApiModel):
|
|
||||||
"""A generic representation of the next airing episode."""
|
|
||||||
|
|
||||||
episode: int
|
|
||||||
airing_at: Optional[datetime] = None
|
|
||||||
|
|
||||||
|
|
||||||
class CharacterName(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a character's name."""
|
|
||||||
|
|
||||||
first: Optional[str] = None
|
|
||||||
middle: Optional[str] = None
|
|
||||||
last: Optional[str] = None
|
|
||||||
full: Optional[str] = None
|
|
||||||
native: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class CharacterImage(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a character's image."""
|
|
||||||
|
|
||||||
medium: Optional[str] = None
|
|
||||||
large: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Character(BaseMediaApiModel):
|
|
||||||
"""A generic representation of an anime character."""
|
|
||||||
|
|
||||||
id: Optional[int] = None
|
|
||||||
name: CharacterName
|
|
||||||
image: Optional[CharacterImage] = None
|
|
||||||
description: Optional[str] = None
|
|
||||||
gender: Optional[str] = None
|
|
||||||
age: Optional[str] = None
|
|
||||||
blood_type: Optional[str] = None
|
|
||||||
favourites: Optional[int] = None
|
|
||||||
date_of_birth: Optional[datetime] = None
|
|
||||||
|
|
||||||
|
|
||||||
class AiringScheduleItem(BaseMediaApiModel):
|
|
||||||
"""A generic representation of an airing schedule item."""
|
|
||||||
|
|
||||||
episode: int
|
|
||||||
airing_at: Optional[datetime] = None
|
|
||||||
time_until_airing: Optional[int] = None # In seconds
|
|
||||||
|
|
||||||
|
|
||||||
class CharacterSearchResult(BaseMediaApiModel):
|
|
||||||
"""A generic representation of character search results."""
|
|
||||||
|
|
||||||
characters: List[Character] = Field(default_factory=list)
|
|
||||||
page_info: Optional[PageInfo] = None
|
|
||||||
|
|
||||||
|
|
||||||
class AiringScheduleResult(BaseMediaApiModel):
|
|
||||||
"""A generic representation of airing schedule results."""
|
|
||||||
|
|
||||||
schedule_items: List[AiringScheduleItem] = Field(default_factory=list)
|
|
||||||
page_info: Optional[PageInfo] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Studio(BaseMediaApiModel):
|
|
||||||
"""A generic representation of an animation studio."""
|
|
||||||
|
|
||||||
id: Optional[int] = None
|
|
||||||
name: Optional[str] = None
|
|
||||||
favourites: Optional[int] = None
|
|
||||||
is_animation_studio: Optional[bool] = None
|
|
||||||
|
|
||||||
|
|
||||||
class MediaTagItem(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a descriptive tag."""
|
|
||||||
|
|
||||||
name: MediaTag
|
|
||||||
rank: Optional[int] = None # Percentage relevance from 0-100
|
|
||||||
|
|
||||||
|
|
||||||
class StreamingEpisode(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a streaming episode."""
|
|
||||||
|
|
||||||
title: str
|
|
||||||
thumbnail: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class UserListItem(BaseMediaApiModel):
|
|
||||||
"""Generic representation of a user's list status for a media item."""
|
|
||||||
|
|
||||||
id: Optional[int] = None
|
|
||||||
status: Optional[UserMediaListStatus] = None
|
|
||||||
progress: Optional[int] = None
|
|
||||||
score: Optional[float] = None
|
|
||||||
repeat: Optional[int] = None
|
|
||||||
notes: Optional[str] = None
|
|
||||||
start_date: Optional[datetime] = None
|
|
||||||
completed_at: Optional[datetime] = None
|
|
||||||
created_at: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class MediaItem(BaseMediaApiModel):
|
|
||||||
id: int
|
|
||||||
title: MediaTitle
|
|
||||||
id_mal: Optional[int] = None
|
|
||||||
type: MediaType = MediaType.ANIME
|
|
||||||
status: MediaStatus = MediaStatus.FINISHED
|
|
||||||
format: Optional[MediaFormat] = MediaFormat.TV
|
|
||||||
|
|
||||||
cover_image: Optional[MediaImage] = None
|
|
||||||
banner_image: Optional[str] = None
|
|
||||||
trailer: Optional[MediaTrailer] = None
|
|
||||||
|
|
||||||
description: Optional[str] = None
|
|
||||||
episodes: Optional[int] = None
|
|
||||||
duration: Optional[int] = None # In minutes
|
|
||||||
genres: List[MediaGenre] = Field(default_factory=list)
|
|
||||||
tags: List[MediaTagItem] = Field(default_factory=list)
|
|
||||||
studios: List[Studio] = Field(default_factory=list)
|
|
||||||
synonymns: List[str] = Field(default_factory=list)
|
|
||||||
|
|
||||||
average_score: Optional[float] = None
|
|
||||||
popularity: Optional[int] = None
|
|
||||||
favourites: Optional[int] = None
|
|
||||||
|
|
||||||
start_date: Optional[datetime] = None
|
|
||||||
end_date: Optional[datetime] = None
|
|
||||||
|
|
||||||
next_airing: Optional[AiringSchedule] = None
|
|
||||||
|
|
||||||
# streaming episodes
|
|
||||||
streaming_episodes: Dict[str, StreamingEpisode] = Field(default_factory=dict)
|
|
||||||
|
|
||||||
# user related
|
|
||||||
user_status: Optional[UserListItem] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Notification(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a user notification."""
|
|
||||||
|
|
||||||
id: int
|
|
||||||
type: NotificationType
|
|
||||||
episode: Optional[int] = None
|
|
||||||
contexts: List[str] = Field(default_factory=list)
|
|
||||||
created_at: datetime
|
|
||||||
media: MediaItem
|
|
||||||
|
|
||||||
|
|
||||||
class PageInfo(BaseMediaApiModel):
|
|
||||||
"""Generic pagination information."""
|
|
||||||
|
|
||||||
total: int = 1
|
|
||||||
current_page: int = 1
|
|
||||||
has_next_page: bool = False
|
|
||||||
per_page: int = 15
|
|
||||||
|
|
||||||
|
|
||||||
class MediaSearchResult(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a page of media search results."""
|
|
||||||
|
|
||||||
page_info: PageInfo
|
|
||||||
media: List[MediaItem] = Field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a user's profile."""
|
|
||||||
|
|
||||||
id: int
|
|
||||||
name: str
|
|
||||||
avatar_url: Optional[str] = None
|
|
||||||
banner_url: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class Reviewer(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a user who wrote a review."""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
avatar_url: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class MediaReview(BaseMediaApiModel):
|
|
||||||
"""A generic representation of a media review."""
|
|
||||||
|
|
||||||
summary: Optional[str] = None
|
|
||||||
body: str
|
|
||||||
user: Reviewer
|
|
||||||
|
|
||||||
|
|
||||||
# ENUMS
|
|
||||||
|
|
||||||
|
|
||||||
class MediaTag(Enum):
|
|
||||||
# Cast
|
|
||||||
POLYAMOROUS = "Polyamorous"
|
|
||||||
|
|
||||||
# Cast Main Cast
|
|
||||||
ANTI_HERO = "Anti-Hero"
|
|
||||||
ELDERLY_PROTAGONIST = "Elderly Protagonist"
|
|
||||||
ENSEMBLE_CAST = "Ensemble Cast"
|
|
||||||
ESTRANGED_FAMILY = "Estranged Family"
|
|
||||||
FEMALE_PROTAGONIST = "Female Protagonist"
|
|
||||||
MALE_PROTAGONIST = "Male Protagonist"
|
|
||||||
PRIMARILY_ADULT_CAST = "Primarily Adult Cast"
|
|
||||||
PRIMARILY_ANIMAL_CAST = "Primarily Animal Cast"
|
|
||||||
PRIMARILY_CHILD_CAST = "Primarily Child Cast"
|
|
||||||
PRIMARILY_FEMALE_CAST = "Primarily Female Cast"
|
|
||||||
PRIMARILY_MALE_CAST = "Primarily Male Cast"
|
|
||||||
PRIMARILY_TEEN_CAST = "Primarily Teen Cast"
|
|
||||||
|
|
||||||
# Cast Traits
|
|
||||||
AGE_REGRESSION = "Age Regression"
|
|
||||||
AGENDER = "Agender"
|
|
||||||
ALIENS = "Aliens"
|
|
||||||
AMNESIA = "Amnesia"
|
|
||||||
ANGELS = "Angels"
|
|
||||||
ANTHROPOMORPHISM = "Anthropomorphism"
|
|
||||||
AROMANTIC = "Aromantic"
|
|
||||||
ARRANGED_MARRIAGE = "Arranged Marriage"
|
|
||||||
ARTIFICIAL_INTELLIGENCE = "Artificial Intelligence"
|
|
||||||
ASEXUAL = "Asexual"
|
|
||||||
BISEXUAL = "Bisexual"
|
|
||||||
BUTLER = "Butler"
|
|
||||||
CENTAUR = "Centaur"
|
|
||||||
CHIMERA = "Chimera"
|
|
||||||
CHUUNIBYOU = "Chuunibyou"
|
|
||||||
CLONE = "Clone"
|
|
||||||
COSPLAY = "Cosplay"
|
|
||||||
COWBOYS = "Cowboys"
|
|
||||||
CROSSDRESSING = "Crossdressing"
|
|
||||||
CYBORG = "Cyborg"
|
|
||||||
DELINQUENTS = "Delinquents"
|
|
||||||
DEMONS = "Demons"
|
|
||||||
DETECTIVE = "Detective"
|
|
||||||
DINOSAURS = "Dinosaurs"
|
|
||||||
DISABILITY = "Disability"
|
|
||||||
DISSOCIATIVE_IDENTITIES = "Dissociative Identities"
|
|
||||||
DRAGONS = "Dragons"
|
|
||||||
DULLAHAN = "Dullahan"
|
|
||||||
ELF = "Elf"
|
|
||||||
FAIRY = "Fairy"
|
|
||||||
FEMBOY = "Femboy"
|
|
||||||
GHOST = "Ghost"
|
|
||||||
GOBLIN = "Goblin"
|
|
||||||
GODS = "Gods"
|
|
||||||
GYARU = "Gyaru"
|
|
||||||
HIKIKOMORI = "Hikikomori"
|
|
||||||
HOMELESS = "Homeless"
|
|
||||||
IDOL = "Idol"
|
|
||||||
KEMONOMIMI = "Kemonomimi"
|
|
||||||
KUUDERE = "Kuudere"
|
|
||||||
MAIDS = "Maids"
|
|
||||||
MERMAID = "Mermaid"
|
|
||||||
MONSTER_BOY = "Monster Boy"
|
|
||||||
MONSTER_GIRL = "Monster Girl"
|
|
||||||
NEKOMIMI = "Nekomimi"
|
|
||||||
NINJA = "Ninja"
|
|
||||||
NUDITY = "Nudity"
|
|
||||||
NUN = "Nun"
|
|
||||||
OFFICE_LADY = "Office Lady"
|
|
||||||
OIRAN = "Oiran"
|
|
||||||
OJOU_SAMA = "Ojou-sama"
|
|
||||||
ORPHAN = "Orphan"
|
|
||||||
PIRATES = "Pirates"
|
|
||||||
ROBOTS = "Robots"
|
|
||||||
SAMURAI = "Samurai"
|
|
||||||
SHRINE_MAIDEN = "Shrine Maiden"
|
|
||||||
SKELETON = "Skeleton"
|
|
||||||
SUCCUBUS = "Succubus"
|
|
||||||
TANNED_SKIN = "Tanned Skin"
|
|
||||||
TEACHER = "Teacher"
|
|
||||||
TOMBOY = "Tomboy"
|
|
||||||
TRANSGENDER = "Transgender"
|
|
||||||
TSUNDERE = "Tsundere"
|
|
||||||
TWINS = "Twins"
|
|
||||||
VAMPIRE = "Vampire"
|
|
||||||
VETERINARIAN = "Veterinarian"
|
|
||||||
VIKINGS = "Vikings"
|
|
||||||
VILLAINESS = "Villainess"
|
|
||||||
VTUBER = "VTuber"
|
|
||||||
WEREWOLF = "Werewolf"
|
|
||||||
WITCH = "Witch"
|
|
||||||
YANDERE = "Yandere"
|
|
||||||
YOUKAI = "Youkai"
|
|
||||||
ZOMBIE = "Zombie"
|
|
||||||
|
|
||||||
# Demographic
|
|
||||||
JOSEI = "Josei"
|
|
||||||
KIDS = "Kids"
|
|
||||||
SEINEN = "Seinen"
|
|
||||||
SHOUJO = "Shoujo"
|
|
||||||
SHOUNEN = "Shounen"
|
|
||||||
|
|
||||||
# Setting
|
|
||||||
MATRIARCHY = "Matriarchy"
|
|
||||||
|
|
||||||
# Setting Scene
|
|
||||||
BAR = "Bar"
|
|
||||||
BOARDING_SCHOOL = "Boarding School"
|
|
||||||
CAMPING = "Camping"
|
|
||||||
CIRCUS = "Circus"
|
|
||||||
COASTAL = "Coastal"
|
|
||||||
COLLEGE = "College"
|
|
||||||
DESERT = "Desert"
|
|
||||||
DUNGEON = "Dungeon"
|
|
||||||
FOREIGN = "Foreign"
|
|
||||||
INN = "Inn"
|
|
||||||
KONBINI = "Konbini"
|
|
||||||
NATURAL_DISASTER = "Natural Disaster"
|
|
||||||
OFFICE = "Office"
|
|
||||||
OUTDOOR_ACTIVITIES = "Outdoor Activities"
|
|
||||||
PRISON = "Prison"
|
|
||||||
RESTAURANT = "Restaurant"
|
|
||||||
RURAL = "Rural"
|
|
||||||
SCHOOL = "School"
|
|
||||||
SCHOOL_CLUB = "School Club"
|
|
||||||
SNOWSCAPE = "Snowscape"
|
|
||||||
URBAN = "Urban"
|
|
||||||
WILDERNESS = "Wilderness"
|
|
||||||
WORK = "Work"
|
|
||||||
|
|
||||||
# Setting Time
|
|
||||||
ACHRONOLOGICAL_ORDER = "Achronological Order"
|
|
||||||
ANACHRONISM = "Anachronism"
|
|
||||||
ANCIENT_CHINA = "Ancient China"
|
|
||||||
DYSTOPIAN = "Dystopian"
|
|
||||||
HISTORICAL = "Historical"
|
|
||||||
MEDIEVAL = "Medieval"
|
|
||||||
TIME_SKIP = "Time Skip"
|
|
||||||
|
|
||||||
# Setting Universe
|
|
||||||
AFTERLIFE = "Afterlife"
|
|
||||||
ALTERNATE_UNIVERSE = "Alternate Universe"
|
|
||||||
AUGMENTED_REALITY = "Augmented Reality"
|
|
||||||
OMEGAVERSE = "Omegaverse"
|
|
||||||
POST_APOCALYPTIC = "Post-Apocalyptic"
|
|
||||||
SPACE = "Space"
|
|
||||||
URBAN_FANTASY = "Urban Fantasy"
|
|
||||||
VIRTUAL_WORLD = "Virtual World"
|
|
||||||
|
|
||||||
# Sexual Content
|
|
||||||
AHEGAO = "Ahegao"
|
|
||||||
AMPUTATION = "Amputation"
|
|
||||||
ANAL_SEX = "Anal Sex"
|
|
||||||
ARMPITS = "Armpits"
|
|
||||||
ASHIKOKI = "Ashikoki"
|
|
||||||
ASPHYXIATION = "Asphyxiation"
|
|
||||||
BONDAGE = "Bondage"
|
|
||||||
BOOBJOB = "Boobjob"
|
|
||||||
CERVIX_PENETRATION = "Cervix Penetration"
|
|
||||||
CHEATING = "Cheating"
|
|
||||||
CUMFLATION = "Cumflation"
|
|
||||||
CUNNILINGUS = "Cunnilingus"
|
|
||||||
DEEPTHROAT = "Deepthroat"
|
|
||||||
DEFLORATION = "Defloration"
|
|
||||||
DILF = "DILF"
|
|
||||||
DOUBLE_PENETRATION = "Double Penetration"
|
|
||||||
EROTIC_PIERCINGS = "Erotic Piercings"
|
|
||||||
EXHIBITIONISM = "Exhibitionism"
|
|
||||||
FACIAL = "Facial"
|
|
||||||
FEET = "Feet"
|
|
||||||
FELLATIO = "Fellatio"
|
|
||||||
FEMDOM = "Femdom"
|
|
||||||
FISTING = "Fisting"
|
|
||||||
FLAT_CHEST = "Flat Chest"
|
|
||||||
FUTANARI = "Futanari"
|
|
||||||
GROUP_SEX = "Group Sex"
|
|
||||||
HAIR_PULLING = "Hair Pulling"
|
|
||||||
HANDJOB = "Handjob"
|
|
||||||
HUMAN_PET = "Human Pet"
|
|
||||||
HYPERSEXUALITY = "Hypersexuality"
|
|
||||||
INCEST = "Incest"
|
|
||||||
INSEKI = "Inseki"
|
|
||||||
IRRUMATIO = "Irrumatio"
|
|
||||||
LACTATION = "Lactation"
|
|
||||||
LARGE_BREASTS = "Large Breasts"
|
|
||||||
MALE_PREGNANCY = "Male Pregnancy"
|
|
||||||
MASOCHISM = "Masochism"
|
|
||||||
MASTURBATION = "Masturbation"
|
|
||||||
MATING_PRESS = "Mating Press"
|
|
||||||
MILF = "MILF"
|
|
||||||
NAKADASHI = "Nakadashi"
|
|
||||||
NETORARE = "Netorare"
|
|
||||||
NETORASE = "Netorase"
|
|
||||||
NETORI = "Netori"
|
|
||||||
PET_PLAY = "Pet Play"
|
|
||||||
PROSTITUTION = "Prostitution"
|
|
||||||
PUBLIC_SEX = "Public Sex"
|
|
||||||
RAPE = "Rape"
|
|
||||||
RIMJOB = "Rimjob"
|
|
||||||
SADISM = "Sadism"
|
|
||||||
SCAT = "Scat"
|
|
||||||
SCISSORING = "Scissoring"
|
|
||||||
SEX_TOYS = "Sex Toys"
|
|
||||||
SHIMAIDON = "Shimaidon"
|
|
||||||
SQUIRTING = "Squirting"
|
|
||||||
SUMATA = "Sumata"
|
|
||||||
SWEAT = "Sweat"
|
|
||||||
TENTACLES = "Tentacles"
|
|
||||||
THREESOME = "Threesome"
|
|
||||||
VIRGINITY = "Virginity"
|
|
||||||
VORE = "Vore"
|
|
||||||
VOYEUR = "Voyeur"
|
|
||||||
WATERSPORTS = "Watersports"
|
|
||||||
ZOOPHILIA = "Zoophilia"
|
|
||||||
|
|
||||||
# Technical
|
|
||||||
_4_KOMA = "4-koma"
|
|
||||||
ACHROMATIC = "Achromatic"
|
|
||||||
ADVERTISEMENT = "Advertisement"
|
|
||||||
ANTHOLOGY = "Anthology"
|
|
||||||
CGI = "CGI"
|
|
||||||
EPISODIC = "Episodic"
|
|
||||||
FLASH = "Flash"
|
|
||||||
FULL_CGI = "Full CGI"
|
|
||||||
FULL_COLOR = "Full Color"
|
|
||||||
LONG_STRIP = "Long Strip"
|
|
||||||
MIXED_MEDIA = "Mixed Media"
|
|
||||||
NO_DIALOGUE = "No Dialogue"
|
|
||||||
NON_FICTION = "Non-fiction"
|
|
||||||
POV = "POV"
|
|
||||||
PUPPETRY = "Puppetry"
|
|
||||||
ROTOSCOPING = "Rotoscoping"
|
|
||||||
STOP_MOTION = "Stop Motion"
|
|
||||||
VERTICAL_VIDEO = "Vertical Video"
|
|
||||||
|
|
||||||
# Theme Action
|
|
||||||
ARCHERY = "Archery"
|
|
||||||
BATTLE_ROYALE = "Battle Royale"
|
|
||||||
ESPIONAGE = "Espionage"
|
|
||||||
FUGITIVE = "Fugitive"
|
|
||||||
GUNS = "Guns"
|
|
||||||
MARTIAL_ARTS = "Martial Arts"
|
|
||||||
SPEARPLAY = "Spearplay"
|
|
||||||
SWORDPLAY = "Swordplay"
|
|
||||||
|
|
||||||
# Theme Arts
|
|
||||||
ACTING = "Acting"
|
|
||||||
CALLIGRAPHY = "Calligraphy"
|
|
||||||
CLASSIC_LITERATURE = "Classic Literature"
|
|
||||||
DRAWING = "Drawing"
|
|
||||||
FASHION = "Fashion"
|
|
||||||
FOOD = "Food"
|
|
||||||
MAKEUP = "Makeup"
|
|
||||||
PHOTOGRAPHY = "Photography"
|
|
||||||
RAKUGO = "Rakugo"
|
|
||||||
WRITING = "Writing"
|
|
||||||
|
|
||||||
# Theme Arts-Music
|
|
||||||
BAND = "Band"
|
|
||||||
CLASSICAL_MUSIC = "Classical Music"
|
|
||||||
DANCING = "Dancing"
|
|
||||||
HIP_HOP_MUSIC = "Hip-hop Music"
|
|
||||||
JAZZ_MUSIC = "Jazz Music"
|
|
||||||
METAL_MUSIC = "Metal Music"
|
|
||||||
MUSICAL_THEATER = "Musical Theater"
|
|
||||||
ROCK_MUSIC = "Rock Music"
|
|
||||||
|
|
||||||
# Theme Comedy
|
|
||||||
PARODY = "Parody"
|
|
||||||
SATIRE = "Satire"
|
|
||||||
SLAPSTICK = "Slapstick"
|
|
||||||
SURREAL_COMEDY = "Surreal Comedy"
|
|
||||||
|
|
||||||
# Theme Drama
|
|
||||||
BULLYING = "Bullying"
|
|
||||||
CLASS_STRUGGLE = "Class Struggle"
|
|
||||||
COMING_OF_AGE = "Coming of Age"
|
|
||||||
CONSPIRACY = "Conspiracy"
|
|
||||||
ECO_HORROR = "Eco-Horror"
|
|
||||||
FAKE_RELATIONSHIP = "Fake Relationship"
|
|
||||||
KINGDOM_MANAGEMENT = "Kingdom Management"
|
|
||||||
REHABILITATION = "Rehabilitation"
|
|
||||||
REVENGE = "Revenge"
|
|
||||||
SUICIDE = "Suicide"
|
|
||||||
TRAGEDY = "Tragedy"
|
|
||||||
|
|
||||||
# Theme Fantasy
|
|
||||||
ALCHEMY = "Alchemy"
|
|
||||||
BODY_SWAPPING = "Body Swapping"
|
|
||||||
CULTIVATION = "Cultivation"
|
|
||||||
CURSES = "Curses"
|
|
||||||
EXORCISM = "Exorcism"
|
|
||||||
FAIRY_TALE = "Fairy Tale"
|
|
||||||
HENSHIN = "Henshin"
|
|
||||||
ISEKAI = "Isekai"
|
|
||||||
KAIJU = "Kaiju"
|
|
||||||
MAGIC = "Magic"
|
|
||||||
MYTHOLOGY = "Mythology"
|
|
||||||
NECROMANCY = "Necromancy"
|
|
||||||
SHAPESHIFTING = "Shapeshifting"
|
|
||||||
STEAMPUNK = "Steampunk"
|
|
||||||
SUPER_POWER = "Super Power"
|
|
||||||
SUPERHERO = "Superhero"
|
|
||||||
WUXIA = "Wuxia"
|
|
||||||
|
|
||||||
# Theme Game
|
|
||||||
BOARD_GAME = "Board Game"
|
|
||||||
E_SPORTS = "E-Sports"
|
|
||||||
VIDEO_GAMES = "Video Games"
|
|
||||||
|
|
||||||
# Theme Game-Card & Board Game
|
|
||||||
CARD_BATTLE = "Card Battle"
|
|
||||||
GO = "Go"
|
|
||||||
KARUTA = "Karuta"
|
|
||||||
MAHJONG = "Mahjong"
|
|
||||||
POKER = "Poker"
|
|
||||||
SHOGI = "Shogi"
|
|
||||||
|
|
||||||
# Theme Game-Sport
|
|
||||||
ACROBATICS = "Acrobatics"
|
|
||||||
AIRSOFT = "Airsoft"
|
|
||||||
AMERICAN_FOOTBALL = "American Football"
|
|
||||||
ATHLETICS = "Athletics"
|
|
||||||
BADMINTON = "Badminton"
|
|
||||||
BASEBALL = "Baseball"
|
|
||||||
BASKETBALL = "Basketball"
|
|
||||||
BOWLING = "Bowling"
|
|
||||||
BOXING = "Boxing"
|
|
||||||
CHEERLEADING = "Cheerleading"
|
|
||||||
CYCLING = "Cycling"
|
|
||||||
FENCING = "Fencing"
|
|
||||||
FISHING = "Fishing"
|
|
||||||
FITNESS = "Fitness"
|
|
||||||
FOOTBALL = "Football"
|
|
||||||
GOLF = "Golf"
|
|
||||||
HANDBALL = "Handball"
|
|
||||||
ICE_SKATING = "Ice Skating"
|
|
||||||
JUDO = "Judo"
|
|
||||||
LACROSSE = "Lacrosse"
|
|
||||||
PARKOUR = "Parkour"
|
|
||||||
RUGBY = "Rugby"
|
|
||||||
SCUBA_DIVING = "Scuba Diving"
|
|
||||||
SKATEBOARDING = "Skateboarding"
|
|
||||||
SUMO = "Sumo"
|
|
||||||
SURFING = "Surfing"
|
|
||||||
SWIMMING = "Swimming"
|
|
||||||
TABLE_TENNIS = "Table Tennis"
|
|
||||||
TENNIS = "Tennis"
|
|
||||||
VOLLEYBALL = "Volleyball"
|
|
||||||
WRESTLING = "Wrestling"
|
|
||||||
|
|
||||||
# Theme Other
|
|
||||||
ADOPTION = "Adoption"
|
|
||||||
ANIMALS = "Animals"
|
|
||||||
ASTRONOMY = "Astronomy"
|
|
||||||
AUTOBIOGRAPHICAL = "Autobiographical"
|
|
||||||
BIOGRAPHICAL = "Biographical"
|
|
||||||
BLACKMAIL = "Blackmail"
|
|
||||||
BODY_HORROR = "Body Horror"
|
|
||||||
BODY_IMAGE = "Body Image"
|
|
||||||
CANNIBALISM = "Cannibalism"
|
|
||||||
CHIBI = "Chibi"
|
|
||||||
COSMIC_HORROR = "Cosmic Horror"
|
|
||||||
CREATURE_TAMING = "Creature Taming"
|
|
||||||
CRIME = "Crime"
|
|
||||||
CROSSOVER = "Crossover"
|
|
||||||
DEATH_GAME = "Death Game"
|
|
||||||
DENPA = "Denpa"
|
|
||||||
DRUGS = "Drugs"
|
|
||||||
ECONOMICS = "Economics"
|
|
||||||
EDUCATIONAL = "Educational"
|
|
||||||
ENVIRONMENTAL = "Environmental"
|
|
||||||
ERO_GURO = "Ero Guro"
|
|
||||||
FILMMAKING = "Filmmaking"
|
|
||||||
FOUND_FAMILY = "Found Family"
|
|
||||||
GAMBLING = "Gambling"
|
|
||||||
GENDER_BENDING = "Gender Bending"
|
|
||||||
GORE = "Gore"
|
|
||||||
INDIGENOUS_CULTURES = "Indigenous Cultures"
|
|
||||||
LANGUAGE_BARRIER = "Language Barrier"
|
|
||||||
LGBTQ_PLUS_THEMES = "LGBTQ+ Themes"
|
|
||||||
LOST_CIVILIZATION = "Lost Civilization"
|
|
||||||
MARRIAGE = "Marriage"
|
|
||||||
MEDICINE = "Medicine"
|
|
||||||
MEMORY_MANIPULATION = "Memory Manipulation"
|
|
||||||
META = "Meta"
|
|
||||||
MOUNTAINEERING = "Mountaineering"
|
|
||||||
NOIR = "Noir"
|
|
||||||
OTAKU_CULTURE = "Otaku Culture"
|
|
||||||
PANDEMIC = "Pandemic"
|
|
||||||
PHILOSOPHY = "Philosophy"
|
|
||||||
POLITICS = "Politics"
|
|
||||||
PREGNANCY = "Pregnancy"
|
|
||||||
PROXY_BATTLE = "Proxy Battle"
|
|
||||||
PSYCHOSEXUAL = "Psychosexual"
|
|
||||||
REINCARNATION = "Reincarnation"
|
|
||||||
RELIGION = "Religion"
|
|
||||||
RESCUE = "Rescue"
|
|
||||||
ROYAL_AFFAIRS = "Royal Affairs"
|
|
||||||
SLAVERY = "Slavery"
|
|
||||||
SOFTWARE_DEVELOPMENT = "Software Development"
|
|
||||||
SURVIVAL = "Survival"
|
|
||||||
TERRORISM = "Terrorism"
|
|
||||||
TORTURE = "Torture"
|
|
||||||
TRAVEL = "Travel"
|
|
||||||
VOCAL_SYNTH = "Vocal Synth"
|
|
||||||
WAR = "War"
|
|
||||||
|
|
||||||
# Theme Other-Organisations
|
|
||||||
ASSASSINS = "Assassins"
|
|
||||||
CRIMINAL_ORGANIZATION = "Criminal Organization"
|
|
||||||
CULT = "Cult"
|
|
||||||
FIREFIGHTERS = "Firefighters"
|
|
||||||
GANGS = "Gangs"
|
|
||||||
MAFIA = "Mafia"
|
|
||||||
MILITARY = "Military"
|
|
||||||
POLICE = "Police"
|
|
||||||
TRIADS = "Triads"
|
|
||||||
YAKUZA = "Yakuza"
|
|
||||||
|
|
||||||
# Theme Other-Vehicle
|
|
||||||
AVIATION = "Aviation"
|
|
||||||
CARS = "Cars"
|
|
||||||
MOPEDS = "Mopeds"
|
|
||||||
MOTORCYCLES = "Motorcycles"
|
|
||||||
SHIPS = "Ships"
|
|
||||||
TANKS = "Tanks"
|
|
||||||
TRAINS = "Trains"
|
|
||||||
|
|
||||||
# Theme Romance
|
|
||||||
AGE_GAP = "Age Gap"
|
|
||||||
BOYS_LOVE = "Boys' Love"
|
|
||||||
COHABITATION = "Cohabitation"
|
|
||||||
FEMALE_HAREM = "Female Harem"
|
|
||||||
HETEROSEXUAL = "Heterosexual"
|
|
||||||
LOVE_TRIANGLE = "Love Triangle"
|
|
||||||
MALE_HAREM = "Male Harem"
|
|
||||||
MATCHMAKING = "Matchmaking"
|
|
||||||
MIXED_GENDER_HAREM = "Mixed Gender Harem"
|
|
||||||
TEENS_LOVE = "Teens' Love"
|
|
||||||
UNREQUITED_LOVE = "Unrequited Love"
|
|
||||||
YURI = "Yuri"
|
|
||||||
|
|
||||||
# Theme Sci-Fi
|
|
||||||
CYBERPUNK = "Cyberpunk"
|
|
||||||
SPACE_OPERA = "Space Opera"
|
|
||||||
TIME_LOOP = "Time Loop"
|
|
||||||
TIME_MANIPULATION = "Time Manipulation"
|
|
||||||
TOKUSATSU = "Tokusatsu"
|
|
||||||
|
|
||||||
# Theme Sci-Fi-Mecha
|
|
||||||
REAL_ROBOT = "Real Robot"
|
|
||||||
SUPER_ROBOT = "Super Robot"
|
|
||||||
|
|
||||||
# Theme Slice of Life
|
|
||||||
AGRICULTURE = "Agriculture"
|
|
||||||
CUTE_BOYS_DOING_CUTE_THINGS = "Cute Boys Doing Cute Things"
|
|
||||||
CUTE_GIRLS_DOING_CUTE_THINGS = "Cute Girls Doing Cute Things"
|
|
||||||
FAMILY_LIFE = "Family Life"
|
|
||||||
HORTICULTURE = "Horticulture"
|
|
||||||
IYASHIKEI = "Iyashikei"
|
|
||||||
PARENTHOOD = "Parenthood"
|
|
||||||
|
|
||||||
|
|
||||||
class MediaSort(Enum):
|
|
||||||
ID = "ID"
|
|
||||||
ID_DESC = "ID_DESC"
|
|
||||||
TITLE_ROMAJI = "TITLE_ROMAJI"
|
|
||||||
TITLE_ROMAJI_DESC = "TITLE_ROMAJI_DESC"
|
|
||||||
TITLE_ENGLISH = "TITLE_ENGLISH"
|
|
||||||
TITLE_ENGLISH_DESC = "TITLE_ENGLISH_DESC"
|
|
||||||
TITLE_NATIVE = "TITLE_NATIVE"
|
|
||||||
TITLE_NATIVE_DESC = "TITLE_NATIVE_DESC"
|
|
||||||
TYPE = "TYPE"
|
|
||||||
TYPE_DESC = "TYPE_DESC"
|
|
||||||
FORMAT = "FORMAT"
|
|
||||||
FORMAT_DESC = "FORMAT_DESC"
|
|
||||||
START_DATE = "START_DATE"
|
|
||||||
START_DATE_DESC = "START_DATE_DESC"
|
|
||||||
END_DATE = "END_DATE"
|
|
||||||
END_DATE_DESC = "END_DATE_DESC"
|
|
||||||
SCORE = "SCORE"
|
|
||||||
SCORE_DESC = "SCORE_DESC"
|
|
||||||
POPULARITY = "POPULARITY"
|
|
||||||
POPULARITY_DESC = "POPULARITY_DESC"
|
|
||||||
TRENDING = "TRENDING"
|
|
||||||
TRENDING_DESC = "TRENDING_DESC"
|
|
||||||
EPISODES = "EPISODES"
|
|
||||||
EPISODES_DESC = "EPISODES_DESC"
|
|
||||||
DURATION = "DURATION"
|
|
||||||
DURATION_DESC = "DURATION_DESC"
|
|
||||||
STATUS = "STATUS"
|
|
||||||
STATUS_DESC = "STATUS_DESC"
|
|
||||||
CHAPTERS = "CHAPTERS"
|
|
||||||
CHAPTERS_DESC = "CHAPTERS_DESC"
|
|
||||||
VOLUMES = "VOLUMES"
|
|
||||||
VOLUMES_DESC = "VOLUMES_DESC"
|
|
||||||
UPDATED_AT = "UPDATED_AT"
|
|
||||||
UPDATED_AT_DESC = "UPDATED_AT_DESC"
|
|
||||||
SEARCH_MATCH = "SEARCH_MATCH"
|
|
||||||
FAVOURITES = "FAVOURITES"
|
|
||||||
FAVOURITES_DESC = "FAVOURITES_DESC"
|
|
||||||
|
|
||||||
|
|
||||||
class UserMediaListSort(Enum):
|
|
||||||
MEDIA_ID = "MEDIA_ID"
|
|
||||||
MEDIA_ID_DESC = "MEDIA_ID_DESC"
|
|
||||||
SCORE = "SCORE"
|
|
||||||
SCORE_DESC = "SCORE_DESC"
|
|
||||||
STATUS = "STATUS"
|
|
||||||
STATUS_DESC = "STATUS_DESC"
|
|
||||||
PROGRESS = "PROGRESS"
|
|
||||||
PROGRESS_DESC = "PROGRESS_DESC"
|
|
||||||
PROGRESS_VOLUMES = "PROGRESS_VOLUMES"
|
|
||||||
PROGRESS_VOLUMES_DESC = "PROGRESS_VOLUMES_DESC"
|
|
||||||
REPEAT = "REPEAT"
|
|
||||||
REPEAT_DESC = "REPEAT_DESC"
|
|
||||||
PRIORITY = "PRIORITY"
|
|
||||||
PRIORITY_DESC = "PRIORITY_DESC"
|
|
||||||
STARTED_ON = "STARTED_ON"
|
|
||||||
STARTED_ON_DESC = "STARTED_ON_DESC"
|
|
||||||
FINISHED_ON = "FINISHED_ON"
|
|
||||||
FINISHED_ON_DESC = "FINISHED_ON_DESC"
|
|
||||||
ADDED_TIME = "ADDED_TIME"
|
|
||||||
ADDED_TIME_DESC = "ADDED_TIME_DESC"
|
|
||||||
UPDATED_TIME = "UPDATED_TIME"
|
|
||||||
UPDATED_TIME_DESC = "UPDATED_TIME_DESC"
|
|
||||||
MEDIA_TITLE_ROMAJI = "MEDIA_TITLE_ROMAJI"
|
|
||||||
MEDIA_TITLE_ROMAJI_DESC = "MEDIA_TITLE_ROMAJI_DESC"
|
|
||||||
MEDIA_TITLE_ENGLISH = "MEDIA_TITLE_ENGLISH"
|
|
||||||
MEDIA_TITLE_ENGLISH_DESC = "MEDIA_TITLE_ENGLISH_DESC"
|
|
||||||
MEDIA_TITLE_NATIVE = "MEDIA_TITLE_NATIVE"
|
|
||||||
MEDIA_TITLE_NATIVE_DESC = "MEDIA_TITLE_NATIVE_DESC"
|
|
||||||
MEDIA_POPULARITY = "MEDIA_POPULARITY"
|
|
||||||
MEDIA_POPULARITY_DESC = "MEDIA_POPULARITY_DESC"
|
|
||||||
MEDIA_SCORE = "MEDIA_SCORE"
|
|
||||||
MEDIA_SCORE_DESC = "MEDIA_SCORE_DESC"
|
|
||||||
MEDIA_START_DATE = "MEDIA_START_DATE"
|
|
||||||
MEDIA_START_DATE_DESC = "MEDIA_START_DATE_DESC"
|
|
||||||
MEDIA_RATING = "MEDIA_RATING"
|
|
||||||
MEDIA_RATING_DESC = "MEDIA_RATING_DESC"
|
|
||||||
|
|
||||||
|
|
||||||
class MediaSeason(Enum):
|
|
||||||
WINTER = "WINTER"
|
|
||||||
SPRING = "SPRING"
|
|
||||||
SUMMER = "SUMMER"
|
|
||||||
FALL = "FALL"
|
|
||||||
|
|
||||||
|
|
||||||
class MediaYear(Enum):
|
|
||||||
_1900 = "1900"
|
|
||||||
_1910 = "1910"
|
|
||||||
_1920 = "1920"
|
|
||||||
_1930 = "1930"
|
|
||||||
_1940 = "1940"
|
|
||||||
_1950 = "1950"
|
|
||||||
_1960 = "1960"
|
|
||||||
_1970 = "1970"
|
|
||||||
_1980 = "1980"
|
|
||||||
_1990 = "1990"
|
|
||||||
_2000 = "2000"
|
|
||||||
_2004 = "2004"
|
|
||||||
_2005 = "2005"
|
|
||||||
_2006 = "2006"
|
|
||||||
_2007 = "2007"
|
|
||||||
_2008 = "2008"
|
|
||||||
_2009 = "2009"
|
|
||||||
_2010 = "2010"
|
|
||||||
_2011 = "2011"
|
|
||||||
_2012 = "2012"
|
|
||||||
_2013 = "2013"
|
|
||||||
_2014 = "2014"
|
|
||||||
_2015 = "2015"
|
|
||||||
_2016 = "2016"
|
|
||||||
_2017 = "2017"
|
|
||||||
_2018 = "2018"
|
|
||||||
_2019 = "2019"
|
|
||||||
_2020 = "2020"
|
|
||||||
_2021 = "2021"
|
|
||||||
_2022 = "2022"
|
|
||||||
_2023 = "2023"
|
|
||||||
_2024 = "2024"
|
|
||||||
_2025 = "2025"
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.version_info < (3, 10):
|
if sys.version_info < (3, 11):
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"You are using an unsupported version of Python. Only Python versions 3.10 and above are supported by Viu"
|
"You are using an unsupported version of Python. Only Python versions 3.10 and above are supported by Viu"
|
||||||
) # noqa: F541
|
) # noqa: F541
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
██╗░░░██╗██╗██╗░░░██╗
|
██╗░░░██╗██╗██╗░░░██╗
|
||||||
██║░░░██║██║██║░░░██║
|
██║░░░██║██║██║░░░██║
|
||||||
╚██╗░██╔╝██║██║░░░██║
|
╚██╗░██╔╝██║██║░░░██║
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
@@ -13,5 +13,12 @@
|
|||||||
"Azumanga Daiou The Animation": "Azumanga Daioh",
|
"Azumanga Daiou The Animation": "Azumanga Daioh",
|
||||||
"Mairimashita! Iruma-kun 2nd Season": "Mairimashita! Iruma-kun 2",
|
"Mairimashita! Iruma-kun 2nd Season": "Mairimashita! Iruma-kun 2",
|
||||||
"Mairimashita! Iruma-kun 3rd Season": "Mairimashita! Iruma-kun 3"
|
"Mairimashita! Iruma-kun 3rd Season": "Mairimashita! Iruma-kun 3"
|
||||||
|
},
|
||||||
|
"animeunity": {
|
||||||
|
"Kaiju No. 8": "Kaiju No.8",
|
||||||
|
"Naruto Shippuden": "Naruto: Shippuden",
|
||||||
|
"Psycho-Pass: Sinners of the System Case.1 - Crime and Punishment": "PSYCHO-PASS Sinners of the System: Case.1 Crime and Punishment",
|
||||||
|
"Psycho-Pass: Sinners of the System Case.2 - First Guardian": "PSYCHO-PASS Sinners of the System: Case.2 First Guardian",
|
||||||
|
"Psycho-Pass: Sinners of the System Case.3 - On the Other Side of Love and Hate": "PSYCHO-PASS Sinners of the System: Case.3 Beyond the Pale of Vengeance"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import click
|
|||||||
from click.core import ParameterSource
|
from click.core import ParameterSource
|
||||||
|
|
||||||
from ..core.config import AppConfig
|
from ..core.config import AppConfig
|
||||||
from ..core.constants import PROJECT_NAME, USER_CONFIG, __version__
|
from ..core.constants import CLI_NAME, USER_CONFIG, __version__
|
||||||
from .config import ConfigLoader
|
from .config import ConfigLoader
|
||||||
from .options import options_from_model
|
from .options import options_from_model
|
||||||
from .utils.exception import setup_exceptions_handler
|
from .utils.exception import setup_exceptions_handler
|
||||||
@@ -44,10 +44,10 @@ commands = {
|
|||||||
|
|
||||||
@click.group(
|
@click.group(
|
||||||
cls=LazyGroup,
|
cls=LazyGroup,
|
||||||
root="viu_cli.cli.commands",
|
root="viu_media.cli.commands",
|
||||||
invoke_without_command=True,
|
invoke_without_command=True,
|
||||||
lazy_subcommands=commands,
|
lazy_subcommands=commands,
|
||||||
context_settings=dict(auto_envvar_prefix=PROJECT_NAME),
|
context_settings=dict(auto_envvar_prefix=CLI_NAME),
|
||||||
)
|
)
|
||||||
@click.version_option(__version__, "--version")
|
@click.version_option(__version__, "--version")
|
||||||
@click.option("--no-config", is_flag=True, help="Don't load the user config file.")
|
@click.option("--no-config", is_flag=True, help="Don't load the user config file.")
|
||||||
@@ -108,6 +108,49 @@ def cli(ctx: click.Context, **options: "Unpack[Options]"):
|
|||||||
else loader.load(cli_overrides)
|
else loader.load(cli_overrides)
|
||||||
)
|
)
|
||||||
ctx.obj = config
|
ctx.obj = config
|
||||||
|
|
||||||
|
if config.general.check_for_updates:
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ..core.constants import APP_CACHE_DIR
|
||||||
|
|
||||||
|
last_updated_at_file = APP_CACHE_DIR / "last_update"
|
||||||
|
should_check_for_update = False
|
||||||
|
if last_updated_at_file.exists():
|
||||||
|
try:
|
||||||
|
last_updated_at_time = float(
|
||||||
|
last_updated_at_file.read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
time.time() - last_updated_at_time
|
||||||
|
) > config.general.update_check_interval * 3600:
|
||||||
|
should_check_for_update = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to check for update: {e}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
should_check_for_update = True
|
||||||
|
if should_check_for_update:
|
||||||
|
last_updated_at_file.write_text(str(time.time()), encoding="utf-8")
|
||||||
|
from .service.feedback import FeedbackService
|
||||||
|
from .utils.update import check_for_updates, print_release_json, update_app
|
||||||
|
|
||||||
|
feedback = FeedbackService(config)
|
||||||
|
feedback.info("Checking for updates...")
|
||||||
|
is_latest, release_json = check_for_updates()
|
||||||
|
if not is_latest:
|
||||||
|
from ..libs.selectors.selector import create_selector
|
||||||
|
|
||||||
|
selector = create_selector(config)
|
||||||
|
if release_json and selector.confirm(
|
||||||
|
"Theres an update available would you like to see the release notes before deciding to update?"
|
||||||
|
):
|
||||||
|
print_release_json(release_json)
|
||||||
|
selector.ask("Enter to continue...")
|
||||||
|
if selector.confirm("Would you like to update?"):
|
||||||
|
update_app()
|
||||||
|
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
from .commands.anilist import cmd
|
from .commands.anilist import cmd
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ commands = {
|
|||||||
@click.group(
|
@click.group(
|
||||||
cls=LazyGroup,
|
cls=LazyGroup,
|
||||||
name="anilist",
|
name="anilist",
|
||||||
root="viu_cli.cli.commands.anilist.commands",
|
root="viu_media.cli.commands.anilist.commands",
|
||||||
invoke_without_command=True,
|
invoke_without_command=True,
|
||||||
help="A beautiful interface that gives you access to a commplete streaming experience",
|
help="A beautiful interface that gives you access to a commplete streaming experience",
|
||||||
short_help="Access all streaming options",
|
short_help="Access all streaming options",
|
||||||
@@ -45,7 +45,9 @@ def auth(config: AppConfig, status: bool, logout: bool):
|
|||||||
open_success = webbrowser.open(ANILIST_AUTH, new=2)
|
open_success = webbrowser.open(ANILIST_AUTH, new=2)
|
||||||
if open_success:
|
if open_success:
|
||||||
feedback.info("Your browser has been opened to obtain an AniList token.")
|
feedback.info("Your browser has been opened to obtain an AniList token.")
|
||||||
feedback.info(f"or you can visit the site manually [magenta][link={ANILIST_AUTH}]here[/link][/magenta].")
|
feedback.info(
|
||||||
|
f"or you can visit the site manually [magenta][link={ANILIST_AUTH}]here[/link][/magenta]."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
feedback.warning(
|
feedback.warning(
|
||||||
f"Failed to open the browser. Please visit the site manually [magenta][link={ANILIST_AUTH}]here[/link][/magenta]."
|
f"Failed to open the browser. Please visit the site manually [magenta][link={ANILIST_AUTH}]here[/link][/magenta]."
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from typing import TYPE_CHECKING, Dict, List
|
from typing import TYPE_CHECKING, Dict, List
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from viu_cli.cli.utils.completion import anime_titles_shell_complete
|
from viu_media.cli.utils.completion import anime_titles_shell_complete
|
||||||
from viu_cli.core.config import AppConfig
|
from viu_media.core.config import AppConfig
|
||||||
from viu_cli.core.exceptions import ViuError
|
from viu_media.core.exceptions import ViuError
|
||||||
from viu_cli.libs.media_api.types import (
|
from viu_media.libs.media_api.types import (
|
||||||
MediaFormat,
|
MediaFormat,
|
||||||
MediaGenre,
|
MediaGenre,
|
||||||
MediaItem,
|
MediaItem,
|
||||||
@@ -112,15 +112,15 @@ if TYPE_CHECKING:
|
|||||||
)
|
)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def download(config: AppConfig, **options: "Unpack[DownloadOptions]"):
|
def download(config: AppConfig, **options: "Unpack[DownloadOptions]"):
|
||||||
from viu_cli.cli.service.download.service import DownloadService
|
from viu_media.cli.service.download.service import DownloadService
|
||||||
from viu_cli.cli.service.feedback import FeedbackService
|
from viu_media.cli.service.feedback import FeedbackService
|
||||||
from viu_cli.cli.service.registry import MediaRegistryService
|
from viu_media.cli.service.registry import MediaRegistryService
|
||||||
from viu_cli.cli.service.watch_history import WatchHistoryService
|
from viu_media.cli.service.watch_history import WatchHistoryService
|
||||||
from viu_cli.cli.utils.parser import parse_episode_range
|
from viu_media.cli.utils.parser import parse_episode_range
|
||||||
from viu_cli.libs.media_api.api import create_api_client
|
from viu_media.libs.media_api.api import create_api_client
|
||||||
from viu_cli.libs.media_api.params import MediaSearchParams
|
from viu_media.libs.media_api.params import MediaSearchParams
|
||||||
from viu_cli.libs.provider.anime.provider import create_provider
|
from viu_media.libs.provider.anime.provider import create_provider
|
||||||
from viu_cli.libs.selectors import create_selector
|
from viu_media.libs.selectors import create_selector
|
||||||
from rich.progress import Progress
|
from rich.progress import Progress
|
||||||
|
|
||||||
feedback = FeedbackService(config)
|
feedback = FeedbackService(config)
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import click
|
import click
|
||||||
from viu_cli.core.config import AppConfig
|
from viu_media.core.config import AppConfig
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
|
|
||||||
@@ -11,8 +11,8 @@ def notifications(config: AppConfig):
|
|||||||
Displays unread notifications from AniList.
|
Displays unread notifications from AniList.
|
||||||
Running this command will also mark the notifications as read on the AniList website.
|
Running this command will also mark the notifications as read on the AniList website.
|
||||||
"""
|
"""
|
||||||
from viu_cli.cli.service.feedback import FeedbackService
|
from viu_media.cli.service.feedback import FeedbackService
|
||||||
from viu_cli.libs.media_api.api import create_api_client
|
from viu_media.libs.media_api.api import create_api_client
|
||||||
|
|
||||||
from ....service.auth import AuthService
|
from ....service.auth import AuthService
|
||||||
|
|
||||||
@@ -251,18 +251,14 @@ def search(config: AppConfig, **options: "Unpack[SearchOptions]"):
|
|||||||
and start_date_lesser is not None
|
and start_date_lesser is not None
|
||||||
and start_date_greater > start_date_lesser
|
and start_date_greater > start_date_lesser
|
||||||
):
|
):
|
||||||
raise ViuError(
|
raise ViuError("Start date greater cannot be later than start date lesser")
|
||||||
"Start date greater cannot be later than start date lesser"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
end_date_greater is not None
|
end_date_greater is not None
|
||||||
and end_date_lesser is not None
|
and end_date_lesser is not None
|
||||||
and end_date_greater > end_date_lesser
|
and end_date_greater > end_date_lesser
|
||||||
):
|
):
|
||||||
raise ViuError(
|
raise ViuError("End date greater cannot be later than end date lesser")
|
||||||
"End date greater cannot be later than end date lesser"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Build search parameters
|
# Build search parameters
|
||||||
search_params = MediaSearchParams(
|
search_params = MediaSearchParams(
|
||||||
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from viu_cli.core.config import AppConfig
|
from viu_media.core.config import AppConfig
|
||||||
|
|
||||||
|
|
||||||
@click.command(help="Print out your anilist stats")
|
@click.command(help="Print out your anilist stats")
|
||||||
@@ -72,7 +72,7 @@ def config(
|
|||||||
):
|
):
|
||||||
from ...core.constants import USER_CONFIG
|
from ...core.constants import USER_CONFIG
|
||||||
from ..config.editor import InteractiveConfigEditor
|
from ..config.editor import InteractiveConfigEditor
|
||||||
from ..config.generate import generate_config_ini_from_app_model
|
from ..config.generate import generate_config_toml_from_app_model
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
print(USER_CONFIG)
|
print(USER_CONFIG)
|
||||||
@@ -81,9 +81,9 @@ def config(
|
|||||||
from rich.syntax import Syntax
|
from rich.syntax import Syntax
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
config_ini = generate_config_ini_from_app_model(user_config)
|
config_toml = generate_config_toml_from_app_model(user_config)
|
||||||
syntax = Syntax(
|
syntax = Syntax(
|
||||||
config_ini,
|
config_toml,
|
||||||
"ini",
|
"ini",
|
||||||
theme=user_config.general.pygment_style,
|
theme=user_config.general.pygment_style,
|
||||||
line_numbers=True,
|
line_numbers=True,
|
||||||
@@ -99,12 +99,14 @@ def config(
|
|||||||
elif interactive:
|
elif interactive:
|
||||||
editor = InteractiveConfigEditor(current_config=user_config)
|
editor = InteractiveConfigEditor(current_config=user_config)
|
||||||
new_config = editor.run()
|
new_config = editor.run()
|
||||||
with open(USER_CONFIG, "w", encoding="utf-8") as file:
|
USER_CONFIG.write_text(
|
||||||
file.write(generate_config_ini_from_app_model(new_config))
|
generate_config_toml_from_app_model(new_config), encoding="utf-8"
|
||||||
|
)
|
||||||
click.echo(f"Configuration saved successfully to {USER_CONFIG}")
|
click.echo(f"Configuration saved successfully to {USER_CONFIG}")
|
||||||
elif update:
|
elif update:
|
||||||
with open(USER_CONFIG, "w", encoding="utf-8") as file:
|
USER_CONFIG.write_text(
|
||||||
file.write(generate_config_ini_from_app_model(user_config))
|
generate_config_toml_from_app_model(user_config), encoding="utf-8"
|
||||||
|
)
|
||||||
print("update successfull")
|
print("update successfull")
|
||||||
else:
|
else:
|
||||||
click.edit(filename=str(USER_CONFIG))
|
click.edit(filename=str(USER_CONFIG))
|
||||||
@@ -123,9 +125,9 @@ def _generate_desktop_entry():
|
|||||||
from rich.prompt import Confirm
|
from rich.prompt import Confirm
|
||||||
|
|
||||||
from ...core.constants import (
|
from ...core.constants import (
|
||||||
|
CLI_NAME,
|
||||||
ICON_PATH,
|
ICON_PATH,
|
||||||
PLATFORM,
|
PLATFORM,
|
||||||
PROJECT_NAME,
|
|
||||||
USER_APPLICATIONS,
|
USER_APPLICATIONS,
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
@@ -149,7 +151,7 @@ def _generate_desktop_entry():
|
|||||||
desktop_entry = dedent(
|
desktop_entry = dedent(
|
||||||
f"""
|
f"""
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name={PROJECT_NAME.title()}
|
Name={CLI_NAME.title()}
|
||||||
Type=Application
|
Type=Application
|
||||||
version={__version__}
|
version={__version__}
|
||||||
Path={Path().home()}
|
Path={Path().home()}
|
||||||
@@ -160,7 +162,7 @@ def _generate_desktop_entry():
|
|||||||
Categories=Entertainment
|
Categories=Entertainment
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
desktop_entry_path = USER_APPLICATIONS / f"{PROJECT_NAME}.desktop"
|
desktop_entry_path = USER_APPLICATIONS / f"{CLI_NAME}.desktop"
|
||||||
if desktop_entry_path.exists():
|
if desktop_entry_path.exists():
|
||||||
if not Confirm.ask(
|
if not Confirm.ask(
|
||||||
f"The file already exists {desktop_entry_path}; or would you like to rewrite it",
|
f"The file already exists {desktop_entry_path}; or would you like to rewrite it",
|
||||||
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from viu_cli.cli.service.feedback.service import FeedbackService
|
from viu_media.cli.service.feedback.service import FeedbackService
|
||||||
from typing_extensions import Unpack
|
from typing_extensions import Unpack
|
||||||
|
|
||||||
from ...libs.provider.anime.base import BaseAnimeProvider
|
from ...libs.provider.anime.base import BaseAnimeProvider
|
||||||
@@ -103,7 +103,7 @@ if TYPE_CHECKING:
|
|||||||
)
|
)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def download(config: AppConfig, **options: "Unpack[Options]"):
|
def download(config: AppConfig, **options: "Unpack[Options]"):
|
||||||
from viu_cli.cli.service.feedback.service import FeedbackService
|
from viu_media.cli.service.feedback.service import FeedbackService
|
||||||
|
|
||||||
from ...core.exceptions import ViuError
|
from ...core.exceptions import ViuError
|
||||||
from ...libs.provider.anime.params import (
|
from ...libs.provider.anime.params import (
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import click
|
import click
|
||||||
from viu_cli.core.config import AppConfig
|
from viu_media.core.config import AppConfig
|
||||||
from viu_cli.core.exceptions import ViuError
|
from viu_media.core.exceptions import ViuError
|
||||||
from viu_cli.libs.media_api.types import (
|
from viu_media.libs.media_api.types import (
|
||||||
MediaFormat,
|
MediaFormat,
|
||||||
MediaGenre,
|
MediaGenre,
|
||||||
MediaItem,
|
MediaItem,
|
||||||
@@ -33,8 +33,12 @@ from viu_cli.libs.media_api.types import (
|
|||||||
@click.option(
|
@click.option(
|
||||||
"--genres-not", multiple=True, type=click.Choice([g.value for g in MediaGenre])
|
"--genres-not", multiple=True, type=click.Choice([g.value for g in MediaGenre])
|
||||||
)
|
)
|
||||||
@click.option("--tags", "-T", multiple=True, type=click.Choice([t.value for t in MediaTag]))
|
@click.option(
|
||||||
@click.option("--tags-not", multiple=True, type=click.Choice([t.value for t in MediaTag]))
|
"--tags", "-T", multiple=True, type=click.Choice([t.value for t in MediaTag])
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--tags-not", multiple=True, type=click.Choice([t.value for t in MediaTag])
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--media-format",
|
"--media-format",
|
||||||
"-f",
|
"-f",
|
||||||
@@ -72,14 +76,14 @@ def queue(config: AppConfig, **options):
|
|||||||
and queue the specified episode range for background download.
|
and queue the specified episode range for background download.
|
||||||
The background worker should be running to process the queue.
|
The background worker should be running to process the queue.
|
||||||
"""
|
"""
|
||||||
from viu_cli.cli.service.download.service import DownloadService
|
from viu_media.cli.service.download.service import DownloadService
|
||||||
from viu_cli.cli.service.feedback import FeedbackService
|
from viu_media.cli.service.feedback import FeedbackService
|
||||||
from viu_cli.cli.service.registry import MediaRegistryService
|
from viu_media.cli.service.registry import MediaRegistryService
|
||||||
from viu_cli.cli.utils.parser import parse_episode_range
|
from viu_media.cli.utils.parser import parse_episode_range
|
||||||
from viu_cli.libs.media_api.params import MediaSearchParams
|
from viu_media.libs.media_api.params import MediaSearchParams
|
||||||
from viu_cli.libs.media_api.api import create_api_client
|
from viu_media.libs.media_api.api import create_api_client
|
||||||
from viu_cli.libs.provider.anime.provider import create_provider
|
from viu_media.libs.provider.anime.provider import create_provider
|
||||||
from viu_cli.libs.selectors import create_selector
|
from viu_media.libs.selectors import create_selector
|
||||||
from rich.progress import Progress
|
from rich.progress import Progress
|
||||||
|
|
||||||
feedback = FeedbackService(config)
|
feedback = FeedbackService(config)
|
||||||
@@ -13,7 +13,7 @@ commands = {
|
|||||||
@click.group(
|
@click.group(
|
||||||
cls=LazyGroup,
|
cls=LazyGroup,
|
||||||
name="queue",
|
name="queue",
|
||||||
root="viu_cli.cli.commands.queue.commands",
|
root="viu_media.cli.commands.queue.commands",
|
||||||
invoke_without_command=False,
|
invoke_without_command=False,
|
||||||
help="Manage the download queue (add, list, resume, clear).",
|
help="Manage the download queue (add, list, resume, clear).",
|
||||||
short_help="Manage the download queue.",
|
short_help="Manage the download queue.",
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import click
|
import click
|
||||||
from viu_cli.core.config import AppConfig
|
from viu_media.core.config import AppConfig
|
||||||
from viu_cli.core.exceptions import ViuError
|
from viu_media.core.exceptions import ViuError
|
||||||
from viu_cli.libs.media_api.types import (
|
from viu_media.libs.media_api.types import (
|
||||||
MediaFormat,
|
MediaFormat,
|
||||||
MediaGenre,
|
MediaGenre,
|
||||||
MediaItem,
|
MediaItem,
|
||||||
@@ -70,14 +70,14 @@ from viu_cli.libs.media_api.types import (
|
|||||||
)
|
)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def add(config: AppConfig, **options):
|
def add(config: AppConfig, **options):
|
||||||
from viu_cli.cli.service.download import DownloadService
|
from viu_media.cli.service.download import DownloadService
|
||||||
from viu_cli.cli.service.feedback import FeedbackService
|
from viu_media.cli.service.feedback import FeedbackService
|
||||||
from viu_cli.cli.service.registry import MediaRegistryService
|
from viu_media.cli.service.registry import MediaRegistryService
|
||||||
from viu_cli.cli.utils.parser import parse_episode_range
|
from viu_media.cli.utils.parser import parse_episode_range
|
||||||
from viu_cli.libs.media_api.api import create_api_client
|
from viu_media.libs.media_api.api import create_api_client
|
||||||
from viu_cli.libs.media_api.params import MediaSearchParams
|
from viu_media.libs.media_api.params import MediaSearchParams
|
||||||
from viu_cli.libs.provider.anime.provider import create_provider
|
from viu_media.libs.provider.anime.provider import create_provider
|
||||||
from viu_cli.libs.selectors import create_selector
|
from viu_media.libs.selectors import create_selector
|
||||||
from rich.progress import Progress
|
from rich.progress import Progress
|
||||||
|
|
||||||
feedback = FeedbackService(config)
|
feedback = FeedbackService(config)
|
||||||
@@ -149,7 +149,7 @@ def add(config: AppConfig, **options):
|
|||||||
}
|
}
|
||||||
preview_command = None
|
preview_command = None
|
||||||
if config.general.preview != "none":
|
if config.general.preview != "none":
|
||||||
from viu_cli.cli.utils.preview import create_preview_context
|
from viu_media.cli.utils.preview import create_preview_context
|
||||||
|
|
||||||
with create_preview_context() as preview_ctx:
|
with create_preview_context() as preview_ctx:
|
||||||
preview_command = preview_ctx.get_anime_preview(
|
preview_command = preview_ctx.get_anime_preview(
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import click
|
import click
|
||||||
from viu_cli.core.config import AppConfig
|
from viu_media.core.config import AppConfig
|
||||||
|
|
||||||
|
|
||||||
@click.command(name="clear", help="Clear queued items from the registry (QUEUED -> NOT_DOWNLOADED).")
|
@click.command(
|
||||||
|
name="clear",
|
||||||
|
help="Clear queued items from the registry (QUEUED -> NOT_DOWNLOADED).",
|
||||||
|
)
|
||||||
@click.option("--force", is_flag=True, help="Do not prompt for confirmation.")
|
@click.option("--force", is_flag=True, help="Do not prompt for confirmation.")
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def clear_cmd(config: AppConfig, force: bool):
|
def clear_cmd(config: AppConfig, force: bool):
|
||||||
from viu_cli.cli.service.feedback import FeedbackService
|
from viu_media.cli.service.feedback import FeedbackService
|
||||||
from viu_cli.cli.service.registry import MediaRegistryService
|
from viu_media.cli.service.registry import MediaRegistryService
|
||||||
from viu_cli.cli.service.registry.models import DownloadStatus
|
from viu_media.cli.service.registry.models import DownloadStatus
|
||||||
|
|
||||||
feedback = FeedbackService(config)
|
feedback = FeedbackService(config)
|
||||||
registry = MediaRegistryService(config.general.media_api, config.media_registry)
|
registry = MediaRegistryService(config.general.media_api, config.media_registry)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import click
|
import click
|
||||||
from viu_cli.core.config import AppConfig
|
from viu_media.core.config import AppConfig
|
||||||
|
|
||||||
|
|
||||||
@click.command(name="list", help="List items in the download queue and their statuses.")
|
@click.command(name="list", help="List items in the download queue and their statuses.")
|
||||||
@@ -10,9 +10,9 @@ from viu_cli.core.config import AppConfig
|
|||||||
@click.option("--detailed", is_flag=True)
|
@click.option("--detailed", is_flag=True)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def list_cmd(config: AppConfig, status: str | None, detailed: bool | None):
|
def list_cmd(config: AppConfig, status: str | None, detailed: bool | None):
|
||||||
from viu_cli.cli.service.feedback import FeedbackService
|
from viu_media.cli.service.feedback import FeedbackService
|
||||||
from viu_cli.cli.service.registry import MediaRegistryService
|
from viu_media.cli.service.registry import MediaRegistryService
|
||||||
from viu_cli.cli.service.registry.models import DownloadStatus
|
from viu_media.cli.service.registry.models import DownloadStatus
|
||||||
|
|
||||||
feedback = FeedbackService(config)
|
feedback = FeedbackService(config)
|
||||||
registry = MediaRegistryService(config.general.media_api, config.media_registry)
|
registry = MediaRegistryService(config.general.media_api, config.media_registry)
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
import click
|
import click
|
||||||
from viu_cli.core.config import AppConfig
|
from viu_media.core.config import AppConfig
|
||||||
|
|
||||||
|
|
||||||
@click.command(name="resume", help="Submit any queued or in-progress downloads to the worker.")
|
@click.command(
|
||||||
|
name="resume", help="Submit any queued or in-progress downloads to the worker."
|
||||||
|
)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def resume(config: AppConfig):
|
def resume(config: AppConfig):
|
||||||
from viu_cli.cli.service.download.service import DownloadService
|
from viu_media.cli.service.download.service import DownloadService
|
||||||
from viu_cli.cli.service.feedback import FeedbackService
|
from viu_media.cli.service.feedback import FeedbackService
|
||||||
from viu_cli.cli.service.registry import MediaRegistryService
|
from viu_media.cli.service.registry import MediaRegistryService
|
||||||
from viu_cli.libs.media_api.api import create_api_client
|
from viu_media.libs.media_api.api import create_api_client
|
||||||
from viu_cli.libs.provider.anime.provider import create_provider
|
from viu_media.libs.provider.anime.provider import create_provider
|
||||||
|
|
||||||
feedback = FeedbackService(config)
|
feedback = FeedbackService(config)
|
||||||
media_api = create_api_client(config.general.media_api, config)
|
media_api = create_api_client(config.general.media_api, config)
|
||||||
@@ -19,7 +19,7 @@ commands = {
|
|||||||
@click.group(
|
@click.group(
|
||||||
cls=LazyGroup,
|
cls=LazyGroup,
|
||||||
name="registry",
|
name="registry",
|
||||||
root="viu_cli.cli.commands.registry.commands",
|
root="viu_media.cli.commands.registry.commands",
|
||||||
invoke_without_command=True,
|
invoke_without_command=True,
|
||||||
help="Manage your local media registry - sync, search, backup and maintain your anime database",
|
help="Manage your local media registry - sync, search, backup and maintain your anime database",
|
||||||
short_help="Local media registry management",
|
short_help="Local media registry management",
|
||||||
@@ -3,8 +3,8 @@ Registry sync command - synchronize local registry with remote media API
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from viu_cli.cli.service.feedback.service import FeedbackService
|
from viu_media.cli.service.feedback.service import FeedbackService
|
||||||
from viu_cli.cli.service.registry.service import MediaRegistryService
|
from viu_media.cli.service.registry.service import MediaRegistryService
|
||||||
|
|
||||||
from .....core.config import AppConfig
|
from .....core.config import AppConfig
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ from . import examples
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from viu_cli.cli.service.feedback.service import FeedbackService
|
from viu_media.cli.service.feedback.service import FeedbackService
|
||||||
from typing_extensions import Unpack
|
from typing_extensions import Unpack
|
||||||
|
|
||||||
from ...libs.provider.anime.base import BaseAnimeProvider
|
from ...libs.provider.anime.base import BaseAnimeProvider
|
||||||
@@ -42,7 +42,7 @@ if TYPE_CHECKING:
|
|||||||
)
|
)
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
def search(config: AppConfig, **options: "Unpack[Options]"):
|
def search(config: AppConfig, **options: "Unpack[Options]"):
|
||||||
from viu_cli.cli.service.feedback.service import FeedbackService
|
from viu_media.cli.service.feedback.service import FeedbackService
|
||||||
|
|
||||||
from ...core.exceptions import ViuError
|
from ...core.exceptions import ViuError
|
||||||
from ...libs.provider.anime.params import (
|
from ...libs.provider.anime.params import (
|
||||||
@@ -134,7 +134,7 @@ def stream_anime(
|
|||||||
episode: str,
|
episode: str,
|
||||||
anime_title: str,
|
anime_title: str,
|
||||||
):
|
):
|
||||||
from viu_cli.cli.service.player.service import PlayerService
|
from viu_media.cli.service.player.service import PlayerService
|
||||||
|
|
||||||
from ...libs.player.params import PlayerParams
|
from ...libs.player.params import PlayerParams
|
||||||
from ...libs.provider.anime.params import EpisodeStreamsParams
|
from ...libs.provider.anime.params import EpisodeStreamsParams
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user