├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ ├── install_issue.yml
│ └── xfeature.yml
├── apple.png
├── archlinux-icon.svg
├── korben.png
├── pull_request_template.md
├── tux.png
└── workflows
│ └── release.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── SECURITY.md
├── assets
├── demo_1.gif
├── demo_2.gif
└── demo_3.gif
├── config.example.toml
├── hello_toutui.sh
├── known_bugs.md
├── linux
└── toutui.desktop
├── macos
├── Info.plist
└── launch.command
└── src
├── api
├── libraries
│ ├── get_all_books.rs
│ ├── get_all_libraries.rs
│ ├── get_library_perso_view.rs
│ ├── get_library_perso_view_pod.rs
│ └── mod.rs
├── library_items
│ ├── get_pod_ep.rs
│ ├── mod.rs
│ └── play_lib_item_or_pod.rs
├── me
│ ├── get_media_progress.rs
│ ├── mod.rs
│ └── update_media_progress.rs
├── mod.rs
├── server
│ ├── auth_process.rs
│ └── mod.rs
├── sessions
│ ├── close_open_session.rs
│ ├── mod.rs
│ └── sync_open_session.rs
└── utils
│ ├── collect_get_all_books.rs
│ ├── collect_get_all_libraries.rs
│ ├── collect_get_media_progress.rs
│ ├── collect_get_pod_ep.rs
│ ├── collect_personalized_view.rs
│ ├── collect_personalized_view_pod.rs
│ └── mod.rs
├── app.rs
├── config.rs
├── db
├── crud.rs
├── database_struct.rs
└── mod.rs
├── logic
├── auth
│ ├── auth_input.rs
│ └── mod.rs
├── handle_input
│ ├── handle_l_book.rs
│ ├── handle_l_pod.rs
│ ├── handle_l_pod_home.rs
│ └── mod.rs
├── mod.rs
├── search
│ ├── mod.rs
│ └── search_active.rs
└── sync_session
│ ├── mod.rs
│ ├── sync_session_from_database.rs
│ └── wait_prev_session_finished.rs
├── login_app.rs
├── main.rs
├── player
├── integrated
│ ├── handle_key_player.rs
│ ├── mod.rs
│ └── player_info.rs
├── mod.rs
└── vlc
│ ├── exec_nc.rs
│ ├── fetch_vlc_data.rs
│ ├── mod.rs
│ ├── quit_vlc.rs
│ └── start_vlc.rs
├── ui
├── login_tui.rs
├── mod.rs
├── player_tui.rs
└── tui.rs
└── utils
├── changelog.rs
├── check_update.rs
├── clap.rs
├── convert_seconds.rs
├── encrypt_token.rs
├── exit_app.rs
├── logs.rs
├── mod.rs
├── pop_up_message.rs
└── vlc_tcp_stream.rs
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sh linguist-vendored
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: albandavid
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: #albdav
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: 😬 Bug Report
2 | description: File a bug/issue allow to improve Toutui
3 | title: '[Bug]: '
4 | labels: ['bug']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: '🦜 Thanks for taking the time to fill out this bug report!'
9 | - type: markdown
10 | attributes:
11 | value: 'Please first check if the bug is listed into [known bugs](https://github.com/AlbanDAVID/Toutui/blob/main/known_bugs.md) or issues.'
12 | - type: textarea
13 | id: what-happened
14 | attributes:
15 | label: What happened?
16 | placeholder: Tell us what you see and give screenshot if it's applicable.
17 | validations:
18 | required: true
19 | - type: textarea
20 | id: what-was-expected
21 | attributes:
22 | label: What did you expect to happen?
23 | placeholder: Explain what you expected to see, give screenshot if it's applicable.
24 | validations:
25 | required: true
26 | - type: textarea
27 | id: steps-to-reproduce
28 | attributes:
29 | label: Steps to reproduce the issue
30 | value: '1. '
31 | validations:
32 | required: true
33 | - type: markdown
34 | attributes:
35 | value: '## Install Environment'
36 | - type: input
37 | id: version
38 | attributes:
39 | label: Toutui version
40 | description: Do not put 'Latest version', please put the actual version here
41 | placeholder: 'e.g. v0.1.0-beta'
42 | validations:
43 | required: true
44 | - type: input
45 | id: audiobookshelf-version
46 | attributes:
47 | label: Audiobookshelf version
48 | description: Do not put 'Latest version', please put the actual version here
49 | placeholder: 'e.g. v2.19.4'
50 | validations:
51 | required: true
52 | - type: dropdown
53 | id: install-distro
54 | attributes:
55 | label: On which OS are you running Toutui?
56 | options:
57 | - Arch Linux
58 | - Ubuntu
59 | - Debian
60 | - macOS
61 | - Other (list in "Additional Notes" box)
62 | validations:
63 | required: true
64 | - type: dropdown
65 | id: install-method
66 | attributes:
67 | label: How did you install Toutui?
68 | options:
69 | - Easy installation (option 1, download the binary)
70 | - Easy installation (option 2, compilation)
71 | - From source, local clone
72 | - Other (list in "Additional Notes" box)
73 | validations:
74 | required: true
75 | - type: dropdown
76 | id: terminal-emulator
77 | attributes:
78 | label: On which terminal emulator are you running Toutui?
79 | options:
80 | - Alacritty
81 | - Kitty
82 | - GNOME Terminal
83 | - Konsole
84 | - Other
85 | validations:
86 | required: true
87 | - type: textarea
88 | id: logs
89 | attributes:
90 | label: Logs
91 | description: Logs are present in ~/.config/toutui/toutui.log
92 | placeholder: Paste logs here
93 | - type: textarea
94 | id: panicked-message
95 | attributes:
96 | label: Panicked/Crash message
97 | description: If the app panicked/crashed and left a message
98 | placeholder: Paste message here
99 | - type: textarea
100 | id: additional-notes
101 | attributes:
102 | label: Additional Notes
103 | description: Anything else you want to add?
104 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/install_issue.yml:
--------------------------------------------------------------------------------
1 | name: 🔧 Install / Update / Uninstall Issue
2 | description: If you have any issue during the installation, update or uninstall
3 | title: '[Install / Update / Uninstall Issue]: '
4 | labels: ['Install / Update / Uninstall Issue']
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: '🦜 Thanks for taking the time to fill out this issue!'
9 | - type: markdown
10 | attributes:
11 | value: 'Please first check if the issue is listed into issues.'
12 | - type: textarea
13 | id: what-happened
14 | attributes:
15 | label: What happened?
16 | placeholder: Tell us what you see and give a screenshot if it's applicable.
17 | validations:
18 | required: true
19 | - type: textarea
20 | id: steps-to-reproduce
21 | attributes:
22 | label: Steps to reproduce the issue
23 | value: '1. '
24 | - type: markdown
25 | attributes:
26 | value: '## Install Environment'
27 | - type: input
28 | id: version
29 | attributes:
30 | label: Toutui version
31 | description: Do not put 'Latest version', please put the actual version here
32 | placeholder: 'e.g. v0.1.0-beta'
33 | validations:
34 | required: true
35 | - type: dropdown
36 | id: install-distro
37 | attributes:
38 | label: On which OS ?
39 | options:
40 | - Arch Linux
41 | - Ubuntu
42 | - Debian
43 | - macOS
44 | - Other (list in "Additional Notes" box)
45 | validations:
46 | required: true
47 | - type: dropdown
48 | id: install-method
49 | attributes:
50 | label: Which install, update method?
51 | options:
52 | - Easy installation (option 1, download the binary)
53 | - Easy installation (option 2, compilation)
54 | - yay
55 | - From source, local clone
56 | - Other (list in "Additional Notes" box)
57 | validations:
58 | required: true
59 | - type: textarea
60 | id: error-message
61 | attributes:
62 | label: Error message
63 | description: Error message during the installation
64 | placeholder: Paste message here
65 | - type: textarea
66 | id: additional-notes
67 | attributes:
68 | label: Additional Notes
69 | description: Anything else you want to add?
70 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/xfeature.yml:
--------------------------------------------------------------------------------
1 | name: 🚀 Feature Request
2 | description: Request a feature/enhancement
3 | title: '[Enhancement]: '
4 | labels: ['enhancement']
5 | body:
6 | - type: dropdown
7 | id: enhancement-type
8 | attributes:
9 | label: Type of Enhancement
10 | options:
11 | - Feature
12 | - Code
13 | - UI
14 | - Documentation
15 | - type: textarea
16 | id: describe
17 | attributes:
18 | label: Describe the Feature/Enhancement
19 | description: Please help us understand what you want.
20 | placeholder: What is your vision?
21 | validations:
22 | required: true
23 | - type: textarea
24 | id: the-why
25 | attributes:
26 | label: Why would this be helpful?
27 | description: Please help us understand why this would enhance your experience.
28 | placeholder: Explain the "why" or "use case".
29 | validations:
30 | required: true
31 |
--------------------------------------------------------------------------------
/.github/apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlbanDAVID/Toutui/62ee88ee61871c9eb31228c6a00d4d9ce1d4a161/.github/apple.png
--------------------------------------------------------------------------------
/.github/archlinux-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/korben.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlbanDAVID/Toutui/62ee88ee61871c9eb31228c6a00d4d9ce1d4a161/.github/korben.png
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | ## Brief summary
8 |
9 |
10 |
11 | ## Which issue is fixed?
12 |
13 |
14 |
15 | ## In-depth Description
16 |
17 |
22 |
23 | ## How have you tested this?
24 |
25 |
26 |
27 | ## Screenshots
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.github/tux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlbanDAVID/Toutui/62ee88ee61871c9eb31228c6a00d4d9ce1d4a161/.github/tux.png
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | permissions:
4 | contents: write
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | create-release:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: taiki-e/create-gh-release-action@v1
16 | with:
17 | # (optional) Path to changelog.
18 | #changelog: CHANGELOG.md
19 | # (required) GitHub token for creating GitHub Releases.
20 | draft: true
21 | token: ${{ secrets.GITHUB_TOKEN }}
22 |
23 | upload-assets:
24 | needs: create-release
25 | strategy:
26 | matrix:
27 | include:
28 | - target: aarch64-unknown-linux-gnu
29 | os: ubuntu-latest
30 | # - target: aarch64-apple-darwin
31 | # os: macos-latest
32 | - target: x86_64-unknown-linux-gnu
33 | os: ubuntu-latest
34 | # - target: x86_64-apple-darwin
35 | # os: macos-latest
36 | # Universal macOS binary is supported as universal-apple-darwin.
37 | - target: universal-apple-darwin
38 | os: macos-latest
39 | runs-on: ${{ matrix.os }}
40 | steps:
41 | - uses: actions/checkout@v4
42 | - uses: taiki-e/upload-rust-binary-action@v1
43 | with:
44 | # (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload.
45 | # Note that glob pattern is not supported yet.
46 | bin: toutui
47 | # (optional) Target triple, default is host triple.
48 | target: ${{ matrix.target }}
49 | # (required) GitHub token for uploading assets to GitHub Releases.
50 | token: ${{ secrets.GITHUB_TOKEN }}
51 |
52 | - name: Prepare assets
53 | run: |
54 | mkdir -p dist
55 | cp ./config.example.toml dist/
56 | cp ./linux/toutui.desktop dist/
57 | cp ./hello_toutui.sh dist/
58 |
59 | - name: Create GitHub release and upload files
60 | uses: softprops/action-gh-release@v1
61 | with:
62 | draft: true
63 | files: dist/*
64 | env:
65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | config.toml
3 | /src/db/db.sqlite3
4 | /src/toutui.log
5 | /src/changelog.txt
6 | /src/macos-sonoma
7 | /src/macos-sonoma.conf
8 |
9 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | .
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Toutui
2 |
3 | Thanks for your interest in **Toutui**! 🦜
4 |
5 | ## ⚠️ Beta Version
6 | This project is still in **heavy development**. I built this app to learn Rust. The code isn’t fully optimized or clear. However, feel free to contact me with any questions. There are known bugs (check [known_bugs](https://github.com/AlbanDAVID/Toutui/blob/main/known_bugs.md) and open issues).
7 |
8 | Check the [roadmap](https://github.com/AlbanDAVID/Toutui?tab=readme-ov-file#%EF%B8%8F-roadmap) to see what I'm currently working on.
9 | If you want to contribute new features but don't have any ideas, feel free to check the [future features](https://github.com/AlbanDAVID/Toutui?tab=readme-ov-file#-future-features) section for inspiration.
10 |
11 | ## 🔁 Branching workflow
12 | This project follow this [branching workflow](https://gist.github.com/digitaljhelms/4287848).
13 |
14 | ## 💬 How to Contribute
15 | - **Share your theme**: Check [here](https://github.com/AlbanDAVID/Toutui-theme).
16 | - **Suggestions/feedback**: Open an issue (feature request) or use [discussions](https://github.com/AlbanDAVID/Toutui/discussions).
17 | - **Bugs**: Report bugs not listed in issues or [known bugs](https://github.com/AlbanDAVID/Toutui/blob/main/known_bugs.md). Use the appropriate issue section (Installation issue or bug report).
18 | - **Code**: Fork the repo, create a branch, and submit a pull request. **I encourage you to discuss your ideas with me before a PR** (to ensure no one else is working on it). Code doesn’t need to be perfect—let’s collaborate and improve it together!
19 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "toutui"
3 | version = "0.4.2-beta"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | openssl = { version = "0.10.71", features = ["vendored"] }
8 | tokio = { version = "1", features = ["full"] }
9 | reqwest = { version = "0.11", features = ["json"] }
10 | serde = { version = "1.0", features = ["derive"] }
11 | serde_json = "1.0"
12 | serde_derive = "1.0"
13 | config = "0.15.6"
14 | color-eyre = "0.6.3"
15 | crossterm = "0.28.1"
16 | ratatui = "0.29.0"
17 | vlc-rc = "0.1.1"
18 | tui-textarea = "0.7.0"
19 | rusqlite = { version = "0.33.0", features = ["bundled"] }
20 | regex = "1.11.1"
21 | log = "0.4.25"
22 | fern = "0.7.1"
23 | chrono = "0.4.39"
24 | magic-crypt = "4.0.1"
25 | dotenv = "0.15.0"
26 | dirs = "6.0.0"
27 | clap = "4.5.37"
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/AlbanDAVID/Toutui/releases/latest)
2 | 
3 | [](https://github.com/AlbanDAVID/Toutui/actions/workflows/release.yml)
4 |
5 | # 🦜 Toutui: A TUI Audiobookshelf client for Linux and macOS
6 |
7 |
8 | In French, being "tout ouïe" (toutui) means being all ears.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 🎨 Explore and try various themes
here.
17 |
18 |
19 | ## ✨ Features
20 | **Cross-platform** –
Linux and
macOS
21 | **Lightweight & Fast** – A minimalist terminal user interface (TUI) written in Rust 🦀
22 | **Supports Books & Podcasts** – Enjoy both audiobooks and podcasts
23 | **Sync Progress & Stats** – Keep your listening progress in sync
24 | **Streaming Support** – Play directly without downloading
25 | **Customizable Color Theme** – A config file will allow you to customize the color theme. Explore and try various themes [here](https://github.com/AlbanDAVID/Toutui-theme).
26 |
27 | ## 📰 Media
28 |
Featured on [Korben](https://korben.info/toutui-client-terminal-audiobookshelf.html), a well-known French tech blog covering open source and technology.
29 |
30 |
31 | ## 🛠️ Roadmap
32 | **Short-term Goals**
33 | - Since this is a beta version, the main focus is on tracking and fixing bugs.
34 | - Improve the design of the integrated player.
35 | - **Currently working on the next release: [v0.4.3-beta].**
36 |
37 |
38 | **Mid-term Goals**
39 | - CI/CD Implementation
40 | - Add future features described bellow.
41 |
42 | ## 🔮 Future features
43 | Here are some features that could be added in future releases:
44 | - Ability to add new podcasts from the app
45 | - Add stats
46 | - Offline mode
47 |
48 | ## ⚠️ Caution: Beta Version
49 | This beta app is still in **heavy development and contains bugs**.
50 | ❗Please check [here](https://github.com/AlbanDAVID/Toutui/blob/main/known_bugs.md) for known bugs especially **MAJOR BUGS** before using the app, so you can use it with full awareness of any known issues.
51 | If you encounter any issues that are **not yet listed** in the Issues section or into [known bugs](https://github.com/AlbanDAVID/Toutui/blob/main/known_bugs.md), please **open a new issue** to report them.
52 |
53 | 🔐 Although it's a beta version, you can use this app with **minimal risk** to your Audiobookshelf library.
54 | At worst, you may experience **sync issues**, but there is **no risk** of data loss, deletion, or irreversible changes (API is just used to retrieve books and sync them).
55 |
56 | ## 📝 Notes
57 | ### 🐛 **Issues**
58 | For any issues, check first the [wiki](https://github.com/AlbanDAVID/Toutui/wiki/) and [issues](https://github.com/AlbanDAVID/Toutui/issues). Otherwise, open a new one.
59 |
60 | ### 🤝 **Contributing**
61 | Do not hesitate to contribute to this project by submitting your code, ideas, or feedback. Please make sure to read the [contributing guidelines](https://github.com/AlbanDAVID/Toutui/blob/main/CONTRIBUTING.md) first.
62 |
63 | ### 🔁 Branching workflow
64 | This project follow this [branching workflow](https://gist.github.com/digitaljhelms/4287848).
65 |
66 | ### 🎨 **UI**
67 | Explore and share themes [here](https://github.com/AlbanDAVID/Toutui-theme).
68 | The **font** and **emojis** may vary depending on the terminal you are using.
69 | To ensure the best experience, it's recommended to use **Kitty** or **Alacritty** terminal.
70 |
71 |
72 |
73 | ## 🚨 Installation Instructions
74 |
75 | >[!WARNING]
76 | > - **This is a beta app, please read [this](https://github.com/AlbanDAVID/Toutui?tab=readme-ov-file#%EF%B8%8F-caution-beta-version).**
77 | > - For any issues, check first the [wiki](https://github.com/AlbanDAVID/Toutui/wiki/) and [issues](https://github.com/AlbanDAVID/Toutui/issues). Otherwise, open a new one.
78 |
79 | ###
Arch Linux
80 | [](https://github.com/AlbanDAVID/Toutui/releases/latest)
81 | 
82 |
83 | Installation and initial configuration
84 | ```
85 | yay -S toutui
86 | mkdir -p ~/.config/toutui
87 | cp /usr/share/toutui/config.example.toml ~/.config/toutui/config.toml
88 | # Token encryption in the database (NOTE: replace 'secret'):
89 | echo 'TOUTUI_SECRET_KEY=secret' >> ~/.config/toutui/.env
90 | ```
91 | Update
92 | ```
93 | yay -S toutui
94 | ```
95 | Uninstall
96 | ```
97 | yay -R toutui-bin
98 | ```
99 |
100 | ### ⚡ Easy installation
101 |
102 | **Run the following in your terminal, then follow the on-screen instructions:**
103 |
104 | [](https://github.com/AlbanDAVID/Toutui/releases/latest)
105 |
106 |
107 | ```bash
108 | bash -c 'expected_sha256="b5c41bcd3c480fd2ca6ec0031ccecf2cf7cf4ae01f591cad64a320fa7d72331d" export expected_sha256 tmpfile=$(mktemp) && curl -LsSf https://github.com/AlbanDAVID/Toutui/raw/stable/hello_toutui.sh -o "$tmpfile" && bash "$tmpfile" install && rm -f "$tmpfile"'
109 | ```
110 |
111 | #### **Update**
112 |
113 | > [!IMPORTANT]
114 | > `toutui --update` is not working. You can do this instead:
115 | > ```
116 | > bash -c 'expected_sha256="b5c41bcd3c480fd2ca6ec0031ccecf2cf7cf4ae01f591cad64a320fa7d72331d" export expected_sha256 tmpfile=$(mktemp) && curl -LsSf https://github.com/AlbanDAVID/Toutui/raw/stable/hello_toutui.sh -o "$tmpfile" && bash "$tmpfile" update && rm -f "$tmpfile"'
117 | > ```
118 |
119 | Quit the app and run the following in your terminal
120 |
121 | ```bash
122 | bash -c 'expected_sha256="b5c41bcd3c480fd2ca6ec0031ccecf2cf7cf4ae01f591cad64a320fa7d72331d" export expected_sha256 tmpfile=$(mktemp) && curl -LsSf https://github.com/AlbanDAVID/Toutui/raw/stable/hello_toutui.sh -o "$tmpfile" && bash "$tmpfile" update && rm -f "$tmpfile"'
123 | ```
124 |
125 | #### **Uninstall**
126 |
127 | > [!IMPORTANT]
128 | > `toutui --uninstall` is not working. You can do this instead:
129 | > ```
130 | > bash -c 'expected_sha256="b5c41bcd3c480fd2ca6ec0031ccecf2cf7cf4ae01f591cad64a320fa7d72331d" export expected_sha256 tmpfile=$(mktemp) && curl -LsSf https://github.com/AlbanDAVID/Toutui/raw/stable/hello_toutui.sh -o "$tmpfile" && bash "$tmpfile" uninstall && rm -f "$tmpfile"'
131 | > ```
132 |
133 | Quit the app and run the following in your terminal
134 |
135 |
136 | ```bash
137 | bash -c 'expected_sha256="b5c41bcd3c480fd2ca6ec0031ccecf2cf7cf4ae01f591cad64a320fa7d72331d" export expected_sha256 tmpfile=$(mktemp) && curl -LsSf https://github.com/AlbanDAVID/Toutui/raw/stable/hello_toutui.sh -o "$tmpfile" && bash "$tmpfile" uninstall && rm -f "$tmpfile"'
138 | ```
139 |
140 | #### **Notes**
141 |
142 | ##### Files installed:
143 | In `/usr/local/bin` (option 1, from install script) or `~/.cargo/bin` (option 2, from install script) or `/usr/bin` (yay) :
144 | - `toutui` — The binary file.
145 |
146 | In `~/.config/toutui` for Linux or `~/Library/Preferences` for macOS:
147 | **Note**: This is the default path if `XDG_CONFIG_HOME` is empty.
148 | - `.env` — Contains the secret key.
149 | - `config.toml` — Configuration file.
150 | - `toutui.log` — Log file.
151 | - `db.sqlite3` — SQLite database file.
152 |
153 | In `~/.local/share/applications` (option 1, from install script) or `/usr/share/applications` (yay) for Linux:
154 | - `toutui.desktop` — Config file to launch Toutui from a launcher app.
155 |
156 | In `/usr/share/toutui` (yay):
157 | - `config.example.toml` — Configuration file.
158 |
159 | ### Install from source
160 |
161 | >[!WARNING]
162 | > This is a beta app, please read [this](https://github.com/AlbanDAVID/Toutui?tab=readme-ov-file#%EF%B8%8F-caution-beta-version).
163 |
164 | #### **Requirements**
165 | - `Rust`
166 | - `Netcat`
167 | - `VLC`
168 |
169 | [](https://github.com/AlbanDAVID/Toutui/releases/latest)
170 |
171 | Note: `main` might be unstable. Prefer `git clone --branch stable --single-branch https://github.com/AlbanDAVID/Toutui` if you want to have the last stable release.
172 | ```bash
173 | git clone https://github.com/AlbanDAVID/Toutui
174 | cd Toutui/
175 | mkdir -p ~/.config/toutui
176 | cp config.example.toml ~/.config/toutui/config.toml
177 | ```
178 |
179 | Token encryption in the database (**NOTE**: replace `secret`)
180 | ```bash
181 | echo TOUTUI_SECRET_KEY=secret >> ~/.config/toutui/.env
182 | ```
183 |
184 | ```bash
185 | cargo run --release
186 | ```
187 |
188 | #### **Update**
189 |
190 | When a new release is available, follow these steps:
191 |
192 | ```bash
193 | git pull https://github.com/AlbanDAVID/Toutui
194 | cargo run --release
195 | ```
196 |
197 | #### **Notes**
198 | ##### Exec the binary:
199 | ```bash
200 | cd target/release
201 | ./Toutui
202 | ```
203 |
204 | ##### Files installed:
205 | After installation, you will have the following files in `~/.config/toutui`
206 | - `.env` — Contains the secret key.
207 | - `config.toml` — Configuration file.
208 | - `toutui.log` — Log file.
209 | - `db.sqlite3` — SQLite database file.
210 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | Contact me or use the GitHub Security Advisories to report any vulnerabilities.
4 |
--------------------------------------------------------------------------------
/assets/demo_1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlbanDAVID/Toutui/62ee88ee61871c9eb31228c6a00d4d9ce1d4a161/assets/demo_1.gif
--------------------------------------------------------------------------------
/assets/demo_2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlbanDAVID/Toutui/62ee88ee61871c9eb31228c6a00d4d9ce1d4a161/assets/demo_2.gif
--------------------------------------------------------------------------------
/assets/demo_3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlbanDAVID/Toutui/62ee88ee61871c9eb31228c6a00d4d9ce1d4a161/assets/demo_3.gif
--------------------------------------------------------------------------------
/config.example.toml:
--------------------------------------------------------------------------------
1 | #### PLAYER OPTIONS ####
2 | [player]
3 | # By default, Toutui integrated player is launched (works with cvlc, launched in background, the command line
4 | # version of vlc).
5 | # 0: No, 1: Yes
6 | # Launch Toutui integrated player:
7 | # Set to 0 if you want to have vlc GUI.
8 | cvlc = "1"
9 | # Launch a terminal to control cvlc:
10 | # NOTE: `cvlc` must be set to 1 if you set `cvlc_term` to 1.
11 | cvlc_term = "0"
12 |
13 | # #### PLAYER NETWORK ####
14 | # By default, the player is launched to "localhost:1234"
15 | # Address:
16 | address = "localhost"
17 | # Port:
18 | port = "1234"
19 |
20 | #### COLORS (RGB) ####
21 | [colors]
22 | # General background color of the app:
23 | background_color = [40, 40, 40]
24 | # Login background color:
25 | log_background_color = [40, 40, 40]
26 | # Background color for section headers (e.g., "Continue Listening", "Library", etc.):
27 | header_background_color = [60, 60, 60]
28 | # Line color surrounding the header title:
29 | line_header_color = [180, 180, 180]
30 | # Background color of the book list:
31 | list_background_color = [50, 50, 50]
32 | # Background color for alternate rows of the book list:
33 | list_background_color_alt_row = [60, 60, 60]
34 | # Background color of the selected row of the book list:
35 | list_selected_background_color = [80, 80, 80]
36 | # Text color of the selected row of the book list:
37 | list_selected_foreground_color = [180, 180, 180]
38 | # Text and border color of the search bar:
39 | search_bar_foreground_color = [180, 180, 180]
40 | # Text and border color of the login section:
41 | login_foreground_color = [180, 180, 180]
42 | # Background color of the player:
43 | player_background_color = [80, 80, 80]
44 |
--------------------------------------------------------------------------------
/known_bugs.md:
--------------------------------------------------------------------------------
1 | **MAJOR**
2 |
3 | No major bug for the moment 🙏
4 |
5 | **MINOR**
6 |
7 | `bug_id: 255b86`
8 | **Losing config after an update**: Ex: You change colors in config file and after an update, this configuration is lost and replaced by the config from main version.
9 |
10 | `bug_id: 4b3045`
11 | **Authentification Bug:** Even if you fill in valid credentials, the database sync can be buggy, and authentication may fail. Normally, it works on the second try.
12 |
13 | `bug_id: 2eb9e3`
14 | **Display:** At the launch, the app is not displayed and no error message appears (especially if you change user, quit and restart the app). Solution: quit the terminal and try it again.
15 |
16 | `bug_id: 2d358c53`
17 | **Mark as finished:** When a title reach the end, mark as finished not always work.
18 |
19 | `bug_id: a49eza`
20 | **cvlc error sync with ctrl vlc from a terminal:** If you use other command that `shutdown` to quit `cvlc` it may result of a sync issue.
21 |
22 |
23 | **FIXED**
24 | `bug_id: 9bacac`
25 | **Sync**: If you open VLC to listen X, close VLC and quickly open VLC again to listen Y: X will still be sync — according to Y (normally, only Y has to be sync in this case).
26 | `bug_id: 86384e`
27 | **Sync**: Rarely and especially if you open VLC to listen X, close VLC and quickly open VLC again to listen Y: the progress of X is set to 0 seconds.
28 | `bug_id: 06e548`
29 | **Terminal broken**: The terminal is broken after the app is quit.
30 | `bug_id: 6ac5d8`
31 | **Data loss if app crash or disgracefully quit**: If app crash, the last session is not closed.
32 | `bug_id: bf10cd`
33 | **Launch a new media**: Have to close manually VLC to close and sync a session.
34 | `bug_id: 3f729c`
35 | **Loading time**: for now, not optimized for a library with a lot of items (long start loading and refresh time)
36 | `bug_id: dd9a649`
37 | **Listening Session:** Sometimes, the session (that you can see in `yourserveraddress/audiobookshelf/config/sessions`) does not close correctly, especially if you open VLC, quit it quickly, and start another book.
38 | `bug_id: e0b61c`
39 | **VLC:** `VLC` continue to run after the app is quit.
40 | `bug_id: fc695f`
41 | **Listening session:** The session (that you can see in `yourserveraddress/audiobookshelf/config/sessions`) does not close when the app is quit.
42 | `bug_id: 40f48d`
43 | **Cursor:** When you quit the app, terminal cursor disappear.
44 | `bug_id: fe4116`
45 | **cvlc macOS:** `cvlc` option is not available for now in macOS.
46 |
--------------------------------------------------------------------------------
/linux/toutui.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Toutui
3 | GenericName=Audiobookshelf client
4 | Exec=toutui
5 | Icon=utilities-terminal
6 | Type=Application
7 | Categories=Utility;
8 | Terminal=true
9 |
10 |
--------------------------------------------------------------------------------
/macos/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CFBundleName
7 | Toutui
8 | CFBundleIdentifier
9 | com.example.toutui
10 | CFBundleVersion
11 | 1.0
12 | CFBundleExecutable
13 | launch.command
14 |
15 |
16 |
--------------------------------------------------------------------------------
/macos/launch.command:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | terminal="Terminal"
3 | open -a "$terminal" "$HOME/.cargo/bin/toutui"
4 |
--------------------------------------------------------------------------------
/src/api/libraries/get_all_books.rs:
--------------------------------------------------------------------------------
1 | use reqwest::Client;
2 | use serde_json::Value;
3 | use reqwest::header::AUTHORIZATION;
4 | use color_eyre::eyre::{Result, Report};
5 | use serde::Deserialize;
6 | use serde::Serialize;
7 |
8 | /// Get all books or podcasts from a library
9 | /// https://api.audiobookshelf.org/#get-a-library-39-s-items
10 |
11 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
12 | #[serde(rename_all = "camelCase")]
13 | pub struct Root {
14 | pub results: Option>,
15 | pub total: Option,
16 | pub limit: Option,
17 | pub page: Option,
18 | pub sort_by: Option,
19 | pub sort_desc: Option,
20 | pub filter_by: Option,
21 | pub media_type: Option,
22 | pub minified: Option,
23 | pub collapseseries: Option,
24 | pub include: Option,
25 | }
26 |
27 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
28 | #[serde(rename_all = "camelCase")]
29 | pub struct LibraryItem {
30 | pub id: Option,
31 | pub ino: Option,
32 | pub library_id: Option,
33 | pub folder_id: Option,
34 | pub path: Option,
35 | pub rel_path: Option,
36 | pub is_file: Option,
37 | pub mtime_ms: Option,
38 | pub ctime_ms: Option,
39 | pub birthtime_ms: Option,
40 | pub added_at: Option,
41 | pub updated_at: Option,
42 | pub is_missing: Option,
43 | pub is_invalid: Option,
44 | pub media_type: Option,
45 | pub media: Option,
46 | pub num_files: Option,
47 | pub size: Option,
48 | pub collapsed_series: Option,
49 | }
50 |
51 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
52 | #[serde(rename_all = "camelCase")]
53 | pub struct Media {
54 | pub metadata: Option,
55 | pub cover_path: Option,
56 | pub tags: Option>,
57 | pub num_tracks: Option,
58 | pub num_audio_files: Option,
59 | pub num_chapters: Option,
60 | pub duration: Option,
61 | pub size: Option,
62 | pub ebook_file_format: Option,
63 | }
64 |
65 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
66 | #[serde(rename_all = "camelCase")]
67 | pub struct Metadata {
68 | pub title: Option,
69 | pub title_ignore_prefix: Option,
70 | pub subtitle: Option,
71 | pub author_name: Option,
72 | pub author: Option,
73 | pub narrator_name: Option,
74 | pub series_name: Option,
75 | pub genres: Option>,
76 | pub published_year: Option,
77 | pub published_date: Option,
78 | pub publisher: Option,
79 | pub description: Option,
80 | pub isbn: Option,
81 | pub asin: Option,
82 | pub language: Option,
83 | pub explicit: Option,
84 | }
85 |
86 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
87 | #[serde(rename_all = "camelCase")]
88 | pub struct CollapsedSeries {
89 | pub id: Option,
90 | pub name: Option,
91 | pub name_ignore_prefix: Option,
92 | pub num_books: Option,
93 | }
94 |
95 | // get all books or podcasts
96 | pub async fn get_all_books(token: &str, id_selected_lib: &String, server_address: String) -> Result {
97 | let client = Client::new();
98 | let url = format!("{}/api/libraries/{}/items?limit=0", server_address, id_selected_lib);
99 |
100 |
101 | // Send GET request
102 | let response = client
103 | .get(url)
104 | .header(AUTHORIZATION, format!("Bearer {}", token))
105 | .send()
106 | .await?;
107 |
108 | // Check response status
109 | if !response.status().is_success() {
110 | return Err(Report::new(std::io::Error::new(
111 | std::io::ErrorKind::Other,
112 | "Failed to fetch data from the API",
113 | )));
114 | }
115 |
116 | // Deserialize JSON response into Vec
117 | let library: Root = response.json().await?;
118 |
119 | Ok(library)
120 | }
121 |
122 |
--------------------------------------------------------------------------------
/src/api/libraries/get_all_libraries.rs:
--------------------------------------------------------------------------------
1 | use reqwest::Client;
2 | use serde_json::Value;
3 | use reqwest::header::AUTHORIZATION;
4 | use color_eyre::eyre::{Result, Report};
5 | use serde::Deserialize;
6 | use serde::Serialize;
7 |
8 |
9 | /// Get All Libraries (can be a podcast or book library (shelf))
10 | /// https://api.audiobookshelf.org/#get-all-libraries
11 |
12 |
13 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
14 | #[serde(rename_all = "camelCase")]
15 | pub struct Root {
16 | pub libraries: Vec,
17 | }
18 |
19 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
20 | #[serde(rename_all = "camelCase")]
21 | pub struct Library {
22 | pub id: String,
23 | pub name: String,
24 | pub folders: Vec,
25 | pub display_order: i64,
26 | pub icon: String,
27 | pub media_type: String,
28 | pub provider: String,
29 | pub settings: Settings,
30 | pub last_scan: Option,
31 | pub last_scan_version: Option,
32 | pub created_at: i64,
33 | pub last_update: i64,
34 | }
35 |
36 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
37 | #[serde(rename_all = "camelCase")]
38 | pub struct Folder {
39 | pub id: String,
40 | pub full_path: String,
41 | pub library_id: String,
42 | pub added_at: i64,
43 | }
44 |
45 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
46 | #[serde(rename_all = "camelCase")]
47 | pub struct Settings {
48 | pub cover_aspect_ratio: i64,
49 | pub disable_watcher: bool,
50 | pub auto_scan_cron_expression: Value,
51 | pub skip_matching_media_with_asin: Option,
52 | pub skip_matching_media_with_isbn: Option,
53 | pub audiobooks_only: Option,
54 | pub epubs_allow_scripted_content: Option,
55 | pub hide_single_book_series: Option,
56 | pub only_show_later_books_in_continue_series: Option,
57 | pub metadata_precedence: Option>,
58 | #[serde(default)]
59 | pub mark_as_finished_percent_complete: Value,
60 | #[serde(default)]
61 | pub mark_as_finished_time_remaining: i64,
62 | pub podcast_search_region: Option,
63 | }
64 |
65 | // get all libraries (shelf). A library can be a Podcast or a Book type
66 | pub async fn get_all_libraries(token: &str, server_address: String) -> Result {
67 | let client = Client::new();
68 | let url = format!("{}/api/libraries", server_address);
69 |
70 | // Send GET request
71 | let response = client
72 | .get(url)
73 | .header(AUTHORIZATION, format!("Bearer {}", token))
74 | .send()
75 | .await?;
76 |
77 | // Check response status
78 | if !response.status().is_success() {
79 | return Err(Report::new(std::io::Error::new(
80 | std::io::ErrorKind::Other,
81 | "Failed to fetch data from the API",
82 | )));
83 | }
84 |
85 | // Deserialize JSON response into Vec
86 | let libraries: Root = response.json().await?;
87 |
88 | Ok(libraries)
89 | }
90 |
--------------------------------------------------------------------------------
/src/api/libraries/get_library_perso_view.rs:
--------------------------------------------------------------------------------
1 | use reqwest::Client;
2 | use serde_json::Value;
3 | use reqwest::header::AUTHORIZATION;
4 | use color_eyre::eyre::{Result, Report};
5 | use serde::Deserialize;
6 | use serde::Serialize;
7 |
8 | /// Get a PersonalizedView's Personalized View for book (allow to have continue linstening)
9 | /// https://api.audiobookshelf.org/#get-a-library-39-s-personalized-view
10 |
11 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
12 | #[serde(rename_all = "camelCase")]
13 | pub struct Root {
14 | pub id: Option,
15 | pub label: String,
16 | pub entities: Option>,
17 | }
18 |
19 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
20 | #[serde(rename_all = "camelCase")]
21 | pub struct Entity {
22 | pub id: Option,
23 | pub library_id: Option,
24 | pub folder_id: Option,
25 | pub path: Option,
26 | pub media: Option,
27 | pub name: Option,
28 | #[serde(default)]
29 | pub books: Option>,
30 | pub in_progress: Option,
31 | pub has_active_book: Option,
32 | pub hide_from_continue_listening: Option,
33 | pub book_in_progress_last_update: Option,
34 | }
35 |
36 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
37 | #[serde(rename_all = "camelCase")]
38 | pub struct Media {
39 | pub metadata: Option,
40 | pub cover_path: Option,
41 | pub tags: Option>,
42 | pub num_tracks: Option,
43 | pub num_audio_files: Option,
44 | pub num_chapters: Option,
45 | pub duration: Option,
46 | pub size: Option,
47 | }
48 |
49 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
50 | #[serde(rename_all = "camelCase")]
51 | pub struct Metadata {
52 | pub title: Option,
53 | pub title_ignore_prefix: Option,
54 | pub author_name: Option,
55 | pub narrator_name: Option,
56 | pub series_name: Option,
57 | pub genres: Option>,
58 | pub published_year: Option,
59 | pub publisher: Option,
60 | pub description: Option,
61 | pub asin: Option,
62 | pub explicit: Option,
63 | pub series: Option,
64 | }
65 |
66 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
67 | #[serde(rename_all = "camelCase")]
68 | pub struct Series {
69 | pub id: Option,
70 | pub name: Option,
71 | pub sequence: Option,
72 | }
73 |
74 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
75 | #[serde(rename_all = "camelCase")]
76 | pub struct Book {
77 | pub id: Option,
78 | pub ino: Option,
79 | pub library_id: Option,
80 | pub folder_id: Option,
81 | pub path: Option,
82 | pub rel_path: Option,
83 | pub is_file: Option,
84 | pub mtime_ms: Option,
85 | pub ctime_ms: Option,
86 | pub birthtime_ms: Option,
87 | pub added_at: Option,
88 | pub updated_at: Option,
89 | pub is_missing: Option,
90 | pub is_invalid: Option,
91 | pub media_type: Option,
92 | pub num_files: Option,
93 | pub size: Option,
94 | pub series_sequence: Option,
95 | }
96 |
97 | // filter only book continue to listening from personalized view
98 | pub async fn get_continue_listening(token: &str, server_address: String, id_selected_lib: &String) -> Result> {
99 | let client = Client::new();
100 | let url = format!("{}/api/libraries/{}/personalized", server_address, id_selected_lib);
101 |
102 | // Send GET request
103 | let response = client
104 | .get(url)
105 | .header(AUTHORIZATION, format!("Bearer {}", token))
106 | .send()
107 | .await?;
108 |
109 | // Check response status
110 | if !response.status().is_success() {
111 | return Err(Report::new(std::io::Error::new(
112 | std::io::ErrorKind::Other,
113 | "Failed to fetch data from the API",
114 | )));
115 | }
116 |
117 | // Deserialize JSON response into Vec
118 | let libraries: Vec = response.json().await?;
119 |
120 | // Filter libraries to keep only those with label "Continue Listening"
121 | let continue_listening: Vec = libraries
122 | .into_iter()
123 | .filter(|lib| lib.label == "Continue Listening")
124 | .collect();
125 |
126 | Ok(continue_listening)
127 | }
128 |
129 |
--------------------------------------------------------------------------------
/src/api/libraries/get_library_perso_view_pod.rs:
--------------------------------------------------------------------------------
1 | use reqwest::Client;
2 | use serde_json::Value;
3 | use reqwest::header::AUTHORIZATION;
4 | use color_eyre::eyre::{Result, Report};
5 | use serde::Deserialize;
6 | use serde::Serialize;
7 |
8 | /// Get a PersonalizedView's Personalized View for podcast(allow to have continue linstening)
9 | /// https://api.audiobookshelf.org/#get-a-library-39-s-personalized-view
10 |
11 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
12 | #[serde(rename_all = "camelCase")]
13 | pub struct Root {
14 | pub id: Option,
15 | pub label: String,
16 | pub label_string_key: Option,
17 | #[serde(rename = "type")]
18 | pub type_field: Option,
19 | pub entities: Option>,
20 | pub total: Option,
21 | }
22 |
23 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
24 | #[serde(rename_all = "camelCase")]
25 | pub struct Entity {
26 | pub id: Option,
27 | pub ino: Option,
28 | pub old_library_item_id: Option,
29 | pub library_id: Option,
30 | pub folder_id: Option,
31 | pub path: Option,
32 | pub rel_path: Option,
33 | pub is_file: Option,
34 | pub mtime_ms: Option,
35 | pub ctime_ms: Option,
36 | pub birthtime_ms: Option,
37 | pub added_at: Option,
38 | pub updated_at: Option,
39 | pub is_missing: Option,
40 | pub is_invalid: Option,
41 | pub media_type: Option,
42 | pub media: Option,
43 | pub num_files: Option,
44 | pub size: Option,
45 | pub recent_episode: Option,
46 | }
47 |
48 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
49 | #[serde(rename_all = "camelCase")]
50 | pub struct Media {
51 | pub id: Option,
52 | pub metadata: Option,
53 | pub cover_path: Option,
54 | pub tags: Option>,
55 | pub num_episodes: Option,
56 | pub auto_download_episodes: Option,
57 | pub auto_download_schedule: Option,
58 | pub last_episode_check: Option,
59 | pub max_episodes_to_keep: Option,
60 | pub max_new_episodes_to_download: Option,
61 | pub size: Option,
62 | }
63 |
64 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
65 | #[serde(rename_all = "camelCase")]
66 | pub struct Metadata {
67 | pub title: Option,
68 | pub author: Option,
69 | pub description: Option,
70 | pub release_date: Option,
71 | pub genres: Option>,
72 | pub feed_url: Option,
73 | pub image_url: Option,
74 | pub itunes_page_url: Option,
75 | pub itunes_id: Option,
76 | pub itunes_artist_id: Option,
77 | pub explicit: Option,
78 | pub language: Option,
79 | #[serde(rename = "type")]
80 | pub type_field: Option,
81 | pub title_ignore_prefix: Option,
82 | }
83 |
84 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
85 | #[serde(rename_all = "camelCase")]
86 | pub struct RecentEpisode {
87 | pub library_item_id: Option,
88 | pub podcast_id: Option,
89 | pub id: Option,
90 | pub old_episode_id: Option,
91 | pub index: Option,
92 | pub season: Option,
93 | pub episode: Option,
94 | pub episode_type: Option,
95 | pub title: Option,
96 | pub subtitle: Option,
97 | pub description: Option,
98 | pub enclosure: Option,
99 | pub guid: Option,
100 | pub pub_date: Option,
101 | pub chapters: Option>,
102 | pub audio_file: Option,
103 | pub published_at: Option,
104 | pub added_at: Option,
105 | pub updated_at: Option,
106 | }
107 |
108 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
109 | #[serde(rename_all = "camelCase")]
110 | pub struct Enclosure {
111 | pub url: Option,
112 | #[serde(rename = "type")]
113 | pub type_field: Option,
114 | pub length: Option,
115 | }
116 |
117 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
118 | #[serde(rename_all = "camelCase")]
119 | pub struct Chapter {
120 | pub start: Option,
121 | pub end: Option,
122 | pub title: Option,
123 | pub id: Option,
124 | }
125 |
126 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
127 | #[serde(rename_all = "camelCase")]
128 | pub struct AudioFile {
129 | pub index: Option,
130 | pub ino: Option,
131 | pub added_at: Option,
132 | pub updated_at: Option,
133 | pub track_num_from_meta: Option,
134 | pub disc_num_from_meta: Option,
135 | pub track_num_from_filename: Option,
136 | pub disc_num_from_filename: Option,
137 | pub manually_verified: Option,
138 | pub exclude: Option,
139 | pub error: Option,
140 | pub format: Option,
141 | pub duration: Option,
142 | pub bit_rate: Option,
143 | pub language: Option,
144 | pub codec: Option,
145 | pub time_base: Option,
146 | pub channels: Option,
147 | pub channel_layout: Option,
148 | pub embedded_cover_art: Option,
149 | pub mime_type: Option,
150 | }
151 |
152 | // filter only podcast continue to listening from personalized view
153 | pub async fn get_continue_listening_pod(token: &str, server_address: String, id_selected_lib: &String) -> Result> {
154 | let client = Client::new();
155 | let url = format!("{}/api/libraries/{}/personalized", server_address, id_selected_lib);
156 |
157 | // Send GET request
158 | let response = client
159 | .get(url)
160 | .header(AUTHORIZATION, format!("Bearer {}", token))
161 | .send()
162 | .await?;
163 |
164 | // Check response status
165 | if !response.status().is_success() {
166 | return Err(Report::new(std::io::Error::new(
167 | std::io::ErrorKind::Other,
168 | "Failed to fetch data from the API",
169 | )));
170 | }
171 |
172 | // Deserialize JSON response into Vec
173 | let libraries: Vec = response.json().await?;
174 |
175 | // Filter libraries to keep only those with label "Continue Listening"
176 | let continue_listening: Vec = libraries
177 | .into_iter()
178 | .filter(|lib| lib.label == "Continue Listening")
179 | .collect();
180 |
181 | Ok(continue_listening)
182 | }
183 |
184 |
--------------------------------------------------------------------------------
/src/api/libraries/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod get_library_perso_view;
2 | pub mod get_library_perso_view_pod;
3 | pub mod get_all_books;
4 | pub mod get_all_libraries;
5 |
--------------------------------------------------------------------------------
/src/api/library_items/get_pod_ep.rs:
--------------------------------------------------------------------------------
1 | use reqwest::Client;
2 | use serde_json::Value;
3 | use reqwest::header::AUTHORIZATION;
4 | use color_eyre::eyre::{Result, Report};
5 | use serde::Deserialize;
6 | use serde::Serialize;
7 |
8 |
9 | /// Get a Library Item, used for collect podact info (allow in particular to retrieve all podcast episode id)
10 | /// This endpoint retrieves a library item, allow in particular to retrieve all podcast episode id.
11 | /// https://api.audiobookshelf.org/#get-a-library-item
12 |
13 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
14 | #[serde(rename_all = "camelCase")]
15 | pub struct Root {
16 | pub id: Option,
17 | pub ino: Option,
18 | pub old_library_item_id: Option,
19 | pub library_id: Option,
20 | pub folder_id: Option,
21 | pub path: Option,
22 | pub rel_path: Option,
23 | pub is_file: Option,
24 | pub mtime_ms: Option,
25 | pub ctime_ms: Option,
26 | pub birthtime_ms: Option,
27 | pub added_at: Option,
28 | pub updated_at: Option,
29 | pub scan_version: Option,
30 | pub is_missing: Option,
31 | pub is_invalid: Option,
32 | pub media_type: Option,
33 | pub media: Option,
34 | pub library_files: Option>,
35 | }
36 |
37 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
38 | #[serde(rename_all = "camelCase")]
39 | pub struct Media {
40 | pub id: Option