mirror of
https://github.com/chatmail/core.git
synced 2026-05-20 07:16:31 +03:00
Use Auto-Submitted: auto-generated header to identify bots
New `dc_msg_is_bot()` C API and corresponding `Message.is_bot()` Python API can be used to check if incoming message is sent by a bot, e.g. to avoid two echo bots replying indefinitely to each other. "Bot" flag is not set for outgoing messages, but may be set for BCC-self messages. For now documentation says that `dc_msg_is_bot()` return value is unspecified for outgoing messages. It can be better specified later if needed for specific applications, e.g. sharing an account with a helper bot.
This commit is contained in:
@@ -340,7 +340,9 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* https://github.com/cracker0dks/basicwebrtc which some UIs have native support for.
|
* https://github.com/cracker0dks/basicwebrtc which some UIs have native support for.
|
||||||
* The type `jitsi:` may be handled by external apps.
|
* The type `jitsi:` may be handled by external apps.
|
||||||
* If no type is prefixed, the videochat is handled completely in a browser.
|
* If no type is prefixed, the videochat is handled completely in a browser.
|
||||||
* - `bot` = Set to "1" if this is a bot. E.g. prevents adding the "Device messages" and "Saved messages" chats.
|
* - `bot` = Set to "1" if this is a bot.
|
||||||
|
* Prevents adding the "Device messages" and "Saved messages" chats,
|
||||||
|
* adds Auto-Submitted header to outgoing messages.
|
||||||
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
|
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
|
||||||
* 0=do not fetch existing messages on configure.
|
* 0=do not fetch existing messages on configure.
|
||||||
* In both cases, existing recipients are added to the contact database.
|
* In both cases, existing recipients are added to the contact database.
|
||||||
@@ -3494,6 +3496,16 @@ int dc_msg_get_duration (const dc_msg_t* msg);
|
|||||||
*/
|
*/
|
||||||
int dc_msg_get_showpadlock (const dc_msg_t* msg);
|
int dc_msg_get_showpadlock (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if incoming message is a bot message, i.e. automatically submitted.
|
||||||
|
*
|
||||||
|
* Return value for outgoing messages is unspecified.
|
||||||
|
*
|
||||||
|
* @memberof dc_msg_t
|
||||||
|
* @param msg The message object.
|
||||||
|
* @return 1=message is submitted automatically, 0=message is not automatically submitted.
|
||||||
|
*/
|
||||||
|
int dc_msg_is_bot (const dc_msg_t* msg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ephemeral timer duration for message.
|
* Get ephemeral timer duration for message.
|
||||||
|
|||||||
@@ -2861,6 +2861,16 @@ pub unsafe extern "C" fn dc_msg_get_showpadlock(msg: *mut dc_msg_t) -> libc::c_i
|
|||||||
ffi_msg.message.get_showpadlock() as libc::c_int
|
ffi_msg.message.get_showpadlock() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_msg_is_bot(msg: *mut dc_msg_t) -> libc::c_int {
|
||||||
|
if msg.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_msg_is_bot()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let ffi_msg = &*msg;
|
||||||
|
ffi_msg.message.is_bot() as libc::c_int
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_msg_get_ephemeral_timer(msg: *mut dc_msg_t) -> u32 {
|
pub unsafe extern "C" fn dc_msg_get_ephemeral_timer(msg: *mut dc_msg_t) -> u32 {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
|
|||||||
@@ -141,6 +141,10 @@ class Message(object):
|
|||||||
""" return True if this message was encrypted. """
|
""" return True if this message was encrypted. """
|
||||||
return bool(lib.dc_msg_get_showpadlock(self._dc_msg))
|
return bool(lib.dc_msg_get_showpadlock(self._dc_msg))
|
||||||
|
|
||||||
|
def is_bot(self):
|
||||||
|
""" return True if this message is submitted automatically. """
|
||||||
|
return bool(lib.dc_msg_is_bot(self._dc_msg))
|
||||||
|
|
||||||
def is_forwarded(self):
|
def is_forwarded(self):
|
||||||
""" return True if this message was forwarded. """
|
""" return True if this message was forwarded. """
|
||||||
return bool(lib.dc_msg_is_forwarded(self._dc_msg))
|
return bool(lib.dc_msg_is_forwarded(self._dc_msg))
|
||||||
|
|||||||
@@ -1430,6 +1430,33 @@ class TestOnlineAccount:
|
|||||||
# Majority prefers encryption now
|
# Majority prefers encryption now
|
||||||
assert msg5.is_encrypted()
|
assert msg5.is_encrypted()
|
||||||
|
|
||||||
|
def test_bot(self, acfactory, lp):
|
||||||
|
"""Test that bot messages can be identified as such"""
|
||||||
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
ac1.set_config("bot", "0")
|
||||||
|
ac2.set_config("bot", "1")
|
||||||
|
|
||||||
|
lp.sec("ac1: create chat with ac2")
|
||||||
|
chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||||
|
|
||||||
|
lp.sec("sending a message from ac1 to ac2")
|
||||||
|
text1 = "hello"
|
||||||
|
chat.send_text(text1)
|
||||||
|
|
||||||
|
lp.sec("wait for ac2 to receive a message")
|
||||||
|
msg_in = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert msg_in.text == text1
|
||||||
|
assert not msg_in.is_bot()
|
||||||
|
|
||||||
|
lp.sec("sending a message from ac2 to ac1")
|
||||||
|
text2 = "reply"
|
||||||
|
msg_in.chat.send_text(text2)
|
||||||
|
|
||||||
|
lp.sec("wait for ac1 to receive a message")
|
||||||
|
msg_in = ac1._evtracker.wait_next_incoming_message()
|
||||||
|
assert msg_in.text == text2
|
||||||
|
assert msg_in.is_bot()
|
||||||
|
|
||||||
def test_quote_encrypted(self, acfactory, lp):
|
def test_quote_encrypted(self, acfactory, lp):
|
||||||
"""Test that replies to encrypted messages with quotes are encrypted."""
|
"""Test that replies to encrypted messages with quotes are encrypted."""
|
||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
|
|||||||
@@ -601,6 +601,11 @@ impl Message {
|
|||||||
self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
|
self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if message is Auto-Submitted.
|
||||||
|
pub fn is_bot(&self) -> bool {
|
||||||
|
self.param.get_bool(Param::Bot).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
|
pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
|
||||||
self.ephemeral_timer
|
self.ephemeral_timer
|
||||||
}
|
}
|
||||||
@@ -2868,4 +2873,50 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_is_bot() -> Result<()> {
|
||||||
|
let alice = TestContext::new_alice().await;
|
||||||
|
|
||||||
|
// Alice receives a message from Bob the bot.
|
||||||
|
dc_receive_imf(
|
||||||
|
&alice,
|
||||||
|
b"From: Bob <bob@example.com>\n\
|
||||||
|
To: alice@example.com\n\
|
||||||
|
Chat-Version: 1.0\n\
|
||||||
|
Message-ID: <123@example.com>\n\
|
||||||
|
Auto-Submitted: auto-generated\n\
|
||||||
|
Date: Fri, 29 Jan 2021 21:37:55 +0000\n\
|
||||||
|
\n\
|
||||||
|
hello\n",
|
||||||
|
"INBOX",
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let msg = alice.get_last_msg().await;
|
||||||
|
assert_eq!(msg.get_text().unwrap(), "hello".to_string());
|
||||||
|
assert!(msg.is_bot());
|
||||||
|
|
||||||
|
// Alice receives a message from Bob who is not the bot anymore.
|
||||||
|
dc_receive_imf(
|
||||||
|
&alice,
|
||||||
|
b"From: Bob <bob@example.com>\n\
|
||||||
|
To: alice@example.com\n\
|
||||||
|
Chat-Version: 1.0\n\
|
||||||
|
Message-ID: <456@example.com>\n\
|
||||||
|
Date: Fri, 29 Jan 2021 21:37:55 +0000\n\
|
||||||
|
\n\
|
||||||
|
hello again\n",
|
||||||
|
"INBOX",
|
||||||
|
2,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let msg = alice.get_last_msg().await;
|
||||||
|
assert_eq!(msg.get_text().unwrap(), "hello again".to_string());
|
||||||
|
assert!(!msg.is_bot());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -491,6 +491,11 @@ impl<'a> MimeFactory<'a> {
|
|||||||
"Auto-Submitted".to_string(),
|
"Auto-Submitted".to_string(),
|
||||||
"auto-replied".to_string(),
|
"auto-replied".to_string(),
|
||||||
));
|
));
|
||||||
|
} else if context.get_config_bool(Config::Bot).await? {
|
||||||
|
headers.unprotected.push(Header::new(
|
||||||
|
"Auto-Submitted".to_string(),
|
||||||
|
"auto-generated".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.req_mdn {
|
if self.req_mdn {
|
||||||
|
|||||||
@@ -498,6 +498,12 @@ impl MimeMessage {
|
|||||||
|
|
||||||
self.parts.push(part);
|
self.parts.push(part);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.header.contains_key("auto-submitted") {
|
||||||
|
for part in &mut self.parts {
|
||||||
|
part.param.set(Param::Bot, "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn avatar_action_from_header(
|
async fn avatar_action_from_header(
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ pub enum Param {
|
|||||||
/// For Messages
|
/// For Messages
|
||||||
WantsMdn = b'r',
|
WantsMdn = b'r',
|
||||||
|
|
||||||
|
/// For Messages: a message with Auto-Submitted header ("bot").
|
||||||
|
Bot = b'b',
|
||||||
|
|
||||||
/// For Messages: unset or 0=not forwarded,
|
/// For Messages: unset or 0=not forwarded,
|
||||||
/// 1=forwarded from unknown msg_id, >9 forwarded from msg_id
|
/// 1=forwarded from unknown msg_id, >9 forwarded from msg_id
|
||||||
Forwarded = b'a',
|
Forwarded = b'a',
|
||||||
|
|||||||
Reference in New Issue
Block a user