mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add intercom functionality for setting factory thumbnails
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2585,6 +2585,8 @@ dependencies = [
|
||||
"libfj",
|
||||
"log",
|
||||
"sea-orm",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -14521,7 +14521,7 @@
|
||||
"factory": {
|
||||
"adapter": {
|
||||
"variant": "Arc",
|
||||
"uri": "sqlite:../../arc/rc_archive.db?mode=ro"
|
||||
"uri": "sqlite:../data/robocraft/rc_archive.db?mode=rw"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -32,6 +32,8 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(robocraft::brawl_data::get)
|
||||
.service(robocraft::campaign_data::get)
|
||||
.service(robocraft::factory::arc::get)
|
||||
.service(robocraft::factory::thumbnail::get)
|
||||
.service(robocraft::factory::thumbnail::post)
|
||||
.service(robocraft::favicon::get)
|
||||
})
|
||||
.bind((cli_args.ip, cli_args.port))?
|
||||
|
||||
@@ -8,7 +8,7 @@ static ZIP_FILE: std::sync::Mutex<Option<zip::read::ZipArchive<std::io::BufReade
|
||||
pub async fn get(cli: Data<crate::cli::CliArgs>, id: Path<u32>) -> HttpResponse {
|
||||
let id: u32 = *id;
|
||||
let zip_path = std::path::PathBuf::from(&cli.data_robocraft).join("rc_archive_thumbnails.zip");
|
||||
let thumb_dir_path = std::path::PathBuf::from(&cli.data_robocraft).join("factorythumbnails");
|
||||
let thumb_dir_path = std::path::PathBuf::from(&cli.data_robocraft).join(super::THUMBNAIL_DIR);
|
||||
try_find_file(zip_path, thumb_dir_path, id).await
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ fn get_file_in_zip(zip_path: std::path::PathBuf, id: u32) -> zip::result::ZipRes
|
||||
}
|
||||
|
||||
fn get_file_in_thumbnails(dir: std::path::PathBuf, id: u32) -> std::io::Result<Vec<u8>> {
|
||||
// in case someone has extracted the thumbnail zip
|
||||
// (newly-uploaded vehicles use the general thumbnail CDN endpoint)
|
||||
let prefix = format!("{} - ", id);
|
||||
for ent in std::fs::read_dir(&dir)? {
|
||||
let ent = ent?;
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
pub mod arc;
|
||||
pub mod thumbnail;
|
||||
|
||||
const THUMBNAIL_DIR: &str = "factorythumbnails";
|
||||
|
||||
22
cdn/src/robocraft/factory/thumbnail.rs
Normal file
22
cdn/src/robocraft/factory/thumbnail.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use actix_web::{get, post, web::{Data, Path, Bytes}, Responder};
|
||||
|
||||
#[get("/roboshop/Live/{id}")]
|
||||
pub async fn get(cli: Data<crate::cli::CliArgs>, id: Path<u32>) -> impl Responder {
|
||||
let path = std::path::PathBuf::from(&cli.data_robocraft).join(super::THUMBNAIL_DIR).join(format!("{}.jpg", id));
|
||||
log::debug!("RC asset at {} (exists? {})", path.display(), path.exists());
|
||||
if path.exists() {
|
||||
actix_files::NamedFile::open_async(path).await
|
||||
} else {
|
||||
log::info!("Not found /roboshop/Live/{} -> {}, using default image", id, path.display());
|
||||
actix_files::NamedFile::open_async(std::path::PathBuf::from(&cli.assets_robocraft).join(super::super::DEFAULT_IMAGE)).await
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/roboshop/Live/{id}")]
|
||||
pub async fn post(cli: Data<crate::cli::CliArgs>, auth: Data<crate::robocraft::IntercomAuth>, id: Path<i32>, body: Bytes, req: actix_web::HttpRequest) -> Result<actix_web::HttpResponse, crate::robocraft::IntercomOpError> {
|
||||
auth.validate(&req, &id.to_string())?;
|
||||
let path = std::path::PathBuf::from(&cli.data_robocraft).join(super::THUMBNAIL_DIR).join(format!("{}.jpg", *id));
|
||||
log::debug!("Saving factory thumbnail for {} to {}: {}B", id, path.display(), body.len());
|
||||
std::fs::write(path, &body).map_err(crate::robocraft::IntercomOpError::Io)?;
|
||||
Ok(actix_web::HttpResponse::NoContent().finish())
|
||||
}
|
||||
@@ -22,19 +22,23 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
|
||||
}
|
||||
}
|
||||
|
||||
async fn upload(&self, vehicle: oj_rc_factory::VehicleUploadInfo) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
async fn upload(&self, vehicle: oj_rc_factory::VehicleUploadInfo) -> Result<oj_rc_factory::VehicleThumbnailInfo, Box<dyn std::error::Error>> {
|
||||
match self {
|
||||
Self::Arc(x) => x.upload(vehicle).await,
|
||||
Self::Custom(x) => x.upload(vehicle).await,
|
||||
Self::None => Ok(false),
|
||||
Self::None => Ok(oj_rc_factory::VehicleThumbnailInfo {
|
||||
id: i32::MIN,
|
||||
thumbnail: vehicle.thumbnail,
|
||||
needs_upload: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Factory {
|
||||
pub async fn from_config(conf: &crate::persist::FactoryConfig) -> Result<Self, Box<dyn std::error::Error + 'static>> {
|
||||
pub async fn from_config(conf: &crate::persist::FactoryConfig, settings: &crate::persist::config::ServerConfig) -> Result<Self, Box<dyn std::error::Error + 'static>> {
|
||||
Ok(match &conf.adapter {
|
||||
crate::persist::AdapterSettings::Arc(x) => Self::Arc(oj_rc_factory::arc::ArcAdapter::init(&x.uri, x.show_expired, x.cdn.clone(), x.override_cdn, x.spoof_username).await?),
|
||||
crate::persist::AdapterSettings::Arc(x) => Self::Arc(oj_rc_factory::arc::ArcAdapter::init(&x.uri, x.show_expired, settings.cdn_url.to_owned(), x.override_cdn, x.spoof_username).await?),
|
||||
crate::persist::AdapterSettings::None => Self::None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -293,8 +293,8 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
||||
auto_signup: self.settings.server.auto_signup,
|
||||
queue_mode: super::QueueChangeMode::from_persist(self.settings.server.queue_mode.clone()),
|
||||
cdn_url: self.settings.server.cdn_url.trim_end_matches('/').to_owned(),
|
||||
auth_url: self.settings.server.auth_url.trim_matches('/').to_owned(),
|
||||
intercom_url: self.settings.server.intercom_url.trim_matches('/').to_owned(),
|
||||
auth_url: self.settings.server.auth_url.trim_end_matches('/').to_owned(),
|
||||
intercom_url: self.settings.server.intercom_url.trim_end_matches('/').to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
||||
}
|
||||
|
||||
async fn factory(&self) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>> {
|
||||
crate::factory::Factory::from_config(&self.factory).await
|
||||
crate::factory::Factory::from_config(&self.factory, &<Self as super::ConfigProvider<()>>::server_config(self)).await
|
||||
}
|
||||
|
||||
fn cubes(&self) -> &'_ indexmap::IndexMap<String, crate::persist::Cube> {
|
||||
|
||||
@@ -124,7 +124,7 @@ fn default_server_conf() -> ServerSettings {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_cdn_root_url() -> String {
|
||||
fn default_cdn_root_url() -> String {
|
||||
"http://127.0.0.1:8010".to_owned()
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,21 @@ impl super::IntercomUser for super::account_json::UserData {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn save_factory_thumbnail(&self, factory_id: i32, image: Vec<u8>) -> Result<(), polariton_server::operations::SimpleOpError> {
|
||||
let token = generate_token(factory_id.to_string().as_bytes(), &self.secret);
|
||||
let auth_header_val = format!("Internal {}", token);
|
||||
let url = format!("{}/roboshop/Live/{}", self.cdn, factory_id);
|
||||
if let Err(e) = self.http_client.post(url)
|
||||
.header("Authorization", auth_header_val)
|
||||
.body(image)
|
||||
.send()
|
||||
.await {
|
||||
log::error!("Failed to update factory thumbnail for {} ({}): {}", self.account.public_id, self.account.id, e);
|
||||
return Err((crate::data::error_codes::WebServicesError::UnexpectedError as i16).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn webservice_listener(&self) -> Result<super::IntercomListener<IntercomWebServiceUserMessage>, polariton_server::operations::SimpleOpError> {
|
||||
self.listen_on_websocket(".oj_services").await
|
||||
.map_err(|e| polariton_server::operations::SimpleOpError::with_message(
|
||||
|
||||
@@ -336,6 +336,7 @@ pub trait MultiplayerUser: IntercomUser + CommonUser {
|
||||
#[async_trait::async_trait]
|
||||
pub trait IntercomUser: CommonUser {
|
||||
async fn save_custom_avatar(&self, image: Vec<u8>) -> Result<(), polariton_server::operations::SimpleOpError>;
|
||||
async fn save_factory_thumbnail(&self, factory_id: i32, image: Vec<u8>) -> Result<(), polariton_server::operations::SimpleOpError>;
|
||||
async fn webservice_listener(&self) -> Result<IntercomListener<super::intercom::IntercomWebServiceUserMessage>, polariton_server::operations::SimpleOpError>;
|
||||
async fn show_dev_message(&self, msg: super::intercom::IntercomDevMessage, to: Vec<String>);
|
||||
async fn enter_maintenance(&self, msg: super::intercom::IntercomMaintenanceMessage, to: Vec<String>);
|
||||
|
||||
@@ -31,19 +31,12 @@ pub struct ArcFactorySettings {
|
||||
pub uri: String,
|
||||
#[serde(default = "default_true")]
|
||||
pub show_expired: bool,
|
||||
/// should probably end with /roboshop/arc/Live/
|
||||
#[serde(default = "default_arc_live_url")]
|
||||
pub cdn: String,
|
||||
#[serde(default)]
|
||||
pub override_cdn: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub spoof_username: bool,
|
||||
}
|
||||
|
||||
fn default_arc_live_url() -> String {
|
||||
format!("{}/roboshop/arc/Live/", super::settings::default_cdn_root_url())
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, ActiveModelTrait, Set, TransactionTrait};
|
||||
use base64::Engine;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct ArcAdapter {
|
||||
orm: sea_orm::DatabaseConnection,
|
||||
@@ -15,11 +14,11 @@ impl ArcAdapter {
|
||||
pub async fn init(uri: &str, show_expired: bool, cdn: String, override_cdn: bool, username_spoofing: bool) -> Result<Self, sea_orm::DbErr>{
|
||||
log::debug!("Connecting to Archive of RoboCraft (ARC) vehicle factory database URI: {}", uri);
|
||||
let db = sea_orm::Database::connect(uri).await?;
|
||||
let good_cdn = if cdn.ends_with('/') { cdn } else { format!("{}/", cdn) };
|
||||
//let good_cdn = cdn.to_owned()format!("{}/roboshop/Live/", cdn);
|
||||
let adapter = Self {
|
||||
orm: db,
|
||||
ignore_expiry: show_expired,
|
||||
cdn: good_cdn,
|
||||
cdn: cdn.to_owned(),
|
||||
override_cdn: override_cdn,
|
||||
spoof_users: username_spoofing,
|
||||
};
|
||||
@@ -36,7 +35,7 @@ impl ArcAdapter {
|
||||
|
||||
fn thumbnail_url(&self, meta: String, id: u32) -> String {
|
||||
if self.override_cdn {
|
||||
format!("{}{}", &self.cdn, id)
|
||||
format!("{}/roboshop/arc/Live/{}", &self.cdn, id)
|
||||
} else {
|
||||
meta
|
||||
}
|
||||
@@ -187,7 +186,7 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
|
||||
Ok(infos)
|
||||
}
|
||||
|
||||
async fn upload(&self, vehicle: crate::VehicleUploadInfo) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
async fn upload(&self, vehicle: crate::VehicleUploadInfo) -> Result<crate::VehicleThumbnailInfo, Box<dyn std::error::Error>> {
|
||||
let transaction = self.orm.begin().await?;
|
||||
|
||||
let cube_amounts = {
|
||||
@@ -207,17 +206,18 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
|
||||
serde_json::to_string(&str_map).unwrap_or_else(|_| "{}".to_string())
|
||||
};
|
||||
let cubes = super::entities::robot_cubes::ActiveModel {
|
||||
id: sea_orm::ActiveValue::NotSet,
|
||||
cube_data: Set(base64::prelude::BASE64_STANDARD.encode(&vehicle.cube_data)),
|
||||
colour_data: Set(base64::prelude::BASE64_STANDARD.encode(&vehicle.colour_data)),
|
||||
cube_amounts: Set(cube_amounts),
|
||||
..Default::default()
|
||||
}.insert(&transaction).await?;
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let meta = super::entities::robot_metadata::ActiveModel {
|
||||
let _meta = super::entities::robot_metadata::ActiveModel {
|
||||
id: Set(cubes.id),
|
||||
name: Set(vehicle.name),
|
||||
description: Set(vehicle.description),
|
||||
thumbnail: Set(format!("{}{}", &self.cdn, cubes.id)),
|
||||
thumbnail: Set("REPLACE ME".to_string()),
|
||||
added_by: Set(vehicle.added_by),
|
||||
added_by_display_name: Set(vehicle.added_by_display_name),
|
||||
added_date: Set(now.format("%Y-%m-%dT%H:%M:%S").to_string()),
|
||||
@@ -230,16 +230,20 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
|
||||
featured: Set(0),
|
||||
combat_rating: Set(3.0),
|
||||
cosmetic_rating: Set(3.0),
|
||||
..Default::default()
|
||||
}.insert(&transaction).await?;
|
||||
super::entities::robot_metadata::ActiveModel {
|
||||
id: Set(cubes.id),
|
||||
thumbnail: Set(format!("{}/roboshop/Live/{}", &self.cdn, cubes.id)),
|
||||
..Default::default()
|
||||
}.update(&transaction).await?;
|
||||
transaction.commit().await?;
|
||||
|
||||
let entry_name = format!("{} - {}.jpg", meta.id, meta.name);
|
||||
let thumbnail = vehicle.thumbnail;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
std::fs::write(Path::new("../data/robocraft/factorythumbnails").join(&entry_name), &thumbnail).ok();
|
||||
});
|
||||
let result = crate::VehicleThumbnailInfo {
|
||||
id: cubes.id as i32,
|
||||
thumbnail: vehicle.thumbnail,
|
||||
needs_upload: true,
|
||||
};
|
||||
|
||||
Ok(true)
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod arc;
|
||||
|
||||
mod traits;
|
||||
pub use traits::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo, VehicleUploadInfo};
|
||||
pub use traits::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo, VehicleUploadInfo, VehicleThumbnailInfo};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
pub trait VehicleFactoryAdapter: Send + Sync + 'static {
|
||||
async fn vehicle(&self, id: u32) -> Result<Option<(VehicleInfo, VehicleQueryInfo)>, Box<dyn std::error::Error>>;
|
||||
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<VehicleQueryInfo>, Box<dyn std::error::Error>>;
|
||||
async fn upload(&self, vehicle: VehicleUploadInfo) -> Result<bool, Box<dyn std::error::Error>>;
|
||||
async fn upload(&self, vehicle: VehicleUploadInfo) -> Result<VehicleThumbnailInfo, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -65,3 +65,10 @@ pub struct VehicleUploadInfo {
|
||||
pub colour_data: Vec<u8>,
|
||||
pub build_version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VehicleThumbnailInfo {
|
||||
pub id: i32,
|
||||
pub thumbnail: Vec<u8>,
|
||||
pub needs_upload: bool,
|
||||
}
|
||||
|
||||
@@ -15,11 +15,15 @@ async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy, factory:
|
||||
let upload_info = crate::data::crf::UploadData::from_transmissibles(version.string, data)?;
|
||||
let user_info = user.user()?;
|
||||
let prepared = user_info.prepare_factory_upload(upload_info.into_core()).await?;
|
||||
let success = factory.upload(prepared).await.map_err(|e| {
|
||||
let result = factory.upload(prepared).await.map_err(|e| {
|
||||
log::error!("Failed to upload to factory: {}", e);
|
||||
oj_rc_core::data::error_codes::WebServicesError::UnexpectedError as i16
|
||||
})?;
|
||||
params.insert(SUCCESS_PARAM_KEY, Typed::Bool(success));
|
||||
if result.needs_upload {
|
||||
user_info.save_factory_thumbnail(result.id, result.thumbnail).await?;
|
||||
}
|
||||
params.insert(SUCCESS_PARAM_KEY, Typed::Bool(true));
|
||||
|
||||
}
|
||||
}
|
||||
Ok(params.into())
|
||||
|
||||
@@ -220,5 +220,6 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
||||
.add(garage_slot_copy::garage_slot_copy_provider())
|
||||
.add(polariton_server::operations::Ack::<12, _>::default()) // TODO handle UpdatePlayerDailyQuestProgressRequest instead of ignoring it
|
||||
.add(polariton_server::operations::Ack::<90, _>::default()) // TODO handle SubmitCRFRatingRequest instead of ignoring it
|
||||
//.add(polariton_server::operations::Ack::<97, _>::default()) // TODO handle UpdateShopRobotOffsetRequest instead of ignoring it (this seems to break newly-uploaded vehicles for now)
|
||||
.add(steam_promo::steam_promos_provider())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user