mirror of
https://github.com/chatmail/core.git
synced 2026-05-02 04:46:29 +03:00
Merge pull request #1735 from deltachat/invite-call-api
add APIs for videochats
This commit is contained in:
@@ -318,11 +318,11 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* The library uses the `media_quality` setting to use different defaults
|
* The library uses the `media_quality` setting to use different defaults
|
||||||
* for recoding images sent with type DC_MSG_IMAGE.
|
* for recoding images sent with type DC_MSG_IMAGE.
|
||||||
* If needed, recoding other file types is up to the UI.
|
* If needed, recoding other file types is up to the UI.
|
||||||
* - `basic_web_rtc_instance` = address to webrtc signaling server (https://github.com/cracker0dks/basicwebrtc)
|
* - `webrtc_instance` = webrtc instance to use for videochats in the form
|
||||||
* that should be used for opening video hangouts.
|
* `[basicwebrtc:]https://example.com/subdir#roomname=$ROOM`
|
||||||
* This property is only used in the UIs not by the core itself.
|
* if the url is prefixed by `basicwebrtc`, the server is assumed to be of the type
|
||||||
* Format: https://example.com/subdir
|
* https://github.com/cracker0dks/basicwebrtc which some UIs have native support for.
|
||||||
* The other properties that are needed for a call such as the roomname will be set by the client in the anchor part of the url.
|
* If no type is prefixed, the videochat is handled completely in a browser.
|
||||||
*
|
*
|
||||||
* If you want to retrieve a value, use dc_get_config().
|
* If you want to retrieve a value, use dc_get_config().
|
||||||
*
|
*
|
||||||
@@ -839,6 +839,41 @@ uint32_t dc_send_msg_sync (dc_context_t* context, uint32
|
|||||||
uint32_t dc_send_text_msg (dc_context_t* context, uint32_t chat_id, const char* text_to_send);
|
uint32_t dc_send_text_msg (dc_context_t* context, uint32_t chat_id, const char* text_to_send);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send invitation to a videochat.
|
||||||
|
*
|
||||||
|
* This function reads the `webrtc_instance` config value,
|
||||||
|
* may check that the server is working in some way
|
||||||
|
* and creates a unique room for this chat, if needed doing a TOKEN roundtrip for that.
|
||||||
|
*
|
||||||
|
* After that, the function sends out a message that contains information to join the room:
|
||||||
|
*
|
||||||
|
* - To allow non-delta-clients to join the chat,
|
||||||
|
* the message contains a text-area with some descriptive text
|
||||||
|
* and a url that can be opened in a supported browser to join the videochat
|
||||||
|
*
|
||||||
|
* - delta-clients can get all information needed from
|
||||||
|
* the message object, using eg.
|
||||||
|
* dc_msg_get_videochat_url() and check dc_msg_get_viewtype() for DC_MSG_VIDEOCHAT_INVITATION
|
||||||
|
*
|
||||||
|
* dc_send_videochat_invitation() is blocking and may take a while,
|
||||||
|
* so the UIs will typically call the function from within a thread.
|
||||||
|
* Moreover, UIs will typically enter the room directly without an additional click on the message,
|
||||||
|
* for this purpose, the function returns the message-id directly.
|
||||||
|
*
|
||||||
|
* As for other messages sent, this function
|
||||||
|
* sends the event #DC_EVENT_MSGS_CHANGED on succcess, the message has a delivery state, and so on.
|
||||||
|
* The recipient will get noticed by the call as usual by DC_EVENT_INCOMING_MSG or DC_EVENT_MSGS_CHANGED,
|
||||||
|
* However, UIs might some things differently, eg. play a different sound.
|
||||||
|
*
|
||||||
|
* @param context The context object.
|
||||||
|
* @param chat_id The chat to start a videochat for.
|
||||||
|
* @return The id if the message sent out
|
||||||
|
* or 0 for errors.
|
||||||
|
*/
|
||||||
|
uint32_t dc_send_videochat_invitation (dc_context_t* context, uint32_t chat_id);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a draft for a chat in the database.
|
* Save a draft for a chat in the database.
|
||||||
*
|
*
|
||||||
@@ -3240,6 +3275,55 @@ int dc_msg_is_setupmessage (const dc_msg_t* msg);
|
|||||||
char* dc_msg_get_setupcodebegin (const dc_msg_t* msg);
|
char* dc_msg_get_setupcodebegin (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get url of a videochat invitation.
|
||||||
|
*
|
||||||
|
* Videochat invitations are sent out using dc_send_videochat_invitation()
|
||||||
|
* and dc_msg_get_viewtype() returns #DC_MSG_VIDEOCHAT_INVITATION for such invitations.
|
||||||
|
*
|
||||||
|
* @param msg The message object.
|
||||||
|
* @return If the message contains a videochat invitation,
|
||||||
|
* the url of the invitation is returned.
|
||||||
|
* If the message is no videochat invitation, NULL is returned.
|
||||||
|
* Must be released using dc_str_unref() when done.
|
||||||
|
*/
|
||||||
|
char* dc_msg_get_videochat_url (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get type of videochat.
|
||||||
|
*
|
||||||
|
* Calling this functions only makes sense for messages of type #DC_MSG_VIDEOCHAT_INVITATION,
|
||||||
|
* in this case, if "basic webrtc" as of https://github.com/cracker0dks/basicwebrtc was used to initiate the videochat,
|
||||||
|
* dc_msg_get_videochat_type() returns DC_VIDEOCHATTYPE_BASICWEBRTC.
|
||||||
|
* "basic webrtc" videochat may be processed natively by the app
|
||||||
|
* whereas for other urls just the browser is opened.
|
||||||
|
*
|
||||||
|
* The videochat-url can be retrieved using dc_msg_get_videochat_url().
|
||||||
|
* To check if a message is a videochat invitation at all, check the message type for #DC_MSG_VIDEOCHAT_INVITATION.
|
||||||
|
*
|
||||||
|
* @param msg The message object.
|
||||||
|
* @return Type of the videochat as of DC_VIDEOCHATTYPE_BASICWEBRTC or DC_VIDEOCHATTYPE_UNKNOWN.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ~~~
|
||||||
|
* if (dc_msg_get_viewtype(msg) == DC_MSG_VIDEOCHAT_INVITATION) {
|
||||||
|
* if (dc_msg_get_videochat_type(msg) == DC_VIDEOCHATTYPE_BASICWEBRTC) {
|
||||||
|
* // videochat invitation that we ship a client for
|
||||||
|
* } else {
|
||||||
|
* // use browser for videochat, just open the url
|
||||||
|
* }
|
||||||
|
* } else {
|
||||||
|
* // not a videochat invitation
|
||||||
|
* }
|
||||||
|
* ~~~
|
||||||
|
*/
|
||||||
|
int dc_msg_get_videochat_type (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
#define DC_VIDEOCHATTYPE_UNKNOWN 0
|
||||||
|
#define DC_VIDEOCHATTYPE_BASICWEBRTC 1
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the text of a message object.
|
* Set the text of a message object.
|
||||||
* This does not alter any information in the database; this may be done by dc_send_msg() later.
|
* This does not alter any information in the database; this may be done by dc_send_msg() later.
|
||||||
@@ -3775,6 +3859,18 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*/
|
*/
|
||||||
#define DC_MSG_FILE 60
|
#define DC_MSG_FILE 60
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message indicating an incoming or outgoing videochat.
|
||||||
|
* The message was created via dc_send_videochat_invitation() on this or a remote device.
|
||||||
|
*
|
||||||
|
* Typically, such messages are rendered differently by the UIs,
|
||||||
|
* eg. contain a button to join the videochat.
|
||||||
|
* The url for joining can be retrieved using dc_msg_get_videochat_url().
|
||||||
|
*/
|
||||||
|
#define DC_MSG_VIDEOCHAT_INVITATION 70
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
@@ -4517,8 +4613,10 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
#define DC_STR_EPHEMERAL_DAY 79
|
#define DC_STR_EPHEMERAL_DAY 79
|
||||||
#define DC_STR_EPHEMERAL_WEEK 80
|
#define DC_STR_EPHEMERAL_WEEK 80
|
||||||
#define DC_STR_EPHEMERAL_FOUR_WEEKS 81
|
#define DC_STR_EPHEMERAL_FOUR_WEEKS 81
|
||||||
|
#define DC_STR_VIDEOCHAT_INVITATION 82
|
||||||
|
#define DC_STR_VIDEOCHAT_INVITE_MSG_BODY 83
|
||||||
|
|
||||||
#define DC_STR_COUNT 81
|
#define DC_STR_COUNT 83
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @}
|
* @}
|
||||||
|
|||||||
@@ -710,6 +710,25 @@ pub unsafe extern "C" fn dc_send_text_msg(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_send_videochat_invitation(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
chat_id: u32,
|
||||||
|
) -> u32 {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_send_videochat_invitation()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let ctx = &*context;
|
||||||
|
|
||||||
|
block_on(async move {
|
||||||
|
chat::send_videochat_invitation(&ctx, ChatId::new(chat_id))
|
||||||
|
.await
|
||||||
|
.map(|msg_id| msg_id.to_u32())
|
||||||
|
.unwrap_or_log_default(&ctx, "Failed to send video chat invitation")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_set_draft(
|
pub unsafe extern "C" fn dc_set_draft(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -2820,6 +2839,31 @@ pub unsafe extern "C" fn dc_msg_is_setupmessage(msg: *mut dc_msg_t) -> libc::c_i
|
|||||||
ffi_msg.message.is_setupmessage().into()
|
ffi_msg.message.is_setupmessage().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_msg_get_videochat_url(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||||
|
if msg.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_msg_get_videochat_url()");
|
||||||
|
return "".strdup();
|
||||||
|
}
|
||||||
|
let ffi_msg = &*msg;
|
||||||
|
|
||||||
|
ffi_msg
|
||||||
|
.message
|
||||||
|
.get_videochat_url()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.strdup()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_msg_get_videochat_type(msg: *mut dc_msg_t) -> libc::c_int {
|
||||||
|
if msg.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_msg_get_videochat_type()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let ffi_msg = &*msg;
|
||||||
|
ffi_msg.message.get_videochat_type().unwrap_or_default() as i32
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
|||||||
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
|
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
|
||||||
let msgtext = msg.get_text();
|
let msgtext = msg.get_text();
|
||||||
println!(
|
println!(
|
||||||
"{}{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
|
"{}{}{}{}: {} (Contact#{}): {} {}{}{}{}{}{} [{}]",
|
||||||
prefix.as_ref(),
|
prefix.as_ref(),
|
||||||
msg.get_id(),
|
msg.get_id(),
|
||||||
if msg.get_showpadlock() { "🔒" } else { "" },
|
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||||
@@ -202,6 +202,15 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
|||||||
"[FRESH]"
|
"[FRESH]"
|
||||||
},
|
},
|
||||||
if msg.is_info() { "[INFO]" } else { "" },
|
if msg.is_info() { "[INFO]" } else { "" },
|
||||||
|
if msg.get_viewtype() == Viewtype::VideochatInvitation {
|
||||||
|
format!(
|
||||||
|
"[VIDEOCHAT-INVITATION: {}, type={}]",
|
||||||
|
msg.get_videochat_url().unwrap_or_default(),
|
||||||
|
msg.get_videochat_type().unwrap_or_default()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
if msg.is_forwarded() {
|
if msg.is_forwarded() {
|
||||||
"[FORWARDED]"
|
"[FORWARDED]"
|
||||||
} else {
|
} else {
|
||||||
@@ -359,6 +368,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
send-garbage\n\
|
send-garbage\n\
|
||||||
sendimage <file> [<text>]\n\
|
sendimage <file> [<text>]\n\
|
||||||
sendfile <file> [<text>]\n\
|
sendfile <file> [<text>]\n\
|
||||||
|
videochat\n\
|
||||||
draft [<text>]\n\
|
draft [<text>]\n\
|
||||||
devicemsg <text>\n\
|
devicemsg <text>\n\
|
||||||
listmedia\n\
|
listmedia\n\
|
||||||
@@ -808,6 +818,10 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
}
|
}
|
||||||
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
||||||
}
|
}
|
||||||
|
"videochat" => {
|
||||||
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
|
chat::send_videochat_invitation(&context, sel_chat.as_ref().unwrap().get_id()).await?;
|
||||||
|
}
|
||||||
"listmsgs" => {
|
"listmsgs" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <query> missing.");
|
ensure!(!arg1.is_empty(), "Argument <query> missing.");
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ const DB_COMMANDS: [&str; 9] = [
|
|||||||
"housekeeping",
|
"housekeeping",
|
||||||
];
|
];
|
||||||
|
|
||||||
const CHAT_COMMANDS: [&str; 26] = [
|
const CHAT_COMMANDS: [&str; 27] = [
|
||||||
"listchats",
|
"listchats",
|
||||||
"listarchived",
|
"listarchived",
|
||||||
"chat",
|
"chat",
|
||||||
@@ -178,6 +178,7 @@ const CHAT_COMMANDS: [&str; 26] = [
|
|||||||
"send",
|
"send",
|
||||||
"sendimage",
|
"sendimage",
|
||||||
"sendfile",
|
"sendfile",
|
||||||
|
"videochat",
|
||||||
"draft",
|
"draft",
|
||||||
"listmedia",
|
"listmedia",
|
||||||
"archive",
|
"archive",
|
||||||
|
|||||||
41
src/chat.rs
41
src/chat.rs
@@ -1374,11 +1374,12 @@ pub(crate) fn msgtype_has_file(msgtype: Viewtype) -> bool {
|
|||||||
Viewtype::Voice => true,
|
Viewtype::Voice => true,
|
||||||
Viewtype::Video => true,
|
Viewtype::Video => true,
|
||||||
Viewtype::File => true,
|
Viewtype::File => true,
|
||||||
|
Viewtype::VideochatInvitation => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
|
||||||
if msg.viewtype == Viewtype::Text {
|
if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::VideochatInvitation {
|
||||||
// the caller should check if the message text is empty
|
// the caller should check if the message text is empty
|
||||||
} else if msgtype_has_file(msg.viewtype) {
|
} else if msgtype_has_file(msg.viewtype) {
|
||||||
let blob = msg
|
let blob = msg
|
||||||
@@ -1611,6 +1612,44 @@ pub async fn send_text_msg(
|
|||||||
send_msg(context, chat_id, &mut msg).await
|
send_msg(context, chat_id, &mut msg).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Result<MsgId, Error> {
|
||||||
|
ensure!(
|
||||||
|
!chat_id.is_special(),
|
||||||
|
"video chat invitation cannot be sent to special chat: {}",
|
||||||
|
chat_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let instance = if let Some(instance) = context.get_config(Config::WebrtcInstance).await {
|
||||||
|
if !instance.is_empty() {
|
||||||
|
instance
|
||||||
|
} else {
|
||||||
|
bail!("webrtc_instance is empty");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("webrtc_instance not set");
|
||||||
|
};
|
||||||
|
|
||||||
|
let room = dc_create_id();
|
||||||
|
|
||||||
|
let instance = if instance.contains("$ROOM") {
|
||||||
|
instance.replace("$ROOM", &room)
|
||||||
|
} else {
|
||||||
|
format!("{}{}", instance, room)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut msg = Message::new(Viewtype::VideochatInvitation);
|
||||||
|
msg.param.set(Param::WebrtcRoom, &instance);
|
||||||
|
msg.text = Some(
|
||||||
|
context
|
||||||
|
.stock_string_repl_str(
|
||||||
|
StockMessage::VideochatInviteMsgBody,
|
||||||
|
Message::parse_webrtc_instance(&instance).1,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
);
|
||||||
|
send_msg(context, chat_id, &mut msg).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_chat_msgs(
|
pub async fn get_chat_msgs(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
chat_id: ChatId,
|
chat_id: ChatId,
|
||||||
|
|||||||
@@ -123,12 +123,8 @@ pub enum Config {
|
|||||||
/// because we do not want to send a second warning)
|
/// because we do not want to send a second warning)
|
||||||
NotifyAboutWrongPw,
|
NotifyAboutWrongPw,
|
||||||
|
|
||||||
/// address to webrtc signaling server (https://github.com/cracker0dks/basicwebrtc)
|
/// address to webrtc instance to use for videochats
|
||||||
/// that should be used for opening video hangouts.
|
WebrtcInstance,
|
||||||
/// This property is only used in the UIs not by the core itself.
|
|
||||||
/// Format: https://example.com/subdir
|
|
||||||
/// The other properties that are needed for a call such as the roomname will be set by the client in the anchor part of the url.
|
|
||||||
BasicWebRTCInstance,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
|||||||
@@ -84,6 +84,19 @@ impl Default for KeyGenType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)]
|
||||||
|
#[repr(i8)]
|
||||||
|
pub enum VideochatType {
|
||||||
|
Unknown = 0,
|
||||||
|
BasicWebrtc = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VideochatType {
|
||||||
|
fn default() -> Self {
|
||||||
|
VideochatType::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
|
pub const DC_HANDSHAKE_CONTINUE_NORMAL_PROCESSING: i32 = 0x01;
|
||||||
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
|
pub const DC_HANDSHAKE_STOP_NORMAL_PROCESSING: i32 = 0x02;
|
||||||
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
|
pub const DC_HANDSHAKE_ADD_DELETE_JOB: i32 = 0x04;
|
||||||
@@ -296,6 +309,9 @@ pub enum Viewtype {
|
|||||||
/// The file is set via dc_msg_set_file()
|
/// The file is set via dc_msg_set_file()
|
||||||
/// and retrieved via dc_msg_get_file().
|
/// and retrieved via dc_msg_get_file().
|
||||||
File = 60,
|
File = 60,
|
||||||
|
|
||||||
|
/// Message is an invitation to a videochat.
|
||||||
|
VideochatInvitation = 70,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Viewtype {
|
impl Default for Viewtype {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ pub enum HeaderDef {
|
|||||||
ChatContent,
|
ChatContent,
|
||||||
ChatDuration,
|
ChatDuration,
|
||||||
ChatDispositionNotificationTo,
|
ChatDispositionNotificationTo,
|
||||||
|
ChatWebrtcRoom,
|
||||||
Autocrypt,
|
Autocrypt,
|
||||||
AutocryptSetupMessage,
|
AutocryptSetupMessage,
|
||||||
SecureJoin,
|
SecureJoin,
|
||||||
|
|||||||
@@ -638,6 +638,39 @@ impl Message {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// split a webrtc_instance as defined by the corresponding config-value into a type and a url
|
||||||
|
pub fn parse_webrtc_instance(instance: &str) -> (VideochatType, String) {
|
||||||
|
let mut split = instance.splitn(2, ':');
|
||||||
|
let type_str = split.next().unwrap_or_default().to_lowercase();
|
||||||
|
let url = split.next();
|
||||||
|
if type_str == "basicwebrtc" {
|
||||||
|
(
|
||||||
|
VideochatType::BasicWebrtc,
|
||||||
|
url.unwrap_or_default().to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(VideochatType::Unknown, instance.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_videochat_url(&self) -> Option<String> {
|
||||||
|
if self.viewtype == Viewtype::VideochatInvitation {
|
||||||
|
if let Some(instance) = self.param.get(Param::WebrtcRoom) {
|
||||||
|
return Some(Message::parse_webrtc_instance(instance).1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_videochat_type(&self) -> Option<VideochatType> {
|
||||||
|
if self.viewtype == Viewtype::VideochatInvitation {
|
||||||
|
if let Some(instance) = self.param.get(Param::WebrtcRoom) {
|
||||||
|
return Some(Message::parse_webrtc_instance(instance).0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: Option<String>) {
|
pub fn set_text(&mut self, text: Option<String>) {
|
||||||
self.text = text;
|
self.text = text;
|
||||||
}
|
}
|
||||||
@@ -1241,6 +1274,13 @@ pub async fn get_summarytext_by_raw(
|
|||||||
format!("{} – {}", label, file_name)
|
format!("{} – {}", label, file_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Viewtype::VideochatInvitation => {
|
||||||
|
append_text = false;
|
||||||
|
context
|
||||||
|
.stock_str(StockMessage::VideochatInvitation)
|
||||||
|
.await
|
||||||
|
.into_owned()
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if param.get_cmd() != SystemMessage::LocationOnly {
|
if param.get_cmd() != SystemMessage::LocationOnly {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
@@ -1799,4 +1839,19 @@ mod tests {
|
|||||||
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
|
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_parse_webrtc_instance() {
|
||||||
|
let (webrtc_type, url) = Message::parse_webrtc_instance("basicwebrtc:https://foo/bar");
|
||||||
|
assert_eq!(webrtc_type, VideochatType::BasicWebrtc);
|
||||||
|
assert_eq!(url, "https://foo/bar");
|
||||||
|
|
||||||
|
let (webrtc_type, url) = Message::parse_webrtc_instance("bAsIcwEbrTc:url");
|
||||||
|
assert_eq!(webrtc_type, VideochatType::BasicWebrtc);
|
||||||
|
assert_eq!(url, "url");
|
||||||
|
|
||||||
|
let (webrtc_type, url) = Message::parse_webrtc_instance("https://foo/bar?key=val#key=val");
|
||||||
|
assert_eq!(webrtc_type, VideochatType::Unknown);
|
||||||
|
assert_eq!(url, "https://foo/bar?key=val#key=val");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -875,6 +875,19 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
|||||||
|
|
||||||
if self.msg.viewtype == Viewtype::Sticker {
|
if self.msg.viewtype == Viewtype::Sticker {
|
||||||
protected_headers.push(Header::new("Chat-Content".into(), "sticker".into()));
|
protected_headers.push(Header::new("Chat-Content".into(), "sticker".into()));
|
||||||
|
} else if self.msg.viewtype == Viewtype::VideochatInvitation {
|
||||||
|
protected_headers.push(Header::new(
|
||||||
|
"Chat-Content".into(),
|
||||||
|
"videochat-invitation".into(),
|
||||||
|
));
|
||||||
|
protected_headers.push(Header::new(
|
||||||
|
"Chat-Webrtc-Room".into(),
|
||||||
|
self.msg
|
||||||
|
.param
|
||||||
|
.get(Param::WebrtcRoom)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.msg.viewtype == Viewtype::Voice
|
if self.msg.viewtype == Viewtype::Voice
|
||||||
|
|||||||
@@ -242,6 +242,19 @@ impl MimeMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_videochat_headers(&mut self) {
|
||||||
|
if let Some(value) = self.get(HeaderDef::ChatContent).cloned() {
|
||||||
|
if value == "videochat-invitation" {
|
||||||
|
let instance = self.get(HeaderDef::ChatWebrtcRoom).cloned();
|
||||||
|
if let Some(part) = self.parts.first_mut() {
|
||||||
|
part.typ = Viewtype::VideochatInvitation;
|
||||||
|
part.param
|
||||||
|
.set(Param::WebrtcRoom, instance.unwrap_or_default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Squashes mutlipart chat messages with attachment into single-part messages.
|
/// Squashes mutlipart chat messages with attachment into single-part messages.
|
||||||
///
|
///
|
||||||
/// Delta Chat sends attachments, such as images, in two-part messages, with the first message
|
/// Delta Chat sends attachments, such as images, in two-part messages, with the first message
|
||||||
@@ -314,6 +327,7 @@ impl MimeMessage {
|
|||||||
fn parse_headers(&mut self, context: &Context) -> Result<()> {
|
fn parse_headers(&mut self, context: &Context) -> Result<()> {
|
||||||
self.parse_system_message_headers(context)?;
|
self.parse_system_message_headers(context)?;
|
||||||
self.parse_avatar_headers();
|
self.parse_avatar_headers();
|
||||||
|
self.parse_videochat_headers();
|
||||||
self.squash_attachment_parts();
|
self.squash_attachment_parts();
|
||||||
|
|
||||||
if let Some(ref subject) = self.get_subject() {
|
if let Some(ref subject) = self.get_subject() {
|
||||||
@@ -1449,6 +1463,28 @@ mod tests {
|
|||||||
assert!(mimeparser.group_avatar.unwrap().is_change());
|
assert!(mimeparser.group_avatar.unwrap().is_change());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_mimeparser_with_videochat() {
|
||||||
|
let t = TestContext::new().await;
|
||||||
|
|
||||||
|
let raw = include_bytes!("../test-data/message/videochat_invitation.eml");
|
||||||
|
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap();
|
||||||
|
assert_eq!(mimeparser.parts.len(), 1);
|
||||||
|
assert_eq!(mimeparser.parts[0].typ, Viewtype::VideochatInvitation);
|
||||||
|
assert_eq!(
|
||||||
|
mimeparser.parts[0]
|
||||||
|
.param
|
||||||
|
.get(Param::WebrtcRoom)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
"https://example.org/p2p/?roomname=6HiduoAn4xN"
|
||||||
|
);
|
||||||
|
assert!(mimeparser.parts[0]
|
||||||
|
.msg
|
||||||
|
.contains("https://example.org/p2p/?roomname=6HiduoAn4xN"));
|
||||||
|
assert_eq!(mimeparser.user_avatar, None);
|
||||||
|
assert_eq!(mimeparser.group_avatar, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_mimeparser_message_kml() {
|
async fn test_mimeparser_message_kml() {
|
||||||
let context = TestContext::new().await;
|
let context = TestContext::new().await;
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ pub enum Param {
|
|||||||
/// For Messages
|
/// For Messages
|
||||||
AttachGroupImage = b'A',
|
AttachGroupImage = b'A',
|
||||||
|
|
||||||
|
/// For Messages
|
||||||
|
WebrtcRoom = b'V',
|
||||||
|
|
||||||
/// For Messages: space-separated list of messaged IDs of forwarded copies.
|
/// For Messages: space-separated list of messaged IDs of forwarded copies.
|
||||||
///
|
///
|
||||||
/// This is used when a [crate::message::Message] is in the
|
/// This is used when a [crate::message::Message] is in the
|
||||||
|
|||||||
@@ -210,6 +210,12 @@ pub enum StockMessage {
|
|||||||
|
|
||||||
#[strum(props(fallback = "Message deletion timer is set to 4 weeks."))]
|
#[strum(props(fallback = "Message deletion timer is set to 4 weeks."))]
|
||||||
MsgEphemeralTimerFourWeeks = 81,
|
MsgEphemeralTimerFourWeeks = 81,
|
||||||
|
|
||||||
|
#[strum(props(fallback = "Video chat invitation"))]
|
||||||
|
VideochatInvitation = 82,
|
||||||
|
|
||||||
|
#[strum(props(fallback = "You are invited to a video chat, click %1$s to join."))]
|
||||||
|
VideochatInviteMsgBody = 83,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
15
test-data/message/videochat_invitation.eml
Normal file
15
test-data/message/videochat_invitation.eml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Subject: Message from user
|
||||||
|
Message-ID: <Mr.f1O61111evx.ikocf333353@example.org>
|
||||||
|
Date: Mon, 20 Jul 2020 14:28:30 +0000
|
||||||
|
X-Mailer: Delta Chat Core 1.40.0/CLI
|
||||||
|
Chat-Version: 1.0
|
||||||
|
Chat-Content: videochat-invitation
|
||||||
|
Chat-Webrtc-Room: https://example.org/p2p/?roomname=6HiduoAn4xN
|
||||||
|
To: <tunis3@example.org>
|
||||||
|
From: "=?utf-8?q??=" <tunis4@example.org>
|
||||||
|
|
||||||
|
You are invited to an videochat, click https://example.org/p2p/?roomname=6HiduoAn4xN to join.
|
||||||
|
|
||||||
|
--
|
||||||
|
Sent with my Delta Chat Messenger: https://delta.chat
|
||||||
Reference in New Issue
Block a user