@@ -3,7 +3,7 @@ use anyhow::{ Result, format_err };
33use regex:: Regex ;
44use reqwest:: StatusCode ;
55use serde:: { Deserialize , de:: DeserializeOwned } ;
6- use std:: { mem, sync:: LazyLock } ;
6+ use std:: { fmt :: Write , mem, sync:: LazyLock } ;
77use thiserror:: Error ;
88use tokio:: time:: { Duration , sleep } ;
99
@@ -25,9 +25,7 @@ async fn try_fetch<D: DeserializeOwned>(url: &str) -> Result<D, ApiError> {
2525
2626 if status == StatusCode :: BAD_REQUEST && RE_OUT_OF_BOUNDS . is_match ( & text) {
2727 return Ok ( serde_json:: from_str ( "[]" ) . unwrap ( ) ) ;
28- }
29-
30- if status != StatusCode :: OK {
28+ } else if status != StatusCode :: OK {
3129 return Err ( ApiError :: Status ( status) ) ;
3230 }
3331
@@ -38,7 +36,7 @@ pub trait Post {
3836 fn files ( & mut self ) -> Vec < PostFile > ;
3937}
4038
41- #[ derive( Debug , Clone , Error , PartialEq , Eq , PartialOrd , Ord ) ]
39+ #[ derive( Debug , Error ) ]
4240pub enum ApiError {
4341 #[ error( "connection error" ) ] Connect ( String ) ,
4442 #[ error( "non-success status code" ) ] Status ( StatusCode ) ,
@@ -73,32 +71,30 @@ impl ApiError {
7371 }
7472}
7573
76- #[ derive( Debug , Clone , Deserialize , PartialEq , Eq , PartialOrd , Ord ) ]
74+ #[ derive( Deserialize ) ]
7775pub struct SinglePost {
7876 post : SinglePostInner ,
7977}
8078
81- #[ derive( Debug , Clone , Deserialize , PartialEq , Eq , PartialOrd , Ord , Default ) ]
79+ #[ derive( Deserialize , Default ) ]
8280struct SinglePostInner {
8381 file : Option < PostFileRaw > ,
8482 attachments : Vec < PostFileRaw > ,
8583}
8684
8785impl Post for SinglePost {
8886 fn files ( & mut self ) -> Vec < PostFile > {
89- let post = mem :: take ( & mut self . post ) ;
87+ self . post . attachments . retain ( |file| file . path . is_some ( ) ) ;
9088
91- let mut files = Vec :: with_capacity ( post. attachments . len ( ) + 1 ) ;
89+ let attachments = mem :: take ( & mut self . post . attachments ) ;
9290
93- files. append (
94- & mut post. attachments
95- . into_iter ( )
96- . filter_map ( |raw| raw. path )
97- . map ( PostFile :: new)
98- . collect ( )
99- ) ;
91+ let mut files = Vec :: with_capacity ( attachments. len ( ) + 1 ) ;
10092
101- if let Some ( file) = post. file && let Some ( path) = file. path {
93+ for raw in attachments {
94+ files. push ( PostFile :: new ( raw. path . unwrap ( ) ) ) ;
95+ }
96+
97+ if let Some ( raw) = self . post . file . take ( ) && let Some ( path) = raw. path {
10298 files. push ( PostFile :: new ( path) ) ;
10399 }
104100
@@ -111,68 +107,94 @@ pub async fn try_fetch_page(
111107 user : & str ,
112108 offset : usize
113109) -> Result < Vec < PagePost > , ApiError > {
114- try_fetch (
115- & format ! (
116- "https://{site}/api/v1/{service}/user/{user}/posts?o={offset}" ,
117- site = target. as_service( ) . site( ) ,
118- service = target. as_service( )
119- )
120- ) . await
110+ let ( host, service, offset) = (
111+ target. as_service ( ) . host ( ) ,
112+ target. as_service ( ) . as_static_str ( ) ,
113+ offset. to_string ( ) ,
114+ ) ;
115+
116+ let mut url = String :: with_capacity (
117+ 8 + host. len ( ) + 8 + service. len ( ) + 6 + user. len ( ) + 9 + offset. len ( )
118+ ) ;
119+
120+ write ! ( url, "https://{host}/api/v1/{service}/user/{user}/posts?o={offset}" ) . unwrap ( ) ;
121+
122+ drop ( offset) ;
123+
124+ try_fetch ( & url) . await
121125}
122126
123- #[ derive( Debug , Clone , Deserialize , PartialEq , Eq , PartialOrd , Ord , Default ) ]
127+ #[ derive( Deserialize ) ]
124128pub struct PagePost {
125129 file : Option < PostFileRaw > ,
126130 attachments : Vec < PostFileRaw > ,
127131}
128132
129133impl Post for PagePost {
130134 fn files ( & mut self ) -> Vec < PostFile > {
135+ self . attachments . retain ( |file| file. path . is_some ( ) ) ;
136+
131137 let attachments = mem:: take ( & mut self . attachments ) ;
132138
133139 let mut files = Vec :: with_capacity ( attachments. len ( ) + 1 ) ;
134140
135- files. append (
136- & mut attachments
137- . into_iter ( )
138- . filter_map ( |raw| raw. path )
139- . map ( PostFile :: new)
140- . collect ( )
141- ) ;
141+ for raw in attachments {
142+ if let Some ( path) = raw. path {
143+ files. push ( PostFile :: new ( path) ) ;
144+ }
145+ }
142146
143- if let Some ( file ) = self . file . take ( ) && let Some ( path) = file . path {
147+ if let Some ( raw ) = self . file . take ( ) && let Some ( path) = raw . path {
144148 files. push ( PostFile :: new ( path) ) ;
145149 }
146150
147151 files
148152 }
149153}
150154
151- #[ derive( Debug , Clone , Deserialize , PartialEq , Eq , PartialOrd , Ord ) ]
155+ #[ derive( Deserialize ) ]
152156pub struct DiscordChannel {
153157 pub id : String , // "455285536341491716",
154158 // name: String, // "news"
155159}
156160
157161pub async fn try_discord_server ( server : & str ) -> Result < Vec < DiscordChannel > , ApiError > {
158- try_fetch ( & format ! ( "https://kemono.cr/api/v1/discord/channel/lookup/{server}" ) ) . await
162+ let mut url = String :: with_capacity ( 48 + server. len ( ) ) ;
163+
164+ write ! ( url, "https://kemono.cr/api/v1/discord/channel/lookup/{server}" ) . unwrap ( ) ;
165+
166+ try_fetch ( & url) . await
159167}
160168
161- #[ derive( Debug , Clone , Deserialize , PartialEq , Eq , PartialOrd , Ord , Default ) ]
169+ #[ derive( Deserialize ) ]
162170pub struct DiscordPost {
163171 attachments : Vec < PostFileRaw > ,
164172}
165173
166174impl Post for DiscordPost {
167175 fn files ( & mut self ) -> Vec < PostFile > {
168- self . attachments
169- . drain ( ..)
170- . filter_map ( |file| file. path )
171- . map ( PostFile :: new)
172- . collect ( )
176+ self . attachments . retain ( |file| file. path . is_some ( ) ) ;
177+
178+ let attachments = mem:: take ( & mut self . attachments ) ;
179+
180+ let mut files = Vec :: with_capacity ( attachments. len ( ) ) ;
181+
182+ for raw in attachments {
183+ files. push ( PostFile :: new ( raw. path . unwrap ( ) ) ) ;
184+ }
185+
186+ files
173187 }
174188}
175189
176190pub async fn try_discord_page ( channel : & str , offset : usize ) -> Result < Vec < DiscordPost > , ApiError > {
177- try_fetch ( & format ! ( "https://kemono.cr/api/v1/discord/channel/{channel}?o={offset}" ) ) . await
191+ let offset = offset. to_string ( ) ;
192+
193+ let mut url = String :: with_capacity ( 41 + channel. len ( ) + 3 + offset. len ( ) ) ;
194+
195+ write ! ( url, "https://kemono.cr/api/v1/discord/channel/lookup/{channel}?o={offset}" ) . unwrap ( ) ;
196+
197+ drop ( offset) ;
198+
199+ try_fetch ( & url) . await
178200}
0 commit comments