diff --git a/CHANGELOG.md b/CHANGELOG.md index 810ce29e4..0d4d7056c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### API-Changes +- jsonrpc: add `miscSaveSticker` method ### Changes - add JSON-RPC stdio server `deltachat-rpc-server` and use it for JSON-RPC tests #3695 diff --git a/Cargo.lock b/Cargo.lock index a9e97845b..93fa9d75d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -906,6 +906,7 @@ dependencies = [ "futures", "log", "num-traits", + "sanitize-filename", "serde", "serde_json", "tempfile", diff --git a/deltachat-jsonrpc/Cargo.toml b/deltachat-jsonrpc/Cargo.toml index 87b029dcd..66a8f92b2 100644 --- a/deltachat-jsonrpc/Cargo.toml +++ b/deltachat-jsonrpc/Cargo.toml @@ -25,11 +25,12 @@ serde_json = "1.0.87" yerpc = { version = "^0.3.1", features = ["anyhow_expose"] } typescript-type-def = { version = "0.5.3", features = ["json_value"] } tokio = { version = "1.21.2" } +sanitize-filename = "0.4" +walkdir = "2.3.2" # optional dependencies axum = { version = "0.5.17", optional = true, features = ["ws"] } env_logger = { version = "0.9.1", optional = true } -walkdir = "2.3.2" [dev-dependencies] tokio = { version = "1.21.2", features = ["full", "rt-multi-thread"] } diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index 99a9d0611..bbc9c56a7 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{anyhow, bail, ensure, Context, Result}; use deltachat::{ chat::{ self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, marknoticed_chat, @@ -22,6 +22,7 @@ use deltachat::{ stock_str::StockMessage, webxdc::StatusUpdateSerial, }; +use sanitize_filename::is_sanitized; use std::collections::BTreeMap; use std::sync::Arc; use std::{collections::HashMap, str::FromStr}; @@ -1512,7 +1513,7 @@ impl CommandApi { .get_dbfile() .parent() .context("account folder not found")?; - let sticker_folder_path = account_folder.join("./stickers"); + let sticker_folder_path = account_folder.join("stickers"); fs::create_dir_all(&sticker_folder_path).await?; sticker_folder_path .to_str() @@ -1520,15 +1521,55 @@ impl CommandApi { .context("path conversion to string failed") } + /// save a sticker to a collection/folder in the account's sticker folder + async fn misc_save_sticker( + &self, + account_id: u32, + msg_id: u32, + collection: String, + ) -> Result<()> { + let ctx = self.get_context(account_id).await?; + let message = Message::load_from_db(&ctx, MsgId::new(msg_id)).await?; + ensure!( + message.get_viewtype() == Viewtype::Sticker, + "message {} is not a sticker", + msg_id + ); + let account_folder = ctx + .get_dbfile() + .parent() + .context("account folder not found")?; + ensure!( + is_sanitized(&collection), + "illegal characters in collection name" + ); + let destination_path = account_folder.join("stickers").join(collection); + fs::create_dir_all(&destination_path).await?; + let file = message.get_file(&ctx).context("no file")?; + fs::copy( + &file, + destination_path.join(format!( + "{}.{}", + msg_id, + file.extension() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + )), + ) + .await?; + Ok(()) + } + /// for desktop, get stickers from stickers folder, - /// grouped by the folder they are in. + /// grouped by the collection/folder they are in. async fn misc_get_stickers(&self, account_id: u32) -> Result>> { let ctx = self.get_context(account_id).await?; let account_folder = ctx .get_dbfile() .parent() .context("account folder not found")?; - let sticker_folder_path = account_folder.join("./stickers"); + let sticker_folder_path = account_folder.join("stickers"); fs::create_dir_all(&sticker_folder_path).await?; let mut result = HashMap::new(); diff --git a/deltachat-jsonrpc/typescript/generated/client.ts b/deltachat-jsonrpc/typescript/generated/client.ts index 7556c11bc..bb207be81 100644 --- a/deltachat-jsonrpc/typescript/generated/client.ts +++ b/deltachat-jsonrpc/typescript/generated/client.ts @@ -912,9 +912,16 @@ export class RawClient { return (this._transport.request('misc_get_sticker_folder', [accountId] as RPC.Params)) as Promise; } + /** + * save a sticker to a collection/folder in the account's sticker folder + */ + public miscSaveSticker(accountId: T.U32, msgId: T.U32, collection: string): Promise { + return (this._transport.request('misc_save_sticker', [accountId, msgId, collection] as RPC.Params)) as Promise; + } + /** * for desktop, get stickers from stickers folder, - * grouped by the folder they are in. + * grouped by the collection/folder they are in. */ public miscGetStickers(accountId: T.U32): Promise> { return (this._transport.request('misc_get_stickers', [accountId] as RPC.Params)) as Promise>; diff --git a/deltachat-jsonrpc/typescript/generated/types.ts b/deltachat-jsonrpc/typescript/generated/types.ts index c8d451b44..e3a3055a2 100644 --- a/deltachat-jsonrpc/typescript/generated/types.ts +++ b/deltachat-jsonrpc/typescript/generated/types.ts @@ -195,4 +195,4 @@ export type MessageNotificationInfo={"id":U32;"chatId":U32;"accountId":U32;"imag export type MessageSearchResult={"id":U32;"authorProfileImage":(string|null);"authorName":string;"authorColor":string;"chatName":(string|null);"message":string;"timestamp":I64;}; export type F64=number; export type Location={"locationId":U32;"isIndependent":boolean;"latitude":F64;"longitude":F64;"accuracy":F64;"timestamp":I64;"contactId":U32;"msgId":U32;"chatId":U32;"marker":(string|null);}; -export type __AllTyps=[string,boolean,Record,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],null,null,U32,null,U32,null,U32,Account,U32,U64,U32,string,(ProviderInfo|null),U32,boolean,U32,Record,U32,string,(string|null),null,U32,Record,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record,Record,null,U32,null,U32,null,U32,string,(string|null),null,U32,string,(string|null),null,U32,(U32)[],U32,U32,Usize,U32,boolean,I64,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record,U32,U32,FullChat,U32,U32,BasicChat,U32,U32,null,U32,U32,null,U32,U32,null,U32,U32,string,U32,(U32|null),[string,string],U32,string,U32,U32,U32,null,U32,U32,U32,null,U32,U32,U32,null,U32,U32,(U32)[],U32,string,boolean,U32,U32,U32,U32,U32,string,null,U32,U32,(string|null),null,U32,U32,ChatVisibility,null,U32,U32,U32,null,U32,U32,U32,U32,string,string,U32,U32,U32,null,U32,U32,(U32|null),U32,U32,MuteDuration,null,U32,U32,boolean,U32,(U32)[],null,U32,U32,U32,(U32)[],U32,U32,U32,(MessageListItem)[],U32,U32,Message,U32,U32,(string|null),U32,(U32)[],Record,U32,U32,MessageNotificationInfo,U32,(U32)[],null,U32,U32,string,U32,U32,null,U32,string,(U32|null),(U32)[],U32,(U32)[],Record,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record,U32,U32,boolean,U32,U32,string,null,U32,U32,string,U32,string,(U32|null),U32,(U32|null),Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,Viewtype,(Viewtype|null),(Viewtype|null),[(U32|null),(U32|null)],U32,string,(string|null),null,U32,string,(string|null),null,null,U32,U32,U32,string,U32,(U32|null),(U32|null),I64,I64,(Location)[],U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,(U32)[],U32,null,U32,U32,string,U32,U32,U32,(string)[],U32,U32,U32,null,U32,U32,(Message|null),U32,U32,U32,U32,string,U32,Record,U32,U32,string,U32,U32,U32,(string|null),(string|null),([F64,F64]|null),(U32|null),[U32,Message],U32,U32,(string|null),(string|null),(U32|null),null]; +export type __AllTyps=[string,boolean,Record,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],null,null,U32,null,U32,null,U32,Account,U32,U64,U32,string,(ProviderInfo|null),U32,boolean,U32,Record,U32,string,(string|null),null,U32,Record,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record,Record,null,U32,null,U32,null,U32,string,(string|null),null,U32,string,(string|null),null,U32,(U32)[],U32,U32,Usize,U32,boolean,I64,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record,U32,U32,FullChat,U32,U32,BasicChat,U32,U32,null,U32,U32,null,U32,U32,null,U32,U32,string,U32,(U32|null),[string,string],U32,string,U32,U32,U32,null,U32,U32,U32,null,U32,U32,U32,null,U32,U32,(U32)[],U32,string,boolean,U32,U32,U32,U32,U32,string,null,U32,U32,(string|null),null,U32,U32,ChatVisibility,null,U32,U32,U32,null,U32,U32,U32,U32,string,string,U32,U32,U32,null,U32,U32,(U32|null),U32,U32,MuteDuration,null,U32,U32,boolean,U32,(U32)[],null,U32,U32,U32,(U32)[],U32,U32,U32,(MessageListItem)[],U32,U32,Message,U32,U32,(string|null),U32,(U32)[],Record,U32,U32,MessageNotificationInfo,U32,(U32)[],null,U32,U32,string,U32,U32,null,U32,string,(U32|null),(U32)[],U32,(U32)[],Record,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record,U32,U32,boolean,U32,U32,string,null,U32,U32,string,U32,string,(U32|null),U32,(U32|null),Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,Viewtype,(Viewtype|null),(Viewtype|null),[(U32|null),(U32|null)],U32,string,(string|null),null,U32,string,(string|null),null,null,U32,U32,U32,string,U32,(U32|null),(U32|null),I64,I64,(Location)[],U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,(U32)[],U32,null,U32,U32,string,U32,U32,U32,(string)[],U32,U32,U32,null,U32,U32,(Message|null),U32,U32,U32,U32,string,U32,U32,string,null,U32,Record,U32,U32,string,U32,U32,U32,(string|null),(string|null),([F64,F64]|null),(U32|null),[U32,Message],U32,U32,(string|null),(string|null),(U32|null),null];