├── .envrc ├── .gitignore ├── systemd-examples ├── feeds-to-pocket.service ├── feeds-to-pocket.timer └── README.md ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── CONTRIBUTING.md ├── LICENSE-MIT ├── flake.nix ├── CHANGELOG.md ├── flake.lock ├── README.md ├── src ├── pocket.rs └── main.rs ├── LICENSE-APACHE └── Cargo.lock /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result* 2 | target 3 | -------------------------------------------------------------------------------- /systemd-examples/feeds-to-pocket.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Feeds to Pocket 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=feeds-to-pocket ~/feeds-to-pocket.yaml 7 | -------------------------------------------------------------------------------- /systemd-examples/feeds-to-pocket.timer: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Timer for Feeds to Pocket 3 | 4 | [Timer] 5 | OnStartupSec=1m 6 | OnUnitActiveSec=1h 7 | 8 | [Install] 9 | WantedBy=timers.target 10 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Build 20 | run: cargo build --verbose 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "feeds-to-pocket" 3 | version = "0.1.8" 4 | authors = ["Francis Gagné "] 5 | edition = "2021" 6 | description = "Sends items from your RSS and Atom feeds to your Pocket list." 7 | repository = "https://github.com/FraGag/feeds-to-pocket" 8 | readme = "README.md" 9 | keywords = ["atom", "feed", "pocket", "rss", "syndication"] 10 | license = "MIT/Apache-2.0" 11 | exclude = [".envrc", ".github", "flake.lock", "flake.nix"] 12 | 13 | [dependencies] 14 | atom_syndication = "0.12.5" 15 | clap = { version = "4.5.22", features = ["derive"] } 16 | quick-error = "2.0.1" 17 | reqwest = { version = "0.12.9", features = ["blocking"] } 18 | rss = "2.0.11" 19 | serde = { version = "1.0.215", features = ["derive"] } 20 | serde_json = "1.0.133" 21 | serde_yaml = "0.9.34" 22 | url = { version = "2.5.4", features = ["serde"] } 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Feeds to Pocket 2 | 3 | Any significant code contributions that you submit 4 | must be made available 5 | under the terms of the [project's license][license]. 6 | By submitting a pull request, 7 | I assume that you agree to license your code 8 | in accordance with the project's license terms, 9 | without any additional terms or conditions. 10 | 11 | If you would like to contribute a new feature, 12 | please [file an issue][new-issue] to discuss the feature 13 | (unless there's already an issue for that feature). 14 | I will consider if the feature makes sense for the application, 15 | and I may reject the feature. 16 | By filing an issue first, 17 | you can avoid wasting time 18 | on a feature that I end up rejecting. 19 | 20 | If you would like to submit changes 21 | that fix a bug or implement an accepted feature, 22 | please [submit a pull request][create-pull-request]. 23 | 24 | [license]: README.md#license 25 | [new-issue]: https://github.com/FraGag/feeds-to-pocket/issues/new 26 | [create-pull-request]: https://help.github.com/articles/creating-a-pull-request/ 27 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Francis Gagné 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | flake-utils.url = "github:numtide/flake-utils"; 4 | naersk.url = "github:nix-community/naersk"; 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 | 7 | naersk.inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | 10 | outputs = { self, flake-utils, naersk, nixpkgs }: 11 | flake-utils.lib.eachDefaultSystem (system: 12 | let 13 | pkgs = import nixpkgs { inherit system; }; 14 | 15 | naersk' = pkgs.callPackage naersk { }; 16 | 17 | buildPackage = devMode: naersk'.buildPackage { 18 | src = ./.; 19 | 20 | nativeBuildInputs = with pkgs; [ pkg-config ]; 21 | buildInputs = with pkgs; [ openssl_3 ]; 22 | 23 | singleStep = devMode; 24 | }; 25 | in 26 | { 27 | # For `nix build` & `nix run`: 28 | packages.default = buildPackage false; 29 | 30 | # For `nix develop`: 31 | devShells.default = (buildPackage true).overrideAttrs (finalAttrs: previousAttrs: { 32 | nativeBuildInputs = previousAttrs.nativeBuildInputs ++ (with pkgs; [ 33 | clippy 34 | gitFull 35 | rustfmt 36 | ]); 37 | RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}"; 38 | }); 39 | } 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /systemd-examples/README.md: -------------------------------------------------------------------------------- 1 | # systemd example units 2 | 3 | If you would like to use systemd as a scheduler for Feeds to Pocket, 4 | you can use the unit files in this directory as a basis. 5 | You will most likely want to set this up for your user instance, 6 | rather than for the system instance, 7 | so copy `feeds-to-pocket.service` and `feeds-to-pocket.timer` 8 | to `~/.config/systemd/user`. 9 | 10 | In `feeds-to-pocket.service`, 11 | edit the `ExecStart` option in the `[Service]` section 12 | to refer to your own configuration file. 13 | 14 | In `feeds-to-pocket.timer`, 15 | you may configure the frequency 16 | at which Feeds to Pocket will run. 17 | The provided timer will run 1 minute after login 18 | and every hour after that. 19 | 20 | Once you've configured the unit files to your liking, 21 | enable the timer with the following command: 22 | 23 | $ systemctl --user enable feeds-to-pocket.timer 24 | 25 | When the timer is enabled, 26 | it will be started automatically when you log in. 27 | 28 | You can start the timer without having to log out and log back in 29 | with the following command: 30 | 31 | $ systemctl --user start feeds-to-pocket.timer 32 | 33 | You can also run Feeds to Pocket at any time 34 | with the following command: 35 | 36 | $ systemctl --user start feeds-to-pocket.service 37 | 38 | systemd will capture Feeds To Pocket's output 39 | and save it in its journal. 40 | You can view the captured output 41 | with this command: 42 | 43 | $ journalctl --user-unit feeds-to-pocket.service 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.1.8 - 2024-10-13 4 | 5 | - Fix the HTTP header name X-Error-Code 6 | - Improve error detection and reporting 7 | - Fix pushes not working anymore by not sending `null` for the `title` field 8 | (fixes [#8](https://github.com/FraGag/feeds-to-pocket/issues/8)) 9 | - login: Replace the data: redirect URI with an https: one 10 | (fixes [#7](https://github.com/FraGag/feeds-to-pocket/issues/7)) 11 | - Update dependencies 12 | 13 | ## 0.1.7 - 2023-06-03 14 | 15 | - Fix incorrect instructions in README.md 16 | (PR [#5](https://github.com/FraGag/feeds-to-pocket/pull/5)) 17 | - Update dependencies 18 | - General code cleanup 19 | 20 | ## 0.1.6 - 2018-11-10 21 | 22 | - Update dependencies, enabling support for OpenSSL 1.1.1 23 | - Add the 'remove' subcommand to remove a feed from a configuration file 24 | (issue [#2](https://github.com/FraGag/feeds-to-pocket/issues/2)) 25 | 26 | ## 0.1.5 - 2017-12-10 27 | 28 | - Update dependencies, including notably updating `atom_syndication` to 29 | 0.5, which is more lenient in parsing some invalid Atom feeds (e.g. 30 | feeds that are missing `` elements) 31 | 32 | ## 0.1.4 - 2017-09-03 33 | 34 | - Update dependencies 35 | 36 | ## 0.1.3 - 2016-12-18 37 | 38 | - Ignore invalid URLs in feeds (instead of panicking) 39 | - Update dependencies 40 | 41 | ## 0.1.2 - 2016-09-20 42 | 43 | - Update dependencies, including notably updating `atom_syndication` to 44 | 0.2, which prevents feeds-to-pocket from panicking when decoding some 45 | invalid feeds 46 | 47 | ## 0.1.1 - 2016-07-24 48 | 49 | - Support compiling with the stable Rust compiler 50 | - Update dependencies 51 | 52 | ## 0.1.0 - 2016-06-26 53 | 54 | Initial release. 55 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "naersk": { 22 | "inputs": { 23 | "nixpkgs": [ 24 | "nixpkgs" 25 | ] 26 | }, 27 | "locked": { 28 | "lastModified": 1733346208, 29 | "narHash": "sha256-a4WZp1xQkrnA4BbnKrzJNr+dYoQr5Xneh2syJoddFyE=", 30 | "owner": "nix-community", 31 | "repo": "naersk", 32 | "rev": "378614f37a6bee5a3f2ef4f825a73d948d3ae921", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "nix-community", 37 | "repo": "naersk", 38 | "type": "github" 39 | } 40 | }, 41 | "nixpkgs": { 42 | "locked": { 43 | "lastModified": 1733229606, 44 | "narHash": "sha256-FLYY5M0rpa5C2QAE3CKLYAM6TwbKicdRK6qNrSHlNrE=", 45 | "owner": "NixOS", 46 | "repo": "nixpkgs", 47 | "rev": "566e53c2ad750c84f6d31f9ccb9d00f823165550", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "NixOS", 52 | "ref": "nixpkgs-unstable", 53 | "repo": "nixpkgs", 54 | "type": "github" 55 | } 56 | }, 57 | "root": { 58 | "inputs": { 59 | "flake-utils": "flake-utils", 60 | "naersk": "naersk", 61 | "nixpkgs": "nixpkgs" 62 | } 63 | }, 64 | "systems": { 65 | "locked": { 66 | "lastModified": 1681028828, 67 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 68 | "owner": "nix-systems", 69 | "repo": "default", 70 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "nix-systems", 75 | "repo": "default", 76 | "type": "github" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Feeds to Pocket 2 | 3 | > **:warning: Pocket was [shut down][pocket-has-shut-down] on November 12, 2025. 4 | > Therefore, this application is no longer functional.** 5 | 6 | Feeds to Pocket watches your RSS and Atom feeds 7 | and pushes new items to your [Pocket][pocket] list. 8 | 9 | [pocket]: https://getpocket.com/ 10 | [pocket-has-shut-down]: https://support.mozilla.org/en-US/kb/future-of-pocket 11 | 12 | ## License 13 | 14 | Feeds to Pocket is licensed 15 | under the terms of either the [MIT license][license-mit] 16 | or the [Apache License, version 2.0][license-apache], at your option. 17 | Feeds to Pocket also uses third party libraries, 18 | some of which have different licenses. 19 | 20 | ### Contribution 21 | 22 | Unless you explicitly state otherwise, 23 | any contribution intentionally submitted for inclusion in the work by you, 24 | as defined in the Apache-2.0 license, 25 | shall be dual licensed as above, 26 | without any additional terms or conditions. 27 | 28 | [license-mit]: LICENSE-MIT 29 | [license-apache]: LICENSE-APACHE 30 | 31 | ## Prerequisites 32 | 33 | Feeds to Pocket uses [OpenSSL][openssl] for HTTPS requests. 34 | If you don't have OpenSSL, 35 | you'll have to install it first. 36 | 37 | You'll need Cargo, Rust's package manager. 38 | If you don't already have it, 39 | go to the [Rust][rust] home page, 40 | then download and install Rust for your platform, 41 | which will install the Rust compiler and Cargo. 42 | 43 | ## Usage 44 | 45 | ### Installation 46 | 47 | In a terminal or command prompt, 48 | run the following command: 49 | 50 | $ cargo install feeds-to-pocket 51 | 52 | This will install the last version of Feeds to Pocket 53 | that was published to [crates.io][crate]. 54 | 55 | If you want to install an update, run: 56 | 57 | $ cargo install --force feeds-to-pocket 58 | 59 | [openssl]: https://www.openssl.org/ 60 | [rust]: https://www.rust-lang.org/ 61 | [crate]: https://crates.io/crates/feeds-to-pocket 62 | 63 | ### Configuration 64 | 65 | Feeds to Pocket uses a file to store your configuration 66 | (list of feeds to monitor, Pocket access credentials). 67 | You must specify a file name as a command-line argument 68 | when you call the program; 69 | there's no default file name. 70 | 71 | First, you must create your configuration file: 72 | 73 | $ feeds-to-pocket ~/feeds-to-pocket.yaml init 74 | 75 | > `~/feeds-to-pocket.yaml` is just an example, 76 | > you can use any file name you want! 77 | 78 | Then, you must [create an application][create-app] 79 | on the developer section of Pocket's website. 80 | Make sure you select at least the Add permission. 81 | This will give you a *consumer key*, 82 | which is necessary to use Pocket's API. 83 | Customer keys have [rate limits][rate-limits], 84 | so I suggest you keep your consumer key private. 85 | 86 | When you've obtained your consumer key, 87 | save it in your configuration file: 88 | 89 | $ feeds-to-pocket ~/feeds-to-pocket.yaml set-consumer-key 1234-abcd1234abcd1234abcd1234 90 | 91 | After that, you need to login. 92 | Just run: 93 | 94 | $ feeds-to-pocket ~/feeds-to-pocket.yaml login 95 | 96 | and follow the instructions. 97 | This will save an access token in your configuration file. 98 | The access token acts like your account's password, 99 | so keep it safe! 100 | 101 | Congratulations, Feeds to Pocket is now ready to talk to Pocket! 102 | 103 | ### Adding feeds 104 | 105 | Once the above configuration steps are done, 106 | you're ready to add feeds. 107 | Use the `add` subcommand to add a feed: 108 | 109 | $ feeds-to-pocket ~/feeds-to-pocket.yaml add https://xkcd.com/atom.xml 110 | 111 | This will download the feed 112 | and mark all current entries as "processed" 113 | without sending them to Pocket. 114 | If you would like all current entries to be sent to Pocket, 115 | pass the `--unread` flag: 116 | 117 | $ feeds-to-pocket ~/feeds-to-pocket.yaml add --unread https://xkcd.com/atom.xml 118 | 119 | Repeat this for every feed you'd like Feeds to Pocket to monitor. 120 | 121 | ### Sending new entries to Pocket 122 | 123 | Call `feeds-to-pocket` without a subcommand 124 | to have it download your feeds 125 | and send new entries to Pocket. 126 | 127 | $ feeds-to-pocket ~/feeds-to-pocket.yaml 128 | 129 | Once an entry has been sent to Pocket, 130 | Feeds to Pocket marks it as "processed" 131 | and will not send it again. 132 | 133 | ### Assigning tags to feeds 134 | 135 | You can assign tags to feeds. 136 | When a new entry is pushed to Pocket, 137 | it will be assigned the tags that were set 138 | on the feed the entry comes from. 139 | 140 | To do this, pass the `--tags` option 141 | to the `add` subcommand. 142 | You can do this while adding a new feed 143 | or for an existing feed 144 | (then it will *replace* the list of tags for that feed). 145 | The `--tags` option is followed by a comma-separated list of tags. 146 | 147 | $ feeds-to-pocket ~/feeds-to-pocket.yaml add --tags comics,xkcd https://xkcd.com/atom.xml 148 | 149 | ### Scheduling 150 | 151 | Feeds to Pocket doesn't have any built-in scheduling mechanisms. 152 | You should use an existing task scheduler 153 | to run the `feeds-to-pocket` program periodically. 154 | 155 | If you are using Linux with systemd, 156 | you can set up a systemd timer 157 | for your systemd user instance. 158 | See the example unit files in the `systemd-examples` directory. 159 | 160 | [create-app]: https://getpocket.com/developer/apps/new 161 | [rate-limits]: https://getpocket.com/developer/docs/rate-limits 162 | 163 | ### Removing feeds 164 | 165 | Use the `remove` subcommand to remove a feed: 166 | 167 | $ feeds-to-pocket ~/feeds-to-pocket.yaml remove https://xkcd.com/atom.xml 168 | 169 | ## Compiling from source 170 | 171 | To build the project, just run: 172 | 173 | $ cargo build 174 | 175 | from the project's directory. 176 | This will download and compile 177 | all of the project's Rust dependencies automatically. 178 | 179 | ## Issues 180 | 181 | If you find a bug, 182 | first check if you're using the latest version, 183 | and update if that's not the case. 184 | If the bug still occurs, 185 | please check if there's already a similar [issue][issues] 186 | (check both open and closed issues!). 187 | If there isn't, then [file a new issue][new-issue]. 188 | If the program outputs an error message, 189 | please include it in your issue. 190 | Also mention what operating system you're using and which version. 191 | 192 | [issues]: https://github.com/FraGag/feeds-to-pocket/issues 193 | [new-issue]: https://github.com/FraGag/feeds-to-pocket/issues/new 194 | 195 | ## Contributing 196 | 197 | See [CONTRIBUTING][contributing]. 198 | 199 | [contributing]: CONTRIBUTING.md 200 | -------------------------------------------------------------------------------- /src/pocket.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The rust-pocket Developers 2 | // Copyright 2016 Francis Gagné 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | // The code for this module 11 | // is derived from the rust-pocket library, 12 | // available at https://github.com/kstep/rust-pocket 13 | // under the Apache 2.0 and MIT licenses. 14 | // I made this derived version for two reasons: 15 | // 1) The library would fail to decode some responses for the "add" endpoint. 16 | // I don't actually need to decode those responses, 17 | // so I removed that here. 18 | // 2) The library provides methods for endpoints that I don't need, 19 | // but they're also presumably broken, 20 | // and I didn't feel like fixing and testing them. 21 | 22 | use std::convert::From; 23 | use std::error::Error; 24 | use std::fmt; 25 | use std::io::Error as IoError; 26 | use std::io::Read; 27 | use std::result::Result; 28 | 29 | use reqwest::{ 30 | blocking::Client, 31 | header::{self, HeaderValue}, 32 | Error as HttpError, 33 | }; 34 | use serde::{Deserialize, Serialize}; 35 | use url::Url; 36 | 37 | #[derive(Debug)] 38 | pub enum PocketError { 39 | Http(HttpError, Option), 40 | Io(IoError), 41 | SerdeJson(serde_json::Error), 42 | Proto(String, String, Option), 43 | } 44 | 45 | struct HttpErrorWithBody { 46 | http_error: HttpError, 47 | body: Option, 48 | } 49 | 50 | pub type PocketResult = Result; 51 | 52 | impl From for PocketError { 53 | fn from(err: serde_json::Error) -> PocketError { 54 | PocketError::SerdeJson(err) 55 | } 56 | } 57 | 58 | impl From for PocketError { 59 | fn from(err: IoError) -> PocketError { 60 | PocketError::Io(err) 61 | } 62 | } 63 | 64 | impl From for PocketError { 65 | fn from(err: HttpError) -> PocketError { 66 | PocketError::Http(err, None) 67 | } 68 | } 69 | 70 | impl From for PocketError { 71 | fn from(err: HttpErrorWithBody) -> PocketError { 72 | PocketError::Http(err.http_error, err.body) 73 | } 74 | } 75 | 76 | impl Error for PocketError { 77 | fn cause(&self) -> Option<&dyn Error> { 78 | match self { 79 | PocketError::Http(e, _) => Some(e), 80 | PocketError::Io(e) => Some(e), 81 | PocketError::SerdeJson(e) => Some(e), 82 | PocketError::Proto(..) => None, 83 | } 84 | } 85 | } 86 | 87 | impl fmt::Display for PocketError { 88 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { 89 | match self { 90 | PocketError::Http(e, body) => { 91 | e.fmt(fmt)?; 92 | if let Some(body) = body { 93 | writeln!(fmt)?; 94 | body.fmt(fmt)?; 95 | } 96 | Ok(()) 97 | } 98 | PocketError::Io(e) => e.fmt(fmt), 99 | PocketError::SerdeJson(e) => e.fmt(fmt), 100 | PocketError::Proto(code, msg, body) => { 101 | write!(fmt, "{} (code {})", msg, code)?; 102 | if let Some(body) = body { 103 | writeln!(fmt)?; 104 | body.fmt(fmt)?; 105 | } 106 | Ok(()) 107 | } 108 | } 109 | } 110 | } 111 | 112 | const X_ACCEPT: &str = "X-Accept"; 113 | const X_ERROR: &str = "X-Error"; 114 | const X_ERROR_CODE: &str = "X-Error-Code"; 115 | 116 | pub struct Pocket { 117 | consumer_key: String, 118 | access_token: Option, 119 | code: Option, 120 | client: Client, 121 | } 122 | 123 | #[derive(Serialize)] 124 | pub struct PocketOAuthRequest<'a> { 125 | consumer_key: &'a str, 126 | redirect_uri: &'a str, 127 | #[serde(skip_serializing_if = "Option::is_none")] 128 | state: Option<&'a str>, 129 | } 130 | 131 | #[derive(Deserialize)] 132 | pub struct PocketOAuthResponse { 133 | code: String, 134 | } 135 | 136 | #[derive(Serialize)] 137 | pub struct PocketAuthorizeRequest<'a> { 138 | consumer_key: &'a str, 139 | code: &'a str, 140 | } 141 | 142 | #[derive(Deserialize)] 143 | pub struct PocketAuthorizeResponse { 144 | access_token: String, 145 | username: String, 146 | } 147 | 148 | #[derive(Serialize)] 149 | pub struct PocketAddRequest<'a> { 150 | consumer_key: &'a str, 151 | access_token: &'a str, 152 | url: &'a Url, 153 | #[serde(skip_serializing_if = "Option::is_none")] 154 | title: Option<&'a str>, 155 | #[serde(skip_serializing_if = "Option::is_none")] 156 | tags: Option<&'a str>, 157 | #[serde(skip_serializing_if = "Option::is_none")] 158 | tweet_id: Option<&'a str>, 159 | } 160 | 161 | impl Pocket { 162 | pub fn new(consumer_key: &str, access_token: Option<&str>, client: Client) -> Pocket { 163 | Pocket { 164 | consumer_key: consumer_key.to_string(), 165 | access_token: access_token.map(|v| v.to_string()), 166 | code: None, 167 | client, 168 | } 169 | } 170 | 171 | #[inline] 172 | pub fn access_token(&self) -> Option<&str> { 173 | self.access_token.as_deref() 174 | } 175 | 176 | fn request(&self, url: &str, request: &Req) -> PocketResult { 177 | let request = serde_json::to_string(request)?; 178 | 179 | let app_json = "application/json"; 180 | 181 | self.client 182 | .post(url) 183 | .header(X_ACCEPT, HeaderValue::from_static(app_json)) 184 | .header(header::CONTENT_TYPE, HeaderValue::from_static(app_json)) 185 | .body(request) 186 | .send() 187 | .map_err(From::from) 188 | .and_then(|mut r| { 189 | let mut body = String::new(); 190 | let body = r 191 | .read_to_string(&mut body) 192 | .map_err(From::from) 193 | .map(|_| body); 194 | 195 | if let Some(code) = r.headers().get(X_ERROR_CODE) { 196 | return Err(PocketError::Proto( 197 | code.to_str() 198 | .expect("X-Error-Code is not well-formed UTF-8") 199 | .into(), 200 | r.headers() 201 | .get(X_ERROR) 202 | .map(|v| v.to_str().expect("X-Error is not well-formed UTF-8").into()) 203 | .unwrap_or("unknown protocol error".into()), 204 | body.ok(), 205 | )); 206 | } 207 | 208 | if !r.status().is_success() { 209 | r.error_for_status() 210 | .map_err(|http_error| HttpErrorWithBody { 211 | http_error, 212 | body: body.as_ref().ok().cloned(), 213 | })?; 214 | } 215 | 216 | body 217 | }) 218 | } 219 | 220 | pub fn get_auth_url(&mut self) -> PocketResult { 221 | const REDIRECT_URI: &str = "https://fragag.github.io/feeds-to-pocket/login-successful"; 222 | 223 | let response = { 224 | // scope to release borrow on self 225 | let request = PocketOAuthRequest { 226 | consumer_key: &self.consumer_key, 227 | redirect_uri: REDIRECT_URI, 228 | state: None, 229 | }; 230 | 231 | self.request("https://getpocket.com/v3/oauth/request", &request) 232 | }; 233 | 234 | response 235 | .and_then(|r| r.decode()) 236 | .map(|r: PocketOAuthResponse| { 237 | let mut url = Url::parse("https://getpocket.com/auth/authorize").unwrap(); 238 | url.query_pairs_mut() 239 | .append_pair("request_token", &r.code) 240 | .append_pair("redirect_uri", REDIRECT_URI); 241 | self.code = Some(r.code); 242 | url 243 | }) 244 | } 245 | 246 | pub fn authorize(&mut self) -> PocketResult { 247 | { 248 | let request = PocketAuthorizeRequest { 249 | consumer_key: &self.consumer_key, 250 | code: self.code.as_deref().unwrap(), 251 | }; 252 | 253 | self.request("https://getpocket.com/v3/oauth/authorize", &request) 254 | } 255 | .and_then(|r| r.decode()) 256 | .map(|r: PocketAuthorizeResponse| { 257 | self.access_token = Some(r.access_token); 258 | r.username 259 | }) 260 | } 261 | 262 | pub fn add( 263 | &mut self, 264 | url: &Url, 265 | title: Option<&str>, 266 | tags: Option<&str>, 267 | tweet_id: Option<&str>, 268 | ) -> PocketResult<()> { 269 | let request = PocketAddRequest { 270 | consumer_key: &self.consumer_key, 271 | access_token: self.access_token.as_ref().unwrap(), 272 | url, 273 | title, 274 | tags, 275 | tweet_id, 276 | }; 277 | 278 | self.request("https://getpocket.com/v3/add", &request) 279 | .map(|_| ()) 280 | } 281 | } 282 | 283 | trait DecodeExt { 284 | fn decode<'a, Resp: Deserialize<'a>>(&'a self) -> PocketResult; 285 | } 286 | 287 | impl DecodeExt for str { 288 | fn decode<'a, Resp: Deserialize<'a>>(&'a self) -> PocketResult { 289 | serde_json::from_str::(self).map_err(From::from) 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Francis Gagné 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | mod pocket; 10 | 11 | use std::error::Error; 12 | use std::fmt::{self, Display}; 13 | use std::fs::{self, File, OpenOptions}; 14 | use std::io::{self, Read, Write}; 15 | use std::path::{Path, PathBuf}; 16 | use std::process; 17 | use std::str::FromStr; 18 | 19 | use clap::Parser; 20 | use quick_error::quick_error; 21 | use reqwest::header::{self, HeaderValue}; 22 | use reqwest::{blocking::Client, StatusCode}; 23 | use serde::{Deserialize, Serialize}; 24 | use url::Url; 25 | 26 | use crate::pocket::Pocket; 27 | 28 | fn main() { 29 | let args = Args::parse(); 30 | run(&args).unwrap_or_else(|e| { 31 | let _ = writeln!(io::stderr(), "{}", e); 32 | process::exit(1); 33 | }) 34 | } 35 | 36 | fn run(args: &Args) -> Result<(), ErrorWithContext> { 37 | match &args.command { 38 | Some(Command::Init) => init(&args.config), 39 | Some(Command::SetConsumerKey { key }) => args.with_config(|config| { 40 | set_consumer_key(config, key); 41 | Ok(()) 42 | }), 43 | Some(Command::Login) => args.with_config(login), 44 | Some(Command::Add(cmd)) => args.with_config(|config| add(config, cmd)), 45 | Some(Command::Remove { feed_url }) => args.with_config(|config| remove(config, feed_url)), 46 | None => args.with_config(sync), 47 | } 48 | } 49 | 50 | macro_rules! try_with_context { 51 | ($expr:expr, $context:expr) => { 52 | match $expr { 53 | ::std::result::Result::Ok(val) => val, 54 | ::std::result::Result::Err(err) => { 55 | return ::std::result::Result::Err($crate::ErrorWithContext::new( 56 | ::std::convert::From::from(err), 57 | $context, 58 | )) 59 | } 60 | } 61 | }; 62 | } 63 | 64 | fn load_config(config_file_name: &Path) -> Result { 65 | let config_file = try_with_context!( 66 | File::open(config_file_name), 67 | format!("failed to open file {}", config_file_name.to_string_lossy()) 68 | ); 69 | let config = try_with_context!( 70 | serde_yaml::from_reader(config_file), 71 | format!( 72 | "failed to load configuration from {}", 73 | config_file_name.to_string_lossy() 74 | ) 75 | ); 76 | Ok(config) 77 | } 78 | 79 | fn save_config(config: &Configuration, config_file_name: &Path) -> Result<(), ErrorWithContext> { 80 | // Append ".new" to the config file name. 81 | // We'll write the updated configuration in this file, 82 | // then rename the original and the new files 83 | // to avoid corrupting the configuration. 84 | let new_config_file_name = &{ 85 | let mut file_name = config_file_name.as_os_str().to_os_string(); 86 | file_name.push(".new"); 87 | file_name 88 | }; 89 | 90 | // Append ".old" to the config file name. 91 | // We'll rename the original configuration file to this. 92 | let old_config_file_name = &{ 93 | let mut file_name = config_file_name.as_os_str().to_os_string(); 94 | file_name.push(".old"); 95 | file_name 96 | }; 97 | 98 | // Copy the configuration file, to preserve permissions. 99 | try_with_context!( 100 | fs::copy(config_file_name, new_config_file_name), 101 | format!( 102 | "failed to copy {} to {}", 103 | config_file_name.to_string_lossy(), 104 | new_config_file_name.to_string_lossy() 105 | ) 106 | ); 107 | 108 | // Write the updated configuration to the new configuration file. 109 | { 110 | let mut config_file = try_with_context!( 111 | File::create(new_config_file_name), 112 | format!( 113 | "failed to create file {}", 114 | new_config_file_name.to_string_lossy() 115 | ) 116 | ); 117 | try_with_context!( 118 | serde_yaml::to_writer(&mut config_file, config), 119 | format!( 120 | "failed to save configuration to {}", 121 | new_config_file_name.to_string_lossy() 122 | ) 123 | ); 124 | } 125 | 126 | fn rename + Copy, Q: AsRef + Copy>( 127 | from: P, 128 | to: Q, 129 | ) -> Result<(), ErrorWithContext> { 130 | Ok(try_with_context!( 131 | fs::rename(from, to), 132 | format!( 133 | "failed to rename {} to {}", 134 | from.as_ref().to_string_lossy(), 135 | to.as_ref().to_string_lossy() 136 | ) 137 | )) 138 | } 139 | 140 | // Rename the original configuration file. 141 | rename(config_file_name, old_config_file_name)?; 142 | 143 | // Rename the new configuration file. 144 | let rename_new_result = rename(new_config_file_name, config_file_name); 145 | if rename_new_result.is_err() { 146 | // Rename the original configuration file back to its original name. 147 | let rollback_rename_old_result = rename(old_config_file_name, config_file_name); 148 | match rollback_rename_old_result { 149 | Ok(_) => return rename_new_result, 150 | Err(e) => try_with_context!( 151 | Err(Errors::new(vec![ 152 | Box::new(rename_new_result.unwrap_err()), 153 | Box::new(e) 154 | ])), 155 | "failed to save configuration" 156 | ), 157 | } 158 | } 159 | 160 | // Delete the renamed original configuration file. 161 | try_with_context!( 162 | fs::remove_file(old_config_file_name), 163 | format!( 164 | "failed to remove file {}", 165 | old_config_file_name.to_string_lossy() 166 | ) 167 | ); 168 | 169 | Ok(()) 170 | } 171 | 172 | fn init(config_file_name: &Path) -> Result<(), ErrorWithContext> { 173 | // Only write a configuration file if it doesn't exist yet. 174 | let mut config_file = try_with_context!( 175 | OpenOptions::new() 176 | .write(true) 177 | .create_new(true) 178 | .open(config_file_name), 179 | format!( 180 | "failed to create file {}", 181 | config_file_name.to_string_lossy() 182 | ) 183 | ); 184 | 185 | let config = Configuration::default(); 186 | try_with_context!( 187 | serde_yaml::to_writer(&mut config_file, &config), 188 | format!( 189 | "failed to save configuration to {}", 190 | config_file_name.to_string_lossy() 191 | ) 192 | ); 193 | 194 | Ok(()) 195 | } 196 | 197 | fn set_consumer_key(config: &mut Configuration, key: &str) { 198 | config.consumer_key = Some(key.to_string()); 199 | } 200 | 201 | fn login(config: &mut Configuration) -> Result<(), ErrorWithContext> { 202 | let client = Client::new(); 203 | let mut pocket = try_with_context!( 204 | get_pocket(config, client), 205 | "unable to perform authorization" 206 | ); 207 | 208 | if config.access_token.is_some() { 209 | println!( 210 | "note: There's already an access token in the configuration file. \ 211 | Proceeding will overwrite this access token." 212 | ); 213 | } 214 | 215 | let auth_url = try_with_context!( 216 | pocket.get_auth_url(), 217 | "unable to get authorization URL for Pocket" 218 | ); 219 | println!("Go to the following webpage to login: {}", auth_url); 220 | println!("Then, press Enter to continue."); 221 | loop { 222 | // Let the user authorize access to the application before proceeding. 223 | let mut _input = String::new(); 224 | try_with_context!( 225 | std::io::stdin().read_line(&mut _input), 226 | "unable to read from standard input" 227 | ); 228 | 229 | match pocket.authorize() { 230 | Ok(_) => { 231 | config.access_token = Some(String::from(pocket.access_token().unwrap())); 232 | return Ok(()); 233 | } 234 | Err(e) => { 235 | println!( 236 | "Authorization failed: {}\n\ 237 | Make sure you authorized your application at the webpage linked above.\n\ 238 | Press Enter to try again, or press Ctrl+C to exit.", 239 | e 240 | ); 241 | } 242 | } 243 | } 244 | } 245 | 246 | fn sync(config: &mut Configuration) -> Result<(), ErrorWithContext> { 247 | let client = Client::new(); 248 | let mut pocket = try_with_context!( 249 | get_authenticated_pocket(config, client.clone()), 250 | "unable to sync" 251 | ); 252 | 253 | for feed in &mut config.feeds { 254 | process_feed(feed, Some(&mut pocket), &client).unwrap_or_else(|e| { 255 | let _ = writeln!(io::stderr(), "{}", e); 256 | }); 257 | } 258 | 259 | Ok(()) 260 | } 261 | 262 | fn add(config: &mut Configuration, args: &AddCommand) -> Result<(), ErrorWithContext> { 263 | fn apply_tags(feed: &mut FeedConfiguration, args: &AddCommand) { 264 | if let Some(tags) = &args.tags { 265 | feed.tags = tags.to_owned(); 266 | } 267 | } 268 | 269 | let client = Client::new(); 270 | 271 | let feed_url = &args.feed_url; 272 | if let Some(feed) = config.feeds.iter_mut().find(|feed| &feed.url == feed_url) { 273 | apply_tags(feed, args); 274 | return Ok(()); 275 | } 276 | 277 | let send_to_pocket = args.unread; 278 | let mut pocket = if send_to_pocket { 279 | Some(try_with_context!( 280 | get_authenticated_pocket(config, client.clone()), 281 | "unable to add feed" 282 | )) 283 | } else { 284 | None 285 | }; 286 | 287 | let mut feed = FeedConfiguration { 288 | url: String::from(feed_url), 289 | tags: String::new(), 290 | processed_entries: vec![], 291 | last_modified: None, 292 | last_e_tag: None, 293 | }; 294 | apply_tags(&mut feed, args); 295 | config.feeds.push(feed); 296 | 297 | let feed = config.feeds.last_mut().unwrap(); 298 | 299 | process_feed(feed, pocket.as_mut(), &client) 300 | } 301 | 302 | fn remove(config: &mut Configuration, feed_url: &str) -> Result<(), ErrorWithContext> { 303 | let len_before = config.feeds.len(); 304 | config.feeds.retain(|feed| feed.url != feed_url); 305 | let len_after = config.feeds.len(); 306 | if len_before == len_after { 307 | try_with_context!( 308 | Err(FeedNotFound::FeedNotFound(feed_url.into())), 309 | "failed to remove feed" 310 | ); 311 | } 312 | 313 | Ok(()) 314 | } 315 | 316 | fn get_pocket(config: &Configuration, client: Client) -> Result { 317 | match config.consumer_key { 318 | Some(ref consumer_key) => Ok(Pocket::new( 319 | consumer_key, 320 | config.access_token.as_ref().map(|x| x.as_ref()), 321 | client, 322 | )), 323 | None => Err(PocketSetupError::MissingConsumerKey), 324 | } 325 | } 326 | 327 | fn get_authenticated_pocket( 328 | config: &Configuration, 329 | client: Client, 330 | ) -> Result { 331 | get_pocket(config, client).and_then(|pocket| match config.access_token { 332 | Some(_) => Ok(pocket), 333 | None => Err(PocketSetupError::MissingAccessToken), 334 | }) 335 | } 336 | 337 | fn process_feed( 338 | feed: &mut FeedConfiguration, 339 | mut pocket: Option<&mut Pocket>, 340 | client: &Client, 341 | ) -> Result<(), ErrorWithContext> { 342 | println!("downloading {}", feed.url); 343 | let feed_response = try_with_context!( 344 | fetch(feed, client), 345 | format!("failed to download feed at {url}", url = feed.url) 346 | ); 347 | 348 | // Do nothing if we received a 304 Not Modified response. 349 | if let FeedResponse::Success { 350 | body, 351 | last_modified, 352 | e_tag, 353 | } = feed_response 354 | { 355 | let parsed_feed = try_with_context!( 356 | body.parse::(), 357 | format!( 358 | "failed to parse feed at {url} as either RSS or Atom", 359 | url = feed.url 360 | ) 361 | ); 362 | 363 | let (mut rss_entries, mut atom_entries); 364 | let entries: &mut dyn Iterator = match parsed_feed { 365 | Feed::Rss(ref rss) => { 366 | rss_entries = rss.items().iter().rev().flat_map(|item| item.link()); 367 | &mut rss_entries 368 | } 369 | Feed::Atom(ref atom) => { 370 | atom_entries = atom 371 | .entries() 372 | .iter() 373 | .rev() 374 | .flat_map(|entry| entry.links()) 375 | .filter_map(|link| { 376 | match link.rel() { 377 | // Only push links with an "alternate" relation type. 378 | "alternate" | "http://www.iana.org/assignments/relation/alternate" => { 379 | Some(link.href()) 380 | } 381 | _ => None, 382 | } 383 | }); 384 | &mut atom_entries 385 | } 386 | }; 387 | 388 | let mut all_processed_successfully = true; 389 | for entry_url in entries { 390 | // The rss and atom_syndication libraries 391 | // don't trim the values extracted from the XML files. 392 | let entry_url = entry_url.trim(); 393 | 394 | // Ignore entries we've processed previously. 395 | if !feed.processed_entries.iter().rev().any(|x| x == entry_url) { 396 | let is_processed = if let Some(ref mut pocket) = pocket { 397 | match Url::parse(entry_url) { 398 | Ok(parsed_entry_url) => { 399 | // Push the entry to Pocket. 400 | // Only consider the entry processed if the push succeeded. 401 | // That means that if it failed, we'll try again next time. 402 | println!("pushing {} to Pocket", entry_url); 403 | let tags = if feed.tags.is_empty() { 404 | None 405 | } else { 406 | Some(&*feed.tags) 407 | }; 408 | let push_result = pocket.add(&parsed_entry_url, None, tags, None); 409 | match push_result { 410 | Ok(_) => true, 411 | Err(error) => { 412 | println!( 413 | "error while adding URL {url} to Pocket:\n {error}", 414 | url = entry_url, 415 | error = Indented(&error) 416 | ); 417 | false 418 | } 419 | } 420 | } 421 | Err(e) => { 422 | println!("'{}' is not a valid URL ({}). ignoring.", entry_url, e); 423 | 424 | // Mark the entry as processed, 425 | // to avoid noise in subsequent runs. 426 | true 427 | } 428 | } 429 | } else { 430 | // If `pocket` is None, 431 | // then we just want to mark the current feed entries as processed, 432 | // on the assumption that the user has read them already. 433 | true 434 | }; 435 | 436 | if is_processed { 437 | // Remember that we've processed this entry 438 | // so we don't try to send it to Pocket next time. 439 | feed.processed_entries.push(entry_url.into()); 440 | } else { 441 | all_processed_successfully = false; 442 | } 443 | } 444 | } 445 | 446 | // Don't update the last modified and last ETag 447 | // if any push to Pocket failed 448 | // so we can try again next time. 449 | if all_processed_successfully { 450 | feed.last_modified = last_modified.and_then(|v| v.to_str().ok().map(|s| s.into())); 451 | feed.last_e_tag = e_tag.and_then(|v| v.to_str().ok().map(|s| s.into())); 452 | } 453 | } 454 | 455 | Ok(()) 456 | } 457 | 458 | fn fetch(feed: &FeedConfiguration, client: &Client) -> Result { 459 | let mut request = client.get(&feed.url); 460 | request = request.header( 461 | header::USER_AGENT, 462 | HeaderValue::from_static(concat!("feeds-to-pocket/", env!("CARGO_PKG_VERSION"))), 463 | ); 464 | 465 | // Add an If-Modified-Since header if we have a Last-Modified date. 466 | if let Some(ref last_modified) = feed.last_modified { 467 | request = request.header( 468 | header::IF_MODIFIED_SINCE, 469 | HeaderValue::from_str(last_modified) 470 | .expect("Failed to convert last_modified to HeaderValue"), 471 | ); 472 | } 473 | 474 | // Add an If-None-Match header if we have an ETag. 475 | if let Some(ref e_tag) = feed.last_e_tag { 476 | request = request.header( 477 | header::IF_NONE_MATCH, 478 | HeaderValue::from_str(e_tag).expect("Failed to convert last_e_tag to HeaderValue"), 479 | ); 480 | } 481 | 482 | let mut response = try_with_context!(request.send(), "failed to send request"); 483 | if response.status() == StatusCode::NOT_MODIFIED { 484 | Ok(FeedResponse::NotModified) 485 | } else { 486 | if !response.status().is_success() { 487 | try_with_context!( 488 | Err(UnacceptableHttpStatus::UnacceptableHttpStatus( 489 | response.status() 490 | )), 491 | format!( 492 | "the HTTP request to <{}> didn't return a success status", 493 | feed.url 494 | ) 495 | ); 496 | } 497 | 498 | let last_modified = response.headers().get(header::LAST_MODIFIED).cloned(); 499 | let e_tag = response.headers().get(header::ETAG).cloned(); 500 | 501 | let mut body = String::new(); 502 | try_with_context!( 503 | response.read_to_string(&mut body), 504 | "failed to read response" 505 | ); 506 | 507 | Ok(FeedResponse::Success { 508 | body, 509 | last_modified, 510 | e_tag, 511 | }) 512 | } 513 | } 514 | 515 | /// Simple program to greet a person 516 | #[derive(Parser, Debug)] 517 | #[clap(author, version, about, long_about = None, display_name = "Feeds to Pocket")] 518 | struct Args { 519 | /// A YAML file containing your feeds configuration. 520 | //#[clap(short, long, value_parser)] 521 | #[clap(index = 1)] 522 | config: PathBuf, 523 | 524 | #[clap(subcommand)] 525 | command: Option, 526 | } 527 | 528 | impl Args { 529 | fn with_config( 530 | &self, 531 | mut callback: impl FnMut(&mut Configuration) -> Result<(), ErrorWithContext>, 532 | ) -> Result<(), ErrorWithContext> { 533 | let mut config = load_config(&self.config)?; 534 | 535 | callback(&mut config)?; 536 | 537 | save_config(&config, &self.config) 538 | } 539 | } 540 | 541 | #[derive(Parser, Debug)] 542 | enum Command { 543 | /// Creates an empty configuration file (if it doesn't already exist). 544 | Init, 545 | 546 | /// Sets the consumer key in the configuration file. 547 | SetConsumerKey { 548 | /// A consumer key obtained from Pocket's website. 549 | /// You must create your own application 550 | /// at https://getpocket.com/developer/apps/new 551 | /// to obtain a consumer key; 552 | /// I don't want you kicking me out of my own application! :) 553 | /// Make sure your application has at least the "Add" permission. 554 | key: String, 555 | }, 556 | 557 | /// Obtains and saves an access token from Pocket. 558 | /// This will print a URL on the standard output, 559 | /// which you must open in a web browser 560 | /// in order to grant your application access to your Pocket account. 561 | /// Once authorization has been obtained, 562 | /// an access token is saved in the configuration file, 563 | /// which will be used to queue up entries in your Pocket list. 564 | Login, 565 | 566 | /// Adds a feed to your feeds configuration 567 | /// or updates an existing feed in your feeds configuration. 568 | Add(AddCommand), 569 | 570 | /// Removes a feed from your feeds configuration. 571 | Remove { 572 | /// The URL of the feed to remove. 573 | feed_url: String, 574 | }, 575 | } 576 | 577 | #[derive(Parser, Debug)] 578 | struct AddCommand { 579 | /// Consider all the entries in the feed to be unread. 580 | /// All entries will be sent to Pocket immediately. 581 | /// By default, all the entries present when the feed is added 582 | /// are considered read and are not sent to Pocket. 583 | #[clap(long)] 584 | unread: bool, 585 | 586 | /// A comma-separated list of tags to attach to the URLs sent to Pocket. 587 | #[clap(long)] 588 | tags: Option, 589 | 590 | /// The URL of the feed to add. 591 | feed_url: String, 592 | } 593 | 594 | #[derive(Default, Deserialize, Serialize)] 595 | struct Configuration { 596 | #[serde(skip_serializing_if = "Option::is_none")] 597 | consumer_key: Option, 598 | #[serde(skip_serializing_if = "Option::is_none")] 599 | access_token: Option, 600 | #[serde(skip_serializing_if = "Vec::is_empty")] 601 | #[serde(default)] 602 | feeds: Vec, 603 | } 604 | 605 | #[derive(Deserialize, Serialize)] 606 | struct FeedConfiguration { 607 | url: String, 608 | #[serde(skip_serializing_if = "str::is_empty")] 609 | #[serde(default)] 610 | tags: String, 611 | #[serde(skip_serializing_if = "Vec::is_empty")] 612 | #[serde(default)] 613 | processed_entries: Vec, 614 | #[serde(skip_serializing_if = "Option::is_none")] 615 | last_modified: Option, 616 | #[serde(skip_serializing_if = "Option::is_none")] 617 | last_e_tag: Option, 618 | } 619 | 620 | enum FeedResponse { 621 | Success { 622 | body: String, 623 | last_modified: Option, 624 | e_tag: Option, 625 | }, 626 | NotModified, 627 | } 628 | 629 | enum Feed { 630 | Atom(Box), 631 | Rss(Box), 632 | } 633 | 634 | impl FromStr for Feed { 635 | type Err = FeedError; 636 | 637 | fn from_str(s: &str) -> Result { 638 | match s.parse::() { 639 | Ok(feed) => Ok(Feed::Atom(Box::new(feed))), 640 | Err(atom_error) => match s.parse::() { 641 | Ok(channel) => Ok(Feed::Rss(Box::new(channel))), 642 | Err(rss_error) => Err(FeedError { 643 | atom_error, 644 | rss_error, 645 | }), 646 | }, 647 | } 648 | } 649 | } 650 | 651 | #[derive(Debug)] 652 | struct FeedError { 653 | atom_error: atom_syndication::Error, 654 | rss_error: rss::Error, 655 | } 656 | 657 | impl Display for FeedError { 658 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 659 | write!(fmt, "could not parse input as either Atom or RSS:\n parsing as Atom failed with:\n {}\n parsing as RSS failed with:\n {}", 660 | Indented(Indented(&self.atom_error)), Indented(Indented(&self.rss_error))) 661 | } 662 | } 663 | 664 | impl Error for FeedError { 665 | fn description(&self) -> &str { 666 | "could not parse input as either Atom or RSS" 667 | } 668 | } 669 | 670 | #[derive(Debug)] 671 | struct ErrorWithContext { 672 | error: Box, 673 | context: String, 674 | } 675 | 676 | impl ErrorWithContext { 677 | fn new>(error: Box, context: S) -> ErrorWithContext { 678 | ErrorWithContext { 679 | error, 680 | context: context.into(), 681 | } 682 | } 683 | } 684 | 685 | impl Display for ErrorWithContext { 686 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 687 | write!(fmt, "{}:\n {}", self.context, Indented(&self.error)) 688 | } 689 | } 690 | 691 | impl Error for ErrorWithContext { 692 | fn description(&self) -> &str { 693 | &self.context 694 | } 695 | 696 | fn cause(&self) -> Option<&dyn Error> { 697 | Some(&*self.error) 698 | } 699 | } 700 | 701 | quick_error! { 702 | #[derive(Debug)] 703 | enum PocketSetupError { 704 | MissingConsumerKey { 705 | display("The consumer key is not set in the configuration file. Run `feeds-to-pocket help set-consumer-key` for help and instructions.") 706 | } 707 | MissingAccessToken { 708 | display("The access token is not set in the configuration file. Run `feeds-to-pocket help login` for help and instructions.") 709 | } 710 | } 711 | } 712 | 713 | quick_error! { 714 | #[derive(Debug)] 715 | enum UnacceptableHttpStatus { 716 | UnacceptableHttpStatus(status: StatusCode) { 717 | display("{}", status) 718 | } 719 | } 720 | } 721 | 722 | quick_error! { 723 | #[derive(Debug)] 724 | enum Errors { 725 | Errors(errors: Vec>) { 726 | display("{}", errors.iter().map(|error| format!("- {}", Indented(error))).collect::>().join("\n")) 727 | } 728 | } 729 | } 730 | 731 | quick_error! { 732 | #[derive(Debug)] 733 | enum FeedNotFound { 734 | FeedNotFound(url: String) { 735 | display("No feed with URL {} was found.", url) 736 | } 737 | } 738 | } 739 | 740 | impl Errors { 741 | fn new(errors: Vec>) -> Errors { 742 | Errors::Errors(errors) 743 | } 744 | } 745 | 746 | /// Wraps a type implementing Display 747 | /// and adds two spaces after each line feed in its display output. 748 | struct Indented(D); 749 | 750 | impl Display for Indented { 751 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { 752 | use std::fmt::Write; 753 | write!(IndentedWrite(fmt), "{}", self.0) 754 | } 755 | } 756 | 757 | /// Intercepts writes to a `std::fmt::Formatter` 758 | /// and adds two spaces after each line feed written to it. 759 | struct IndentedWrite<'a: 'f, 'f>(&'f mut fmt::Formatter<'a>); 760 | 761 | // The documentation recommends implementing std::io::Write, 762 | // but that trait operates on a stream of bytes, 763 | // whereas std::fmt::Write operates on string slices. 764 | // Additionally, we call Formatter::write_str(), 765 | // which returns a Result<(), std::fmt::Error>, 766 | // which matches the signature of std::fmt::Write::write_str(). 767 | impl<'a: 'f, 'f> fmt::Write for IndentedWrite<'a, 'f> { 768 | fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { 769 | let mut lines = s.split('\n'); 770 | if let Some(line) = lines.next() { 771 | self.0.write_str(line)?; 772 | for line in lines { 773 | self.0.write_str("\n ")?; 774 | self.0.write_str(line)?; 775 | } 776 | } 777 | 778 | Ok(()) 779 | } 780 | } 781 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "anstream" 22 | version = "0.6.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "is_terminal_polyfill", 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle" 37 | version = "1.0.10" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 40 | 41 | [[package]] 42 | name = "anstyle-parse" 43 | version = "0.2.6" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 46 | dependencies = [ 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-query" 52 | version = "1.1.2" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 55 | dependencies = [ 56 | "windows-sys 0.59.0", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-wincon" 61 | version = "3.0.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 64 | dependencies = [ 65 | "anstyle", 66 | "windows-sys 0.59.0", 67 | ] 68 | 69 | [[package]] 70 | name = "atom_syndication" 71 | version = "0.12.5" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "3ee79fb83c725eae67b55218870813d2fc39fd85e4f1583848ef9f4f823cfe7c" 74 | dependencies = [ 75 | "chrono", 76 | "derive_builder", 77 | "diligent-date-parser", 78 | "never", 79 | "quick-xml", 80 | ] 81 | 82 | [[package]] 83 | name = "atomic-waker" 84 | version = "1.1.2" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 87 | 88 | [[package]] 89 | name = "autocfg" 90 | version = "1.4.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 93 | 94 | [[package]] 95 | name = "backtrace" 96 | version = "0.3.74" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 99 | dependencies = [ 100 | "addr2line", 101 | "cfg-if", 102 | "libc", 103 | "miniz_oxide", 104 | "object", 105 | "rustc-demangle", 106 | "windows-targets", 107 | ] 108 | 109 | [[package]] 110 | name = "base64" 111 | version = "0.22.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 114 | 115 | [[package]] 116 | name = "bitflags" 117 | version = "2.6.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 120 | 121 | [[package]] 122 | name = "bumpalo" 123 | version = "3.16.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 126 | 127 | [[package]] 128 | name = "bytes" 129 | version = "1.9.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" 132 | 133 | [[package]] 134 | name = "cc" 135 | version = "1.2.2" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" 138 | dependencies = [ 139 | "shlex", 140 | ] 141 | 142 | [[package]] 143 | name = "cfg-if" 144 | version = "1.0.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 147 | 148 | [[package]] 149 | name = "chrono" 150 | version = "0.4.38" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 153 | dependencies = [ 154 | "num-traits", 155 | ] 156 | 157 | [[package]] 158 | name = "clap" 159 | version = "4.5.22" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" 162 | dependencies = [ 163 | "clap_builder", 164 | "clap_derive", 165 | ] 166 | 167 | [[package]] 168 | name = "clap_builder" 169 | version = "4.5.22" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" 172 | dependencies = [ 173 | "anstream", 174 | "anstyle", 175 | "clap_lex", 176 | "strsim", 177 | ] 178 | 179 | [[package]] 180 | name = "clap_derive" 181 | version = "4.5.18" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 184 | dependencies = [ 185 | "heck", 186 | "proc-macro2", 187 | "quote", 188 | "syn", 189 | ] 190 | 191 | [[package]] 192 | name = "clap_lex" 193 | version = "0.7.3" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 196 | 197 | [[package]] 198 | name = "colorchoice" 199 | version = "1.0.3" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 202 | 203 | [[package]] 204 | name = "core-foundation" 205 | version = "0.9.4" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 208 | dependencies = [ 209 | "core-foundation-sys", 210 | "libc", 211 | ] 212 | 213 | [[package]] 214 | name = "core-foundation-sys" 215 | version = "0.8.7" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 218 | 219 | [[package]] 220 | name = "darling" 221 | version = "0.20.10" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 224 | dependencies = [ 225 | "darling_core", 226 | "darling_macro", 227 | ] 228 | 229 | [[package]] 230 | name = "darling_core" 231 | version = "0.20.10" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 234 | dependencies = [ 235 | "fnv", 236 | "ident_case", 237 | "proc-macro2", 238 | "quote", 239 | "strsim", 240 | "syn", 241 | ] 242 | 243 | [[package]] 244 | name = "darling_macro" 245 | version = "0.20.10" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 248 | dependencies = [ 249 | "darling_core", 250 | "quote", 251 | "syn", 252 | ] 253 | 254 | [[package]] 255 | name = "derive_builder" 256 | version = "0.20.2" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 259 | dependencies = [ 260 | "derive_builder_macro", 261 | ] 262 | 263 | [[package]] 264 | name = "derive_builder_core" 265 | version = "0.20.2" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 268 | dependencies = [ 269 | "darling", 270 | "proc-macro2", 271 | "quote", 272 | "syn", 273 | ] 274 | 275 | [[package]] 276 | name = "derive_builder_macro" 277 | version = "0.20.2" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 280 | dependencies = [ 281 | "derive_builder_core", 282 | "syn", 283 | ] 284 | 285 | [[package]] 286 | name = "diligent-date-parser" 287 | version = "0.1.5" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "c8ede7d79366f419921e2e2f67889c12125726692a313bffb474bd5f37a581e9" 290 | dependencies = [ 291 | "chrono", 292 | ] 293 | 294 | [[package]] 295 | name = "displaydoc" 296 | version = "0.2.5" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 299 | dependencies = [ 300 | "proc-macro2", 301 | "quote", 302 | "syn", 303 | ] 304 | 305 | [[package]] 306 | name = "encoding_rs" 307 | version = "0.8.35" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 310 | dependencies = [ 311 | "cfg-if", 312 | ] 313 | 314 | [[package]] 315 | name = "equivalent" 316 | version = "1.0.1" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 319 | 320 | [[package]] 321 | name = "errno" 322 | version = "0.3.10" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 325 | dependencies = [ 326 | "libc", 327 | "windows-sys 0.59.0", 328 | ] 329 | 330 | [[package]] 331 | name = "fastrand" 332 | version = "2.2.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" 335 | 336 | [[package]] 337 | name = "feeds-to-pocket" 338 | version = "0.1.8" 339 | dependencies = [ 340 | "atom_syndication", 341 | "clap", 342 | "quick-error", 343 | "reqwest", 344 | "rss", 345 | "serde", 346 | "serde_json", 347 | "serde_yaml", 348 | "url", 349 | ] 350 | 351 | [[package]] 352 | name = "fnv" 353 | version = "1.0.7" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 356 | 357 | [[package]] 358 | name = "foreign-types" 359 | version = "0.3.2" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 362 | dependencies = [ 363 | "foreign-types-shared", 364 | ] 365 | 366 | [[package]] 367 | name = "foreign-types-shared" 368 | version = "0.1.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 371 | 372 | [[package]] 373 | name = "form_urlencoded" 374 | version = "1.2.1" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 377 | dependencies = [ 378 | "percent-encoding", 379 | ] 380 | 381 | [[package]] 382 | name = "futures-channel" 383 | version = "0.3.31" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 386 | dependencies = [ 387 | "futures-core", 388 | "futures-sink", 389 | ] 390 | 391 | [[package]] 392 | name = "futures-core" 393 | version = "0.3.31" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 396 | 397 | [[package]] 398 | name = "futures-io" 399 | version = "0.3.31" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 402 | 403 | [[package]] 404 | name = "futures-sink" 405 | version = "0.3.31" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 408 | 409 | [[package]] 410 | name = "futures-task" 411 | version = "0.3.31" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 414 | 415 | [[package]] 416 | name = "futures-util" 417 | version = "0.3.31" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 420 | dependencies = [ 421 | "futures-core", 422 | "futures-io", 423 | "futures-sink", 424 | "futures-task", 425 | "memchr", 426 | "pin-project-lite", 427 | "pin-utils", 428 | "slab", 429 | ] 430 | 431 | [[package]] 432 | name = "getrandom" 433 | version = "0.2.15" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 436 | dependencies = [ 437 | "cfg-if", 438 | "libc", 439 | "wasi", 440 | ] 441 | 442 | [[package]] 443 | name = "gimli" 444 | version = "0.31.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 447 | 448 | [[package]] 449 | name = "h2" 450 | version = "0.4.7" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" 453 | dependencies = [ 454 | "atomic-waker", 455 | "bytes", 456 | "fnv", 457 | "futures-core", 458 | "futures-sink", 459 | "http", 460 | "indexmap", 461 | "slab", 462 | "tokio", 463 | "tokio-util", 464 | "tracing", 465 | ] 466 | 467 | [[package]] 468 | name = "hashbrown" 469 | version = "0.15.2" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 472 | 473 | [[package]] 474 | name = "heck" 475 | version = "0.5.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 478 | 479 | [[package]] 480 | name = "http" 481 | version = "1.2.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 484 | dependencies = [ 485 | "bytes", 486 | "fnv", 487 | "itoa", 488 | ] 489 | 490 | [[package]] 491 | name = "http-body" 492 | version = "1.0.1" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 495 | dependencies = [ 496 | "bytes", 497 | "http", 498 | ] 499 | 500 | [[package]] 501 | name = "http-body-util" 502 | version = "0.1.2" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 505 | dependencies = [ 506 | "bytes", 507 | "futures-util", 508 | "http", 509 | "http-body", 510 | "pin-project-lite", 511 | ] 512 | 513 | [[package]] 514 | name = "httparse" 515 | version = "1.9.5" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 518 | 519 | [[package]] 520 | name = "hyper" 521 | version = "1.5.1" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" 524 | dependencies = [ 525 | "bytes", 526 | "futures-channel", 527 | "futures-util", 528 | "h2", 529 | "http", 530 | "http-body", 531 | "httparse", 532 | "itoa", 533 | "pin-project-lite", 534 | "smallvec", 535 | "tokio", 536 | "want", 537 | ] 538 | 539 | [[package]] 540 | name = "hyper-rustls" 541 | version = "0.27.3" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" 544 | dependencies = [ 545 | "futures-util", 546 | "http", 547 | "hyper", 548 | "hyper-util", 549 | "rustls", 550 | "rustls-pki-types", 551 | "tokio", 552 | "tokio-rustls", 553 | "tower-service", 554 | ] 555 | 556 | [[package]] 557 | name = "hyper-tls" 558 | version = "0.6.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 561 | dependencies = [ 562 | "bytes", 563 | "http-body-util", 564 | "hyper", 565 | "hyper-util", 566 | "native-tls", 567 | "tokio", 568 | "tokio-native-tls", 569 | "tower-service", 570 | ] 571 | 572 | [[package]] 573 | name = "hyper-util" 574 | version = "0.1.10" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 577 | dependencies = [ 578 | "bytes", 579 | "futures-channel", 580 | "futures-util", 581 | "http", 582 | "http-body", 583 | "hyper", 584 | "pin-project-lite", 585 | "socket2", 586 | "tokio", 587 | "tower-service", 588 | "tracing", 589 | ] 590 | 591 | [[package]] 592 | name = "icu_collections" 593 | version = "1.5.0" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 596 | dependencies = [ 597 | "displaydoc", 598 | "yoke", 599 | "zerofrom", 600 | "zerovec", 601 | ] 602 | 603 | [[package]] 604 | name = "icu_locid" 605 | version = "1.5.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 608 | dependencies = [ 609 | "displaydoc", 610 | "litemap", 611 | "tinystr", 612 | "writeable", 613 | "zerovec", 614 | ] 615 | 616 | [[package]] 617 | name = "icu_locid_transform" 618 | version = "1.5.0" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 621 | dependencies = [ 622 | "displaydoc", 623 | "icu_locid", 624 | "icu_locid_transform_data", 625 | "icu_provider", 626 | "tinystr", 627 | "zerovec", 628 | ] 629 | 630 | [[package]] 631 | name = "icu_locid_transform_data" 632 | version = "1.5.0" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 635 | 636 | [[package]] 637 | name = "icu_normalizer" 638 | version = "1.5.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 641 | dependencies = [ 642 | "displaydoc", 643 | "icu_collections", 644 | "icu_normalizer_data", 645 | "icu_properties", 646 | "icu_provider", 647 | "smallvec", 648 | "utf16_iter", 649 | "utf8_iter", 650 | "write16", 651 | "zerovec", 652 | ] 653 | 654 | [[package]] 655 | name = "icu_normalizer_data" 656 | version = "1.5.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 659 | 660 | [[package]] 661 | name = "icu_properties" 662 | version = "1.5.1" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 665 | dependencies = [ 666 | "displaydoc", 667 | "icu_collections", 668 | "icu_locid_transform", 669 | "icu_properties_data", 670 | "icu_provider", 671 | "tinystr", 672 | "zerovec", 673 | ] 674 | 675 | [[package]] 676 | name = "icu_properties_data" 677 | version = "1.5.0" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 680 | 681 | [[package]] 682 | name = "icu_provider" 683 | version = "1.5.0" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 686 | dependencies = [ 687 | "displaydoc", 688 | "icu_locid", 689 | "icu_provider_macros", 690 | "stable_deref_trait", 691 | "tinystr", 692 | "writeable", 693 | "yoke", 694 | "zerofrom", 695 | "zerovec", 696 | ] 697 | 698 | [[package]] 699 | name = "icu_provider_macros" 700 | version = "1.5.0" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 703 | dependencies = [ 704 | "proc-macro2", 705 | "quote", 706 | "syn", 707 | ] 708 | 709 | [[package]] 710 | name = "ident_case" 711 | version = "1.0.1" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 714 | 715 | [[package]] 716 | name = "idna" 717 | version = "1.0.3" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 720 | dependencies = [ 721 | "idna_adapter", 722 | "smallvec", 723 | "utf8_iter", 724 | ] 725 | 726 | [[package]] 727 | name = "idna_adapter" 728 | version = "1.2.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 731 | dependencies = [ 732 | "icu_normalizer", 733 | "icu_properties", 734 | ] 735 | 736 | [[package]] 737 | name = "indexmap" 738 | version = "2.7.0" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 741 | dependencies = [ 742 | "equivalent", 743 | "hashbrown", 744 | ] 745 | 746 | [[package]] 747 | name = "ipnet" 748 | version = "2.10.1" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 751 | 752 | [[package]] 753 | name = "is_terminal_polyfill" 754 | version = "1.70.1" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 757 | 758 | [[package]] 759 | name = "itoa" 760 | version = "1.0.14" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 763 | 764 | [[package]] 765 | name = "js-sys" 766 | version = "0.3.74" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" 769 | dependencies = [ 770 | "once_cell", 771 | "wasm-bindgen", 772 | ] 773 | 774 | [[package]] 775 | name = "libc" 776 | version = "0.2.167" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" 779 | 780 | [[package]] 781 | name = "linux-raw-sys" 782 | version = "0.4.14" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 785 | 786 | [[package]] 787 | name = "litemap" 788 | version = "0.7.4" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 791 | 792 | [[package]] 793 | name = "log" 794 | version = "0.4.22" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 797 | 798 | [[package]] 799 | name = "memchr" 800 | version = "2.7.4" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 803 | 804 | [[package]] 805 | name = "mime" 806 | version = "0.3.17" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 809 | 810 | [[package]] 811 | name = "miniz_oxide" 812 | version = "0.8.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 815 | dependencies = [ 816 | "adler2", 817 | ] 818 | 819 | [[package]] 820 | name = "mio" 821 | version = "1.0.3" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 824 | dependencies = [ 825 | "libc", 826 | "wasi", 827 | "windows-sys 0.52.0", 828 | ] 829 | 830 | [[package]] 831 | name = "native-tls" 832 | version = "0.2.12" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 835 | dependencies = [ 836 | "libc", 837 | "log", 838 | "openssl", 839 | "openssl-probe", 840 | "openssl-sys", 841 | "schannel", 842 | "security-framework", 843 | "security-framework-sys", 844 | "tempfile", 845 | ] 846 | 847 | [[package]] 848 | name = "never" 849 | version = "0.1.0" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" 852 | 853 | [[package]] 854 | name = "num-traits" 855 | version = "0.2.19" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 858 | dependencies = [ 859 | "autocfg", 860 | ] 861 | 862 | [[package]] 863 | name = "object" 864 | version = "0.36.5" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 867 | dependencies = [ 868 | "memchr", 869 | ] 870 | 871 | [[package]] 872 | name = "once_cell" 873 | version = "1.20.2" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 876 | 877 | [[package]] 878 | name = "openssl" 879 | version = "0.10.68" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 882 | dependencies = [ 883 | "bitflags", 884 | "cfg-if", 885 | "foreign-types", 886 | "libc", 887 | "once_cell", 888 | "openssl-macros", 889 | "openssl-sys", 890 | ] 891 | 892 | [[package]] 893 | name = "openssl-macros" 894 | version = "0.1.1" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 897 | dependencies = [ 898 | "proc-macro2", 899 | "quote", 900 | "syn", 901 | ] 902 | 903 | [[package]] 904 | name = "openssl-probe" 905 | version = "0.1.5" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 908 | 909 | [[package]] 910 | name = "openssl-sys" 911 | version = "0.9.104" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 914 | dependencies = [ 915 | "cc", 916 | "libc", 917 | "pkg-config", 918 | "vcpkg", 919 | ] 920 | 921 | [[package]] 922 | name = "percent-encoding" 923 | version = "2.3.1" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 926 | 927 | [[package]] 928 | name = "pin-project-lite" 929 | version = "0.2.15" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 932 | 933 | [[package]] 934 | name = "pin-utils" 935 | version = "0.1.0" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 938 | 939 | [[package]] 940 | name = "pkg-config" 941 | version = "0.3.31" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 944 | 945 | [[package]] 946 | name = "proc-macro2" 947 | version = "1.0.92" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 950 | dependencies = [ 951 | "unicode-ident", 952 | ] 953 | 954 | [[package]] 955 | name = "quick-error" 956 | version = "2.0.1" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 959 | 960 | [[package]] 961 | name = "quick-xml" 962 | version = "0.37.1" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03" 965 | dependencies = [ 966 | "encoding_rs", 967 | "memchr", 968 | ] 969 | 970 | [[package]] 971 | name = "quote" 972 | version = "1.0.37" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 975 | dependencies = [ 976 | "proc-macro2", 977 | ] 978 | 979 | [[package]] 980 | name = "reqwest" 981 | version = "0.12.9" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" 984 | dependencies = [ 985 | "base64", 986 | "bytes", 987 | "encoding_rs", 988 | "futures-channel", 989 | "futures-core", 990 | "futures-util", 991 | "h2", 992 | "http", 993 | "http-body", 994 | "http-body-util", 995 | "hyper", 996 | "hyper-rustls", 997 | "hyper-tls", 998 | "hyper-util", 999 | "ipnet", 1000 | "js-sys", 1001 | "log", 1002 | "mime", 1003 | "native-tls", 1004 | "once_cell", 1005 | "percent-encoding", 1006 | "pin-project-lite", 1007 | "rustls-pemfile", 1008 | "serde", 1009 | "serde_json", 1010 | "serde_urlencoded", 1011 | "sync_wrapper", 1012 | "system-configuration", 1013 | "tokio", 1014 | "tokio-native-tls", 1015 | "tower-service", 1016 | "url", 1017 | "wasm-bindgen", 1018 | "wasm-bindgen-futures", 1019 | "web-sys", 1020 | "windows-registry", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "ring" 1025 | version = "0.17.8" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1028 | dependencies = [ 1029 | "cc", 1030 | "cfg-if", 1031 | "getrandom", 1032 | "libc", 1033 | "spin", 1034 | "untrusted", 1035 | "windows-sys 0.52.0", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "rss" 1040 | version = "2.0.11" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "531af70fce504d369cf42ac0a9645f5a62a8ea9265de71cfa25087e9f6080c7c" 1043 | dependencies = [ 1044 | "atom_syndication", 1045 | "derive_builder", 1046 | "never", 1047 | "quick-xml", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "rustc-demangle" 1052 | version = "0.1.24" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1055 | 1056 | [[package]] 1057 | name = "rustix" 1058 | version = "0.38.41" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" 1061 | dependencies = [ 1062 | "bitflags", 1063 | "errno", 1064 | "libc", 1065 | "linux-raw-sys", 1066 | "windows-sys 0.52.0", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "rustls" 1071 | version = "0.23.19" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" 1074 | dependencies = [ 1075 | "once_cell", 1076 | "rustls-pki-types", 1077 | "rustls-webpki", 1078 | "subtle", 1079 | "zeroize", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "rustls-pemfile" 1084 | version = "2.2.0" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1087 | dependencies = [ 1088 | "rustls-pki-types", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "rustls-pki-types" 1093 | version = "1.10.0" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" 1096 | 1097 | [[package]] 1098 | name = "rustls-webpki" 1099 | version = "0.102.8" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1102 | dependencies = [ 1103 | "ring", 1104 | "rustls-pki-types", 1105 | "untrusted", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "ryu" 1110 | version = "1.0.18" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1113 | 1114 | [[package]] 1115 | name = "schannel" 1116 | version = "0.1.27" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1119 | dependencies = [ 1120 | "windows-sys 0.59.0", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "security-framework" 1125 | version = "2.11.1" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1128 | dependencies = [ 1129 | "bitflags", 1130 | "core-foundation", 1131 | "core-foundation-sys", 1132 | "libc", 1133 | "security-framework-sys", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "security-framework-sys" 1138 | version = "2.12.1" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" 1141 | dependencies = [ 1142 | "core-foundation-sys", 1143 | "libc", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "serde" 1148 | version = "1.0.215" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 1151 | dependencies = [ 1152 | "serde_derive", 1153 | ] 1154 | 1155 | [[package]] 1156 | name = "serde_derive" 1157 | version = "1.0.215" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 1160 | dependencies = [ 1161 | "proc-macro2", 1162 | "quote", 1163 | "syn", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "serde_json" 1168 | version = "1.0.133" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 1171 | dependencies = [ 1172 | "itoa", 1173 | "memchr", 1174 | "ryu", 1175 | "serde", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "serde_urlencoded" 1180 | version = "0.7.1" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1183 | dependencies = [ 1184 | "form_urlencoded", 1185 | "itoa", 1186 | "ryu", 1187 | "serde", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "serde_yaml" 1192 | version = "0.9.34+deprecated" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1195 | dependencies = [ 1196 | "indexmap", 1197 | "itoa", 1198 | "ryu", 1199 | "serde", 1200 | "unsafe-libyaml", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "shlex" 1205 | version = "1.3.0" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1208 | 1209 | [[package]] 1210 | name = "slab" 1211 | version = "0.4.9" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1214 | dependencies = [ 1215 | "autocfg", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "smallvec" 1220 | version = "1.13.2" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1223 | 1224 | [[package]] 1225 | name = "socket2" 1226 | version = "0.5.8" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1229 | dependencies = [ 1230 | "libc", 1231 | "windows-sys 0.52.0", 1232 | ] 1233 | 1234 | [[package]] 1235 | name = "spin" 1236 | version = "0.9.8" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1239 | 1240 | [[package]] 1241 | name = "stable_deref_trait" 1242 | version = "1.2.0" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1245 | 1246 | [[package]] 1247 | name = "strsim" 1248 | version = "0.11.1" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1251 | 1252 | [[package]] 1253 | name = "subtle" 1254 | version = "2.6.1" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1257 | 1258 | [[package]] 1259 | name = "syn" 1260 | version = "2.0.90" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 1263 | dependencies = [ 1264 | "proc-macro2", 1265 | "quote", 1266 | "unicode-ident", 1267 | ] 1268 | 1269 | [[package]] 1270 | name = "sync_wrapper" 1271 | version = "1.0.2" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1274 | dependencies = [ 1275 | "futures-core", 1276 | ] 1277 | 1278 | [[package]] 1279 | name = "synstructure" 1280 | version = "0.13.1" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1283 | dependencies = [ 1284 | "proc-macro2", 1285 | "quote", 1286 | "syn", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "system-configuration" 1291 | version = "0.6.1" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1294 | dependencies = [ 1295 | "bitflags", 1296 | "core-foundation", 1297 | "system-configuration-sys", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "system-configuration-sys" 1302 | version = "0.6.0" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1305 | dependencies = [ 1306 | "core-foundation-sys", 1307 | "libc", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "tempfile" 1312 | version = "3.14.0" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 1315 | dependencies = [ 1316 | "cfg-if", 1317 | "fastrand", 1318 | "once_cell", 1319 | "rustix", 1320 | "windows-sys 0.59.0", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "tinystr" 1325 | version = "0.7.6" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1328 | dependencies = [ 1329 | "displaydoc", 1330 | "zerovec", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "tokio" 1335 | version = "1.42.0" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" 1338 | dependencies = [ 1339 | "backtrace", 1340 | "bytes", 1341 | "libc", 1342 | "mio", 1343 | "pin-project-lite", 1344 | "socket2", 1345 | "windows-sys 0.52.0", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "tokio-native-tls" 1350 | version = "0.3.1" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1353 | dependencies = [ 1354 | "native-tls", 1355 | "tokio", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "tokio-rustls" 1360 | version = "0.26.0" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 1363 | dependencies = [ 1364 | "rustls", 1365 | "rustls-pki-types", 1366 | "tokio", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "tokio-util" 1371 | version = "0.7.13" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" 1374 | dependencies = [ 1375 | "bytes", 1376 | "futures-core", 1377 | "futures-sink", 1378 | "pin-project-lite", 1379 | "tokio", 1380 | ] 1381 | 1382 | [[package]] 1383 | name = "tower-service" 1384 | version = "0.3.3" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1387 | 1388 | [[package]] 1389 | name = "tracing" 1390 | version = "0.1.41" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1393 | dependencies = [ 1394 | "pin-project-lite", 1395 | "tracing-core", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "tracing-core" 1400 | version = "0.1.33" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1403 | dependencies = [ 1404 | "once_cell", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "try-lock" 1409 | version = "0.2.5" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1412 | 1413 | [[package]] 1414 | name = "unicode-ident" 1415 | version = "1.0.14" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 1418 | 1419 | [[package]] 1420 | name = "unsafe-libyaml" 1421 | version = "0.2.11" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1424 | 1425 | [[package]] 1426 | name = "untrusted" 1427 | version = "0.9.0" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1430 | 1431 | [[package]] 1432 | name = "url" 1433 | version = "2.5.4" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1436 | dependencies = [ 1437 | "form_urlencoded", 1438 | "idna", 1439 | "percent-encoding", 1440 | "serde", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "utf16_iter" 1445 | version = "1.0.5" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1448 | 1449 | [[package]] 1450 | name = "utf8_iter" 1451 | version = "1.0.4" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1454 | 1455 | [[package]] 1456 | name = "utf8parse" 1457 | version = "0.2.2" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1460 | 1461 | [[package]] 1462 | name = "vcpkg" 1463 | version = "0.2.15" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1466 | 1467 | [[package]] 1468 | name = "want" 1469 | version = "0.3.1" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1472 | dependencies = [ 1473 | "try-lock", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "wasi" 1478 | version = "0.11.0+wasi-snapshot-preview1" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1481 | 1482 | [[package]] 1483 | name = "wasm-bindgen" 1484 | version = "0.2.97" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" 1487 | dependencies = [ 1488 | "cfg-if", 1489 | "once_cell", 1490 | "wasm-bindgen-macro", 1491 | ] 1492 | 1493 | [[package]] 1494 | name = "wasm-bindgen-backend" 1495 | version = "0.2.97" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" 1498 | dependencies = [ 1499 | "bumpalo", 1500 | "log", 1501 | "once_cell", 1502 | "proc-macro2", 1503 | "quote", 1504 | "syn", 1505 | "wasm-bindgen-shared", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "wasm-bindgen-futures" 1510 | version = "0.4.47" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" 1513 | dependencies = [ 1514 | "cfg-if", 1515 | "js-sys", 1516 | "once_cell", 1517 | "wasm-bindgen", 1518 | "web-sys", 1519 | ] 1520 | 1521 | [[package]] 1522 | name = "wasm-bindgen-macro" 1523 | version = "0.2.97" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" 1526 | dependencies = [ 1527 | "quote", 1528 | "wasm-bindgen-macro-support", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "wasm-bindgen-macro-support" 1533 | version = "0.2.97" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" 1536 | dependencies = [ 1537 | "proc-macro2", 1538 | "quote", 1539 | "syn", 1540 | "wasm-bindgen-backend", 1541 | "wasm-bindgen-shared", 1542 | ] 1543 | 1544 | [[package]] 1545 | name = "wasm-bindgen-shared" 1546 | version = "0.2.97" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" 1549 | 1550 | [[package]] 1551 | name = "web-sys" 1552 | version = "0.3.74" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" 1555 | dependencies = [ 1556 | "js-sys", 1557 | "wasm-bindgen", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "windows-registry" 1562 | version = "0.2.0" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1565 | dependencies = [ 1566 | "windows-result", 1567 | "windows-strings", 1568 | "windows-targets", 1569 | ] 1570 | 1571 | [[package]] 1572 | name = "windows-result" 1573 | version = "0.2.0" 1574 | source = "registry+https://github.com/rust-lang/crates.io-index" 1575 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1576 | dependencies = [ 1577 | "windows-targets", 1578 | ] 1579 | 1580 | [[package]] 1581 | name = "windows-strings" 1582 | version = "0.1.0" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1585 | dependencies = [ 1586 | "windows-result", 1587 | "windows-targets", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "windows-sys" 1592 | version = "0.52.0" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1595 | dependencies = [ 1596 | "windows-targets", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "windows-sys" 1601 | version = "0.59.0" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1604 | dependencies = [ 1605 | "windows-targets", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "windows-targets" 1610 | version = "0.52.6" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1613 | dependencies = [ 1614 | "windows_aarch64_gnullvm", 1615 | "windows_aarch64_msvc", 1616 | "windows_i686_gnu", 1617 | "windows_i686_gnullvm", 1618 | "windows_i686_msvc", 1619 | "windows_x86_64_gnu", 1620 | "windows_x86_64_gnullvm", 1621 | "windows_x86_64_msvc", 1622 | ] 1623 | 1624 | [[package]] 1625 | name = "windows_aarch64_gnullvm" 1626 | version = "0.52.6" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1629 | 1630 | [[package]] 1631 | name = "windows_aarch64_msvc" 1632 | version = "0.52.6" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1635 | 1636 | [[package]] 1637 | name = "windows_i686_gnu" 1638 | version = "0.52.6" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1641 | 1642 | [[package]] 1643 | name = "windows_i686_gnullvm" 1644 | version = "0.52.6" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1647 | 1648 | [[package]] 1649 | name = "windows_i686_msvc" 1650 | version = "0.52.6" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1653 | 1654 | [[package]] 1655 | name = "windows_x86_64_gnu" 1656 | version = "0.52.6" 1657 | source = "registry+https://github.com/rust-lang/crates.io-index" 1658 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1659 | 1660 | [[package]] 1661 | name = "windows_x86_64_gnullvm" 1662 | version = "0.52.6" 1663 | source = "registry+https://github.com/rust-lang/crates.io-index" 1664 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1665 | 1666 | [[package]] 1667 | name = "windows_x86_64_msvc" 1668 | version = "0.52.6" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1671 | 1672 | [[package]] 1673 | name = "write16" 1674 | version = "1.0.0" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 1677 | 1678 | [[package]] 1679 | name = "writeable" 1680 | version = "0.5.5" 1681 | source = "registry+https://github.com/rust-lang/crates.io-index" 1682 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 1683 | 1684 | [[package]] 1685 | name = "yoke" 1686 | version = "0.7.5" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 1689 | dependencies = [ 1690 | "serde", 1691 | "stable_deref_trait", 1692 | "yoke-derive", 1693 | "zerofrom", 1694 | ] 1695 | 1696 | [[package]] 1697 | name = "yoke-derive" 1698 | version = "0.7.5" 1699 | source = "registry+https://github.com/rust-lang/crates.io-index" 1700 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 1701 | dependencies = [ 1702 | "proc-macro2", 1703 | "quote", 1704 | "syn", 1705 | "synstructure", 1706 | ] 1707 | 1708 | [[package]] 1709 | name = "zerofrom" 1710 | version = "0.1.5" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 1713 | dependencies = [ 1714 | "zerofrom-derive", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "zerofrom-derive" 1719 | version = "0.1.5" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 1722 | dependencies = [ 1723 | "proc-macro2", 1724 | "quote", 1725 | "syn", 1726 | "synstructure", 1727 | ] 1728 | 1729 | [[package]] 1730 | name = "zeroize" 1731 | version = "1.8.1" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1734 | 1735 | [[package]] 1736 | name = "zerovec" 1737 | version = "0.10.4" 1738 | source = "registry+https://github.com/rust-lang/crates.io-index" 1739 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 1740 | dependencies = [ 1741 | "yoke", 1742 | "zerofrom", 1743 | "zerovec-derive", 1744 | ] 1745 | 1746 | [[package]] 1747 | name = "zerovec-derive" 1748 | version = "0.10.3" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 1751 | dependencies = [ 1752 | "proc-macro2", 1753 | "quote", 1754 | "syn", 1755 | ] 1756 | --------------------------------------------------------------------------------