diff --git a/CHANGELOG.md b/CHANGELOG.md index f78195d16..1fa26cd69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased ### API-Changes -- jsonrpc: add function: #3641 +- jsonrpc: add function: #3641, #3645 - `getChatContacts()` - `createGroupChat()` - `createBroadcastList()` @@ -14,8 +14,18 @@ - `sendVideochatInvitation()` - `searchMessages()` - `messageIdsToSearchResults()` -- jsonrpc: add type: #3641 + - `setChatVisibility()` + - `getChatEphemeralTimer()` + - `setChatEphemeralTimer()` + - `getLocations()` + - `getAccountFileSize()` + - `estimateAutoDeletionCount()` + - `setStockStrings()` + - `exportSelfKeys()` + - `importSelfKeys()` +- jsonrpc: add type: #3641, #3645 - `MessageSearchResult` + - `Location` ### Changes diff --git a/Cargo.lock b/Cargo.lock index 8f4267ad1..9aea9e8a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -980,6 +980,7 @@ dependencies = [ "tempfile", "tokio", "typescript-type-def", + "walkdir", "yerpc", ] diff --git a/deltachat-jsonrpc/Cargo.toml b/deltachat-jsonrpc/Cargo.toml index ada97860e..9e6367586 100644 --- a/deltachat-jsonrpc/Cargo.toml +++ b/deltachat-jsonrpc/Cargo.toml @@ -29,6 +29,7 @@ tokio = { version = "1.19.2" } # optional dependencies axum = { version = "0.5.9", optional = true, features = ["ws"] } env_logger = { version = "0.9.0", optional = true } +walkdir = "2.3.2" [dev-dependencies] tokio = { version = "1.19.2", features = ["full", "rt-multi-thread"] } diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index 97f50bad4..1ce7e6788 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -8,17 +8,23 @@ use deltachat::{ config::Config, contact::{may_be_valid_addr, Contact, ContactId, Origin}, context::get_info, - message::{delete_msgs, get_msg_info, markseen_msgs, Message, MessageState, MsgId, Viewtype}, + ephemeral::Timer, + imex, location, + message::{ + self, delete_msgs, get_msg_info, markseen_msgs, Message, MessageState, MsgId, Viewtype, + }, provider::get_provider_info, qr, qr_code_generator::get_securejoin_qr_svg, securejoin, + stock_str::StockMessage, webxdc::StatusUpdateSerial, }; use std::collections::BTreeMap; use std::sync::Arc; use std::{collections::HashMap, str::FromStr}; use tokio::sync::RwLock; +use walkdir::WalkDir; use yerpc::rpc; pub use deltachat::accounts::Accounts; @@ -38,10 +44,13 @@ use types::provider_info::ProviderInfo; use types::webxdc::WebxdcMessageInfo; use self::types::{ - chat::{BasicChat, MuteDuration}, + chat::{BasicChat, JSONRPCChatVisibility, MuteDuration}, + location::JsonrpcLocation, message::{MessageNotificationInfo, MessageSearchResult, MessageViewtype}, }; +use num_traits::FromPrimitive; + #[derive(Clone, Debug)] pub struct CommandApi { pub(crate) accounts: Arc>, @@ -145,6 +154,21 @@ impl CommandApi { } } + /// Get the combined filesize of an account in bytes + async fn get_account_file_size(&self, account_id: u32) -> Result { + let ctx = self.get_context(account_id).await?; + let dbfile = ctx.get_dbfile().metadata()?.len(); + let total_size = WalkDir::new(ctx.get_blobdir()) + .max_depth(2) + .into_iter() + .filter_map(|entry| entry.ok()) + .filter_map(|entry| entry.metadata().ok()) + .filter(|metadata| metadata.is_file()) + .fold(0, |acc, m| acc + m.len()); + + Ok(dbfile + total_size) + } + /// Returns provider for the given domain. /// /// This function looks up domain in offline database. @@ -233,6 +257,18 @@ impl CommandApi { Ok(result) } + async fn set_stock_strings(&self, strings: HashMap) -> Result<()> { + let accounts = self.accounts.read().await; + for (stock_id, stock_message) in strings { + if let Some(stock_id) = StockMessage::from_u32(stock_id) { + accounts + .set_stock_translation(stock_id, stock_message) + .await?; + } + } + Ok(()) + } + /// Configures this account with the currently set parameters. /// Setup the credential config before calling this. async fn configure(&self, account_id: u32) -> Result<()> { @@ -256,6 +292,38 @@ impl CommandApi { Ok(()) } + async fn export_self_keys( + &self, + account_id: u32, + path: String, + passphrase: Option, + ) -> Result<()> { + let ctx = self.get_context(account_id).await?; + imex::imex( + &ctx, + imex::ImexMode::ExportSelfKeys, + path.as_ref(), + passphrase, + ) + .await + } + + async fn import_self_keys( + &self, + account_id: u32, + path: String, + passphrase: Option, + ) -> Result<()> { + let ctx = self.get_context(account_id).await?; + imex::imex( + &ctx, + imex::ImexMode::ImportSelfKeys, + path.as_ref(), + passphrase, + ) + .await + } + /// Returns the message IDs of all _fresh_ messages of any chat. /// Typically used for implementing notification summaries /// or badge counters e.g. on the app icon. @@ -288,6 +356,20 @@ impl CommandApi { ChatId::new(chat_id).get_fresh_msg_cnt(&ctx).await } + /// Estimate the number of messages that will be deleted + /// by the set_config()-options `delete_device_after` or `delete_server_after`. + /// This is typically used to show the estimated impact to the user + /// before actually enabling deletion of old messages. + async fn estimate_auto_deletion_count( + &self, + account_id: u32, + from_server: bool, + seconds: i64, + ) -> Result { + let ctx = self.get_context(account_id).await?; + message::estimate_deletion_cnt(&ctx, from_server, seconds).await + } + // --------------------------------------------- // autocrypt // --------------------------------------------- @@ -606,6 +688,39 @@ impl CommandApi { .await } + async fn set_chat_visibility( + &self, + account_id: u32, + chat_id: u32, + visibility: JSONRPCChatVisibility, + ) -> Result<()> { + let ctx = self.get_context(account_id).await?; + + ChatId::new(chat_id) + .set_visibility(&ctx, visibility.into_core_type()) + .await + } + + async fn set_chat_ephemeral_timer( + &self, + account_id: u32, + chat_id: u32, + timer: u32, + ) -> Result<()> { + let ctx = self.get_context(account_id).await?; + ChatId::new(chat_id) + .set_ephemeral_timer(&ctx, Timer::from_u32(timer)) + .await + } + + async fn get_chat_ephemeral_timer(&self, account_id: u32, chat_id: u32) -> Result { + let ctx = self.get_context(account_id).await?; + Ok(ChatId::new(chat_id) + .get_ephemeral_timer(&ctx) + .await? + .to_u32()) + } + // for now only text messages, because we only used text messages in desktop thusfar async fn add_device_message( &self, @@ -1125,6 +1240,32 @@ impl CommandApi { ctx.get_connectivity_html().await } + // --------------------------------------------- + // locations + // --------------------------------------------- + + async fn get_locations( + &self, + account_id: u32, + chat_id: Option, + contact_id: Option, + timestamp_begin: i64, + timestamp_end: i64, + ) -> Result> { + let ctx = self.get_context(account_id).await?; + + let locations = location::get_range( + &ctx, + chat_id.map(ChatId::new), + contact_id, + timestamp_begin, + timestamp_end, + ) + .await?; + + Ok(locations.into_iter().map(|l| l.into()).collect()) + } + // --------------------------------------------- // webxdc // --------------------------------------------- diff --git a/deltachat-jsonrpc/src/api/types/chat.rs b/deltachat-jsonrpc/src/api/types/chat.rs index ad09a77b3..f63243112 100644 --- a/deltachat-jsonrpc/src/api/types/chat.rs +++ b/deltachat-jsonrpc/src/api/types/chat.rs @@ -1,7 +1,7 @@ use std::time::{Duration, SystemTime}; use anyhow::{anyhow, bail, Result}; -use deltachat::chat::{self, get_chat_contacts}; +use deltachat::chat::{self, get_chat_contacts, ChatVisibility}; use deltachat::chat::{Chat, ChatId}; use deltachat::constants::Chattype; use deltachat::contact::{Contact, ContactId}; @@ -193,3 +193,21 @@ impl MuteDuration { } } } + +#[derive(Clone, Serialize, Deserialize, TypeDef)] +#[serde(rename = "ChatVisibility")] +pub enum JSONRPCChatVisibility { + Normal, + Archived, + Pinned, +} + +impl JSONRPCChatVisibility { + pub fn into_core_type(self) -> ChatVisibility { + match self { + JSONRPCChatVisibility::Normal => ChatVisibility::Normal, + JSONRPCChatVisibility::Archived => ChatVisibility::Archived, + JSONRPCChatVisibility::Pinned => ChatVisibility::Pinned, + } + } +} diff --git a/deltachat-jsonrpc/src/api/types/location.rs b/deltachat-jsonrpc/src/api/types/location.rs new file mode 100644 index 000000000..374e408d8 --- /dev/null +++ b/deltachat-jsonrpc/src/api/types/location.rs @@ -0,0 +1,47 @@ +use deltachat::location::Location; +use serde::Serialize; +use typescript_type_def::TypeDef; + +#[derive(Serialize, TypeDef)] +#[serde(rename = "Location", rename_all = "camelCase")] +pub struct JsonrpcLocation { + pub location_id: u32, + pub is_independent: bool, + pub latitude: f64, + pub longitude: f64, + pub accuracy: f64, + pub timestamp: i64, + pub contact_id: u32, + pub msg_id: u32, + pub chat_id: u32, + pub marker: Option, +} + +impl From for JsonrpcLocation { + fn from(location: Location) -> Self { + let Location { + location_id, + independent, + latitude, + longitude, + accuracy, + timestamp, + contact_id, + msg_id, + chat_id, + marker, + } = location; + Self { + location_id, + is_independent: independent != 0, + latitude, + longitude, + accuracy, + timestamp, + contact_id: contact_id.to_u32(), + msg_id, + chat_id: chat_id.to_u32(), + marker, + } + } +} diff --git a/deltachat-jsonrpc/src/api/types/mod.rs b/deltachat-jsonrpc/src/api/types/mod.rs index 3d08d3ca6..6f344d498 100644 --- a/deltachat-jsonrpc/src/api/types/mod.rs +++ b/deltachat-jsonrpc/src/api/types/mod.rs @@ -6,6 +6,7 @@ pub mod account; pub mod chat; pub mod chat_list; pub mod contact; +pub mod location; pub mod message; pub mod provider_info; pub mod webxdc; diff --git a/deltachat-jsonrpc/typescript/generated/client.ts b/deltachat-jsonrpc/typescript/generated/client.ts index a7874c0b6..fa1e35302 100644 --- a/deltachat-jsonrpc/typescript/generated/client.ts +++ b/deltachat-jsonrpc/typescript/generated/client.ts @@ -73,6 +73,13 @@ export class RawClient { return (this._transport.request('get_account_info', [accountId] as RPC.Params)) as Promise; } + /** + * Get the combined filesize of an account in bytes + */ + public getAccountFileSize(accountId: T.U32): Promise { + return (this._transport.request('get_account_file_size', [accountId] as RPC.Params)) as Promise; + } + /** * Returns provider for the given domain. * @@ -135,6 +142,11 @@ export class RawClient { return (this._transport.request('batch_get_config', [accountId, keys] as RPC.Params)) as Promise>; } + + public setStockStrings(strings: Record): Promise { + return (this._transport.request('set_stock_strings', [strings] as RPC.Params)) as Promise; + } + /** * Configures this account with the currently set parameters. * Setup the credential config before calling this. @@ -150,6 +162,16 @@ export class RawClient { return (this._transport.request('stop_ongoing_process', [accountId] as RPC.Params)) as Promise; } + + public exportSelfKeys(accountId: T.U32, path: string, passphrase: (string|null)): Promise { + return (this._transport.request('export_self_keys', [accountId, path, passphrase] as RPC.Params)) as Promise; + } + + + public importSelfKeys(accountId: T.U32, path: string, passphrase: (string|null)): Promise { + return (this._transport.request('import_self_keys', [accountId, path, passphrase] as RPC.Params)) as Promise; + } + /** * Returns the message IDs of all _fresh_ messages of any chat. * Typically used for implementing notification summaries @@ -179,6 +201,16 @@ export class RawClient { return (this._transport.request('get_fresh_msg_cnt', [accountId, chatId] as RPC.Params)) as Promise; } + /** + * Estimate the number of messages that will be deleted + * by the set_config()-options `delete_device_after` or `delete_server_after`. + * This is typically used to show the estimated impact to the user + * before actually enabling deletion of old messages. + */ + public estimateAutoDeletionCount(accountId: T.U32, fromServer: boolean, seconds: T.I64): Promise { + return (this._transport.request('estimate_auto_deletion_count', [accountId, fromServer, seconds] as RPC.Params)) as Promise; + } + public autocryptInitiateKeyTransfer(accountId: T.U32): Promise { return (this._transport.request('autocrypt_initiate_key_transfer', [accountId] as RPC.Params)) as Promise; @@ -415,6 +447,21 @@ export class RawClient { } + public setChatVisibility(accountId: T.U32, chatId: T.U32, visibility: T.ChatVisibility): Promise { + return (this._transport.request('set_chat_visibility', [accountId, chatId, visibility] as RPC.Params)) as Promise; + } + + + public setChatEphemeralTimer(accountId: T.U32, chatId: T.U32, timer: T.U32): Promise { + return (this._transport.request('set_chat_ephemeral_timer', [accountId, chatId, timer] as RPC.Params)) as Promise; + } + + + public getChatEphemeralTimer(accountId: T.U32, chatId: T.U32): Promise { + return (this._transport.request('get_chat_ephemeral_timer', [accountId, chatId] as RPC.Params)) as Promise; + } + + public addDeviceMessage(accountId: T.U32, label: string, text: string): Promise { return (this._transport.request('add_device_message', [accountId, label, text] as RPC.Params)) as Promise; } @@ -716,6 +763,11 @@ export class RawClient { } + public getLocations(accountId: T.U32, chatId: (T.U32|null), contactId: (T.U32|null), timestampBegin: T.I64, timestampEnd: T.I64): Promise<(T.Location)[]> { + return (this._transport.request('get_locations', [accountId, chatId, contactId, timestampBegin, timestampEnd] as RPC.Params)) as Promise<(T.Location)[]>; + } + + public webxdcSendStatusUpdate(accountId: T.U32, instanceMsgId: T.U32, updateStr: string, description: string): Promise { return (this._transport.request('webxdc_send_status_update', [accountId, instanceMsgId, updateStr, description] as RPC.Params)) as Promise; } diff --git a/deltachat-jsonrpc/typescript/generated/types.ts b/deltachat-jsonrpc/typescript/generated/types.ts index 5930037f7..55146d3b4 100644 --- a/deltachat-jsonrpc/typescript/generated/types.ts +++ b/deltachat-jsonrpc/typescript/generated/types.ts @@ -2,11 +2,12 @@ export type U32=number; export type Account=(({"type":"Configured";}&{"id":U32;"displayName":(string|null);"addr":(string|null);"profileImage":(string|null);"color":string;})|({"type":"Unconfigured";}&{"id":U32;})); +export type U64=number; export type ProviderInfo={"beforeLoginHint":string;"overviewPage":string;"status":U32;}; export type Qr=(({"type":"askVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"askVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"fprOk";}&{"contact_id":U32;})|({"type":"fprMismatch";}&{"contact_id":(U32|null);})|({"type":"fprWithoutAddr";}&{"fingerprint":string;})|({"type":"account";}&{"domain":string;})|({"type":"webrtcInstance";}&{"domain":string;"instance_pattern":string;})|({"type":"addr";}&{"contact_id":U32;"draft":(string|null);})|({"type":"url";}&{"url":string;})|({"type":"text";}&{"text":string;})|({"type":"withdrawVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"withdrawVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"reviveVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"reviveVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"login";}&{"address":string;})); export type Usize=number; -export type ChatListEntry=[U32,U32]; export type I64=number; +export type ChatListEntry=[U32,U32]; export type ChatListItemFetchResult=(({"type":"ChatListItem";}&{"id":U32;"name":string;"avatarPath":(string|null);"color":string;"lastUpdated":(I64|null);"summaryText1":string;"summaryText2":string;"summaryStatus":U32;"isProtected":boolean;"isGroup":boolean;"freshMessageCounter":Usize;"isSelfTalk":boolean;"isDeviceTalk":boolean;"isSendingLocation":boolean;"isSelfInGroup":boolean;"isArchived":boolean;"isPinned":boolean;"isMuted":boolean;"isContactRequest":boolean; /** * true when chat is a broadcastlist @@ -49,6 +50,7 @@ export type BasicChat= * used when you only need the basic metadata of a chat like type, name, profile picture */ {"id":U32;"name":string;"isProtected":boolean;"profileImage":(string|null);"archived":boolean;"chatType":U32;"isUnpromoted":boolean;"isSelfTalk":boolean;"color":string;"isContactRequest":boolean;"isDeviceChat":boolean;"isMuted":boolean;}; +export type ChatVisibility=("Normal"|"Archived"|"Pinned"); export type MuteDuration=("NotMuted"|"Forever"|{"Until":I64;}); export type MessageQuote=(({"kind":"JustText";}&{"text":string;})|({"kind":"WithMessage";}&{"text":string;"messageId":U32;"authorDisplayName":string;"authorDisplayColor":string;"overrideSenderName":(string|null);"image":(string|null);"isForwarded":boolean;})); export type Viewtype=("Unknown"| @@ -97,7 +99,6 @@ export type Viewtype=("Unknown"| */ "Webxdc"); export type I32=number; -export type U64=number; export type WebxdcMessageInfo={ /** * The name of the app. @@ -148,4 +149,5 @@ export type MessageNotificationInfo={"id":U32;"chatId":U32;"accountId":U32;"imag "summaryText":string;}; 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 __AllTyps=[string,boolean,Record,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],U32,Account,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,U32,null,U32,null,U32,(U32)[],U32,U32,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,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,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,Message,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,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)],null,U32,U32,U32,string,U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,(U32)[],U32,null,U32,U32,null,U32,U32,(Message|null),U32,U32,U32,U32,string,U32,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 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)[],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,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,Message,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,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)],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,null,U32,U32,(Message|null),U32,U32,U32,U32,string,U32,U32,U32,U32,(string|null),(string|null),([F64,F64]|null),(U32|null),[U32,Message],U32,U32,(string|null),(string|null),(U32|null),null]; diff --git a/src/accounts.rs b/src/accounts.rs index 75c7e70bf..0ba7d0f69 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -26,7 +26,7 @@ pub struct Accounts { /// /// This way changing a translation for one context automatically /// changes it for all other contexts. - stockstrings: StockStrings, + pub(crate) stockstrings: StockStrings, } impl Accounts { diff --git a/src/stock_str.rs b/src/stock_str.rs index 82ee6c465..f1b7894db 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -8,6 +8,7 @@ use strum::EnumProperty as EnumPropertyTrait; use strum_macros::EnumProperty; use tokio::sync::RwLock; +use crate::accounts::Accounts; use crate::blob::BlobObject; use crate::chat::{self, Chat, ChatId, ProtectionStatus}; use crate::config::Config; @@ -1302,6 +1303,17 @@ impl Context { } } +impl Accounts { + /// Set the stock string for the [StockMessage]. + /// + pub async fn set_stock_translation(&self, id: StockMessage, stockstring: String) -> Result<()> { + self.stockstrings + .set_stock_translation(id, stockstring) + .await?; + Ok(()) + } +} + #[cfg(test)] mod tests { use num_traits::ToPrimitive;