Compare commits

..

25 Commits

Author SHA1 Message Date
Benex254
c66cc52d53 tests: add tests for cache and completions command 2024-08-09 23:14:46 +03:00
Benex254
e5f4a61a4e chore:bump version 2024-08-09 23:08:55 +03:00
Benex254
739d041c58 chore: update readme 2024-08-09 23:08:35 +03:00
Benex254
f12d5ab06c feat(cli): add helper command completions 2024-08-09 22:59:26 +03:00
Benex254
c3a3041cfb feat(interface): use click.edit 2024-08-09 22:58:48 +03:00
Benex254
594c687c8b chore: remove art as dep 2024-08-09 16:02:29 +03:00
Benex254
91b5d3ea40 chore: bump version 2024-08-09 16:00:49 +03:00
Benex254
8c30a7667c feat(fzf): remove art and instead use a static header 2024-08-09 16:00:30 +03:00
Benex254
179fbe59ac feat(config): let python-mpv to bee disabled by default 2024-08-09 15:59:56 +03:00
Benex254
5bfc210f59 feat(cli): add option to enable or disable python-mpv 2024-08-09 15:59:31 +03:00
Benex254
eb9c200fca feat(mpv): remove useless print statement 2024-08-09 15:58:50 +03:00
Benex254
603efd56e8 chore: remove dbus-python 2024-08-09 13:48:36 +03:00
Benex254
4d74dfa339 feat(mpv): improve auto next 2024-08-09 13:47:23 +03:00
Benex254
4681e38153 chore: bump version 2024-08-09 01:09:34 +03:00
Benex254
242003500d chore: add dbus-python as dep for notifications 2024-08-09 01:07:32 +03:00
Benex254
66ab365657 feat(mpv): add ytdl to true 2024-08-09 01:06:53 +03:00
Benex254
bc8d7b2e28 chore: bump version 2024-08-08 19:43:40 +03:00
Benex254
db3a1f7175 feat(mpv): add select server script message 2024-08-08 19:21:19 +03:00
Benex254
3a51e0225e chore: bump version 2024-08-08 15:42:10 +03:00
Benex254
7b3388939c chore: bump version 2024-08-08 15:40:47 +03:00
Benex254
fcf875bdb2 feat(mpv): force window 2024-08-08 15:16:03 +03:00
Benex254
0e4624297c docs: update readme 2024-08-07 21:15:08 +03:00
Benex254
91b54dfcb9 feat(cache): open cache dir on no options 2024-08-07 21:14:57 +03:00
Benex254
7011489ce5 chore: bump to dev version 2024-08-07 20:14:33 +03:00
Benex254
316ca62ca4 chore: bump version 2024-08-07 20:08:50 +03:00
13 changed files with 346 additions and 80 deletions

105
README.md
View File

@@ -2,18 +2,17 @@
Welcome to **FastAnime**, anime site experience from the terminal.
**fzf mode**
[fa_fzf_demo.webm](https://github.com/user-attachments/assets/b1fecf25-e358-4e8b-a144-bcb7947210cf)
**other modes:**
<details>
<summary><b>rofi mode</b></summary>
[fa_rofi_mode.webm](https://github.com/user-attachments/assets/2ce669bf-b62f-4c44-bd79-cf0dcaddf37a)
</details>
<details>
@@ -23,11 +22,8 @@ Welcome to **FastAnime**, anime site experience from the terminal.
</details>
Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [magic-tape](https://gitlab.com/christosangel/magic-tape/-/tree/main?ref_type=heads) and [ani-cli](https://github.com/pystardust/ani-cli).
<!--toc:start-->
- [FastAnime](#fastanime)
@@ -40,13 +36,17 @@ Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [magi
- [External Dependencies](#external-dependencies)
- [Usage](#usage)
- [The Commandline interface :fire:](#the-commandline-interface-fire)
- [The anilist command](#the-anilist-command)
- [The anilist command :fire: :fire: :fire:](#the-anilist-command-fire-fire-fire)
- [Running without any subcommand](#running-without-any-subcommand)
- [Subcommands](#subcommands)
- [download subcommand](#download-subcommand)
- [search subcommand](#search-subcommand)
- [downloads subcommand](#downloads-subcommand)
- [config subcommand](#config-subcommand)
- [cache subcommand](#cache-subcommand)
- [MPV specific commands](#mpv-specific-commands)
- [Added keybindings](#added-keybindings)
- [Added script messages](#added-script-messages)
- [Configuration](#configuration)
- [Contributing](#contributing)
- [Receiving Support](#receiving-support)
@@ -57,7 +57,6 @@ Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [magi
>
> This project currently scrapes allanime and is in no way related to them. The site is in the public domain and can be access by any one with a browser.
## Installation
The app can run wherever python can run. So all you need to have is python installed on your device.
@@ -75,12 +74,21 @@ Preferred method of installation since [Pipx](https://github.com/pypa/pipx) crea
```bash
pipx install fastanime
# -- or for the development version --
pipx install 'fastanime==<latest-pre-release-tag>.dev1'
# example
# pipx install 'fastanime==0.60.1.dev1'
```
#### Using pip
```bash
pip install fastanime
# -- or for the development version --
pip install 'fastanime==<latest-pre-release-tag>.dev1'
# example
# pip install 'fastanime==0.60.1.dev1'
```
### Installing the bleeding edge version
@@ -143,7 +151,7 @@ fastanime --version
### External Dependencies
The only required external dependency, unless you won't be streaming, is [MPV](https://mpv.io/installation/), which i recommend installing with [uosc](https://github.com/tomasklaen/uosc) and [thumbfast](https://github.com/po5/thumbfast) for the best experience since they add a better interface to it.
The only required external dependency, unless you won't be streaming, is [MPV](https://mpv.io/installation/), which i recommend installing with [uosc](https://github.com/tomasklaen/uosc) :fire: and [thumbfast](https://github.com/po5/thumbfast) for the best experience since they add a better interface to it.
> [!NOTE]
>
@@ -152,13 +160,13 @@ The only required external dependency, unless you won't be streaming, is [MPV](h
> everything you could ever need with a small footprint.
> But if you have a reason feel free to encourage as to do so.
**Other dependencies that will just make your experience better:**
**Other external dependencies that will just make your experience better:**
- [fzf](https://github.com/junegunn/fzf) :fire: which is used as a better alternative to the ui.
- [chafa](https://github.com/hpjansson/chafa) currently the best cross platform and cross terminal image viewer for the terminal.
- [icat](https://sw.kovidgoyal.net/kitty/kittens/icat/) an image viewer that only works in [kitty terminal](https://sw.kovidgoyal.net/kitty/), which is currently the best terminal in my opinion, and by far the best image renderer for the terminal thanks to kitty's terminal graphics protocol. Its terminal graphics is so op that you can [run a browser on it](https://github.com/chase/awrit?tab=readme-ov-file)!!
- [bash](https://www.gnu.org/software/bash/) is used as the preview script language.
- [ani-skip](https://github.com/synacktraa/ani-skip) :fire: used for skipping the opening and ending theme songs
- [ani-skip](https://github.com/synacktraa/ani-skip) used for skipping the opening and ending theme songs
## Usage
@@ -181,6 +189,7 @@ Overview of main commands:
- `fastanime search`: Powerful command meant for binging since it doesn't require the interfaces
- `fastanime downloads`: View downloaded anime and watch with MPV.
- `fastanime config`: Quickly edit configuration settings.
- `fastanime cache`: Quickly manage the cache fastanime uses
Configuration is directly passed into this command at run time to override your config.
@@ -204,6 +213,10 @@ Available options include:
- `--rofi-theme <path>` theme to use with rofi
- `--rofi-theme-input <path>` theme to use with rofi input
- `--rofi-theme-confirm <path>` theme to use with rofi confirm
- `--log` allow logging to stdout
- `--log-file` allow logging to a file
- `--rich-traceback` allow rich traceback
- `--use-mpv-mod/--use-default-player` whether to use python-mpv
#### The anilist command :fire: :fire: :fire:
@@ -331,11 +344,77 @@ fastanime config
# to get config path which is useful if you want to use it for another program.
fastanime config --path
# add a desktop entry
fastanime config --desktop-entry
```
> [!Note]
>
> If it opens [vim](https://www.vim.org/download.php) you can exit by typing `:q` in case you don't know.
> If it opens [vim](https://www.vim.org/download.php) you can exit by typing `:q` .
#### cache subcommand
Easily manage the data fastanime has cached; for the previews.
**Syntax:**
```bash
# delete everything in the cache dir
fastanime cache --clean
# print the path to the cache dir and exit
fastanime cache --path
# print the current size of the cache dir and exit
fastanime cache --size
# open the cache dir and exit
fastanime cache
```
#### completions subcommand
Helper command to setup shell completions
**Syntax:**
```bash
# print fish completions
fastanime completions --fish
# print bash completions
fastanime completions --bash
# print zsh completions
fastanime completions --zsh
```
## MPV specific commands
The project now allows on the fly media controls directly from mpv. This means you can go to the next or previous episode without the window ever closing thus offering a seamless experience.
This is all powered with [python-mpv]() which enables writing mpv scripts with python just like how it would be done in lua.
### Added keybindings
`<shift>+n` fetch the next episode
`<shift>+p` fetch the previous episode
`<shift>+t` toggle the translation type from dub to sub
`<shift>+a` toggle auto next episode
`<shit>+r` reload episode
### Added script messages
Examples:
```bash
# to select episode from mpv without window closing
script-message select-episode <episode-number>
# to select server from mpv without window closing
script-message select-server <server-name>
```
## Configuration
@@ -356,6 +435,7 @@ skip=false
# the maximum delta time in minutes after which the episode should be considered as completed
# used in the continue from time stamp
error=3
use_mpv_mod=False
# the format of downloaded anime and trailer
# based on yt-dlp format and passed directly to it
@@ -393,7 +473,7 @@ notification_duration=2
We welcome your issues and feature requests. However, due to time constraints, we currently do not plan to add another provider.
If you wish to contribute directly, please first open an issue describing your proposed changes so it can be discussed.
If you wish to contribute directly, please first open an issue describing your proposed changes so it can be discussed or if you are in a rush for the feature to be merged just open a pr.
## Receiving Support
@@ -405,7 +485,6 @@ For inquiries, join our [Discord Server](https://discord.gg/4NUTj5Pt).
</a>
</p>
## Supporting the Project
Show your support by starring our GitHub repository or [buying us a coffee](https://ko-fi.com/benex254).

View File

@@ -6,7 +6,7 @@ if sys.version_info < (3, 10):
) # noqa: F541
__version__ = "v0.50.0"
__version__ = "v0.61.5"
APP_NAME = "FastAnime"
AUTHOR = "Benex254"

View File

@@ -15,6 +15,7 @@ commands = {
"config": "config.config",
"downloads": "downloads.downloads",
"cache": "cache.cache",
"completions": "completions.completions",
}
@@ -126,6 +127,9 @@ signal.signal(signal.SIGINT, handle_exit)
help="Rofi theme to use for the user input prompt",
type=click.Path(),
)
@click.option(
"--use-mpv-mod/--use-default-player", help="Whether to use python-mpv", type=bool
)
@click.pass_context
def run_cli(
ctx: click.Context,
@@ -155,6 +159,7 @@ def run_cli(
rofi_theme,
rofi_theme_confirm,
rofi_theme_input,
use_mpv_mod,
):
from .config import Config
@@ -217,6 +222,11 @@ def run_cli(
== click.core.ParameterSource.COMMANDLINE
):
ctx.obj.auto_select = auto_select
if (
ctx.get_parameter_source("use_mpv_mod")
== click.core.ParameterSource.COMMANDLINE
):
ctx.obj.use_mpv_mod = use_mpv_mod
if sort_by:
ctx.obj.sort_by = sort_by
if downloads_dir:

View File

@@ -33,3 +33,7 @@ def cache(clean, path, size):
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
print("Total Size: ", sizeof_fmt(total_size))
else:
import click
click.launch(APP_CACHE_DIR)

View File

@@ -0,0 +1,110 @@
import click
@click.command(help="Helper command to get shell completions")
@click.option("--fish", is_flag=True)
@click.option("--zsh", is_flag=True)
@click.option("--bash", is_flag=True)
def completions(fish, zsh, bash):
if fish:
print(
"""
function _fastanime_completion;
set -l response (env _FASTANIME_COMPLETE=fish_complete COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) fastanime);
for completion in $response;
set -l metadata (string split "," $completion);
if test $metadata[1] = "dir";
__fish_complete_directories $metadata[2];
else if test $metadata[1] = "file";
__fish_complete_path $metadata[2];
else if test $metadata[1] = "plain";
echo $metadata[2];
end;
end;
end;
complete --no-files --command fastanime --arguments "(_fastanime_completion)";
"""
)
elif zsh:
print(
"""
#compdef fastanime
_fastanime_completion() {
local -a completions
local -a completions_with_descriptions
local -a response
(( ! $+commands[fastanime] )) && return 1
response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) _FASTANIME_COMPLETE=zsh_complete fastanime)}")
for type key descr in ${response}; do
if [[ "$type" == "plain" ]]; then
if [[ "$descr" == "_" ]]; then
completions+=("$key")
else
completions_with_descriptions+=("$key":"$descr")
fi
elif [[ "$type" == "dir" ]]; then
_path_files -/
elif [[ "$type" == "file" ]]; then
_path_files -f
fi
done
if [ -n "$completions_with_descriptions" ]; then
_describe -V unsorted completions_with_descriptions -U
fi
if [ -n "$completions" ]; then
compadd -U -V unsorted -a completions
fi
}
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
# autoload from fpath, call function directly
_fastanime_completion "$@"
else
# eval/source/. command, register function for later
compdef _fastanime_completion fastanime
fi
"""
)
elif bash:
print(
"""
_fastanime_completion() {
local IFS=$'\n'
local response
response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD _FASTANIME_COMPLETE=bash_complete $1)
for completion in $response; do
IFS=',' read type value <<< "$completion"
if [[ $type == 'dir' ]]; then
COMPREPLY=()
compopt -o dirnames
elif [[ $type == 'file' ]]; then
COMPREPLY=()
compopt -o default
elif [[ $type == 'plain' ]]; then
COMPREPLY+=($value)
fi
done
return 0
}
_fastanime_completion_setup() {
complete -o nosort -F _fastanime_completion fastanime
}
_fastanime_completion_setup;
"""
)
else:
print("Specify either --zsh/--fish/--bash")

View File

@@ -47,7 +47,8 @@ class Config(object):
"rofi_theme": "",
"rofi_theme_input": "",
"rofi_theme_confirm": "",
"use_mpv_mod": "true",
"use_mpv_mod": "false",
"force_window": "immediate",
}
)
self.configparser.add_section("stream")
@@ -78,6 +79,7 @@ class Config(object):
self.error = self.get_error()
self.server = self.get_server()
self.format = self.get_format()
self.force_window = self.get_force_window()
self.preferred_language = self.get_preferred_language()
self.rofi_theme = self.get_rofi_theme()
Rofi.rofi_theme = self.rofi_theme
@@ -145,6 +147,9 @@ class Config(object):
def get_skip(self):
return self.configparser.getboolean("stream", "skip")
def get_force_window(self):
return self.configparser.get("stream", "force_window")
def get_icons(self):
return self.configparser.getboolean("general", "icons")

View File

@@ -80,6 +80,7 @@ def player_controls(config: "Config", anilist_config: QueryDict):
from ..utils.player import player
mpv = player.create_player(
current_link,
config.anime_provider,
anilist_config,
config,
@@ -92,7 +93,6 @@ def player_controls(config: "Config", anilist_config: QueryDict):
mpv._set_property("chapters-file", chapters_file[1])
mpv._set_property("script-opts", script_opts[1])
mpv.start = start_time
mpv.play(current_link)
mpv.wait_for_shutdown()
mpv.terminate()
stop_time = player.last_stop_time
@@ -373,7 +373,11 @@ def fetch_streams(config: "Config", anilist_config: QueryDict):
from ..utils.player import player
mpv = player.create_player(
anime_provider, anilist_config, config, selected_server["episode_title"]
stream_link,
anime_provider,
anilist_config,
config,
selected_server["episode_title"],
)
if custom_args and None:
@@ -382,7 +386,6 @@ def fetch_streams(config: "Config", anilist_config: QueryDict):
mpv._set_property("chapters-file", chapters_file[1])
mpv._set_property("script-opts", script_opts[1])
mpv.start = start_time
mpv.play(stream_link)
mpv.wait_for_shutdown()
mpv.terminate()
stop_time = player.last_stop_time
@@ -928,9 +931,9 @@ def anilist(config: "Config", anilist_config: QueryDict):
return AniList.search(id_in=anime_list)
def edit_config():
import subprocess
from click import edit
subprocess.run([os.environ.get("EDITOR", "open"), USER_CONFIG_PATH])
edit(filename=USER_CONFIG_PATH)
if config.use_rofi:
config.load_config()
config.use_rofi = True

View File

@@ -27,9 +27,13 @@ class MpvPlayer(object):
last_stop_time_secs = 0
last_total_time_secs = 0
current_media_title = ""
player_fetching = False
def get_episode(
self, type: "Literal['next','previous','reload','custom']", ep_no=None
self,
type: "Literal['next','previous','reload','custom']",
ep_no=None,
server="top",
):
anilist_config = self.anilist_config
config = self.config
@@ -62,7 +66,9 @@ class MpvPlayer(object):
elif type == "custom":
if not ep_no or ep_no not in episodes:
self.mpv_player.show_text("Episode number not specified or invalid")
self.mpv_player.show_text(f"Acceptable episodes are: {episodes}")
self.mpv_player.show_text(
f"Acceptable episodes are: {episodes}",
)
return
self.mpv_player.show_text(f"Fetching episode {ep_no}")
@@ -97,7 +103,19 @@ class MpvPlayer(object):
return None
# always select the first
selected_server = next(episode_streams)
if server == "top":
selected_server = next(episode_streams)
else:
episode_streams_dict = {
episode_stream["server"]: episode_stream
for episode_stream in episode_streams
}
selected_server = episode_streams_dict.get(server)
if selected_server is None:
self.mpv_player.show_text(
f"Invalid server!!; servers available are: {episode_streams_dict.keys()}",
)
return None
self.current_media_title = selected_server["episode_title"]
links = selected_server["links"]
if quality > len(links) - 1:
@@ -108,7 +126,12 @@ class MpvPlayer(object):
return stream_link
def create_player(
self, anime_provider: "AnimeProvider", anilist_config, config: "Config", title
self,
stream_link,
anime_provider: "AnimeProvider",
anilist_config,
config: "Config",
title,
):
self.anime_provider = anime_provider
self.anilist_config = anilist_config
@@ -120,13 +143,52 @@ class MpvPlayer(object):
self.current_media_title = ""
mpv_player = mpv.MPV(
log_handler=print,
loglevel="error",
config=True,
input_default_bindings=True,
input_vo_keyboard=True,
osc=True,
ytdl=True,
)
mpv_player.force_window = config.force_window
# mpv_player.cache = "yes"
# mpv_player.cache_pause = "no"
mpv_player.title = title
mpv_player.play(stream_link)
# -- events --
@mpv_player.event_callback("file-loaded")
def set_total_time(event, *args):
d = mpv_player._get_property("duration")
self.player_fetching = False
if isinstance(d, float):
self.last_total_time = format_time(d)
@mpv_player.property_observer("time-pos")
def handle_time_start_update(*args):
if len(args) > 1:
value = args[1]
if value is not None:
self.last_stop_time = format_time(value)
@mpv_player.property_observer("time-remaining")
def handle_time_remaining_update(
property, time_remaining: float | None = None, *args
):
if time_remaining is not None:
if time_remaining < 1 and config.auto_next and not self.player_fetching:
print("Auto Fetching Next Episode")
self.player_fetching = True
url = self.get_episode("next")
if url:
mpv_player.loadfile(
url,
)
mpv_player.title = self.current_media_title
# -- keybindings --
@mpv_player.on_key_press("shift+n")
def _next_episode():
url = self.get_episode("next")
@@ -134,18 +196,6 @@ class MpvPlayer(object):
mpv_player.loadfile(url, options=f"title={self.current_media_title}")
mpv_player.title = self.current_media_title
@mpv_player.event_callback("file-loaded")
def set_total_time(event, *args):
d = mpv_player._get_property("duration")
if isinstance(d, float):
self.last_total_time = format_time(d)
@mpv_player.event_callback("shutdown")
def set_total_time_on_shutdown(event, *args):
d = mpv_player._get_property("duration")
if isinstance(d, float):
self.last_total_time = format_time(d)
@mpv_player.on_key_press("shift+p")
def _previous_episode():
url = self.get_episode("previous")
@@ -190,31 +240,11 @@ class MpvPlayer(object):
)
mpv_player.title = self.current_media_title
@mpv_player.property_observer("time-pos")
def handle_time_start_update(*args):
if len(args) > 1:
value = args[1]
if value is not None:
self.last_stop_time_secs = value
self.last_stop_time = format_time(value)
@mpv_player.property_observer("time-remaining")
def handle_time_remaining_update(*args):
if len(args) > 1:
value = args[1]
if value is not None:
rem_time = value
if rem_time < 10 and config.auto_next:
url = self.get_episode("next")
if url:
mpv_player.loadfile(
url,
)
mpv_player.title = self.current_media_title
# -- script messages --
@mpv_player.message_handler("select-episode")
def select_episode(episode: bytes | None = None, *args):
if not episode:
mpv_player.show_text("No episode was selected")
return
url = self.get_episode("custom", episode.decode())
if url:
@@ -223,11 +253,29 @@ class MpvPlayer(object):
)
mpv_player.title = self.current_media_title
mpv_player.register_message_handler("select-episode", select_episode)
@mpv_player.message_handler("select-server")
def select_server(server: bytes | None = None, *args):
if not server:
mpv_player.show_text("No server was selected")
return
url = self.get_episode("reload", server=server.decode())
if url:
mpv_player.loadfile(
url,
)
mpv_player.title = self.current_media_title
else:
pass
# -- events --
mpv_player.observe_property("time-pos", handle_time_start_update)
mpv_player.register_event_callback(set_total_time)
mpv_player.register_event_callback(set_total_time_on_shutdown)
mpv_player.observe_property("time-remaining", handle_time_remaining_update)
mpv_player.register_event_callback(set_total_time)
# --script-messages --
mpv_player.register_message_handler("select-episode", select_episode)
mpv_player.register_message_handler("select-server", select_server)
self.mpv_player = mpv_player
return mpv_player

View File

@@ -20,6 +20,8 @@ if PLATFORM == "Windows":
ICON_PATH = os.path.join(ASSETS_DIR, "logo.ico")
else:
ICON_PATH = os.path.join(ASSETS_DIR, "logo.png")
PREVIEW_IMAGE = os.path.join(ASSETS_DIR, "preview")
# ----- user configs and data -----
APP_DATA_DIR = dirs.user_config_dir

View File

@@ -6,7 +6,6 @@ import sys
from typing import Callable, List
# TODO: will probably scrap art not to useful
from art import text2art
from click import clear
from rich import print
@@ -22,6 +21,17 @@ FZF_DEFAULT_OPTS = """
--marker=">" --pointer="" --separator="" --scrollbar=""
"""
HEADER = """
███████╗░█████╗░░██████╗████████╗░█████╗░███╗░░██╗██╗███╗░░░███╗███████╗
██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔══██╗████╗░██║██║████╗░████║██╔════╝
█████╗░░███████║╚█████╗░░░░██║░░░███████║██╔██╗██║██║██╔████╔██║█████╗░░
██╔══╝░░██╔══██║░╚═══██╗░░░██║░░░██╔══██║██║╚████║██║██║╚██╔╝██║██╔══╝░░
██║░░░░░██║░░██║██████╔╝░░░██║░░░██║░░██║██║░╚███║██║██║░╚═╝░██║███████╗
╚═╝░░░░░╚═╝░░╚═╝╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚══╝╚═╝╚═╝░░░░░╚═╝╚══════╝
"""
class FZF:
"""an abstraction over the fzf commandline utility
@@ -149,7 +159,7 @@ class FZF:
_commands = [
*self.default_options,
"--header",
text2art(header),
HEADER,
"--header-first",
"--prompt",
prompt.title(),

16
poetry.lock generated
View File

@@ -1,19 +1,5 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "art"
version = "6.2"
description = "ASCII Art Library For Python"
optional = false
python-versions = ">=3.5"
files = [
{file = "art-6.2-py3-none-any.whl", hash = "sha256:d632d1d3f5fabcaf8673abe934b51df0017bc914d106e89d45ae4ebef0e3149a"},
{file = "art-6.2.tar.gz", hash = "sha256:506a0c4f261289a0e0d088de7beffcb1835078c4e44b0c5353bdaf47b490e76f"},
]
[package.extras]
dev = ["bandit (>=1.5.1)", "coverage (>=4.1)", "pydocstyle (>=3.0.0)", "vulture (>=1.0)"]
[[package]]
name = "autoflake"
version = "2.3.1"
@@ -1421,4 +1407,4 @@ test = ["pytest (>=8.1,<9.0)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "5305621bc02d824065519913f0d754e269f1e4525ba8796be08504120614259d"
content-hash = "83ec7de7d9466dcd1fadef4b21eec2a879cc9a7d526992ed280b6af53b49d9f1"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "fastanime"
version = "0.50.0"
version = "0.61.5.dev1"
description = "A browser anime site experience from the terminal"
authors = ["Benextempest <benextempest@gmail.com>"]
license = "UNLICENSE"
@@ -13,7 +13,6 @@ rich = "^13.7.1"
click = "^8.1.7"
inquirerpy = "^0.3.4"
platformdirs = "^4.2.2"
art = "^6.2"
python-dotenv = "^1.0.1"
thefuzz = "^0.22.1"
requests = "^2.32.3"

View File

@@ -45,6 +45,16 @@ def test_search_help(runner: CliRunner):
assert result.exit_code == 0
def test_cache_help(runner: CliRunner):
result = runner.invoke(run_cli, ["cache", "--help"])
assert result.exit_code == 0
def test_completions_help(runner: CliRunner):
result = runner.invoke(run_cli, ["completions", "--help"])
assert result.exit_code == 0
def test_anilist_help(runner: CliRunner):
result = runner.invoke(run_cli, ["anilist", "--help"])
assert result.exit_code == 0