Skip to content

Commit a180431

Browse files
authored
Add PAW provider. (#23)
* Added PAW. * Implemented unique.
1 parent b5e8b70 commit a180431

File tree

6 files changed

+112
-1
lines changed

6 files changed

+112
-1
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ tracing = "0.1"
3838
tracing-subscriber = { version = "0.3", features = ["env-filter", "time"] }
3939

4040
[features]
41-
default = ["cache", "avtrdb", "vrcdb", "vrcds", "vrcwb", "title"]
41+
default = ["cache", "avtrdb", "vrcdb", "vrcds", "vrcwb", "paw", "title"]
4242

4343
# VRChat Avatar Database Providers
4444
cache = ["dep:tokio-rusqlite-new"]
4545
avtrdb = ["dep:reqwest", "discord"]
4646
vrcdb = ["dep:reqwest", "discord"]
4747
vrcds = ["dep:reqwest", "discord"]
4848
vrcwb = ["dep:reqwest", "discord"]
49+
paw = ["dep:reqwest"]
4950

5051
discord = ["dep:discord-presence", "dep:cached"]
5152
title = ["dep:crossterm"]

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Place the file in the VRChat directory or `PATH` and set your launch options
4444
- [VRCDB - Avatar Search] - [Discord](https://discord.gg/q427ecnUvj) / [VRCX](https://vrcx.vrcdb.com/avatars/Avatar/VRCX) / [Web](https://vrcdb.com) / [World](https://vrchat.com/home/world/wrld_1146f625-5d42-40f5-bfe7-06a7664e2796)
4545
- [VRCDS - Project Dark Star] - [Discord](https://discord.gg/QT4uatfU8h) / [VRCX](https://avtr.nekosunevr.co.uk/vrcx_search) / [Web](https://avtr.nekosunevr.co.uk)
4646
- [VRCWB - World Balancer] - [Discord](https://discord.gg/Uw7aAShdsp) / [VRCX](https://avatar.worldbalancer.com/vrcx_search.php) / [Web](https://avatar.worldbalancer.com/search.php)
47+
- [PAW - Puppy's Avatar World] - [Discord](https://discord.gg/zHhs4nQYxX) / [VRCX](https://paw-api.amelia.fun/vrcx_search) / [Web](https://paw.amelia.fun)
4748

4849
#### Unsupported Avatar Database Providers
4950

@@ -66,3 +67,5 @@ Additional contributions welcome, please open an issue, pull request, or join Di
6667
[VRCWB - World Balancer]: https://avatar.worldbalancer.com
6768

6869
[VRCX]: https://github.com/vrcx-team/VRCX?tab=readme-ov-file#--vrcx
70+
71+
[PAW - Puppy's Avatar World]: https://paw.amelia.fun

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ pub async fn process_avatars((_tx, rx): (Sender<PathBuf>, Receiver<PathBuf>)) ->
127127
#[cfg(feature = "vrcwb")]
128128
let vrcwb = VrcWB::default();
129129

130+
#[cfg(feature = "paw")]
131+
let paw = Paw::default();
132+
130133
let providers = VecDeque::from([
131134
#[cfg(feature = "avtrdb")]
132135
Type::AVTRDB(&avtrdb),
@@ -136,6 +139,8 @@ pub async fn process_avatars((_tx, rx): (Sender<PathBuf>, Receiver<PathBuf>)) ->
136139
Type::VRCDS(&vrcds),
137140
#[cfg(feature = "vrcwb")]
138141
Type::VRCWB(&vrcwb),
142+
#[cfg(feature = "paw")]
143+
Type::PAW(&paw),
139144
]);
140145

141146
while let Ok(path) = rx.recv() {

src/provider/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub mod vrcdb;
1414
pub mod vrcds;
1515
#[cfg(feature = "vrcwb")]
1616
pub mod vrcwb;
17+
#[cfg(feature = "paw")]
18+
pub mod paw;
1719

1820
pub mod prelude;
1921

@@ -33,6 +35,9 @@ pub enum Type<'a> {
3335
#[cfg(feature = "vrcwb")]
3436
#[strum(to_string = "VRCWB - World Balancer")]
3537
VRCWB(&'a VrcWB),
38+
#[cfg(feature = "paw")]
39+
#[strum(to_string = "PAW - Puppy's Avatar World")]
40+
PAW(&'a Paw),
3641
}
3742

3843
#[async_trait]
@@ -74,6 +79,8 @@ impl Provider for Type<'_> {
7479
Type::VRCDS(p) => p.send_avatar_id(avatar_id).await,
7580
#[cfg(feature = "vrcwb")]
7681
Type::VRCWB(p) => p.send_avatar_id(avatar_id).await,
82+
#[cfg(feature = "paw")]
83+
Type::PAW(p) => p.send_avatar_id(avatar_id).await,
7784
}
7885
}
7986
}

src/provider/paw.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::{time::Duration};
2+
3+
use anyhow::{bail, Result};
4+
use async_trait::async_trait;
5+
use reqwest::{Client, StatusCode};
6+
use serde::Deserialize;
7+
8+
use crate::{
9+
provider::{Provider, Type},
10+
USER_AGENT,
11+
};
12+
13+
const URL: &str = "https://paw-api.amelia.fun/update";
14+
const AVATAR_URL: &str = "https://paw-api.amelia.fun/avatar";
15+
16+
pub struct Paw {
17+
client: Client,
18+
}
19+
20+
impl Default for Paw {
21+
fn default() -> Self {
22+
Self {
23+
client: Client::default(),
24+
}
25+
}
26+
}
27+
28+
#[derive(Debug, Deserialize)]
29+
struct PawResponse {
30+
success: bool,
31+
code: u16,
32+
result: Option<serde_json::Value>,
33+
avatar: Option<serde_json::Value>,
34+
}
35+
36+
#[async_trait]
37+
impl Provider for Paw {
38+
async fn check_avatar_id(&self, _avatar_id: &str) -> Result<bool> {
39+
let name = Type::PAW(self);
40+
let response = self
41+
.client
42+
.get(AVATAR_URL)
43+
.header("User-Agent", USER_AGENT)
44+
.query(&[("avatarId", _avatar_id)])
45+
.timeout(Duration::from_secs(3))
46+
.send()
47+
.await?;
48+
49+
let status = response.status();
50+
let text = response.text().await?;
51+
debug!("[{name}] {status} | {text}");
52+
53+
if status != StatusCode::OK {
54+
bail!("[{name}] Failed to check avatar: {status} | {text}");
55+
}
56+
57+
let data = serde_json::from_str::<PawResponse>(&text)?;
58+
59+
Ok(data.success && data.code == 200 && data.result.is_some())
60+
}
61+
62+
async fn send_avatar_id(&self, avatar_id: &str) -> Result<bool> {
63+
let name = Type::PAW(self);
64+
let response = self
65+
.client
66+
.post(URL)
67+
.header("User-Agent", USER_AGENT)
68+
.query(&[("avatarId", avatar_id)])
69+
.timeout(Duration::from_secs(3))
70+
.send()
71+
.await?;
72+
73+
let status = response.status();
74+
let text = response.text().await?;
75+
debug!("[{name}] {status} | {text}");
76+
77+
let unique = match status {
78+
StatusCode::OK => {
79+
let data = serde_json::from_str::<PawResponse>(&text)?;
80+
81+
!matches!(data.avatar.as_ref(), Some(avatar) if !avatar.is_null() && !(avatar.is_array() && avatar.as_array().unwrap().is_empty()))
82+
},
83+
StatusCode::TOO_MANY_REQUESTS => {
84+
warn!("[{name}] 429 Rate Limit, Please Wait 10 seconds...");
85+
tokio::time::sleep(Duration::from_secs(10)).await;
86+
Box::pin(self.send_avatar_id(avatar_id)).await?
87+
}
88+
_ => bail!("[{name}] {status} | {text}"),
89+
};
90+
91+
Ok(unique)
92+
}
93+
}

src/provider/prelude.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ pub use super::vrcdb::VrcDB;
88
pub use super::vrcds::VrcDS;
99
#[cfg(feature = "vrcwb")]
1010
pub use super::vrcwb::VrcWB;
11+
#[cfg(feature = "paw")]
12+
pub use super::paw::Paw;

0 commit comments

Comments
 (0)