├── .gitignore ├── Cargo.toml ├── README.md ├── LICENSE-MIT ├── src ├── string.rs ├── feed.rs ├── main.rs └── db.rs ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.session 3 | *.env 4 | *.db 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "srsrssrsbot" 3 | version = "0.1.0" 4 | authors = ["Lonami Exo "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | grammers-client = { path = "../grammers/lib/grammers-client" } 9 | grammers-session = { path = "../grammers/lib/grammers-session" } 10 | reqwest = "0.11.3" 11 | feed-rs = "1.3.0" 12 | chrono = "0.4.19" 13 | tokio = { version = "1.5.0", features = ["full"] } 14 | log = "0.4.14" 15 | simple_logger = "1.11.0" 16 | sqlite = "0.26.0" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # srsrssrsbot 2 | 3 | A **s**e**r**iou**s rss r**u**s**t **bot**. No apologies with respect to the name. 4 | 5 | ## What? 6 | 7 | I use Telegram, and follow several RSS feeds. I want to be notifed in Telegram when those feeds 8 | have something new. I want to be in control of the code that does this. So I wrote my own bot. 9 | 10 | The code is using [grammers](https://github.com/lonami/grammers), a library to interact with 11 | Telegram's client API from Rust. You will need to clone that repository in a sibling directory, 12 | since the code targets the very last version of the library. 13 | 14 | ## That's the best name you can come up with? 15 | 16 | Yes. 17 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lonami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | pub static WELCOME: &str = r#"Hi, I'm srsrssrs, a serious RSS Rust bot. Sorry if it gave you a stroke to read that. 2 | 3 | To get started, /add . If you get tired of the feed, use /rm . You can view what feeds you're subscribed to with /ls."#; 4 | 5 | pub static NO_URL: &str = "You need to include a (valid) URL after the command."; 6 | 7 | pub static NO_FEEDS: &str = "You're not subscribed to any feeds. Here's a good one you could try (wink, wink): https://lonami.dev/blog/atom.xml"; 8 | 9 | pub fn try_add(url: &str) -> String { 10 | format!("Trying to add {}...", url) 11 | } 12 | 13 | pub fn add_ok(url: &str) -> String { 14 | format!("Added {} to your list of feeds.", url) 15 | } 16 | 17 | pub fn add_err(url: &str, e: crate::feed::Error) -> String { 18 | format!("Failed to add {} to your list of feeds: {}.", url, e) 19 | } 20 | 21 | pub fn del_ok(url: &str) -> String { 22 | format!("You will no longer receive updates from {}.", url) 23 | } 24 | 25 | pub fn del_err(url: &str) -> String { 26 | format!("You were not subscribed to {}!", url) 27 | } 28 | 29 | pub fn feed_list(feeds: &[String]) -> String { 30 | if feeds.is_empty() { 31 | return NO_FEEDS.to_string(); 32 | } 33 | 34 | let mut result = "These are your feeds:".to_string(); 35 | feeds.iter().for_each(|feed| { 36 | result.push_str("\n• "); 37 | result.push_str(feed); 38 | }); 39 | result 40 | } 41 | 42 | pub fn new_entry(feed: &feed_rs::model::Entry) -> String { 43 | let title = feed 44 | .title 45 | .as_ref() 46 | .map(|t| t.content.clone()) 47 | .unwrap_or_else(|| "(untitled)".to_string()); 48 | 49 | let url = feed 50 | .links 51 | .iter() 52 | .next() 53 | .map(|link| link.href.clone()) 54 | .unwrap_or_else(|| "(no online url)".to_string()); 55 | 56 | format!("{}\n{}", title, url) 57 | } 58 | -------------------------------------------------------------------------------- /src/feed.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Duration, NaiveDateTime, Utc}; 2 | use grammers_client::types::chat::PackedChat; 3 | use reqwest::{header, StatusCode}; 4 | use std::time::{SystemTime, UNIX_EPOCH}; 5 | use std::{collections::HashSet, fmt}; 6 | use tokio::time::Instant; 7 | 8 | #[derive(Debug)] 9 | pub struct Feed { 10 | pub url: String, 11 | pub users: Vec, 12 | pub seen_entries: HashSet, 13 | pub last_fetch: DateTime, 14 | pub next_fetch: Instant, 15 | pub etag: Option, 16 | } 17 | 18 | #[derive(Debug)] 19 | pub enum Error { 20 | ReadError(reqwest::Error), 21 | ParseError(feed_rs::parser::ParseFeedError), 22 | MalformedHeader(header::HeaderName), 23 | } 24 | 25 | fn header(headers: &header::HeaderMap, key: header::HeaderName) -> Result, Error> { 26 | Ok(match headers.get(&key) { 27 | Some(v) => Some(v.to_str().map_err(|_| Error::MalformedHeader(key))?), 28 | None => None, 29 | }) 30 | } 31 | 32 | fn find_expiry(headers: &header::HeaderMap) -> Result { 33 | // Can't use constants here, `Duration::seconds` is not a const-fn as of 0.4.19. 34 | // 35 | // Maximum cache delay we're willing to accept. 36 | // 37 | // A bad-behaved server might put an absurd amount for the `max-age`, and then we would never 38 | // check that feed again. 39 | let max_fetch_delay: Duration = Duration::seconds(24 * 60 * 60); 40 | 41 | // If the server returns a very small value (or even in the past), use this instead. 42 | let min_fetch_delay: Duration = Duration::seconds(60); 43 | 44 | // If the server does not have any max age or expiration for the feed, use a default delay. 45 | let default_fetch_delay: Duration = Duration::seconds(10 * 60); 46 | 47 | let now = Utc::now(); 48 | let delay = if let Some(cache_control) = header(headers, header::CACHE_CONTROL)? { 49 | let seconds = cache_control.split(",").find_map(|directive| { 50 | let mut parts = directive.split("="); 51 | let key = parts.next()?; 52 | let value = parts.next()?; 53 | if key.to_lowercase() == "max-age" { 54 | Some(value) 55 | } else { 56 | None 57 | } 58 | }); 59 | if let Some(seconds) = seconds { 60 | Duration::seconds( 61 | seconds 62 | .parse::() 63 | .map_err(|_| Error::MalformedHeader(header::CACHE_CONTROL))?, 64 | ) 65 | } else { 66 | default_fetch_delay 67 | } 68 | } else if let Some(expiry) = header(headers, header::EXPIRES)? { 69 | let expires = DateTime::parse_from_rfc2822(expiry) 70 | .map(DateTime::::from) 71 | .map_err(|_| Error::MalformedHeader(header::EXPIRES))?; 72 | 73 | expires - now 74 | } else { 75 | default_fetch_delay 76 | }; 77 | 78 | // Can't panic, `max(MIN_FETCH_DELAY)` will make it positive, so `to_std()` succeeds. 79 | Ok(Instant::now() 80 | + delay 81 | .min(max_fetch_delay) 82 | .max(min_fetch_delay) 83 | .to_std() 84 | .unwrap()) 85 | } 86 | 87 | impl Feed { 88 | pub async fn new(http: &reqwest::Client, url: &str, user: PackedChat) -> Result { 89 | let resp = http.get(url).send().await?.error_for_status()?; 90 | let last_fetch = Utc::now(); 91 | let next_fetch = find_expiry(resp.headers())?; 92 | let etag = header(resp.headers(), header::ETAG)?.map(String::from); 93 | let xml = resp.bytes().await?; 94 | 95 | let feed = feed_rs::parser::parse(xml.as_ref())?; 96 | let seen_entries = feed 97 | .entries 98 | .into_iter() 99 | .map(|entry| entry.id) 100 | .collect::>(); 101 | 102 | Ok(Self { 103 | url: url.to_string(), 104 | users: vec![user], 105 | seen_entries, 106 | last_fetch, 107 | next_fetch, 108 | etag, 109 | }) 110 | } 111 | 112 | pub async fn check( 113 | &mut self, 114 | http: &reqwest::Client, 115 | ) -> Result, Error> { 116 | let mut request = http 117 | .get(&self.url) 118 | .header(header::IF_MODIFIED_SINCE, self.last_fetch.to_rfc2822()); 119 | 120 | if let Some(etag) = self.etag.as_ref() { 121 | request = request.header(header::IF_NONE_MATCH, etag); 122 | } 123 | 124 | let resp = request.send().await?.error_for_status()?; 125 | let expiry = find_expiry(resp.headers()); 126 | let entries = if resp.status().as_u16() == StatusCode::NOT_MODIFIED { 127 | Vec::new() 128 | } else { 129 | let xml = resp.bytes().await?; 130 | let mut feed = feed_rs::parser::parse(xml.as_ref())?; 131 | feed.entries 132 | .retain(|entry| !self.seen_entries.contains(&entry.id)); 133 | feed.entries 134 | }; 135 | 136 | self.last_fetch = Utc::now(); 137 | match expiry { 138 | Ok(expiry) => self.next_fetch = expiry, 139 | Err(_) => self.reset_expiry(), 140 | }; 141 | Ok(entries) 142 | } 143 | 144 | pub fn reset_entries(&mut self, entries: &[feed_rs::model::Entry]) { 145 | let clear_entries = entries 146 | .iter() 147 | .map(|entry| &entry.id) 148 | .collect::>(); 149 | self.last_fetch = DateTime::::from_utc(NaiveDateTime::from_timestamp_opt(0, 0).unwrap(), Utc); 150 | self.etag = None; 151 | self.seen_entries 152 | .retain(|entry| !clear_entries.contains(&entry)); 153 | } 154 | 155 | pub fn reset_expiry(&mut self) { 156 | self.next_fetch = Instant::now() + Duration::seconds(10 * 60).to_std().unwrap(); 157 | } 158 | 159 | pub fn next_fetch_timestamp(&self) -> i64 { 160 | let duration = self 161 | .next_fetch 162 | .checked_duration_since(Instant::now()) 163 | .unwrap_or_else(|| std::time::Duration::from_secs(0)); 164 | 165 | (SystemTime::now() + duration) 166 | .duration_since(UNIX_EPOCH) 167 | .unwrap() 168 | .as_secs() as i64 169 | } 170 | } 171 | 172 | impl PartialEq for Feed { 173 | fn eq(&self, other: &Self) -> bool { 174 | self.next_fetch == other.next_fetch && self.url == other.url 175 | } 176 | } 177 | 178 | impl Eq for Feed {} 179 | 180 | impl PartialOrd for Feed { 181 | fn partial_cmp(&self, other: &Self) -> Option { 182 | Some(self.cmp(other)) 183 | } 184 | } 185 | 186 | impl Ord for Feed { 187 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 188 | self.next_fetch 189 | .cmp(&other.next_fetch) 190 | .then_with(|| self.url.cmp(&other.url)) 191 | } 192 | } 193 | 194 | impl From for Error { 195 | fn from(e: reqwest::Error) -> Self { 196 | Self::ReadError(e) 197 | } 198 | } 199 | 200 | impl From for Error { 201 | fn from(e: feed_rs::parser::ParseFeedError) -> Self { 202 | Self::ParseError(e) 203 | } 204 | } 205 | 206 | impl fmt::Display for Error { 207 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 208 | match self { 209 | Self::ReadError(e) => write!(f, "network error: {}", e), 210 | Self::ParseError(e) => write!(f, "error parsing feed: {}", e), 211 | Self::MalformedHeader(e) => write!(f, "error parsing header {}", e), 212 | } 213 | } 214 | } 215 | 216 | impl std::error::Error for Error {} 217 | 218 | #[cfg(test)] 219 | mod tests { 220 | use super::*; 221 | 222 | // Fetch an old feed and then its updated variant to figure out how "new entries" works. 223 | static OLD_FEED: &str = env!("OLD_FEED"); 224 | static NEW_FEED: &str = env!("NEW_FEED"); 225 | 226 | #[test] 227 | fn check_feed_fetch_works() -> Result<(), Error> { 228 | let rt = tokio::runtime::Runtime::new().unwrap(); 229 | rt.block_on(async { 230 | let http = reqwest::Client::new(); 231 | let mut feed = Feed::new( 232 | &http, 233 | OLD_FEED, 234 | PackedChat::from_bytes(&[2, 6, 0, 0, 0, 0]).unwrap(), 235 | ) 236 | .await?; 237 | feed.url = NEW_FEED.to_string(); 238 | assert!(!feed.check(&http).await?.is_empty()); 239 | Ok(()) 240 | }) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod db; 2 | mod feed; 3 | mod string; 4 | 5 | use grammers_client::client::chats::InvocationError; 6 | use grammers_client::types::{Chat, Message}; 7 | use grammers_client::{Client, Config, Update}; 8 | use grammers_session::Session; 9 | use log::{self, info, warn}; 10 | use simple_logger::SimpleLogger; 11 | use std::time::Duration; 12 | use tokio::time::sleep; 13 | 14 | /// How long to sleep before attempting to check which feeds we need to refetch. 15 | const FETCH_FEEDS_DELAY: Duration = Duration::from_secs(60); 16 | 17 | static LOG_LEVEL: &str = env!("LOG_LEVEL"); 18 | 19 | // Values required by Telegram. 20 | static TG_API_ID: &str = env!("TG_API_ID"); 21 | static TG_API_HASH: &str = env!("TG_API_HASH"); 22 | static BOT_TOKEN: &str = env!("BOT_TOKEN"); 23 | 24 | static DB_NAME: &str = "srsrssrs.db"; 25 | static SESSION_NAME: &str = "srsrssrs.session"; 26 | 27 | type Result = std::result::Result>; 28 | 29 | fn parse_url(url: Option<&str>) -> Option<&str> { 30 | let url = match url { 31 | Some(url) => url, 32 | None => return None, 33 | }; 34 | 35 | let lower = url.to_lowercase(); 36 | if !lower.starts_with("http://") && !lower.starts_with("https://") { 37 | return None; 38 | } 39 | 40 | // Quick sanity check. Yes there are ways around this. But this should prevent dumb attempts. 41 | // More funny numbers https://daniel.haxx.se/blog/2021/04/19/curl-those-funny-ipv4-addresses/. 42 | if lower.starts_with("http://localhost") || lower.starts_with("http://127.0.0.1") { 43 | return None; 44 | } 45 | 46 | let mut end = url.len(); 47 | end = end.min(url.find('#').unwrap_or(end)); 48 | end = end.min(url.find('?').unwrap_or(end)); 49 | Some(&url[..end]) 50 | } 51 | 52 | async fn handle_updates(mut tg: Client, db: &db::Database) -> Result<()> { 53 | let http = reqwest::Client::new(); 54 | 55 | while let Some(update) = tg.next_update().await? { 56 | match update { 57 | Update::NewMessage(message) 58 | if !message.outgoing() && matches!(message.chat(), Chat::User(_)) => 59 | { 60 | match handle_message(&mut tg, &http, &db, &message).await { 61 | Ok(_) => {} 62 | Err(err) => match err.downcast::() { 63 | Ok(err) => match *err { 64 | InvocationError::Rpc(rpc) if rpc.name == "USER_IS_BLOCKED" => {} 65 | InvocationError::Rpc(rpc) => { 66 | info!("failed to react in {}: {}", message.chat().pack(), rpc) 67 | } 68 | _ => warn!("failed to react in {}: {}", message.chat().pack(), err), 69 | }, 70 | Err(err) => return Err(err), 71 | }, 72 | }; 73 | } 74 | _ => {} 75 | }; 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | async fn handle_message( 82 | tg: &mut Client, 83 | http: &reqwest::Client, 84 | db: &db::Database, 85 | message: &Message, 86 | ) -> Result<()> { 87 | let cmd = match message.text().split_whitespace().next() { 88 | Some(cmd) => cmd, 89 | None => return Ok(()), 90 | }; 91 | 92 | if cmd == "/start" || cmd == "/help" { 93 | tg.send_message(&message.chat(), string::WELCOME) 94 | .await?; 95 | } else if cmd == "/add" { 96 | if let Some(url) = parse_url(message.text().split_whitespace().nth(1)) { 97 | let sent = tg 98 | .send_message(&message.chat(), string::try_add(url)) 99 | .await?; 100 | 101 | let user = message.sender().unwrap().pack(); 102 | let err = if db.try_add_subscriber(url, &user)? { 103 | None 104 | } else { 105 | match feed::Feed::new(&http, url, user).await { 106 | Ok(feed) => { 107 | db.add_feed(&feed)?; 108 | None 109 | } 110 | Err(e) => Some(e), 111 | } 112 | }; 113 | 114 | if let Some(err) = err { 115 | sent.edit(string::add_err(url, err)).await?; 116 | } else { 117 | sent.edit(string::add_ok(url)).await?; 118 | } 119 | } else { 120 | tg.send_message(&message.chat(), string::NO_URL) 121 | .await?; 122 | } 123 | } else if cmd == "/rm" || cmd == "/del" { 124 | let msg = if let Some(url) = parse_url(message.text().split_whitespace().nth(1)) { 125 | let user = message.sender().unwrap().pack(); 126 | if db.try_del_subscriber(url, &user)? { 127 | string::del_ok(url) 128 | } else { 129 | string::del_err(url) 130 | } 131 | } else { 132 | string::NO_URL.to_string() 133 | }; 134 | 135 | tg.send_message(&message.chat(), msg).await?; 136 | } else if cmd == "/ls" || cmd == "/list" { 137 | let feeds = db.get_user_feeds(&message.sender().unwrap().pack())?; 138 | 139 | tg.send_message(&message.chat(), string::feed_list(&feeds)) 140 | .await?; 141 | } 142 | 143 | Ok(()) 144 | } 145 | 146 | async fn handle_feed(tg: Client, db: &db::Database) -> Result<()> { 147 | let http = reqwest::Client::new(); 148 | let mut last_save_failed = false; 149 | 150 | loop { 151 | let feeds = db.load_pending_feeds()?; 152 | let mut updated_feeds = Vec::with_capacity(feeds.len()); 153 | 154 | for mut feed in feeds { 155 | let entries = match feed.check(&http).await { 156 | Ok(entries) => entries, 157 | Err(err) => { 158 | warn!("failed to fetch {}: {}", feed.url, err); 159 | feed.reset_expiry(); 160 | updated_feeds.push(feed); 161 | continue; 162 | } 163 | }; 164 | 165 | for entry in entries.iter() { 166 | let mut fail_count = 0; 167 | for user in feed.users.iter() { 168 | match tg 169 | .send_message(*user, string::new_entry(entry)) 170 | .await 171 | { 172 | Ok(_) => {} 173 | Err(InvocationError::Rpc(rpc)) if rpc.name == "USER_IS_BLOCKED" => {} 174 | Err(InvocationError::Rpc(rpc)) => { 175 | fail_count += 1; 176 | info!( 177 | "failed to notify {} about {}/{}: {}", 178 | user, feed.url, entry.id, rpc 179 | ); 180 | } 181 | Err(err) => { 182 | fail_count += 1; 183 | warn!( 184 | "failed to notify {} about {}/{}: {}", 185 | user, feed.url, entry.id, err 186 | ); 187 | } 188 | }; 189 | } 190 | 191 | if fail_count == feed.users.len() { 192 | warn!( 193 | "failed to notify all {} users about {}/{}", 194 | feed.users.len(), 195 | feed.url, 196 | entry.id 197 | ); 198 | feed.reset_entries(&entries); 199 | break; 200 | } 201 | } 202 | 203 | updated_feeds.push(feed); 204 | } 205 | 206 | match db.update_feeds_and_entries(&updated_feeds) { 207 | Ok(_) => last_save_failed = false, 208 | Err(e) => { 209 | warn!("failed to store updated feeds: {}", e); 210 | if last_save_failed { 211 | panic!("failed to store updated feeds twice in a row"); 212 | } else { 213 | last_save_failed = true; 214 | } 215 | } 216 | } 217 | sleep(FETCH_FEEDS_DELAY).await; 218 | } 219 | } 220 | 221 | #[tokio::main] 222 | async fn main() -> Result<()> { 223 | let db = db::Database::new(DB_NAME)?; 224 | db.cleanup_feeds()?; 225 | 226 | SimpleLogger::new() 227 | .with_level(match LOG_LEVEL { 228 | "ERROR" => log::LevelFilter::Error, 229 | "WARN" => log::LevelFilter::Warn, 230 | "INFO" => log::LevelFilter::Info, 231 | "DEBUG" => log::LevelFilter::Debug, 232 | "TRACE" => log::LevelFilter::Trace, 233 | _ => log::LevelFilter::Off, 234 | }) 235 | .init()?; 236 | 237 | let api_id = TG_API_ID.parse()?; 238 | let client = Client::connect(Config { 239 | session: Session::load_file_or_create(SESSION_NAME)?, 240 | api_id, 241 | api_hash: TG_API_HASH.to_string(), 242 | params: Default::default(), 243 | }) 244 | .await?; 245 | 246 | if !client.is_authorized().await? { 247 | client.bot_sign_in(BOT_TOKEN, api_id, TG_API_HASH).await?; 248 | client.session().save_to_file(SESSION_NAME)?; 249 | } 250 | 251 | tokio::select!( 252 | _ = tokio::signal::ctrl_c() => { 253 | println!("Got SIGINT; quitting early gracefully"); 254 | } 255 | r = handle_updates(client.clone(), &db) => { 256 | match r { 257 | Ok(_) => println!("Got disconnected from Telegram gracefully"), 258 | Err(e) => println!("Error during update handling: {}", e), 259 | } 260 | } 261 | _ = handle_feed(client.clone(), &db) => { 262 | println!("Failed to check feed"); 263 | } 264 | ); 265 | 266 | client.session().save_to_file(SESSION_NAME)?; 267 | Ok(()) 268 | } 269 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | use crate::feed::Feed; 2 | use chrono::{TimeZone, Utc}; 3 | use grammers_client::types::chat::PackedChat; 4 | use sqlite::State; 5 | use std::collections::{BinaryHeap, HashMap, HashSet}; 6 | use std::sync::{Arc, Mutex}; 7 | use std::time::Duration; 8 | use tokio::time::Instant; 9 | 10 | const VERSION: i64 = 1; 11 | 12 | #[derive(Clone)] 13 | pub struct Database(Arc>); 14 | 15 | /// Helper macro to avoid the annoying `prepare` statements and `bind`. 16 | /// 17 | /// # Examples 18 | /// 19 | /// Simplest case: 20 | /// 21 | /// ``` 22 | /// query!(conn."BEGIN"); 23 | /// ``` 24 | /// 25 | /// With parameters: 26 | /// 27 | /// ``` 28 | /// query!(conn."DELETE FROM table WHERE column = ?"(value)); 29 | /// ``` 30 | /// 31 | /// Selection: 32 | /// 33 | /// ``` 34 | /// query!(for (field: i64) in conn."SELECT * FROM table WHERE column = ?"(value) { 35 | /// dbg!(field); 36 | /// }); 37 | /// ``` 38 | /// 39 | /// Fetch one: 40 | /// 41 | /// ``` 42 | /// return query!(fetch (field: i64) in conn."SELECT * FROM table WHERE column = ?"(value)).unwrap(); 43 | /// ``` 44 | macro_rules! query { 45 | ($conn:ident . $query:literal) => {{ 46 | $conn.execute($query)?; 47 | }}; 48 | ($conn:ident . $query:literal ($($arg:expr),+)) => {{ 49 | let mut stmt = $conn.prepare($query)?; 50 | let mut i = 1; 51 | $( 52 | stmt.bind(i, $arg)?; 53 | i += 1; 54 | )+ 55 | let _ = i; 56 | while stmt.next()? != State::Done {} 57 | }}; 58 | (for ($($bind:ident : $ty:ty),+) in $conn:ident . $query:literal ($($arg:expr),*) $body:block) => {{ 59 | let mut stmt = $conn.prepare($query)?; 60 | let mut i = 1; 61 | $( 62 | stmt.bind(i, $arg)?; 63 | i += 1; 64 | )* 65 | let _ = i; 66 | while stmt.next()? == State::Row { 67 | i = 0; 68 | $( 69 | let $bind = stmt.read::<$ty>(i)?; 70 | i += 1; 71 | )+ 72 | let _ = i; 73 | $body 74 | } 75 | }}; 76 | (fetch ($($bind:ident : $ty:ty),+) in $conn:ident . $query:literal ($($arg:expr),*)) => {{ 77 | let mut stmt = $conn.prepare($query)?; 78 | let mut i = 1; 79 | $( 80 | stmt.bind(i, $arg)?; 81 | i += 1; 82 | )* 83 | let _ = i; 84 | if stmt.next()? == State::Row { 85 | i = 0; 86 | $( 87 | let $bind = stmt.read::<$ty>(i)?; 88 | i += 1; 89 | )+ 90 | let _ = i; 91 | Some(($($bind),+)) 92 | } else { 93 | None 94 | } 95 | }}; 96 | } 97 | 98 | impl Database { 99 | pub fn new(name: &str) -> sqlite::Result { 100 | let conn = sqlite::open(name)?; 101 | query!(conn."PRAGMA foreign_keys = ON"); 102 | 103 | let version = match conn.prepare("SELECT version FROM version") { 104 | Ok(mut stmt) => { 105 | assert_eq!(State::Row, stmt.next()?); 106 | stmt.read(0)? 107 | } 108 | Err(err) => { 109 | if err 110 | .message 111 | .as_ref() 112 | .filter(|m| m.starts_with("no such table")) 113 | .is_some() 114 | { 115 | 0 116 | } else { 117 | return Err(err); 118 | } 119 | } 120 | }; 121 | 122 | assert!( 123 | version <= VERSION, 124 | "tried to load a database which is too new" 125 | ); 126 | 127 | if version == VERSION { 128 | return Ok(Self(Arc::new(Mutex::new(conn)))); 129 | } 130 | 131 | query!(conn."BEGIN"); 132 | query!(conn. 133 | "CREATE TABLE version ( 134 | version INTEGER NOT NULL)" 135 | ); 136 | query!(conn."INSERT INTO version (version) VALUES (?)"(VERSION)); 137 | query!(conn. 138 | "CREATE TABLE feed ( 139 | id INTEGER PRIMARY KEY, 140 | url TEXT NOT NULL UNIQUE ON CONFLICT REPLACE, 141 | last_check INTEGER NOT NULL, 142 | next_check INTEGER NOT NULL, 143 | etag TEXT)" 144 | ); 145 | query!(conn. 146 | "CREATE TABLE entry ( 147 | feed_id INTEGER NOT NULL REFERENCES feed (id) ON DELETE CASCADE, 148 | entry_id TEXT NOT NULL, 149 | CONSTRAINT non_dup_entries_con UNIQUE (feed_id, entry_id) ON CONFLICT IGNORE)" 150 | ); 151 | query!(conn. 152 | "CREATE TABLE subscriber ( 153 | feed_id INTEGER NOT NULL REFERENCES feed (id) ON DELETE CASCADE, 154 | user NOT NULL, 155 | CONSTRAINT one_sub_per_feed_con UNIQUE (feed_id, user) ON CONFLICT IGNORE)" 156 | ); 157 | query!(conn."COMMIT"); 158 | Ok(Self(Arc::new(Mutex::new(conn)))) 159 | } 160 | 161 | pub fn add_feed(&self, feed: &Feed) -> sqlite::Result<()> { 162 | let conn = self.0.lock().unwrap(); 163 | query!(conn."BEGIN"); 164 | { 165 | query!(conn."INSERT INTO feed (url, last_check, next_check, etag) VALUES (?, ?, ?, ?)"( 166 | feed.url.as_str(), feed.last_fetch.timestamp(), feed.next_fetch_timestamp(), feed.etag.as_deref() 167 | )); 168 | let feed_id = query!(fetch (id: i64) in conn."SELECT last_insert_rowid()"()).unwrap(); 169 | 170 | for entry_id in feed.seen_entries.iter() { 171 | query!(conn."INSERT INTO entry (feed_id, entry_id) VALUES (?, ?)"(feed_id, entry_id.as_str())); 172 | } 173 | for sub in feed.users.iter() { 174 | query!(conn."INSERT INTO subscriber (feed_id, user) VALUES (?, ?)"( 175 | feed_id, sub.to_bytes().as_slice() 176 | )); 177 | } 178 | } 179 | query!(conn."COMMIT"); 180 | Ok(()) 181 | } 182 | 183 | pub fn update_feeds_and_entries(&self, feeds: &[Feed]) -> sqlite::Result<()> { 184 | let conn = self.0.lock().unwrap(); 185 | query!(conn."BEGIN"); 186 | for feed in feeds { 187 | let feed_id = match query!(fetch (id: i64) in conn."SELECT id FROM feed WHERE url = ?"(feed.url.as_str())) 188 | { 189 | Some(id) => id, 190 | None => continue, 191 | }; 192 | query!(conn."UPDATE feed SET last_check = ?, next_check = ?, etag = ? WHERE id = ?"( 193 | feed.last_fetch.timestamp(), feed.next_fetch_timestamp(), feed.etag.as_deref(), feed_id 194 | )); 195 | for entry_id in feed.seen_entries.iter() { 196 | query!(conn."INSERT INTO entry (feed_id, entry_id) VALUES (?, ?)"(feed_id, entry_id.as_str())); 197 | } 198 | } 199 | query!(conn."COMMIT"); 200 | Ok(()) 201 | } 202 | 203 | pub fn cleanup_feeds(&self) -> sqlite::Result<()> { 204 | let conn = self.0.lock().unwrap(); 205 | query!(conn."DELETE FROM feed AS f WHERE NOT EXISTS ( 206 | SELECT * FROM subscriber AS s WHERE s.feed_id = f.id)"); 207 | Ok(()) 208 | } 209 | 210 | pub fn load_pending_feeds(&self) -> sqlite::Result> { 211 | let conn = self.0.lock().unwrap(); 212 | let mut feeds = HashMap::::new(); 213 | let now = Utc::now().timestamp(); 214 | 215 | query!(for (id: i64, url: String, last_check: i64, next_fetch: i64, etag: Option) 216 | in conn."SELECT id, url, last_check, next_check, etag FROM feed WHERE next_check < ?"(now) { 217 | feeds.entry(id).or_insert_with(|| Feed { 218 | url, 219 | users: Vec::new(), 220 | seen_entries: HashSet::new(), 221 | last_fetch: Utc.timestamp_opt(last_check, 0).unwrap(), 222 | next_fetch: { 223 | let now = Utc::now().timestamp(); 224 | let delta = next_fetch - now; 225 | if delta < 0 { 226 | Instant::now() - Duration::from_secs(-delta as u64) 227 | } else { 228 | Instant::now() + Duration::from_secs(delta as u64) 229 | } 230 | }, 231 | etag, 232 | }); 233 | }); 234 | 235 | query!(for (id: i64, entry: String) 236 | in conn."SELECT id, entry_id FROM feed JOIN entry ON (id = feed_id) WHERE next_check < ?"(now) { 237 | if let Some(feed) = feeds.get_mut(&id) { 238 | feed.seen_entries.insert(entry); 239 | } 240 | }); 241 | 242 | query!(for (id: i64, user: Vec) 243 | in conn."SELECT id, user FROM feed JOIN subscriber ON (id = feed_id) WHERE next_check < ?"(now) { 244 | if let Some(feed) = feeds.get_mut(&id) { 245 | feed.users 246 | .push(PackedChat::from_bytes(&user).unwrap()); 247 | } 248 | }); 249 | 250 | Ok(feeds.into_iter().map(|(_, v)| v).collect()) 251 | } 252 | 253 | pub fn try_add_subscriber(&self, url: &str, user: &PackedChat) -> sqlite::Result { 254 | let conn = self.0.lock().unwrap(); 255 | if let Some(feed_id) = 256 | query!(fetch (id: i64) in conn."SELECT id FROM feed WHERE url = ?"(url)) 257 | { 258 | query!(conn."INSERT INTO subscriber (feed_id, user) VALUES (?, ?)"(feed_id, user.to_bytes().as_slice())); 259 | Ok(true) 260 | } else { 261 | Ok(false) 262 | } 263 | } 264 | 265 | pub fn try_del_subscriber(&self, url: &str, user: &PackedChat) -> sqlite::Result { 266 | let conn = self.0.lock().unwrap(); 267 | query!(conn."DELETE FROM subscriber WHERE user = ? AND feed_id = ( 268 | SELECT id FROM feed WHERE url = ? 269 | )"(user.to_bytes().as_slice(), url)); 270 | if let Some(count) = query!(fetch (count: i64) in conn."SELECT changes()"()) { 271 | Ok(count == 1) 272 | } else { 273 | Ok(false) 274 | } 275 | } 276 | 277 | pub fn get_user_feeds(&self, user: &PackedChat) -> sqlite::Result> { 278 | let conn = self.0.lock().unwrap(); 279 | let mut result = Vec::new(); 280 | query!(for (url: String) 281 | in conn."SELECT url FROM feed AS f 282 | JOIN subscriber AS s ON (f.id = s.feed_id) 283 | WHERE s.user = ?"(user.to_bytes().as_slice()) { 284 | result.push(url); 285 | }); 286 | Ok(result) 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aes" 13 | version = "0.7.5" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 16 | dependencies = [ 17 | "cfg-if", 18 | "cipher", 19 | "cpufeatures", 20 | "opaque-debug", 21 | ] 22 | 23 | [[package]] 24 | name = "aho-corasick" 25 | version = "1.0.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" 28 | dependencies = [ 29 | "memchr", 30 | ] 31 | 32 | [[package]] 33 | name = "android-tzdata" 34 | version = "0.1.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 37 | 38 | [[package]] 39 | name = "android_system_properties" 40 | version = "0.1.5" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 43 | dependencies = [ 44 | "libc", 45 | ] 46 | 47 | [[package]] 48 | name = "atty" 49 | version = "0.2.14" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 52 | dependencies = [ 53 | "hermit-abi", 54 | "libc", 55 | "winapi", 56 | ] 57 | 58 | [[package]] 59 | name = "autocfg" 60 | version = "1.0.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 63 | 64 | [[package]] 65 | name = "base64" 66 | version = "0.13.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 69 | 70 | [[package]] 71 | name = "base64ct" 72 | version = "1.0.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "d0d27fb6b6f1e43147af148af49d49329413ba781aa0d5e10979831c210173b5" 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "1.2.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 81 | 82 | [[package]] 83 | name = "block-buffer" 84 | version = "0.9.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 87 | dependencies = [ 88 | "generic-array", 89 | ] 90 | 91 | [[package]] 92 | name = "bumpalo" 93 | version = "3.6.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" 96 | 97 | [[package]] 98 | name = "bytes" 99 | version = "1.0.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 102 | 103 | [[package]] 104 | name = "cc" 105 | version = "1.0.79" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 108 | 109 | [[package]] 110 | name = "cfg-if" 111 | version = "1.0.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 114 | 115 | [[package]] 116 | name = "chrono" 117 | version = "0.4.26" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" 120 | dependencies = [ 121 | "android-tzdata", 122 | "iana-time-zone", 123 | "js-sys", 124 | "num-traits", 125 | "time", 126 | "wasm-bindgen", 127 | "winapi", 128 | ] 129 | 130 | [[package]] 131 | name = "cipher" 132 | version = "0.3.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 135 | dependencies = [ 136 | "generic-array", 137 | ] 138 | 139 | [[package]] 140 | name = "colored" 141 | version = "1.9.3" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" 144 | dependencies = [ 145 | "atty", 146 | "lazy_static", 147 | "winapi", 148 | ] 149 | 150 | [[package]] 151 | name = "core-foundation" 152 | version = "0.9.1" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" 155 | dependencies = [ 156 | "core-foundation-sys", 157 | "libc", 158 | ] 159 | 160 | [[package]] 161 | name = "core-foundation-sys" 162 | version = "0.8.4" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 165 | 166 | [[package]] 167 | name = "core2" 168 | version = "0.4.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 171 | dependencies = [ 172 | "memchr", 173 | ] 174 | 175 | [[package]] 176 | name = "cpufeatures" 177 | version = "0.2.8" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" 180 | dependencies = [ 181 | "libc", 182 | ] 183 | 184 | [[package]] 185 | name = "cpuid-bool" 186 | version = "0.1.2" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 189 | 190 | [[package]] 191 | name = "crc32fast" 192 | version = "1.2.1" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 195 | dependencies = [ 196 | "cfg-if", 197 | ] 198 | 199 | [[package]] 200 | name = "crypto-mac" 201 | version = "0.11.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" 204 | dependencies = [ 205 | "generic-array", 206 | "subtle", 207 | ] 208 | 209 | [[package]] 210 | name = "digest" 211 | version = "0.9.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 214 | dependencies = [ 215 | "generic-array", 216 | ] 217 | 218 | [[package]] 219 | name = "encoding_rs" 220 | version = "0.8.28" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" 223 | dependencies = [ 224 | "cfg-if", 225 | ] 226 | 227 | [[package]] 228 | name = "feed-rs" 229 | version = "1.3.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "9dbec361cb401c1b86aea784fb809073733da06b1a1fd794222e7bf9845db327" 232 | dependencies = [ 233 | "chrono", 234 | "lazy_static", 235 | "mime", 236 | "quick-xml", 237 | "regex", 238 | "serde", 239 | "serde_json", 240 | "siphasher", 241 | "url", 242 | "uuid", 243 | ] 244 | 245 | [[package]] 246 | name = "flate2" 247 | version = "1.0.20" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" 250 | dependencies = [ 251 | "cfg-if", 252 | "crc32fast", 253 | "libc", 254 | "miniz_oxide", 255 | ] 256 | 257 | [[package]] 258 | name = "fnv" 259 | version = "1.0.7" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 262 | 263 | [[package]] 264 | name = "foreign-types" 265 | version = "0.3.2" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 268 | dependencies = [ 269 | "foreign-types-shared", 270 | ] 271 | 272 | [[package]] 273 | name = "foreign-types-shared" 274 | version = "0.1.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 277 | 278 | [[package]] 279 | name = "form_urlencoded" 280 | version = "1.2.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 283 | dependencies = [ 284 | "percent-encoding", 285 | ] 286 | 287 | [[package]] 288 | name = "futures-channel" 289 | version = "0.3.14" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" 292 | dependencies = [ 293 | "futures-core", 294 | ] 295 | 296 | [[package]] 297 | name = "futures-core" 298 | version = "0.3.28" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 301 | 302 | [[package]] 303 | name = "futures-sink" 304 | version = "0.3.14" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" 307 | 308 | [[package]] 309 | name = "futures-task" 310 | version = "0.3.28" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 313 | 314 | [[package]] 315 | name = "futures-util" 316 | version = "0.3.28" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 319 | dependencies = [ 320 | "futures-core", 321 | "futures-task", 322 | "pin-project-lite", 323 | "pin-utils", 324 | ] 325 | 326 | [[package]] 327 | name = "generic-array" 328 | version = "0.14.4" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 331 | dependencies = [ 332 | "typenum", 333 | "version_check", 334 | ] 335 | 336 | [[package]] 337 | name = "getrandom" 338 | version = "0.2.2" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 341 | dependencies = [ 342 | "cfg-if", 343 | "libc", 344 | "wasi", 345 | ] 346 | 347 | [[package]] 348 | name = "glass_pumpkin" 349 | version = "1.6.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "b9e847fe780e2fd8aa993bef2124361c285349ff0e9315e8285f8126386b54a9" 352 | dependencies = [ 353 | "core2", 354 | "num-bigint", 355 | "num-integer", 356 | "num-traits", 357 | "once_cell", 358 | "rand_core", 359 | ] 360 | 361 | [[package]] 362 | name = "grammers-client" 363 | version = "0.4.0" 364 | source = "git+https://github.com/Lonami/grammers.git?rev=d91dc82#d91dc82f744b04fe846948467eff17852a15762e" 365 | dependencies = [ 366 | "chrono", 367 | "futures-util", 368 | "grammers-crypto", 369 | "grammers-mtproto", 370 | "grammers-mtsender", 371 | "grammers-session", 372 | "grammers-tl-types", 373 | "locate-locale", 374 | "log", 375 | "md5", 376 | "mime_guess", 377 | "os_info", 378 | "pin-project-lite", 379 | "tokio", 380 | ] 381 | 382 | [[package]] 383 | name = "grammers-crypto" 384 | version = "0.4.0" 385 | source = "git+https://github.com/Lonami/grammers.git?rev=d91dc82#d91dc82f744b04fe846948467eff17852a15762e" 386 | dependencies = [ 387 | "aes", 388 | "getrandom", 389 | "glass_pumpkin", 390 | "hmac", 391 | "num-bigint", 392 | "pbkdf2", 393 | "sha1", 394 | "sha2", 395 | ] 396 | 397 | [[package]] 398 | name = "grammers-mtproto" 399 | version = "0.4.0" 400 | source = "git+https://github.com/Lonami/grammers.git?rev=d91dc82#d91dc82f744b04fe846948467eff17852a15762e" 401 | dependencies = [ 402 | "bytes", 403 | "crc32fast", 404 | "flate2", 405 | "getrandom", 406 | "grammers-crypto", 407 | "grammers-tl-types", 408 | "log", 409 | "num-bigint", 410 | "sha1", 411 | ] 412 | 413 | [[package]] 414 | name = "grammers-mtsender" 415 | version = "0.4.0" 416 | source = "git+https://github.com/Lonami/grammers.git?rev=d91dc82#d91dc82f744b04fe846948467eff17852a15762e" 417 | dependencies = [ 418 | "bytes", 419 | "futures-util", 420 | "grammers-mtproto", 421 | "grammers-tl-types", 422 | "log", 423 | "tokio", 424 | ] 425 | 426 | [[package]] 427 | name = "grammers-session" 428 | version = "0.4.1" 429 | source = "git+https://github.com/Lonami/grammers.git?rev=d91dc82#d91dc82f744b04fe846948467eff17852a15762e" 430 | dependencies = [ 431 | "grammers-tl-gen", 432 | "grammers-tl-parser", 433 | "grammers-tl-types", 434 | "log", 435 | ] 436 | 437 | [[package]] 438 | name = "grammers-tl-gen" 439 | version = "0.4.1" 440 | source = "git+https://github.com/Lonami/grammers.git?rev=d91dc82#d91dc82f744b04fe846948467eff17852a15762e" 441 | dependencies = [ 442 | "grammers-tl-parser", 443 | ] 444 | 445 | [[package]] 446 | name = "grammers-tl-parser" 447 | version = "1.1.0" 448 | source = "git+https://github.com/Lonami/grammers.git?rev=d91dc82#d91dc82f744b04fe846948467eff17852a15762e" 449 | dependencies = [ 450 | "crc32fast", 451 | ] 452 | 453 | [[package]] 454 | name = "grammers-tl-types" 455 | version = "0.4.0" 456 | source = "git+https://github.com/Lonami/grammers.git?rev=d91dc82#d91dc82f744b04fe846948467eff17852a15762e" 457 | dependencies = [ 458 | "grammers-tl-gen", 459 | "grammers-tl-parser", 460 | ] 461 | 462 | [[package]] 463 | name = "h2" 464 | version = "0.3.3" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" 467 | dependencies = [ 468 | "bytes", 469 | "fnv", 470 | "futures-core", 471 | "futures-sink", 472 | "futures-util", 473 | "http", 474 | "indexmap", 475 | "slab", 476 | "tokio", 477 | "tokio-util", 478 | "tracing", 479 | ] 480 | 481 | [[package]] 482 | name = "hashbrown" 483 | version = "0.9.1" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 486 | 487 | [[package]] 488 | name = "hermit-abi" 489 | version = "0.1.18" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 492 | dependencies = [ 493 | "libc", 494 | ] 495 | 496 | [[package]] 497 | name = "hmac" 498 | version = "0.11.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" 501 | dependencies = [ 502 | "crypto-mac", 503 | "digest", 504 | ] 505 | 506 | [[package]] 507 | name = "http" 508 | version = "0.2.4" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" 511 | dependencies = [ 512 | "bytes", 513 | "fnv", 514 | "itoa", 515 | ] 516 | 517 | [[package]] 518 | name = "http-body" 519 | version = "0.4.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "5dfb77c123b4e2f72a2069aeae0b4b4949cc7e966df277813fc16347e7549737" 522 | dependencies = [ 523 | "bytes", 524 | "http", 525 | "pin-project-lite", 526 | ] 527 | 528 | [[package]] 529 | name = "httparse" 530 | version = "1.4.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437" 533 | 534 | [[package]] 535 | name = "httpdate" 536 | version = "1.0.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "05842d0d43232b23ccb7060ecb0f0626922c21f30012e97b767b30afd4a5d4b9" 539 | 540 | [[package]] 541 | name = "hyper" 542 | version = "0.14.7" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "1e5f105c494081baa3bf9e200b279e27ec1623895cd504c7dbef8d0b080fcf54" 545 | dependencies = [ 546 | "bytes", 547 | "futures-channel", 548 | "futures-core", 549 | "futures-util", 550 | "h2", 551 | "http", 552 | "http-body", 553 | "httparse", 554 | "httpdate", 555 | "itoa", 556 | "pin-project", 557 | "socket2", 558 | "tokio", 559 | "tower-service", 560 | "tracing", 561 | "want", 562 | ] 563 | 564 | [[package]] 565 | name = "hyper-tls" 566 | version = "0.5.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 569 | dependencies = [ 570 | "bytes", 571 | "hyper", 572 | "native-tls", 573 | "tokio", 574 | "tokio-native-tls", 575 | ] 576 | 577 | [[package]] 578 | name = "iana-time-zone" 579 | version = "0.1.57" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" 582 | dependencies = [ 583 | "android_system_properties", 584 | "core-foundation-sys", 585 | "iana-time-zone-haiku", 586 | "js-sys", 587 | "wasm-bindgen", 588 | "windows", 589 | ] 590 | 591 | [[package]] 592 | name = "iana-time-zone-haiku" 593 | version = "0.1.2" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 596 | dependencies = [ 597 | "cc", 598 | ] 599 | 600 | [[package]] 601 | name = "idna" 602 | version = "0.4.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 605 | dependencies = [ 606 | "unicode-bidi", 607 | "unicode-normalization", 608 | ] 609 | 610 | [[package]] 611 | name = "indexmap" 612 | version = "1.6.2" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" 615 | dependencies = [ 616 | "autocfg", 617 | "hashbrown", 618 | ] 619 | 620 | [[package]] 621 | name = "instant" 622 | version = "0.1.9" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 625 | dependencies = [ 626 | "cfg-if", 627 | ] 628 | 629 | [[package]] 630 | name = "ipnet" 631 | version = "2.3.0" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" 634 | 635 | [[package]] 636 | name = "itoa" 637 | version = "0.4.7" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 640 | 641 | [[package]] 642 | name = "js-sys" 643 | version = "0.3.50" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" 646 | dependencies = [ 647 | "wasm-bindgen", 648 | ] 649 | 650 | [[package]] 651 | name = "lazy_static" 652 | version = "1.4.0" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 655 | 656 | [[package]] 657 | name = "libc" 658 | version = "0.2.147" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 661 | 662 | [[package]] 663 | name = "locate-locale" 664 | version = "0.2.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "2835eaaed39a92511442aff277d4dca3d7674ca058df3bc45170661c2ccb4619" 667 | dependencies = [ 668 | "winapi", 669 | ] 670 | 671 | [[package]] 672 | name = "lock_api" 673 | version = "0.4.4" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" 676 | dependencies = [ 677 | "scopeguard", 678 | ] 679 | 680 | [[package]] 681 | name = "log" 682 | version = "0.4.14" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 685 | dependencies = [ 686 | "cfg-if", 687 | ] 688 | 689 | [[package]] 690 | name = "md5" 691 | version = "0.7.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 694 | 695 | [[package]] 696 | name = "memchr" 697 | version = "2.5.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 700 | 701 | [[package]] 702 | name = "mime" 703 | version = "0.3.16" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 706 | 707 | [[package]] 708 | name = "mime_guess" 709 | version = "2.0.3" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" 712 | dependencies = [ 713 | "mime", 714 | "unicase", 715 | ] 716 | 717 | [[package]] 718 | name = "miniz_oxide" 719 | version = "0.4.4" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 722 | dependencies = [ 723 | "adler", 724 | "autocfg", 725 | ] 726 | 727 | [[package]] 728 | name = "mio" 729 | version = "0.7.11" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" 732 | dependencies = [ 733 | "libc", 734 | "log", 735 | "miow", 736 | "ntapi", 737 | "winapi", 738 | ] 739 | 740 | [[package]] 741 | name = "miow" 742 | version = "0.3.7" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 745 | dependencies = [ 746 | "winapi", 747 | ] 748 | 749 | [[package]] 750 | name = "native-tls" 751 | version = "0.2.7" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" 754 | dependencies = [ 755 | "lazy_static", 756 | "libc", 757 | "log", 758 | "openssl", 759 | "openssl-probe", 760 | "openssl-sys", 761 | "schannel", 762 | "security-framework", 763 | "security-framework-sys", 764 | "tempfile", 765 | ] 766 | 767 | [[package]] 768 | name = "ntapi" 769 | version = "0.3.6" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 772 | dependencies = [ 773 | "winapi", 774 | ] 775 | 776 | [[package]] 777 | name = "num-bigint" 778 | version = "0.4.0" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" 781 | dependencies = [ 782 | "autocfg", 783 | "num-integer", 784 | "num-traits", 785 | "rand", 786 | ] 787 | 788 | [[package]] 789 | name = "num-integer" 790 | version = "0.1.44" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 793 | dependencies = [ 794 | "autocfg", 795 | "num-traits", 796 | ] 797 | 798 | [[package]] 799 | name = "num-traits" 800 | version = "0.2.14" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 803 | dependencies = [ 804 | "autocfg", 805 | ] 806 | 807 | [[package]] 808 | name = "num_cpus" 809 | version = "1.13.0" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 812 | dependencies = [ 813 | "hermit-abi", 814 | "libc", 815 | ] 816 | 817 | [[package]] 818 | name = "once_cell" 819 | version = "1.18.0" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 822 | 823 | [[package]] 824 | name = "opaque-debug" 825 | version = "0.3.0" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 828 | 829 | [[package]] 830 | name = "openssl" 831 | version = "0.10.55" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" 834 | dependencies = [ 835 | "bitflags", 836 | "cfg-if", 837 | "foreign-types", 838 | "libc", 839 | "once_cell", 840 | "openssl-macros", 841 | "openssl-sys", 842 | ] 843 | 844 | [[package]] 845 | name = "openssl-macros" 846 | version = "0.1.1" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 849 | dependencies = [ 850 | "proc-macro2", 851 | "quote", 852 | "syn 2.0.22", 853 | ] 854 | 855 | [[package]] 856 | name = "openssl-probe" 857 | version = "0.1.2" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 860 | 861 | [[package]] 862 | name = "openssl-src" 863 | version = "111.25.0+1.1.1t" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "3173cd3626c43e3854b1b727422a276e568d9ec5fe8cec197822cf52cfb743d6" 866 | dependencies = [ 867 | "cc", 868 | ] 869 | 870 | [[package]] 871 | name = "openssl-sys" 872 | version = "0.9.90" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" 875 | dependencies = [ 876 | "cc", 877 | "libc", 878 | "openssl-src", 879 | "pkg-config", 880 | "vcpkg", 881 | ] 882 | 883 | [[package]] 884 | name = "os_info" 885 | version = "3.0.4" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "afaa687e89d04fcd3573987b14965578e54434a120c9477500e24b95b6fe79ee" 888 | dependencies = [ 889 | "log", 890 | "winapi", 891 | ] 892 | 893 | [[package]] 894 | name = "parking_lot" 895 | version = "0.11.1" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 898 | dependencies = [ 899 | "instant", 900 | "lock_api", 901 | "parking_lot_core", 902 | ] 903 | 904 | [[package]] 905 | name = "parking_lot_core" 906 | version = "0.8.3" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 909 | dependencies = [ 910 | "cfg-if", 911 | "instant", 912 | "libc", 913 | "redox_syscall", 914 | "smallvec", 915 | "winapi", 916 | ] 917 | 918 | [[package]] 919 | name = "password-hash" 920 | version = "0.2.0" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "7561a874264c351c35ccde0da29a98dea5f2483357c049673319c7f3446d14da" 923 | dependencies = [ 924 | "base64ct", 925 | "rand_core", 926 | ] 927 | 928 | [[package]] 929 | name = "pbkdf2" 930 | version = "0.8.0" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" 933 | dependencies = [ 934 | "base64ct", 935 | "crypto-mac", 936 | "hmac", 937 | "password-hash", 938 | "sha2", 939 | ] 940 | 941 | [[package]] 942 | name = "percent-encoding" 943 | version = "2.3.0" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 946 | 947 | [[package]] 948 | name = "pin-project" 949 | version = "1.0.7" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" 952 | dependencies = [ 953 | "pin-project-internal", 954 | ] 955 | 956 | [[package]] 957 | name = "pin-project-internal" 958 | version = "1.0.7" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" 961 | dependencies = [ 962 | "proc-macro2", 963 | "quote", 964 | "syn 1.0.71", 965 | ] 966 | 967 | [[package]] 968 | name = "pin-project-lite" 969 | version = "0.2.6" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" 972 | 973 | [[package]] 974 | name = "pin-utils" 975 | version = "0.1.0" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 978 | 979 | [[package]] 980 | name = "pkg-config" 981 | version = "0.3.19" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 984 | 985 | [[package]] 986 | name = "ppv-lite86" 987 | version = "0.2.10" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 990 | 991 | [[package]] 992 | name = "proc-macro2" 993 | version = "1.0.63" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 996 | dependencies = [ 997 | "unicode-ident", 998 | ] 999 | 1000 | [[package]] 1001 | name = "quick-xml" 1002 | version = "0.27.1" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "ffc053f057dd768a56f62cd7e434c42c831d296968997e9ac1f76ea7c2d14c41" 1005 | dependencies = [ 1006 | "encoding_rs", 1007 | "memchr", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "quote" 1012 | version = "1.0.28" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 1015 | dependencies = [ 1016 | "proc-macro2", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "rand" 1021 | version = "0.8.3" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 1024 | dependencies = [ 1025 | "libc", 1026 | "rand_chacha", 1027 | "rand_core", 1028 | "rand_hc", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "rand_chacha" 1033 | version = "0.3.0" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 1036 | dependencies = [ 1037 | "ppv-lite86", 1038 | "rand_core", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "rand_core" 1043 | version = "0.6.2" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 1046 | dependencies = [ 1047 | "getrandom", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "rand_hc" 1052 | version = "0.3.0" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 1055 | dependencies = [ 1056 | "rand_core", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "redox_syscall" 1061 | version = "0.2.7" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "85dd92e586f7355c633911e11f77f3d12f04b1b1bd76a198bd34ae3af8341ef2" 1064 | dependencies = [ 1065 | "bitflags", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "regex" 1070 | version = "1.9.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "89089e897c013b3deb627116ae56a6955a72b8bed395c9526af31c9fe528b484" 1073 | dependencies = [ 1074 | "aho-corasick", 1075 | "memchr", 1076 | "regex-automata", 1077 | "regex-syntax", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "regex-automata" 1082 | version = "0.3.0" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "fa250384981ea14565685dea16a9ccc4d1c541a13f82b9c168572264d1df8c56" 1085 | dependencies = [ 1086 | "aho-corasick", 1087 | "memchr", 1088 | "regex-syntax", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "regex-syntax" 1093 | version = "0.7.3" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" 1096 | 1097 | [[package]] 1098 | name = "remove_dir_all" 1099 | version = "0.5.3" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1102 | dependencies = [ 1103 | "winapi", 1104 | ] 1105 | 1106 | [[package]] 1107 | name = "reqwest" 1108 | version = "0.11.3" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "2296f2fac53979e8ccbc4a1136b25dcefd37be9ed7e4a1f6b05a6029c84ff124" 1111 | dependencies = [ 1112 | "base64", 1113 | "bytes", 1114 | "encoding_rs", 1115 | "futures-core", 1116 | "futures-util", 1117 | "http", 1118 | "http-body", 1119 | "hyper", 1120 | "hyper-tls", 1121 | "ipnet", 1122 | "js-sys", 1123 | "lazy_static", 1124 | "log", 1125 | "mime", 1126 | "native-tls", 1127 | "percent-encoding", 1128 | "pin-project-lite", 1129 | "serde", 1130 | "serde_urlencoded", 1131 | "tokio", 1132 | "tokio-native-tls", 1133 | "url", 1134 | "wasm-bindgen", 1135 | "wasm-bindgen-futures", 1136 | "web-sys", 1137 | "winreg", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "ryu" 1142 | version = "1.0.5" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1145 | 1146 | [[package]] 1147 | name = "schannel" 1148 | version = "0.1.19" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 1151 | dependencies = [ 1152 | "lazy_static", 1153 | "winapi", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "scopeguard" 1158 | version = "1.1.0" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1161 | 1162 | [[package]] 1163 | name = "security-framework" 1164 | version = "2.2.0" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" 1167 | dependencies = [ 1168 | "bitflags", 1169 | "core-foundation", 1170 | "core-foundation-sys", 1171 | "libc", 1172 | "security-framework-sys", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "security-framework-sys" 1177 | version = "2.2.0" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339" 1180 | dependencies = [ 1181 | "core-foundation-sys", 1182 | "libc", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "serde" 1187 | version = "1.0.125" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 1190 | dependencies = [ 1191 | "serde_derive", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "serde_derive" 1196 | version = "1.0.125" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 1199 | dependencies = [ 1200 | "proc-macro2", 1201 | "quote", 1202 | "syn 1.0.71", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "serde_json" 1207 | version = "1.0.64" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 1210 | dependencies = [ 1211 | "itoa", 1212 | "ryu", 1213 | "serde", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "serde_urlencoded" 1218 | version = "0.7.0" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" 1221 | dependencies = [ 1222 | "form_urlencoded", 1223 | "itoa", 1224 | "ryu", 1225 | "serde", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "sha1" 1230 | version = "0.6.0" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" 1233 | 1234 | [[package]] 1235 | name = "sha2" 1236 | version = "0.9.3" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" 1239 | dependencies = [ 1240 | "block-buffer", 1241 | "cfg-if", 1242 | "cpuid-bool", 1243 | "digest", 1244 | "opaque-debug", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "signal-hook-registry" 1249 | version = "1.3.0" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" 1252 | dependencies = [ 1253 | "libc", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "simple_logger" 1258 | version = "1.11.0" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "cd57f17c093ead1d4a1499dc9acaafdd71240908d64775465543b8d9a9f1d198" 1261 | dependencies = [ 1262 | "atty", 1263 | "chrono", 1264 | "colored", 1265 | "log", 1266 | "winapi", 1267 | ] 1268 | 1269 | [[package]] 1270 | name = "siphasher" 1271 | version = "0.3.5" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" 1274 | 1275 | [[package]] 1276 | name = "slab" 1277 | version = "0.4.3" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" 1280 | 1281 | [[package]] 1282 | name = "smallvec" 1283 | version = "1.6.1" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 1286 | 1287 | [[package]] 1288 | name = "socket2" 1289 | version = "0.4.0" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" 1292 | dependencies = [ 1293 | "libc", 1294 | "winapi", 1295 | ] 1296 | 1297 | [[package]] 1298 | name = "sqlite" 1299 | version = "0.26.0" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "3fb1a534c07ec276fbbe0e55a1c00814d8563da3a2f4d9d9d4c802bd1278db6a" 1302 | dependencies = [ 1303 | "libc", 1304 | "sqlite3-sys", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "sqlite3-src" 1309 | version = "0.3.0" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "a260b07ce75a0644c6f5891f34f46db9869e731838e95293469ab17999abcfa3" 1312 | dependencies = [ 1313 | "cc", 1314 | "pkg-config", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "sqlite3-sys" 1319 | version = "0.13.0" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "04d2f028faeb14352df7934b4771806f60d61ce61be1928ec92396d7492e2e54" 1322 | dependencies = [ 1323 | "libc", 1324 | "sqlite3-src", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "srsrssrsbot" 1329 | version = "0.1.0" 1330 | dependencies = [ 1331 | "chrono", 1332 | "feed-rs", 1333 | "grammers-client", 1334 | "grammers-session", 1335 | "log", 1336 | "openssl", 1337 | "reqwest", 1338 | "simple_logger", 1339 | "sqlite", 1340 | "tokio", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "subtle" 1345 | version = "2.4.0" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" 1348 | 1349 | [[package]] 1350 | name = "syn" 1351 | version = "1.0.71" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373" 1354 | dependencies = [ 1355 | "proc-macro2", 1356 | "quote", 1357 | "unicode-xid", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "syn" 1362 | version = "2.0.22" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" 1365 | dependencies = [ 1366 | "proc-macro2", 1367 | "quote", 1368 | "unicode-ident", 1369 | ] 1370 | 1371 | [[package]] 1372 | name = "tempfile" 1373 | version = "3.2.0" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 1376 | dependencies = [ 1377 | "cfg-if", 1378 | "libc", 1379 | "rand", 1380 | "redox_syscall", 1381 | "remove_dir_all", 1382 | "winapi", 1383 | ] 1384 | 1385 | [[package]] 1386 | name = "time" 1387 | version = "0.1.44" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1390 | dependencies = [ 1391 | "libc", 1392 | "wasi", 1393 | "winapi", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "tinyvec" 1398 | version = "1.2.0" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" 1401 | dependencies = [ 1402 | "tinyvec_macros", 1403 | ] 1404 | 1405 | [[package]] 1406 | name = "tinyvec_macros" 1407 | version = "0.1.0" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1410 | 1411 | [[package]] 1412 | name = "tokio" 1413 | version = "1.5.0" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" 1416 | dependencies = [ 1417 | "autocfg", 1418 | "bytes", 1419 | "libc", 1420 | "memchr", 1421 | "mio", 1422 | "num_cpus", 1423 | "once_cell", 1424 | "parking_lot", 1425 | "pin-project-lite", 1426 | "signal-hook-registry", 1427 | "tokio-macros", 1428 | "winapi", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "tokio-macros" 1433 | version = "1.1.0" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" 1436 | dependencies = [ 1437 | "proc-macro2", 1438 | "quote", 1439 | "syn 1.0.71", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "tokio-native-tls" 1444 | version = "0.3.0" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1447 | dependencies = [ 1448 | "native-tls", 1449 | "tokio", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "tokio-util" 1454 | version = "0.6.6" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e" 1457 | dependencies = [ 1458 | "bytes", 1459 | "futures-core", 1460 | "futures-sink", 1461 | "log", 1462 | "pin-project-lite", 1463 | "tokio", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "tower-service" 1468 | version = "0.3.1" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1471 | 1472 | [[package]] 1473 | name = "tracing" 1474 | version = "0.1.26" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" 1477 | dependencies = [ 1478 | "cfg-if", 1479 | "pin-project-lite", 1480 | "tracing-core", 1481 | ] 1482 | 1483 | [[package]] 1484 | name = "tracing-core" 1485 | version = "0.1.18" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" 1488 | dependencies = [ 1489 | "lazy_static", 1490 | ] 1491 | 1492 | [[package]] 1493 | name = "try-lock" 1494 | version = "0.2.3" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1497 | 1498 | [[package]] 1499 | name = "typenum" 1500 | version = "1.13.0" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" 1503 | 1504 | [[package]] 1505 | name = "unicase" 1506 | version = "2.6.0" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1509 | dependencies = [ 1510 | "version_check", 1511 | ] 1512 | 1513 | [[package]] 1514 | name = "unicode-bidi" 1515 | version = "0.3.13" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1518 | 1519 | [[package]] 1520 | name = "unicode-ident" 1521 | version = "1.0.9" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" 1524 | 1525 | [[package]] 1526 | name = "unicode-normalization" 1527 | version = "0.1.22" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1530 | dependencies = [ 1531 | "tinyvec", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "unicode-xid" 1536 | version = "0.2.2" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1539 | 1540 | [[package]] 1541 | name = "url" 1542 | version = "2.4.0" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" 1545 | dependencies = [ 1546 | "form_urlencoded", 1547 | "idna", 1548 | "percent-encoding", 1549 | ] 1550 | 1551 | [[package]] 1552 | name = "uuid" 1553 | version = "1.4.0" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" 1556 | dependencies = [ 1557 | "getrandom", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "vcpkg" 1562 | version = "0.2.12" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" 1565 | 1566 | [[package]] 1567 | name = "version_check" 1568 | version = "0.9.3" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1571 | 1572 | [[package]] 1573 | name = "want" 1574 | version = "0.3.0" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1577 | dependencies = [ 1578 | "log", 1579 | "try-lock", 1580 | ] 1581 | 1582 | [[package]] 1583 | name = "wasi" 1584 | version = "0.10.0+wasi-snapshot-preview1" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1587 | 1588 | [[package]] 1589 | name = "wasm-bindgen" 1590 | version = "0.2.73" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" 1593 | dependencies = [ 1594 | "cfg-if", 1595 | "serde", 1596 | "serde_json", 1597 | "wasm-bindgen-macro", 1598 | ] 1599 | 1600 | [[package]] 1601 | name = "wasm-bindgen-backend" 1602 | version = "0.2.73" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" 1605 | dependencies = [ 1606 | "bumpalo", 1607 | "lazy_static", 1608 | "log", 1609 | "proc-macro2", 1610 | "quote", 1611 | "syn 1.0.71", 1612 | "wasm-bindgen-shared", 1613 | ] 1614 | 1615 | [[package]] 1616 | name = "wasm-bindgen-futures" 1617 | version = "0.4.23" 1618 | source = "registry+https://github.com/rust-lang/crates.io-index" 1619 | checksum = "81b8b767af23de6ac18bf2168b690bed2902743ddf0fb39252e36f9e2bfc63ea" 1620 | dependencies = [ 1621 | "cfg-if", 1622 | "js-sys", 1623 | "wasm-bindgen", 1624 | "web-sys", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "wasm-bindgen-macro" 1629 | version = "0.2.73" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" 1632 | dependencies = [ 1633 | "quote", 1634 | "wasm-bindgen-macro-support", 1635 | ] 1636 | 1637 | [[package]] 1638 | name = "wasm-bindgen-macro-support" 1639 | version = "0.2.73" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" 1642 | dependencies = [ 1643 | "proc-macro2", 1644 | "quote", 1645 | "syn 1.0.71", 1646 | "wasm-bindgen-backend", 1647 | "wasm-bindgen-shared", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "wasm-bindgen-shared" 1652 | version = "0.2.73" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" 1655 | 1656 | [[package]] 1657 | name = "web-sys" 1658 | version = "0.3.50" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" 1661 | dependencies = [ 1662 | "js-sys", 1663 | "wasm-bindgen", 1664 | ] 1665 | 1666 | [[package]] 1667 | name = "winapi" 1668 | version = "0.3.9" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1671 | dependencies = [ 1672 | "winapi-i686-pc-windows-gnu", 1673 | "winapi-x86_64-pc-windows-gnu", 1674 | ] 1675 | 1676 | [[package]] 1677 | name = "winapi-i686-pc-windows-gnu" 1678 | version = "0.4.0" 1679 | source = "registry+https://github.com/rust-lang/crates.io-index" 1680 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1681 | 1682 | [[package]] 1683 | name = "winapi-x86_64-pc-windows-gnu" 1684 | version = "0.4.0" 1685 | source = "registry+https://github.com/rust-lang/crates.io-index" 1686 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1687 | 1688 | [[package]] 1689 | name = "windows" 1690 | version = "0.48.0" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" 1693 | dependencies = [ 1694 | "windows-targets", 1695 | ] 1696 | 1697 | [[package]] 1698 | name = "windows-targets" 1699 | version = "0.48.1" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 1702 | dependencies = [ 1703 | "windows_aarch64_gnullvm", 1704 | "windows_aarch64_msvc", 1705 | "windows_i686_gnu", 1706 | "windows_i686_msvc", 1707 | "windows_x86_64_gnu", 1708 | "windows_x86_64_gnullvm", 1709 | "windows_x86_64_msvc", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "windows_aarch64_gnullvm" 1714 | version = "0.48.0" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1717 | 1718 | [[package]] 1719 | name = "windows_aarch64_msvc" 1720 | version = "0.48.0" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1723 | 1724 | [[package]] 1725 | name = "windows_i686_gnu" 1726 | version = "0.48.0" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1729 | 1730 | [[package]] 1731 | name = "windows_i686_msvc" 1732 | version = "0.48.0" 1733 | source = "registry+https://github.com/rust-lang/crates.io-index" 1734 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1735 | 1736 | [[package]] 1737 | name = "windows_x86_64_gnu" 1738 | version = "0.48.0" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1741 | 1742 | [[package]] 1743 | name = "windows_x86_64_gnullvm" 1744 | version = "0.48.0" 1745 | source = "registry+https://github.com/rust-lang/crates.io-index" 1746 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1747 | 1748 | [[package]] 1749 | name = "windows_x86_64_msvc" 1750 | version = "0.48.0" 1751 | source = "registry+https://github.com/rust-lang/crates.io-index" 1752 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1753 | 1754 | [[package]] 1755 | name = "winreg" 1756 | version = "0.7.0" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 1759 | dependencies = [ 1760 | "winapi", 1761 | ] 1762 | --------------------------------------------------------------------------------