Crates.io | sf-api |
lib.rs | sf-api |
version | 0.1.3 |
source | src |
created_at | 2024-01-19 05:13:53.13769 |
updated_at | 2024-04-20 23:13:47.249955 |
description | A simple API to send commands to the Shakes & Fidget servers and parse their responses into characters |
homepage | |
repository | https://github.com/the-marenga/sf-api |
max_upload_size | |
id | 1105000 |
size | 296,765 |
This is an unofficial work in progress API to talk to the Shakes & Fidget Servers.
The most basic example on how to use this would be:
// The session is what manages any data relevant to communicating with the
// server accross commands
let mut session = CharacterSession::new(
"username",
"password",
ServerConnection::new("f1.sfgame.net").unwrap(),
);
// The login response will contain information about our character and the
// server
let login_respone = session.login().await.unwrap();
// The game state is what we can use to look at all the data from the
// server in a more comprehensible way.
let mut game_state = GameState::new(login_respone).unwrap();
// Now we are all set to do whatever we want.
let best_description = "I love sushi!".to_string();
// Just like above we get a response. This time however, it will only
// contain a partial response with things, that might have changed
let response = session
.send_command(&Command::SetDescription {
description: best_description.clone(),
})
.await
.unwrap();
// As such, we should use update on our existing game state
game_state.update(response).unwrap();
// Lets make sure the server actually did what we told him
assert!(game_state.character.description == best_description);
println!("YAY, it worked! ๐๐ฃ๐ฃ๐ฃ๐");
If you use a single sign-on S&F Account, you can use it like this:
let account = SFAccount::login(
"username".to_string(),
"password".to_string()
).await.unwrap();
for mut session in account.characters().await.unwrap().into_iter().flatten()
{
// You can use the sessions, that the account returns like
// a normal (logged out) session now
let response = session.login().await.unwrap();
let mut game_state = GameState::new(response).unwrap();
}
You just need to run the following command in your Rust project:
cargo add sf-api
Here are a few things you should note before getting your account banned:
Performace should not matter to you, as you are not supposed to run this library on a scale, where you have to think about this. Disregarding this fact, this library is build with high scalabillity and low resource usage in mind. Parsing the login gamestate will take < 1ms on my machine with full updates after that taking < 100ยตs.
I have tried to minimize allocations, by reusing previous containers. In addition, Hashmaps<U, T>
s are largely replaced by [T;K]
s, where U as usize
is used to index into the vec (largely abstracted via some get function).
Responses are just the raw html response body with a ~HashMap<&str,&str>
, that references into that. This is perfectly safe, saves dozents of allocations per request and keeps everything in one hot place for cache purposes.
Everything is parsed into the exact datatype, that is expected, which also catches weird, or unexpected errors compared to just i64ing every int. Note that I do not expect to maintain this forever (I do not play this game to begin with), so a lot of this is shown as log warnings and defaulting to some value, instead of returning an error. This way you will not get hard stuck, just because the mushroom price of an item somewhere is negative. Feel free to change warn! to panic! in the misc. functions to change this behaviour.
This crate has support for serde
to (de)serialize the character state and the S&F Account (sso
) behind the respective feature flags. Note that sso
depends on the serde crate internally to talk to the server via json.
The API has been designed to keep session and character state seperate. Why? Mainly because I can serialize/deserialize responses and "replay" them for unique things and to reduce server requests.
In addition, this architecture is a bit easier to use, when you have multiple things, that want to use the session, as they do not have to wait for the update to finish before sending again.
Note that I do not think that this is perfect. Especially for basic usecases, this is more annoying, than helpful. I will likely add a unified version, at some point
There are roughly ~500 properties, that get parsed. This is such a huge amount of data, that I have opted to just give you raw access to these fields in the GameState instead of providing get() functions for all of these. This means you can create invalid gamestates with a mutable reference if you want, but as far as I am concerned, that would be your bug, not mine.
I might consider using something like derive_getters in the future, but I dont really care about this issue and it would increase compile times, so for now you just access the fields