mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-18 21:52:30 -04:00
Merge remote-tracking branch 'origin/main' into ben-splitting-out-store-impls
This commit is contained in:
@@ -29,8 +29,8 @@ thiserror = "1.0.25"
|
||||
|
||||
[dependencies.ruma-identifiers]
|
||||
git = "https://github.com/ruma/ruma/"
|
||||
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"
|
||||
rev = "b9f32bc6327542d382d4eb42ec43623495c50e66"
|
||||
|
||||
[dependencies.ruma-serde]
|
||||
git = "https://github.com/ruma/ruma/"
|
||||
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"
|
||||
rev = "b9f32bc6327542d382d4eb42ec43623495c50e66"
|
||||
|
||||
@@ -38,8 +38,8 @@ warp = { version = "0.3.1", optional = true, default-features = false }
|
||||
|
||||
[dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma/"
|
||||
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"
|
||||
features = ["client-api-c", "appservice-api-s", "unstable-pre-spec"]
|
||||
rev = "b9f32bc6327542d382d4eb42ec43623495c50e66"
|
||||
features = ["client-api-c", "appservice-api-s"]
|
||||
|
||||
[dev-dependencies]
|
||||
matrix-sdk-test = { version = "0.4", path = "../matrix-sdk-test", features = ["appservice"] }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{convert::TryFrom, env};
|
||||
use std::env;
|
||||
|
||||
use matrix_sdk_appservice::{
|
||||
matrix_sdk::{
|
||||
@@ -21,7 +21,7 @@ pub async fn handle_room_member(
|
||||
if !appservice.user_id_is_in_namespace(&event.state_key)? {
|
||||
trace!("not an appservice user: {}", event.state_key);
|
||||
} else if let MembershipState::Invite = event.content.membership {
|
||||
let user_id = Box::<UserId>::try_from(event.state_key.as_str())?;
|
||||
let user_id = UserId::parse(event.state_key.as_str())?;
|
||||
appservice.register_virtual_user(user_id.localpart()).await?;
|
||||
|
||||
let client = appservice.virtual_user_client(user_id.localpart()).await?;
|
||||
|
||||
@@ -165,13 +165,13 @@ mod handlers {
|
||||
use super::*;
|
||||
|
||||
pub async fn user(
|
||||
_user_id: String,
|
||||
user_id: String,
|
||||
appservice: AppService,
|
||||
request: http::Request<Bytes>,
|
||||
) -> Result<impl warp::Reply, Rejection> {
|
||||
if let Some(user_exists) = appservice.event_handler.users.lock().await.as_mut() {
|
||||
let request =
|
||||
query_user::IncomingRequest::try_from_http_request(request).map_err(Error::from)?;
|
||||
let request = query_user::IncomingRequest::try_from_http_request(request, &[user_id])
|
||||
.map_err(Error::from)?;
|
||||
return if user_exists(appservice.clone(), request).await {
|
||||
Ok(warp::reply::json(&String::from("{}")))
|
||||
} else {
|
||||
@@ -182,13 +182,13 @@ mod handlers {
|
||||
}
|
||||
|
||||
pub async fn room(
|
||||
_room_id: String,
|
||||
room_id: String,
|
||||
appservice: AppService,
|
||||
request: http::Request<Bytes>,
|
||||
) -> Result<impl warp::Reply, Rejection> {
|
||||
if let Some(room_exists) = appservice.event_handler.rooms.lock().await.as_mut() {
|
||||
let request =
|
||||
query_room::IncomingRequest::try_from_http_request(request).map_err(Error::from)?;
|
||||
let request = query_room::IncomingRequest::try_from_http_request(request, &[room_id])
|
||||
.map_err(Error::from)?;
|
||||
return if room_exists(appservice.clone(), request).await {
|
||||
Ok(warp::reply::json(&String::from("{}")))
|
||||
} else {
|
||||
@@ -199,12 +199,13 @@ mod handlers {
|
||||
}
|
||||
|
||||
pub async fn transaction(
|
||||
_txn_id: String,
|
||||
txn_id: String,
|
||||
appservice: AppService,
|
||||
request: http::Request<Bytes>,
|
||||
) -> Result<impl warp::Reply, Rejection> {
|
||||
let incoming_transaction: ruma::api::appservice::event::push_events::v1::IncomingRequest =
|
||||
ruma::api::IncomingRequest::try_from_http_request(request).map_err(Error::from)?;
|
||||
ruma::api::IncomingRequest::try_from_http_request(request, &[txn_id])
|
||||
.map_err(Error::from)?;
|
||||
|
||||
let client = appservice.get_cached_client(None)?;
|
||||
client.receive_transaction(incoming_transaction).await.map_err(Error::from)?;
|
||||
|
||||
@@ -65,8 +65,8 @@ tokio = { version = "1.7.1", optional = true, default-features = false, features
|
||||
|
||||
[dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma/"
|
||||
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"
|
||||
features = ["client-api-c", "unstable-pre-spec"]
|
||||
rev = "b9f32bc6327542d382d4eb42ec43623495c50e66"
|
||||
features = ["client-api-c"]
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { version = "0.3.15", default-features = false, features = ["executor"] }
|
||||
|
||||
@@ -222,24 +222,24 @@ impl Inspector {
|
||||
async fn run(&self, matches: ArgMatches) {
|
||||
match matches.subcommand() {
|
||||
Some(("get-profiles", args)) => {
|
||||
let room_id = Box::<RoomId>::try_from(args.value_of("room-id").unwrap()).unwrap();
|
||||
let room_id = RoomId::parse(args.value_of("room-id").unwrap()).unwrap();
|
||||
|
||||
self.get_profiles(room_id).await;
|
||||
}
|
||||
|
||||
Some(("get-members", args)) => {
|
||||
let room_id = Box::<RoomId>::try_from(args.value_of("room-id").unwrap()).unwrap();
|
||||
let room_id = RoomId::parse(args.value_of("room-id").unwrap()).unwrap();
|
||||
|
||||
self.get_members(room_id).await;
|
||||
}
|
||||
Some(("list-rooms", _)) => self.list_rooms().await,
|
||||
Some(("get-display-names", args)) => {
|
||||
let room_id = Box::<RoomId>::try_from(args.value_of("room-id").unwrap()).unwrap();
|
||||
let room_id = RoomId::parse(args.value_of("room-id").unwrap()).unwrap();
|
||||
let display_name = args.value_of("display-name").unwrap().to_string();
|
||||
self.get_display_name_owners(room_id, display_name).await;
|
||||
}
|
||||
Some(("get-state", args)) => {
|
||||
let room_id = Box::<RoomId>::try_from(args.value_of("room-id").unwrap()).unwrap();
|
||||
let room_id = RoomId::parse(args.value_of("room-id").unwrap()).unwrap();
|
||||
let event_type = EventType::try_from(args.value_of("event-type").unwrap()).unwrap();
|
||||
self.get_state(room_id, event_type).await;
|
||||
}
|
||||
@@ -285,27 +285,19 @@ impl Inspector {
|
||||
vec![
|
||||
Argparse::new("list-rooms"),
|
||||
Argparse::new("get-members").arg(Arg::new("room-id").required(true).validator(|r| {
|
||||
Box::<RoomId>::try_from(r)
|
||||
.map(|_| ())
|
||||
.map_err(|_| "Invalid room id given".to_owned())
|
||||
RoomId::parse(r).map(|_| ()).map_err(|_| "Invalid room id given".to_owned())
|
||||
})),
|
||||
Argparse::new("get-profiles").arg(Arg::new("room-id").required(true).validator(|r| {
|
||||
Box::<RoomId>::try_from(r)
|
||||
.map(|_| ())
|
||||
.map_err(|_| "Invalid room id given".to_owned())
|
||||
RoomId::parse(r).map(|_| ()).map_err(|_| "Invalid room id given".to_owned())
|
||||
})),
|
||||
Argparse::new("get-display-names")
|
||||
.arg(Arg::new("room-id").required(true).validator(|r| {
|
||||
Box::<RoomId>::try_from(r)
|
||||
.map(|_| ())
|
||||
.map_err(|_| "Invalid room id given".to_owned())
|
||||
RoomId::parse(r).map(|_| ()).map_err(|_| "Invalid room id given".to_owned())
|
||||
}))
|
||||
.arg(Arg::new("display-name").required(true)),
|
||||
Argparse::new("get-state")
|
||||
.arg(Arg::new("room-id").required(true).validator(|r| {
|
||||
Box::<RoomId>::try_from(r)
|
||||
.map(|_| ())
|
||||
.map_err(|_| "Invalid room id given".to_owned())
|
||||
RoomId::parse(r).map(|_| ()).map_err(|_| "Invalid room id given".to_owned())
|
||||
}))
|
||||
.arg(Arg::new("event-type").required(true).validator(|e| {
|
||||
EventType::try_from(e).map(|_| ()).map_err(|_| "Invalid event type".to_string())
|
||||
|
||||
@@ -1286,12 +1286,12 @@ impl BaseClient {
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk_base::BaseClient;
|
||||
/// # use ruma::UserId;
|
||||
/// # use ruma::user_id;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # block_on(async {
|
||||
/// # let client = BaseClient::new().await.unwrap();
|
||||
/// let devices = client.get_user_devices(&alice).await.unwrap();
|
||||
/// let devices = client.get_user_devices(alice).await.unwrap();
|
||||
///
|
||||
/// for device in devices.devices() {
|
||||
/// println!("{:?}", device);
|
||||
|
||||
@@ -79,7 +79,7 @@ impl BaseRoomInfo {
|
||||
true
|
||||
}
|
||||
AnyStateEventContent::RoomAvatar(a) => {
|
||||
self.avatar_url = a.url.clone();
|
||||
self.avatar_url = Some(a.url.clone());
|
||||
true
|
||||
}
|
||||
AnyStateEventContent::RoomName(n) => {
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
sync::{Arc, RwLock as SyncRwLock},
|
||||
};
|
||||
use std::sync::{Arc, RwLock as SyncRwLock};
|
||||
|
||||
use futures_util::stream::{self, StreamExt};
|
||||
use ruma::{
|
||||
@@ -346,7 +343,7 @@ impl Room {
|
||||
let members: Vec<_> =
|
||||
stream::iter(summary.heroes.iter().filter(|u| !is_own_user_id(u)))
|
||||
.filter_map(|u| async move {
|
||||
let user_id = Box::<UserId>::try_from(u.as_str()).ok()?;
|
||||
let user_id = UserId::parse(u.as_str()).ok()?;
|
||||
self.get_member(&user_id).await.transpose()
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
convert::{TryFrom, TryInto},
|
||||
convert::TryInto,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
@@ -660,7 +660,7 @@ impl SledStore {
|
||||
|
||||
let user_id = iter.next().expect("User ids weren't properly encoded");
|
||||
|
||||
Ok(Box::<UserId>::try_from(String::from_utf8_lossy(user_id).to_string())?)
|
||||
Ok(UserId::parse(String::from_utf8_lossy(user_id).to_string())?)
|
||||
};
|
||||
|
||||
let members = self.members.clone();
|
||||
@@ -679,7 +679,7 @@ impl SledStore {
|
||||
let key = room_id.encode();
|
||||
spawn_blocking(move || {
|
||||
stream::iter(db.invited_user_ids.scan_prefix(key).map(|u| {
|
||||
Box::<UserId>::try_from(String::from_utf8_lossy(&u?.1).to_string())
|
||||
UserId::parse(String::from_utf8_lossy(&u?.1).to_string())
|
||||
.map_err(StoreError::Identifier)
|
||||
}))
|
||||
})
|
||||
@@ -695,7 +695,7 @@ impl SledStore {
|
||||
let key = room_id.encode();
|
||||
spawn_blocking(move || {
|
||||
stream::iter(db.joined_user_ids.scan_prefix(key).map(|u| {
|
||||
Box::<UserId>::try_from(String::from_utf8_lossy(&u?.1).to_string())
|
||||
UserId::parse(String::from_utf8_lossy(&u?.1).to_string())
|
||||
.map_err(StoreError::Identifier)
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -21,7 +21,7 @@ serde = "1.0.126"
|
||||
|
||||
[dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma/"
|
||||
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"
|
||||
rev = "b9f32bc6327542d382d4eb42ec43623495c50e66"
|
||||
features = ["client-api-c"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
||||
@@ -58,8 +58,8 @@ http = { version = "0.2.4", optional = true}
|
||||
|
||||
[dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma/"
|
||||
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"
|
||||
features = ["client-api-c", "rand", "unstable-pre-spec"]
|
||||
rev = "b9f32bc6327542d382d4eb42ec43623495c50e66"
|
||||
features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"]
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { version = "0.3.15", default-features = false, features = ["executor"] }
|
||||
|
||||
@@ -22,12 +22,12 @@ use std::{collections::BTreeMap, convert::TryFrom};
|
||||
use matrix_sdk_crypto::{OlmMachine, OlmError};
|
||||
use ruma::{
|
||||
api::client::r0::sync::sync_events::{ToDevice, DeviceLists},
|
||||
device_id, UserId,
|
||||
device_id, user_id,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), OlmError> {
|
||||
let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
let alice = user_id!("@alice:example.org");
|
||||
let machine = OlmMachine::new(&alice, device_id!("DEVICEID"));
|
||||
|
||||
let to_device_events = ToDevice::default();
|
||||
|
||||
@@ -147,7 +147,7 @@ impl<'a, R: Read + 'a> AttachmentDecryptor<'a, R> {
|
||||
}
|
||||
|
||||
/// A wrapper that transparently encrypts anything that implements `Read`.
|
||||
pub struct AttachmentEncryptor<'a, R: Read + 'a> {
|
||||
pub struct AttachmentEncryptor<'a, R: Read + ?Sized + 'a> {
|
||||
finished: bool,
|
||||
inner: &'a mut R,
|
||||
web_key: JsonWebKey,
|
||||
@@ -157,7 +157,7 @@ pub struct AttachmentEncryptor<'a, R: Read + 'a> {
|
||||
sha: Sha256,
|
||||
}
|
||||
|
||||
impl<'a, R: 'a + Read + std::fmt::Debug> std::fmt::Debug for AttachmentEncryptor<'a, R> {
|
||||
impl<'a, R: 'a + Read + std::fmt::Debug + ?Sized> std::fmt::Debug for AttachmentEncryptor<'a, R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AttachmentEncryptor")
|
||||
.field("inner", &self.inner)
|
||||
@@ -166,7 +166,7 @@ impl<'a, R: 'a + Read + std::fmt::Debug> std::fmt::Debug for AttachmentEncryptor
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read + 'a> Read for AttachmentEncryptor<'a, R> {
|
||||
impl<'a, R: Read + ?Sized + 'a> Read for AttachmentEncryptor<'a, R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let read_bytes = self.inner.read(buf)?;
|
||||
|
||||
@@ -185,7 +185,7 @@ impl<'a, R: Read + 'a> Read for AttachmentEncryptor<'a, R> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read + 'a> AttachmentEncryptor<'a, R> {
|
||||
impl<'a, R: Read + ?Sized + 'a> AttachmentEncryptor<'a, R> {
|
||||
/// Wrap the given reader encrypting all the data we read from it.
|
||||
///
|
||||
/// After all the reads are done, and all the data is encrypted that we wish
|
||||
|
||||
@@ -449,10 +449,10 @@ impl OlmMachine {
|
||||
/// ```
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk_crypto::OlmMachine;
|
||||
/// # use ruma::UserId;
|
||||
/// # use ruma::user_id;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
/// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID"));
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let machine = OlmMachine::new(alice, device_id!("DEVICEID"));
|
||||
/// # block_on(async {
|
||||
/// if machine.should_upload_keys().await {
|
||||
/// let request = machine
|
||||
|
||||
@@ -368,7 +368,7 @@ impl InboundGroupSession {
|
||||
|
||||
let room_id = decrypted_object
|
||||
.get("room_id")
|
||||
.and_then(|r| r.as_str().and_then(|r| Box::<RoomId>::try_from(r).ok()));
|
||||
.and_then(|r| r.as_str().and_then(|r| RoomId::parse(r).ok()));
|
||||
|
||||
// Check that we have a room id and that the event wasn't forwarded from
|
||||
// another room.
|
||||
|
||||
@@ -159,7 +159,7 @@ impl MasterSigning {
|
||||
self.inner.sign(message).await.0
|
||||
}
|
||||
|
||||
pub async fn sign_subkey<'a>(&self, subkey: &mut CrossSigningKey) {
|
||||
pub async fn sign_subkey(&self, subkey: &mut CrossSigningKey) {
|
||||
let subkey_without_signatures = json!({
|
||||
"user_id": subkey.user_id.clone(),
|
||||
"keys": subkey.keys.clone(),
|
||||
|
||||
@@ -443,7 +443,7 @@ impl SledStore {
|
||||
async fn load_tracked_users(&self) -> Result<()> {
|
||||
for value in &self.tracked_users {
|
||||
let (user, dirty) = value?;
|
||||
let user = Box::<UserId>::try_from(String::from_utf8_lossy(&user).to_string())?;
|
||||
let user = UserId::parse(String::from_utf8_lossy(&user).to_string())?;
|
||||
let dirty = dirty.get(0).map(|d| *d == 1).unwrap_or(true);
|
||||
|
||||
self.tracked_users_cache.insert(user.to_owned());
|
||||
|
||||
@@ -1379,8 +1379,8 @@ mod test {
|
||||
|
||||
let content = bob.as_content();
|
||||
let content = AcceptContent::from(&content);
|
||||
let sender = Box::<UserId>::try_from("@malory:example.org").unwrap();
|
||||
alice.into_accepted(&sender, &content).expect_err("Didn't cancel on a invalid sender");
|
||||
let sender = user_id!("@malory:example.org");
|
||||
alice.into_accepted(sender, &content).expect_err("Didn't cancel on a invalid sender");
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
||||
@@ -431,7 +431,7 @@ impl IndexeddbStore {
|
||||
Some(Ok(false)) => false,
|
||||
_ => true,
|
||||
};
|
||||
let user = match user_id.as_string().map(|u| Box::<UserId>::try_from(u)) {
|
||||
let user = match user_id.as_string().map(|u| UserId::parse(u)) {
|
||||
Some(Ok(user)) => user,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
@@ -581,7 +581,7 @@ impl IndexeddbStore {
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|key| match key.as_string() {
|
||||
Some(k) => Box::<UserId>::try_from(&k[skip..]).ok(),
|
||||
Some(k) => UserId::parse(&k[skip..]).ok(),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>())
|
||||
@@ -726,7 +726,7 @@ impl IndexeddbStore {
|
||||
let res =
|
||||
store.get(&k)?.await?.ok_or(StoreError::Codec(format!("no data at {:?}", k)))?;
|
||||
let u = if let Some(k_str) = k.as_string() {
|
||||
Box::<UserId>::try_from(&k_str[prefix_len..])
|
||||
UserId::parse(&k_str[prefix_len..])
|
||||
.map_err(|e| StoreError::Codec(format!("{:?}", e)))?
|
||||
} else {
|
||||
return Err(StoreError::Codec(format!("{:?}", k)).into());
|
||||
|
||||
@@ -23,5 +23,5 @@ serde_json = "1.0.64"
|
||||
|
||||
[dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma/"
|
||||
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"
|
||||
rev = "b9f32bc6327542d382d4eb42ec43623495c50e66"
|
||||
features = ["client-api-c"]
|
||||
|
||||
@@ -36,13 +36,16 @@ rustls-tls = ["reqwest/rustls-tls"]
|
||||
socks = ["reqwest/socks"]
|
||||
sso_login = ["warp", "rand", "tokio-stream"]
|
||||
appservice = ["ruma/appservice-api-s", "ruma/appservice-api-helper"]
|
||||
image_proc = ["image"]
|
||||
image_rayon = ["image_proc", "image/jpeg_rayon"]
|
||||
|
||||
docsrs = [
|
||||
"encryption",
|
||||
"sled_cryptostore",
|
||||
"sled_state_store",
|
||||
"sso_login",
|
||||
"qrcode"
|
||||
"qrcode",
|
||||
"image_proc",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
@@ -66,6 +69,26 @@ url = "2.2.2"
|
||||
zeroize = "1.3.0"
|
||||
async-stream = "0.3.2"
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.24.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"gif",
|
||||
"jpeg",
|
||||
"ico",
|
||||
"png",
|
||||
"pnm",
|
||||
"tga",
|
||||
"tiff",
|
||||
"webp",
|
||||
"bmp",
|
||||
"hdr",
|
||||
"dxt",
|
||||
"dds",
|
||||
"farbfeld",
|
||||
]
|
||||
optional = true
|
||||
|
||||
[dependencies.matrix-sdk-base]
|
||||
version = "0.4.0"
|
||||
path = "../matrix-sdk-base"
|
||||
@@ -77,8 +100,8 @@ default_features = false
|
||||
|
||||
[dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma/"
|
||||
rev = "37095f88553b311e7a70adaaabe39976fb8ff71c"
|
||||
features = ["client-api-c", "compat", "rand", "unstable-pre-spec"]
|
||||
rev = "b9f32bc6327542d382d4eb42ec43623495c50e66"
|
||||
features = ["client-api-c", "compat", "rand", "unstable-msc2448"]
|
||||
|
||||
[dependencies.tokio-stream]
|
||||
version = "0.1.6"
|
||||
|
||||
@@ -28,13 +28,13 @@ This is demonstrated in the example below.
|
||||
use std::convert::TryFrom;
|
||||
use matrix_sdk::{
|
||||
Client, config::SyncSettings, Result,
|
||||
ruma::{UserId, events::room::message::SyncRoomMessageEvent},
|
||||
ruma::{user_id, events::room::message::SyncRoomMessageEvent},
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
let client = Client::new_from_user_id(&alice).await?;
|
||||
let alice = user_id!("@alice:example.org");
|
||||
let client = Client::new_from_user_id(alice).await?;
|
||||
|
||||
// First we need to log in.
|
||||
client.login(alice, "password", None, None).await?;
|
||||
@@ -64,6 +64,8 @@ The following crate feature flags are available:
|
||||
| `anyhow` | No | Better logging for event handlers that return `anyhow::Result` |
|
||||
| `encryption` | Yes | End-to-end encryption support |
|
||||
| `eyre` | No | Better logging for event handlers that return `eyre::Result` |
|
||||
| `image_proc` | No | Enables image processing to generate thumbnails |
|
||||
| `image_rayon` | No | Enables faster image processing |
|
||||
| `markdown` | No | Support to send Markdown-formatted messages |
|
||||
| `qrcode` | Yes | QR code verification support |
|
||||
| `sled_cryptostore` | Yes | Persistent storage for E2EE related data |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{convert::TryFrom, env, process::exit};
|
||||
use std::{env, process::exit};
|
||||
|
||||
use matrix_sdk::{
|
||||
ruma::{api::client::r0::profile, MxcUri, UserId},
|
||||
@@ -62,7 +62,7 @@ async fn main() -> Result<(), matrix_sdk::Error> {
|
||||
|
||||
let client = login(homeserver_url, &username, &password).await?;
|
||||
|
||||
let user_id = Box::<UserId>::try_from(username).expect("Couldn't parse the MXID");
|
||||
let user_id = UserId::parse(username).expect("Couldn't parse the MXID");
|
||||
let profile = get_profile(client, &user_id).await?;
|
||||
println!("{:#?}", profile);
|
||||
Ok(())
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::{
|
||||
|
||||
use matrix_sdk::{
|
||||
self,
|
||||
attachment::AttachmentConfig,
|
||||
config::SyncSettings,
|
||||
room::Room,
|
||||
ruma::events::room::message::{
|
||||
@@ -39,7 +40,9 @@ async fn on_room_message(event: SyncRoomMessageEvent, room: Room, image: Arc<Mut
|
||||
println!("sending image");
|
||||
let mut image = image.lock().await;
|
||||
|
||||
room.send_attachment("cat", &mime::IMAGE_JPEG, &mut *image, None).await.unwrap();
|
||||
room.send_attachment("cat", &mime::IMAGE_JPEG, &mut *image, AttachmentConfig::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
image.seek(SeekFrom::Start(0)).unwrap();
|
||||
|
||||
|
||||
635
crates/matrix-sdk/src/account.rs
Normal file
635
crates/matrix-sdk/src/account.rs
Normal file
@@ -0,0 +1,635 @@
|
||||
// Copyright 2020 Damir Jelić
|
||||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
// Copyright 2022 Kévin Commaille
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
use matrix_sdk_base::media::{MediaFormat, MediaRequest, MediaType};
|
||||
use mime::Mime;
|
||||
use ruma::{
|
||||
api::client::r0::{
|
||||
account::{
|
||||
add_3pid, change_password, deactivate, delete_3pid, get_3pids,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
},
|
||||
profile::{
|
||||
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
|
||||
},
|
||||
uiaa::AuthData,
|
||||
},
|
||||
assign,
|
||||
thirdparty::Medium,
|
||||
ClientSecret, MxcUri, SessionId, UInt,
|
||||
};
|
||||
|
||||
use crate::{config::RequestConfig, Client, Error, Result};
|
||||
|
||||
/// A high-level API to manage the client owner's account.
|
||||
///
|
||||
/// All the methods on this struct send a request to the homeserver.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Account {
|
||||
/// The underlying HTTP client.
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub(crate) fn new(client: Client) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
/// Get the display name of the account.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// let user = "example";
|
||||
/// let client = Client::new(homeserver).await?;
|
||||
/// client.login(user, "password", None, None).await?;
|
||||
///
|
||||
/// if let Some(name) = client.account().get_display_name().await? {
|
||||
/// println!("Logged in as user '{}' with display name '{}'", user, name);
|
||||
/// }
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn get_display_name(&self) -> Result<Option<String>> {
|
||||
let user_id = self.client.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = get_display_name::Request::new(&user_id);
|
||||
let response = self.client.send(request, None).await?;
|
||||
Ok(response.displayname)
|
||||
}
|
||||
|
||||
/// Set the display name of the account.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// let user = "example";
|
||||
/// let client = Client::new(homeserver).await?;
|
||||
/// client.login(user, "password", None, None).await?;
|
||||
///
|
||||
/// client.account().set_display_name(Some("Alice")).await?;
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn set_display_name(&self, name: Option<&str>) -> Result<()> {
|
||||
let user_id = self.client.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = set_display_name::Request::new(&user_id, name);
|
||||
self.client.send(request, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the MXC URI of the account's avatar, if set.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let user = "example";
|
||||
/// let client = Client::new(homeserver).await?;
|
||||
/// client.login(user, "password", None, None).await?;
|
||||
///
|
||||
/// if let Some(url) = client.account().get_avatar_url().await? {
|
||||
/// println!("Your avatar's mxc url is {}", url);
|
||||
/// }
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn get_avatar_url(&self) -> Result<Option<Box<MxcUri>>> {
|
||||
let user_id = self.client.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = get_avatar_url::Request::new(&user_id);
|
||||
|
||||
let config = Some(RequestConfig::new().force_auth());
|
||||
|
||||
let response = self.client.send(request, config).await?;
|
||||
Ok(response.avatar_url)
|
||||
}
|
||||
|
||||
/// Set the MXC URI of the account's avatar.
|
||||
///
|
||||
/// The avatar is unset if `url` is `None`.
|
||||
pub async fn set_avatar_url(&self, url: Option<&MxcUri>) -> Result<()> {
|
||||
let user_id = self.client.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = set_avatar_url::Request::new(&user_id, url);
|
||||
self.client.send(request, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the account's avatar, if set.
|
||||
///
|
||||
/// Returns the avatar.
|
||||
///
|
||||
/// If a thumbnail is requested no guarantee on the size of the image is
|
||||
/// given.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `format` - The desired format of the avatar.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::room_id;
|
||||
/// # use matrix_sdk::media::MediaFormat;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let user = "example";
|
||||
/// let client = Client::new(homeserver).await?;
|
||||
/// client.login(user, "password", None, None).await?;
|
||||
///
|
||||
/// if let Some(avatar) = client.account().get_avatar(MediaFormat::File).await? {
|
||||
/// std::fs::write("avatar.png", avatar);
|
||||
/// }
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn get_avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
|
||||
if let Some(url) = self.get_avatar_url().await? {
|
||||
let request = MediaRequest { media_type: MediaType::Uri(url), format };
|
||||
Ok(Some(self.client.get_media_content(&request, true).await?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Upload and set the account's avatar.
|
||||
///
|
||||
/// This will upload the data produced by the reader to the homeserver's
|
||||
/// content repository, and set the user's avatar to the MXC URI for the
|
||||
/// uploaded file.
|
||||
///
|
||||
/// This is a convenience method for calling [`Client::upload()`],
|
||||
/// followed by [`Account::set_avatar_url()`].
|
||||
///
|
||||
/// Returns the MXC URI of the uploaded avatar.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use std::{path::Path, fs::File, io::Read};
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let path = Path::new("/home/example/selfie.jpg");
|
||||
/// let mut image = File::open(&path)?;
|
||||
///
|
||||
/// client.account().upload_avatar(&mime::IMAGE_JPEG, &mut image).await?;
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn upload_avatar<R: Read>(
|
||||
&self,
|
||||
content_type: &Mime,
|
||||
reader: &mut R,
|
||||
) -> Result<Box<MxcUri>> {
|
||||
let upload_response = self.client.upload(content_type, reader).await?;
|
||||
self.set_avatar_url(Some(&upload_response.content_uri)).await?;
|
||||
Ok(upload_response.content_uri)
|
||||
}
|
||||
|
||||
/// Get the profile of the account.
|
||||
///
|
||||
/// Allows to get both the display name and avatar URL in a single call.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// if let profile = client.account().get_profile().await? {
|
||||
/// println!(
|
||||
/// "You are '{:?}' with avatar '{:?}'",
|
||||
/// profile.displayname,
|
||||
/// profile.avatar_url
|
||||
/// );
|
||||
/// }
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn get_profile(&self) -> Result<get_profile::Response> {
|
||||
let user_id = self.client.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = get_profile::Request::new(&user_id);
|
||||
Ok(self.client.send(request, None).await?)
|
||||
}
|
||||
|
||||
/// Change the password of the account.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `new_password` - The new password to set.
|
||||
///
|
||||
/// * `auth_data` - This request uses the [User-Interactive Authentication
|
||||
/// API][uiaa]. The first request needs to set this to `None` and will
|
||||
/// always fail with an [`UiaaResponse`]. The response will contain
|
||||
/// information for the interactive auth and the same request needs to be
|
||||
/// made but this time with some `auth_data` provided.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This method might return an [`ErrorKind::WeakPassword`] error if the new
|
||||
/// password is considered insecure by the homeserver, with details about
|
||||
/// the strength requirements in the error's message.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::{
|
||||
/// # api::client::r0::{
|
||||
/// # account::change_password::{Request as ChangePasswordRequest},
|
||||
/// # uiaa::{AuthData, Dummy},
|
||||
/// # },
|
||||
/// # assign,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// client.account().change_password(
|
||||
/// "myverysecretpassword",
|
||||
/// Some(AuthData::Dummy(Dummy::new())),
|
||||
/// ).await?;
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
/// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
|
||||
/// [`UiaaResponse`]: ruma::api::client::r0::uiaa::UiaaResponse
|
||||
/// [`ErrorKind::WeakPassword`]: ruma::api::client::error::ErrorKind::WeakPassword
|
||||
pub async fn change_password(
|
||||
&self,
|
||||
new_password: &str,
|
||||
auth_data: Option<AuthData<'_>>,
|
||||
) -> Result<change_password::Response> {
|
||||
let request = assign!(change_password::Request::new(new_password), {
|
||||
auth: auth_data,
|
||||
});
|
||||
Ok(self.client.send(request, None).await?)
|
||||
}
|
||||
|
||||
/// Deactivate this account definitively.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id_server` - The identity server from which to unbind the user’s
|
||||
/// [Third Party Identifiers][3pid].
|
||||
///
|
||||
/// * `auth_data` - This request uses the [User-Interactive Authentication
|
||||
/// API][uiaa]. The first request needs to set this to `None` and will
|
||||
/// always fail with an [`UiaaResponse`]. The response will contain
|
||||
/// information for the interactive auth and the same request needs to be
|
||||
/// made but this time with some `auth_data` provided.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::{
|
||||
/// # api::client::r0::{
|
||||
/// # account::change_password::{Request as ChangePasswordRequest},
|
||||
/// # uiaa::{AuthData, Dummy},
|
||||
/// # },
|
||||
/// # assign,
|
||||
/// # };
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// # let account = client.account();
|
||||
/// let response = account.deactivate(None, None).await;
|
||||
///
|
||||
/// // Proceed with UIAA.
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
|
||||
/// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
|
||||
/// [`UiaaResponse`]: ruma::api::client::r0::uiaa::UiaaResponse
|
||||
pub async fn deactivate(
|
||||
&self,
|
||||
id_server: Option<&str>,
|
||||
auth_data: Option<AuthData<'_>>,
|
||||
) -> Result<deactivate::Response> {
|
||||
let request = assign!(deactivate::Request::new(), {
|
||||
id_server,
|
||||
auth: auth_data,
|
||||
});
|
||||
Ok(self.client.send(request, None).await?)
|
||||
}
|
||||
|
||||
/// Get the registered [Third Party Identifiers][3pid] on the homeserver of
|
||||
/// the account.
|
||||
///
|
||||
/// These 3PIDs may be used by the homeserver to authenticate the user
|
||||
/// during sensitive operations.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let threepids = client.account().get_3pids().await?.threepids;
|
||||
///
|
||||
/// for threepid in threepids {
|
||||
/// println!("Found 3PID '{}' of type '{}'", threepid.address, threepid.medium);
|
||||
/// }
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
|
||||
pub async fn get_3pids(&self) -> Result<get_3pids::Response> {
|
||||
let request = get_3pids::Request::new();
|
||||
Ok(self.client.send(request, None).await?)
|
||||
}
|
||||
|
||||
/// Request a token to validate an email address as a [Third Party
|
||||
/// Identifier][3pid].
|
||||
///
|
||||
/// This is the first step in registering an email address as 3PID. Next,
|
||||
/// call [`Account::add_3pid()`] with the same `client_secret` and the
|
||||
/// returned `sid`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_secret` - A client-generated secret string used to protect
|
||||
/// this session.
|
||||
///
|
||||
/// * `email` - The email address to validate.
|
||||
///
|
||||
/// * `send_attempt` - The attempt number. This number needs to be
|
||||
/// incremented if you want to request another token for the same
|
||||
/// validation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `sid` - The session ID to be used in following requests for
|
||||
/// this 3PID.
|
||||
///
|
||||
/// * `submit_url` - If present, the user will submit the token to
|
||||
/// the client, that must send it to this URL. If not, the client will not
|
||||
/// be involved in the token submission.
|
||||
///
|
||||
/// This method might return an [`ErrorKind::ThreepidInUse`] error if the
|
||||
/// email address is already registered for this account or another, or an
|
||||
/// [`ErrorKind::ThreepidDenied`] error if it is denied.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::{ClientSecret, uint};
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// # let account = client.account();
|
||||
/// # let secret = ClientSecret::parse("secret")?;
|
||||
/// let token_response = account.request_3pid_email_token(
|
||||
/// &secret,
|
||||
/// "john@matrix.org",
|
||||
/// uint!(0),
|
||||
/// ).await?;
|
||||
///
|
||||
/// // Wait for the user to confirm that the token was submitted or prompt
|
||||
/// // the user for the token and send it to submit_url.
|
||||
///
|
||||
/// let uiaa_response = account.add_3pid(
|
||||
/// &secret,
|
||||
/// &token_response.sid,
|
||||
/// None
|
||||
/// ).await;
|
||||
///
|
||||
/// // Proceed with UIAA.
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
|
||||
/// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
|
||||
/// [`ErrorKind::ThreepidDenied`]: ruma::api::client::error::ErrorKind::ThreepidDenied
|
||||
pub async fn request_3pid_email_token(
|
||||
&self,
|
||||
client_secret: &ClientSecret,
|
||||
email: &str,
|
||||
send_attempt: UInt,
|
||||
) -> Result<request_3pid_management_token_via_email::Response> {
|
||||
let request = request_3pid_management_token_via_email::Request::new(
|
||||
client_secret,
|
||||
email,
|
||||
send_attempt,
|
||||
);
|
||||
Ok(self.client.send(request, None).await?)
|
||||
}
|
||||
|
||||
/// Request a token to validate a phone number as a [Third Party
|
||||
/// Identifier][3pid].
|
||||
///
|
||||
/// This is the first step in registering a phone number as 3PID. Next,
|
||||
/// call [`Account::add_3pid()`] with the same `client_secret` and the
|
||||
/// returned `sid`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_secret` - A client-generated secret string used to protect
|
||||
/// this session.
|
||||
///
|
||||
/// * `country` - The two-letter uppercase ISO-3166-1 alpha-2 country code
|
||||
/// that the number in phone_number should be parsed as if it were dialled
|
||||
/// from.
|
||||
///
|
||||
/// * `phone_number` - The phone number to validate.
|
||||
///
|
||||
/// * `send_attempt` - The attempt number. This number needs to be
|
||||
/// incremented if you want to request another token for the same
|
||||
/// validation.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `sid` - The session ID to be used in following requests for this 3PID.
|
||||
///
|
||||
/// * `submit_url` - If present, the user will submit the token to the
|
||||
/// client, that must send it to this URL. If not, the client will not be
|
||||
/// involved in the token submission.
|
||||
///
|
||||
/// This method might return an [`ErrorKind::ThreepidInUse`] error if the
|
||||
/// phone number is already registered for this account or another, or an
|
||||
/// [`ErrorKind::ThreepidDenied`] error if it is denied.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::{ClientSecret, uint};
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// # let account = client.account();
|
||||
/// # let secret = ClientSecret::parse("secret")?;
|
||||
/// let token_response = account.request_3pid_msisdn_token(
|
||||
/// &secret,
|
||||
/// "FR",
|
||||
/// "0123456789",
|
||||
/// uint!(0),
|
||||
/// ).await?;
|
||||
///
|
||||
/// // Wait for the user to confirm that the token was submitted or prompt
|
||||
/// // the user for the token and send it to submit_url.
|
||||
///
|
||||
/// let uiaa_response = account.add_3pid(
|
||||
/// &secret,
|
||||
/// &token_response.sid,
|
||||
/// None
|
||||
/// ).await;
|
||||
///
|
||||
/// // Proceed with UIAA.
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
|
||||
/// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
|
||||
/// [`ErrorKind::ThreepidDenied`]: ruma::api::client::error::ErrorKind::ThreepidDenied
|
||||
pub async fn request_3pid_msisdn_token(
|
||||
&self,
|
||||
client_secret: &ClientSecret,
|
||||
country: &str,
|
||||
phone_number: &str,
|
||||
send_attempt: UInt,
|
||||
) -> Result<request_3pid_management_token_via_msisdn::Response> {
|
||||
let request = request_3pid_management_token_via_msisdn::Request::new(
|
||||
client_secret,
|
||||
country,
|
||||
phone_number,
|
||||
send_attempt,
|
||||
);
|
||||
Ok(self.client.send(request, None).await?)
|
||||
}
|
||||
|
||||
/// Add a [Third Party Identifier][3pid] on the homeserver for this
|
||||
/// account.
|
||||
///
|
||||
/// This 3PID may be used by the homeserver to authenticate the user
|
||||
/// during sensitive operations.
|
||||
///
|
||||
/// This method should be called after
|
||||
/// [`Account::request_3pid_email_token()`] or
|
||||
/// [`Account::request_3pid_msisdn_token()`] to complete the 3PID
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_secret` - The same client secret used in
|
||||
/// [`Account::request_3pid_email_token()`] or
|
||||
/// [`Account::request_3pid_msisdn_token()`].
|
||||
///
|
||||
/// * `sid` - The session ID returned in
|
||||
/// [`Account::request_3pid_email_token()`] or
|
||||
/// [`Account::request_3pid_msisdn_token()`].
|
||||
///
|
||||
/// * `auth_data` - This request uses the [User-Interactive Authentication
|
||||
/// API][uiaa]. The first request needs to set this to `None` and will
|
||||
/// always fail with an [`UiaaResponse`]. The response will contain
|
||||
/// information for the interactive auth and the same request needs to be
|
||||
/// made but this time with some `auth_data` provided.
|
||||
///
|
||||
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
|
||||
/// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
|
||||
/// [`UiaaResponse`]: ruma::api::client::r0::uiaa::UiaaResponse
|
||||
pub async fn add_3pid(
|
||||
&self,
|
||||
client_secret: &ClientSecret,
|
||||
sid: &SessionId,
|
||||
auth_data: Option<AuthData<'_>>,
|
||||
) -> Result<add_3pid::Response> {
|
||||
let request = assign!(add_3pid::Request::new(client_secret, sid), {
|
||||
auth: auth_data,
|
||||
});
|
||||
Ok(self.client.send(request, None).await?)
|
||||
}
|
||||
|
||||
/// Delete a [Third Party Identifier][3pid] from the homeserver for this
|
||||
/// account.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `address` - The 3PID being removed.
|
||||
///
|
||||
/// * `medium` - The type of the 3PID.
|
||||
///
|
||||
/// * `id_server` - The identity server to unbind from. If not provided, the
|
||||
/// homeserver should unbind the 3PID from the identity server it was bound
|
||||
/// to previously.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * [`ThirdPartyIdRemovalStatus::Success`] if the 3PID was also unbound
|
||||
/// from the identity server.
|
||||
///
|
||||
/// * [`ThirdPartyIdRemovalStatus::NoSupport`] if the 3PID was not unbound
|
||||
/// from the identity server. This can also mean that the 3PID was not bound
|
||||
/// to an identity server in the first place.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::thirdparty::Medium;
|
||||
/// # use matrix_sdk::ruma::api::client::r0::account::ThirdPartyIdRemovalStatus;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// # let account = client.account();
|
||||
/// match account
|
||||
/// .delete_3pid("paul@matrix.org", Medium::Email, None)
|
||||
/// .await?
|
||||
/// .id_server_unbind_result
|
||||
/// {
|
||||
/// ThirdPartyIdRemovalStatus::Success => {
|
||||
/// println!("3PID unbound from the Identity Server");
|
||||
/// }
|
||||
/// _ => println!("Could not unbind 3PID from the Identity Server"),
|
||||
/// }
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
/// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
|
||||
/// [`ThirdPartyIdRemovalStatus::Success`]: ruma::api::client::r0::account::ThirdPartyIdRemovalStatus::Success
|
||||
/// [`ThirdPartyIdRemovalStatus::NoSupport`]: ruma::api::client::r0::account::ThirdPartyIdRemovalStatus::NoSupport
|
||||
pub async fn delete_3pid(
|
||||
&self,
|
||||
address: &str,
|
||||
medium: Medium,
|
||||
id_server: Option<&str>,
|
||||
) -> Result<delete_3pid::Response> {
|
||||
let request = assign!(delete_3pid::Request::new(medium, address), {
|
||||
id_server: id_server,
|
||||
});
|
||||
Ok(self.client.send(request, None).await?)
|
||||
}
|
||||
}
|
||||
380
crates/matrix-sdk/src/attachment.rs
Normal file
380
crates/matrix-sdk/src/attachment.rs
Normal file
@@ -0,0 +1,380 @@
|
||||
// Copyright 2022 Kévin Commaille
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::io::Read;
|
||||
#[cfg(feature = "image_proc")]
|
||||
use std::io::{BufRead, Cursor, Seek};
|
||||
|
||||
#[cfg(feature = "image_proc")]
|
||||
use image::GenericImageView;
|
||||
use ruma::{
|
||||
assign,
|
||||
events::room::{
|
||||
message::{AudioInfo, FileInfo, VideoInfo},
|
||||
ImageInfo, ThumbnailInfo,
|
||||
},
|
||||
TransactionId, UInt,
|
||||
};
|
||||
|
||||
#[cfg(feature = "image_proc")]
|
||||
use crate::ImageError;
|
||||
|
||||
/// Base metadata about an image.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BaseImageInfo {
|
||||
/// The height of the image in pixels.
|
||||
pub height: Option<UInt>,
|
||||
/// The width of the image in pixels.
|
||||
pub width: Option<UInt>,
|
||||
/// The file size of the image in bytes.
|
||||
pub size: Option<UInt>,
|
||||
/// The [BlurHash](https://blurha.sh/) for this image.
|
||||
pub blurhash: Option<String>,
|
||||
}
|
||||
|
||||
/// Base metadata about a video.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BaseVideoInfo {
|
||||
/// The duration of the video in milliseconds.
|
||||
pub duration: Option<UInt>,
|
||||
/// The height of the video in pixels.
|
||||
pub height: Option<UInt>,
|
||||
/// The width of the video in pixels.
|
||||
pub width: Option<UInt>,
|
||||
/// The file size of the video in bytes.
|
||||
pub size: Option<UInt>,
|
||||
/// The [BlurHash](https://blurha.sh/) for this video.
|
||||
pub blurhash: Option<String>,
|
||||
}
|
||||
|
||||
/// Base metadata about an audio clip.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BaseAudioInfo {
|
||||
/// The duration of the audio clip in milliseconds.
|
||||
pub duration: Option<UInt>,
|
||||
/// The file size of the audio clip in bytes.
|
||||
pub size: Option<UInt>,
|
||||
}
|
||||
|
||||
/// Base metadata about a file.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BaseFileInfo {
|
||||
/// The size of the file in bytes.
|
||||
pub size: Option<UInt>,
|
||||
}
|
||||
|
||||
/// Types of metadata for an attachment.
|
||||
#[derive(Debug)]
|
||||
pub enum AttachmentInfo {
|
||||
/// The metadata of an image.
|
||||
Image(BaseImageInfo),
|
||||
/// The metadata of a video.
|
||||
Video(BaseVideoInfo),
|
||||
/// The metadata of an audio clip.
|
||||
Audio(BaseAudioInfo),
|
||||
/// The metadata of a file.
|
||||
File(BaseFileInfo),
|
||||
}
|
||||
|
||||
impl From<AttachmentInfo> for ImageInfo {
|
||||
fn from(info: AttachmentInfo) -> Self {
|
||||
match info {
|
||||
AttachmentInfo::Image(info) => assign!(ImageInfo::new(), {
|
||||
height: info.height,
|
||||
width: info.width,
|
||||
size: info.size,
|
||||
blurhash: info.blurhash,
|
||||
}),
|
||||
_ => ImageInfo::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttachmentInfo> for VideoInfo {
|
||||
fn from(info: AttachmentInfo) -> Self {
|
||||
match info {
|
||||
AttachmentInfo::Video(info) => assign!(VideoInfo::new(), {
|
||||
duration: info.duration,
|
||||
height: info.height,
|
||||
width: info.width,
|
||||
size: info.size,
|
||||
blurhash: info.blurhash,
|
||||
}),
|
||||
_ => VideoInfo::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttachmentInfo> for AudioInfo {
|
||||
fn from(info: AttachmentInfo) -> Self {
|
||||
match info {
|
||||
AttachmentInfo::Audio(info) => assign!(AudioInfo::new(), {
|
||||
duration: info.duration,
|
||||
size: info.size,
|
||||
}),
|
||||
_ => AudioInfo::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttachmentInfo> for FileInfo {
|
||||
fn from(info: AttachmentInfo) -> Self {
|
||||
match info {
|
||||
AttachmentInfo::File(info) => assign!(FileInfo::new(), {
|
||||
size: info.size,
|
||||
}),
|
||||
_ => FileInfo::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Base metadata about a thumbnail.
|
||||
pub struct BaseThumbnailInfo {
|
||||
/// The height of the thumbnail in pixels.
|
||||
pub height: Option<UInt>,
|
||||
/// The width of the thumbnail in pixels.
|
||||
pub width: Option<UInt>,
|
||||
/// The file size of the thumbnail in bytes.
|
||||
pub size: Option<UInt>,
|
||||
}
|
||||
|
||||
impl From<BaseThumbnailInfo> for ThumbnailInfo {
|
||||
fn from(info: BaseThumbnailInfo) -> Self {
|
||||
assign!(ThumbnailInfo::new(), {
|
||||
height: info.height,
|
||||
width: info.width,
|
||||
size: info.size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A thumbnail to upload and send for an attachment.
|
||||
#[derive(Debug)]
|
||||
pub struct Thumbnail<'a, R: Read> {
|
||||
/// A `Reader` that will be used to fetch the raw bytes of the thumbnail.
|
||||
pub reader: &'a mut R,
|
||||
/// The type of the thumbnail, this will be used as the content-type header.
|
||||
pub content_type: &'a mime::Mime,
|
||||
/// The metadata of the thumbnail.
|
||||
pub info: Option<BaseThumbnailInfo>,
|
||||
}
|
||||
|
||||
impl Thumbnail<'static, &'static [u8]> {
|
||||
/// Typed `None` for an `<Option<Thumbnail>>`.
|
||||
pub const NONE: Option<Thumbnail<'static, &'static [u8]>> = None;
|
||||
}
|
||||
|
||||
/// Configuration for sending an attachment.
|
||||
#[derive(Debug)]
|
||||
pub struct AttachmentConfig<'a, R: Read> {
|
||||
pub(crate) txn_id: Option<&'a TransactionId>,
|
||||
pub(crate) info: Option<AttachmentInfo>,
|
||||
pub(crate) thumbnail: Option<Thumbnail<'a, R>>,
|
||||
#[cfg(feature = "image_proc")]
|
||||
pub(crate) generate_thumbnail: bool,
|
||||
#[cfg(feature = "image_proc")]
|
||||
pub(crate) thumbnail_size: Option<(u32, u32)>,
|
||||
}
|
||||
|
||||
impl AttachmentConfig<'static, &'static [u8]> {
|
||||
/// Create a new default `AttachmentConfig` without providing a thumbnail.
|
||||
///
|
||||
/// To provide a thumbnail use [`AttachmentConfig::with_thumbnail()`].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
txn_id: Default::default(),
|
||||
info: Default::default(),
|
||||
thumbnail: None,
|
||||
#[cfg(feature = "image_proc")]
|
||||
generate_thumbnail: Default::default(),
|
||||
#[cfg(feature = "image_proc")]
|
||||
thumbnail_size: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the thumbnail to send for this media.
|
||||
///
|
||||
/// Uses [`generate_image_thumbnail()`].
|
||||
///
|
||||
/// Thumbnails can only be generated for supported image attachments. For
|
||||
/// more information, see the [image](https://github.com/image-rs/image)
|
||||
/// crate.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `size` - The size of the thumbnail in pixels as a `(width, height)`
|
||||
/// tuple. If set to `None`, defaults to `(800, 600)`.
|
||||
#[cfg(feature = "image_proc")]
|
||||
#[must_use]
|
||||
pub fn generate_thumbnail(mut self, size: Option<(u32, u32)>) -> Self {
|
||||
self.generate_thumbnail = true;
|
||||
self.thumbnail_size = size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AttachmentConfig<'static, &'static [u8]> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read> AttachmentConfig<'a, R> {
|
||||
/// Create a new default `AttachmentConfig` with a `thumbnail`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `thumbnail` - The thumbnail of the media. If the `content_type` does
|
||||
/// not support it (eg audio clips), it is ignored.
|
||||
///
|
||||
/// To generate automatically a thumbnail from an image, use
|
||||
/// [`AttachmentConfig::new()`] and
|
||||
/// [`AttachmentConfig::generate_thumbnail()`].
|
||||
pub fn with_thumbnail(thumbnail: Thumbnail<'a, R>) -> Self {
|
||||
Self {
|
||||
txn_id: Default::default(),
|
||||
info: Default::default(),
|
||||
thumbnail: Some(thumbnail),
|
||||
#[cfg(feature = "image_proc")]
|
||||
generate_thumbnail: Default::default(),
|
||||
#[cfg(feature = "image_proc")]
|
||||
thumbnail_size: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the transaction ID to send.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `txn_id` - A unique ID that can be attached to a `MessageEvent` held
|
||||
/// in its unsigned field as `transaction_id`. If not given, one is created
|
||||
/// for the message.
|
||||
#[must_use]
|
||||
pub fn txn_id(mut self, txn_id: &'a TransactionId) -> Self {
|
||||
self.txn_id = Some(txn_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the media metadata to send.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `info` - The metadata of the media. If the `AttachmentInfo` type
|
||||
/// doesn't match the `content_type`, it is ignored.
|
||||
#[must_use]
|
||||
pub fn info(mut self, info: AttachmentInfo) -> Self {
|
||||
self.info = Some(info);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a thumbnail for an image.
|
||||
///
|
||||
/// This is a convenience method that uses the
|
||||
/// [image](https://github.com/image-rs/image) crate.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `content_type` - The type of the media, this will be used as the
|
||||
/// content-type header.
|
||||
///
|
||||
/// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
|
||||
/// media.
|
||||
///
|
||||
/// * `size` - The size of the thumbnail in pixels as a `(width, height)` tuple.
|
||||
/// If set to `None`, defaults to `(800, 600)`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::{path::PathBuf, fs::File, io::{BufReader, Cursor, Read, Seek}};
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # attachment::{AttachmentConfig, Thumbnail, generate_image_thumbnail},
|
||||
/// # ruma::room_id
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use mime;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||
/// # let mut client = Client::new(homeserver).await?;
|
||||
/// # let room_id = room_id!("!test:localhost");
|
||||
/// let path = PathBuf::from("/home/example/my-cat.jpg");
|
||||
/// let mut image = BufReader::new(File::open(path)?);
|
||||
///
|
||||
/// let (thumbnail_data, thumbnail_info) = generate_image_thumbnail(
|
||||
/// &mime::IMAGE_JPEG,
|
||||
/// &mut image,
|
||||
/// None
|
||||
/// )?;
|
||||
/// let mut cursor = Cursor::new(thumbnail_data);
|
||||
/// let config = AttachmentConfig::with_thumbnail(Thumbnail {
|
||||
/// reader: &mut cursor,
|
||||
/// content_type: &mime::IMAGE_JPEG,
|
||||
/// info: Some(thumbnail_info),
|
||||
/// });
|
||||
///
|
||||
/// image.rewind()?;
|
||||
///
|
||||
/// if let Some(room) = client.get_joined_room(&room_id) {
|
||||
/// room.send_attachment(
|
||||
/// "My favorite cat",
|
||||
/// &mime::IMAGE_JPEG,
|
||||
/// &mut image,
|
||||
/// config,
|
||||
/// ).await?;
|
||||
/// }
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
#[cfg(feature = "image_proc")]
|
||||
pub fn generate_image_thumbnail<R: BufRead + Seek>(
|
||||
content_type: &mime::Mime,
|
||||
reader: &mut R,
|
||||
size: Option<(u32, u32)>,
|
||||
) -> Result<(Vec<u8>, BaseThumbnailInfo), ImageError> {
|
||||
let image_format = image::ImageFormat::from_mime_type(content_type);
|
||||
if image_format.is_none() {
|
||||
return Err(ImageError::FormatNotSupported);
|
||||
}
|
||||
|
||||
let image_format = image_format.unwrap();
|
||||
|
||||
let image = image::load(reader, image_format)?;
|
||||
let (original_width, original_height) = image.dimensions();
|
||||
|
||||
let (width, height) = size.unwrap_or((800, 600));
|
||||
|
||||
// Don't generate a thumbnail if it would be bigger than or equal to the
|
||||
// original.
|
||||
if height >= original_height && width >= original_width {
|
||||
return Err(ImageError::ThumbnailBiggerThanOriginal);
|
||||
}
|
||||
|
||||
let thumbnail = image.thumbnail(width, height);
|
||||
let (thumbnail_width, thumbnail_height) = thumbnail.dimensions();
|
||||
|
||||
let mut data: Vec<u8> = vec![];
|
||||
thumbnail.write_to(&mut Cursor::new(&mut data), image_format)?;
|
||||
let data_size = data.len() as u32;
|
||||
|
||||
Ok((
|
||||
data,
|
||||
BaseThumbnailInfo {
|
||||
width: Some(thumbnail_width.into()),
|
||||
height: Some(thumbnail_height.into()),
|
||||
size: Some(data_size.into()),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -43,12 +43,12 @@ use ruma::{
|
||||
client::{
|
||||
r0::{
|
||||
account::{register, whoami},
|
||||
capabilities::{get_capabilities, Capabilities},
|
||||
device::{delete_devices, get_devices},
|
||||
directory::{get_public_rooms, get_public_rooms_filtered},
|
||||
filter::{create_filter::Request as FilterUploadRequest, FilterDefinition},
|
||||
media::{create_content, get_content, get_content_thumbnail},
|
||||
membership::{join_room_by_id, join_room_by_id_or_alias},
|
||||
profile::{get_avatar_url, get_display_name, set_avatar_url, set_display_name},
|
||||
push::get_notifications::Notification,
|
||||
room::create_room,
|
||||
session::{get_login_types, login, sso_login},
|
||||
@@ -69,11 +69,12 @@ use tracing::{error, info, instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
attachment::{AttachmentInfo, Thumbnail},
|
||||
config::{ClientConfig, RequestConfig},
|
||||
error::{HttpError, HttpResult},
|
||||
event_handler::{EventHandler, EventHandlerData, EventHandlerResult, EventKind, SyncEvent},
|
||||
http_client::{client_with_config, HttpClient},
|
||||
room, Error, Result,
|
||||
room, Account, Error, Result,
|
||||
};
|
||||
|
||||
/// A conservative upload speed of 1Mbps
|
||||
@@ -231,7 +232,7 @@ impl Client {
|
||||
/// use matrix_sdk::{Client, ruma::UserId};
|
||||
///
|
||||
/// // First let's try to construct an user id, presumably from user input.
|
||||
/// let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// let alice = UserId::parse("@alice:example.org")?;
|
||||
///
|
||||
/// // Now let's try to discover the homeserver and create a client object.
|
||||
/// let client = Client::new_from_user_id(&alice).await?;
|
||||
@@ -346,6 +347,33 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the capabilities of the homeserver.
|
||||
///
|
||||
/// This method should be used to check what features are supported by the
|
||||
/// homeserver.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// let client = Client::new(homeserver).await?;
|
||||
///
|
||||
/// let capabilities = client.get_capabilities().await?;
|
||||
///
|
||||
/// if capabilities.change_password.enabled {
|
||||
/// // Change password
|
||||
/// }
|
||||
///
|
||||
/// # Result::<_, anyhow::Error>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn get_capabilities(&self) -> HttpResult<Capabilities> {
|
||||
let res = self.send(get_capabilities::Request::new(), None).await?;
|
||||
Ok(res.capabilities)
|
||||
}
|
||||
|
||||
/// Process a [transaction] received from the homeserver
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -398,161 +426,14 @@ impl Client {
|
||||
self.inner.base_client.session().read().await.clone()
|
||||
}
|
||||
|
||||
/// Fetches the display name of the owner of the client.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// let user = "example";
|
||||
/// let client = Client::new(homeserver).await.unwrap();
|
||||
/// client.login(user, "password", None, None).await.unwrap();
|
||||
///
|
||||
/// if let Some(name) = client.display_name().await.unwrap() {
|
||||
/// println!("Logged in as user '{}' with display name '{}'", user, name);
|
||||
/// }
|
||||
/// # })
|
||||
/// ```
|
||||
pub async fn display_name(&self) -> Result<Option<String>> {
|
||||
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = get_display_name::Request::new(&user_id);
|
||||
let response = self.send(request, None).await?;
|
||||
Ok(response.displayname)
|
||||
}
|
||||
|
||||
/// Sets the display name of the owner of the client.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// let user = "example";
|
||||
/// let client = Client::new(homeserver).await.unwrap();
|
||||
/// client.login(user, "password", None, None).await.unwrap();
|
||||
///
|
||||
/// client.set_display_name(Some("Alice")).await.expect("Failed setting display name");
|
||||
/// # })
|
||||
/// ```
|
||||
pub async fn set_display_name(&self, name: Option<&str>) -> Result<()> {
|
||||
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = set_display_name::Request::new(&user_id, name);
|
||||
self.send(request, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the mxc avatar url of the owner of the client, if set.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let user = "example";
|
||||
/// let client = Client::new(homeserver).await.unwrap();
|
||||
/// client.login(user, "password", None, None).await.unwrap();
|
||||
///
|
||||
/// if let Some(url) = client.avatar_url().await.unwrap() {
|
||||
/// println!("Your avatar's mxc url is {}", url);
|
||||
/// }
|
||||
/// # })
|
||||
/// ```
|
||||
pub async fn avatar_url(&self) -> Result<Option<Box<MxcUri>>> {
|
||||
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = get_avatar_url::Request::new(&user_id);
|
||||
|
||||
let config = Some(RequestConfig::new().force_auth());
|
||||
|
||||
let response = self.send(request, config).await?;
|
||||
Ok(response.avatar_url)
|
||||
}
|
||||
|
||||
/// Gets the avatar of the owner of the client, if set.
|
||||
///
|
||||
/// Returns the avatar.
|
||||
/// If a thumbnail is requested no guarantee on the size of the image is
|
||||
/// given.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `format` - The desired format of the avatar.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use matrix_sdk::ruma::room_id;
|
||||
/// # use matrix_sdk::media::MediaFormat;
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let user = "example";
|
||||
/// let client = Client::new(homeserver).await.unwrap();
|
||||
/// client.login(user, "password", None, None).await.unwrap();
|
||||
///
|
||||
/// if let Some(avatar) = client.avatar(MediaFormat::File).await.unwrap() {
|
||||
/// std::fs::write("avatar.png", avatar);
|
||||
/// }
|
||||
/// # })
|
||||
/// ```
|
||||
pub async fn avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
|
||||
if let Some(url) = self.avatar_url().await? {
|
||||
let request = MediaRequest { media_type: MediaType::Uri(url), format };
|
||||
Ok(Some(self.get_media_content(&request, true).await?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the store.
|
||||
pub fn store(&self) -> &Store {
|
||||
self.inner.base_client.store()
|
||||
}
|
||||
|
||||
/// Sets the mxc avatar url of the client's owner. The avatar gets unset if
|
||||
/// `url` is `None`.
|
||||
pub async fn set_avatar_url(&self, url: Option<&MxcUri>) -> Result<()> {
|
||||
let user_id = self.user_id().await.ok_or(Error::AuthenticationRequired)?;
|
||||
let request = set_avatar_url::Request::new(&user_id, url);
|
||||
self.send(request, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Upload and set the owning client's avatar.
|
||||
///
|
||||
/// The will upload the data produced by the reader to the homeserver's
|
||||
/// content repository, and set the user's avatar to the mxc url for the
|
||||
/// uploaded file.
|
||||
///
|
||||
/// This is a convenience method for calling [`upload()`](#method.upload),
|
||||
/// followed by [`set_avatar_url()`](#method.set_avatar_url).
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use std::{path::Path, fs::File, io::Read};
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::Client;
|
||||
/// # use url::Url;
|
||||
/// # block_on(async {
|
||||
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let path = Path::new("/home/example/selfie.jpg");
|
||||
/// let mut image = File::open(&path).unwrap();
|
||||
///
|
||||
/// client.upload_avatar(&mime::IMAGE_JPEG, &mut image).await.expect("Can't set avatar");
|
||||
/// # })
|
||||
/// ```
|
||||
pub async fn upload_avatar<R: Read>(&self, content_type: &Mime, reader: &mut R) -> Result<()> {
|
||||
let upload_response = self.upload(content_type, reader).await?;
|
||||
self.set_avatar_url(Some(&upload_response.content_uri)).await?;
|
||||
Ok(())
|
||||
/// Get the account of the current owner of the client.
|
||||
pub fn account(&self) -> Account {
|
||||
Account::new(self.clone())
|
||||
}
|
||||
|
||||
/// Register a handler for a specific event type.
|
||||
@@ -1622,7 +1503,7 @@ impl Client {
|
||||
pub async fn upload(
|
||||
&self,
|
||||
content_type: &Mime,
|
||||
reader: &mut impl Read,
|
||||
reader: &mut (impl Read + ?Sized),
|
||||
) -> Result<create_content::Response> {
|
||||
let mut data = Vec::new();
|
||||
reader.read_to_end(&mut data)?;
|
||||
@@ -2321,42 +2202,94 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Upload the file to be read from `reader` and construct an attachment
|
||||
/// message with `body` and the specified `content_type`.
|
||||
pub(crate) async fn prepare_attachment_message<R: Read>(
|
||||
/// message with `body`, `content_type`, `info` and `thumbnail`.
|
||||
pub(crate) async fn prepare_attachment_message<R: Read, T: Read>(
|
||||
&self,
|
||||
body: &str,
|
||||
content_type: &Mime,
|
||||
reader: &mut R,
|
||||
info: Option<AttachmentInfo>,
|
||||
thumbnail: Option<Thumbnail<'_, T>>,
|
||||
) -> Result<ruma::events::room::message::MessageType> {
|
||||
let (thumbnail_url, thumbnail_info) = if let Some(thumbnail) = thumbnail {
|
||||
let response = self.upload(thumbnail.content_type, thumbnail.reader).await?;
|
||||
let url = response.content_uri;
|
||||
|
||||
use ruma::events::room::ThumbnailInfo;
|
||||
let thumbnail_info = assign!(
|
||||
thumbnail.info.as_ref().map(|info| ThumbnailInfo::from(info.clone())).unwrap_or_default(),
|
||||
{ mimetype: Some(thumbnail.content_type.as_ref().to_owned()) }
|
||||
);
|
||||
|
||||
(Some(url), Some(Box::new(thumbnail_info)))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let response = self.upload(content_type, reader).await?;
|
||||
|
||||
let url = response.content_uri;
|
||||
|
||||
use ruma::events::room::message;
|
||||
use ruma::events::room::{self, message};
|
||||
Ok(match content_type.type_() {
|
||||
mime::IMAGE => {
|
||||
// TODO create a thumbnail using the image crate?.
|
||||
let info = assign!(
|
||||
info.map(room::ImageInfo::from).unwrap_or_default(),
|
||||
{
|
||||
mimetype: Some(content_type.as_ref().to_owned()),
|
||||
thumbnail_url,
|
||||
thumbnail_info
|
||||
}
|
||||
);
|
||||
message::MessageType::Image(message::ImageMessageEventContent::plain(
|
||||
body.to_owned(),
|
||||
url,
|
||||
None,
|
||||
Some(Box::new(info)),
|
||||
))
|
||||
}
|
||||
mime::AUDIO => {
|
||||
let info = assign!(
|
||||
info.map(message::AudioInfo::from).unwrap_or_default(),
|
||||
{
|
||||
mimetype: Some(content_type.as_ref().to_owned()),
|
||||
}
|
||||
);
|
||||
message::MessageType::Audio(message::AudioMessageEventContent::plain(
|
||||
body.to_owned(),
|
||||
url,
|
||||
Some(Box::new(info)),
|
||||
))
|
||||
}
|
||||
mime::VIDEO => {
|
||||
let info = assign!(
|
||||
info.map(message::VideoInfo::from).unwrap_or_default(),
|
||||
{
|
||||
mimetype: Some(content_type.as_ref().to_owned()),
|
||||
thumbnail_url,
|
||||
thumbnail_info
|
||||
}
|
||||
);
|
||||
message::MessageType::Video(message::VideoMessageEventContent::plain(
|
||||
body.to_owned(),
|
||||
url,
|
||||
Some(Box::new(info)),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
let info = assign!(
|
||||
info.map(message::FileInfo::from).unwrap_or_default(),
|
||||
{
|
||||
mimetype: Some(content_type.as_ref().to_owned()),
|
||||
thumbnail_url,
|
||||
thumbnail_info
|
||||
}
|
||||
);
|
||||
message::MessageType::File(message::FileMessageEventContent::plain(
|
||||
body.to_owned(),
|
||||
url,
|
||||
Some(Box::new(info)),
|
||||
))
|
||||
}
|
||||
mime::AUDIO => message::MessageType::Audio(message::AudioMessageEventContent::plain(
|
||||
body.to_owned(),
|
||||
url,
|
||||
None,
|
||||
)),
|
||||
mime::VIDEO => message::MessageType::Video(message::VideoMessageEventContent::plain(
|
||||
body.to_owned(),
|
||||
url,
|
||||
None,
|
||||
)),
|
||||
_ => message::MessageType::File(message::FileMessageEventContent::plain(
|
||||
body.to_owned(),
|
||||
url,
|
||||
None,
|
||||
)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2367,13 +2300,7 @@ pub(crate) mod test {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
io::Cursor,
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
use std::{collections::BTreeMap, convert::TryInto, io::Cursor, str::FromStr, time::Duration};
|
||||
|
||||
use matrix_sdk_base::media::{MediaFormat, MediaRequest, MediaThumbnailSize, MediaType};
|
||||
use matrix_sdk_test::{test_json, EventBuilder, EventsJson};
|
||||
@@ -2412,6 +2339,10 @@ pub(crate) mod test {
|
||||
|
||||
use super::{Client, Session, Url};
|
||||
use crate::{
|
||||
attachment::{
|
||||
AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo,
|
||||
Thumbnail,
|
||||
},
|
||||
config::{ClientConfig, RequestConfig, SyncSettings},
|
||||
HttpError, RoomMember,
|
||||
};
|
||||
@@ -2447,7 +2378,7 @@ pub(crate) mod test {
|
||||
async fn successful_discovery() {
|
||||
let server_url = mockito::server_url();
|
||||
let domain = server_url.strip_prefix("http://").unwrap();
|
||||
let alice = Box::<UserId>::try_from("@alice:".to_string() + domain).unwrap();
|
||||
let alice = UserId::parse("@alice:".to_string() + domain).unwrap();
|
||||
|
||||
let _m_well_known = mock("GET", "/.well-known/matrix/client")
|
||||
.with_status(200)
|
||||
@@ -2469,7 +2400,7 @@ pub(crate) mod test {
|
||||
async fn discovery_broken_server() {
|
||||
let server_url = mockito::server_url();
|
||||
let domain = server_url.strip_prefix("http://").unwrap();
|
||||
let alice = Box::<UserId>::try_from("@alice:".to_string() + domain).unwrap();
|
||||
let alice = UserId::parse("@alice:".to_string() + domain).unwrap();
|
||||
|
||||
let _m = mock("GET", "/.well-known/matrix/client")
|
||||
.with_status(200)
|
||||
@@ -3194,6 +3125,11 @@ pub(crate) mod test {
|
||||
let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("authorization", "Bearer 1234")
|
||||
.match_body(Matcher::PartialJson(json!({
|
||||
"info": {
|
||||
"mimetype": "image/jpeg"
|
||||
}
|
||||
})))
|
||||
.with_body(test_json::EVENT_ID.to_string())
|
||||
.create();
|
||||
|
||||
@@ -3222,12 +3158,200 @@ pub(crate) mod test {
|
||||
|
||||
let mut media = Cursor::new("Hello world");
|
||||
|
||||
let response =
|
||||
room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, None).await.unwrap();
|
||||
let response = room
|
||||
.send_attachment("image", &mime::IMAGE_JPEG, &mut media, AttachmentConfig::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id)
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn room_attachment_send_info() {
|
||||
let client = logged_in_client().await;
|
||||
|
||||
let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("authorization", "Bearer 1234")
|
||||
.match_body(Matcher::PartialJson(json!({
|
||||
"info": {
|
||||
"mimetype": "image/jpeg",
|
||||
"h": 600,
|
||||
"w": 800,
|
||||
}
|
||||
})))
|
||||
.with_body(test_json::EVENT_ID.to_string())
|
||||
.create();
|
||||
|
||||
let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("content-type", "image/jpeg")
|
||||
.with_body(
|
||||
json!({
|
||||
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.create();
|
||||
|
||||
let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("authorization", "Bearer 1234")
|
||||
.with_body(test_json::SYNC.to_string())
|
||||
.create();
|
||||
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
|
||||
let mut media = Cursor::new("Hello world");
|
||||
|
||||
let config = AttachmentConfig::new().info(AttachmentInfo::Image(BaseImageInfo {
|
||||
height: Some(uint!(600)),
|
||||
width: Some(uint!(800)),
|
||||
size: None,
|
||||
blurhash: None,
|
||||
}));
|
||||
|
||||
let response =
|
||||
room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap();
|
||||
|
||||
upload_mock.assert();
|
||||
assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id)
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn room_attachment_send_wrong_info() {
|
||||
let client = logged_in_client().await;
|
||||
|
||||
let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("authorization", "Bearer 1234")
|
||||
.match_body(Matcher::PartialJson(json!({
|
||||
"info": {
|
||||
"mimetype": "image/jpeg",
|
||||
"h": 600,
|
||||
"w": 800,
|
||||
}
|
||||
})))
|
||||
.with_body(test_json::EVENT_ID.to_string())
|
||||
.create();
|
||||
|
||||
let _m = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("content-type", "image/jpeg")
|
||||
.with_body(
|
||||
json!({
|
||||
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.create();
|
||||
|
||||
let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("authorization", "Bearer 1234")
|
||||
.with_body(test_json::SYNC.to_string())
|
||||
.create();
|
||||
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
|
||||
let mut media = Cursor::new("Hello world");
|
||||
|
||||
let config = AttachmentConfig::new().info(AttachmentInfo::Video(BaseVideoInfo {
|
||||
height: Some(uint!(600)),
|
||||
width: Some(uint!(800)),
|
||||
duration: Some(uint!(3600)),
|
||||
size: None,
|
||||
blurhash: None,
|
||||
}));
|
||||
|
||||
let response = room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await;
|
||||
|
||||
assert!(response.is_err())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn room_attachment_send_info_thumbnail() {
|
||||
let client = logged_in_client().await;
|
||||
|
||||
let _m = mock("PUT", Matcher::Regex(r"^/_matrix/client/r0/rooms/.*/send/".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("authorization", "Bearer 1234")
|
||||
.match_body(Matcher::PartialJson(json!({
|
||||
"info": {
|
||||
"mimetype": "image/jpeg",
|
||||
"h": 600,
|
||||
"w": 800,
|
||||
"thumbnail_info": {
|
||||
"h": 360,
|
||||
"w": 480,
|
||||
"mimetype":"image/jpeg",
|
||||
"size": 3600,
|
||||
},
|
||||
"thumbnail_url": "mxc://example.com/AQwafuaFswefuhsfAFAgsw",
|
||||
}
|
||||
})))
|
||||
.with_body(test_json::EVENT_ID.to_string())
|
||||
.create();
|
||||
|
||||
let upload_mock = mock("POST", Matcher::Regex(r"^/_matrix/media/r0/upload".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("content-type", "image/jpeg")
|
||||
.with_body(
|
||||
json!({
|
||||
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect(2)
|
||||
.create();
|
||||
|
||||
let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()))
|
||||
.with_status(200)
|
||||
.match_header("authorization", "Bearer 1234")
|
||||
.with_body(test_json::SYNC.to_string())
|
||||
.create();
|
||||
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id!("!SVkFJHzfwvuaIEawgC:localhost")).unwrap();
|
||||
|
||||
let mut media = Cursor::new("Hello world");
|
||||
|
||||
let mut thumbnail_reader = Cursor::new("Thumbnail");
|
||||
|
||||
let config = AttachmentConfig::with_thumbnail(Thumbnail {
|
||||
reader: &mut thumbnail_reader,
|
||||
content_type: &mime::IMAGE_JPEG,
|
||||
info: Some(BaseThumbnailInfo {
|
||||
height: Some(uint!(360)),
|
||||
width: Some(uint!(480)),
|
||||
size: Some(uint!(3600)),
|
||||
}),
|
||||
})
|
||||
.info(AttachmentInfo::Image(BaseImageInfo {
|
||||
height: Some(uint!(600)),
|
||||
width: Some(uint!(800)),
|
||||
size: None,
|
||||
blurhash: None,
|
||||
}));
|
||||
|
||||
let response =
|
||||
room.send_attachment("image", &mime::IMAGE_JPEG, &mut media, config).await.unwrap();
|
||||
|
||||
upload_mock.assert();
|
||||
assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id)
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn room_redact() {
|
||||
let client = logged_in_client().await;
|
||||
|
||||
@@ -82,14 +82,14 @@ impl Device {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::{device_id, UserId}};
|
||||
/// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(&alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// if let Some(device) = device {
|
||||
/// let verification = device.request_verification().await?;
|
||||
@@ -127,17 +127,17 @@ impl Device {
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # ruma::{
|
||||
/// # device_id, UserId,
|
||||
/// # device_id, user_id,
|
||||
/// # events::key::verification::VerificationMethod,
|
||||
/// # }
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(&alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// // We don't want to support showing a QR code, we only support SAS
|
||||
/// // verification
|
||||
@@ -172,14 +172,14 @@ impl Device {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::{device_id, UserId}};
|
||||
/// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(&alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// if let Some(device) = device {
|
||||
/// let verification = device.start_verification().await?;
|
||||
@@ -233,17 +233,17 @@ impl Device {
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # ruma::{
|
||||
/// # device_id, UserId,
|
||||
/// # device_id, user_id,
|
||||
/// # events::key::verification::VerificationMethod,
|
||||
/// # }
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(&alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// if let Some(device) = device {
|
||||
/// device.verify().await?;
|
||||
@@ -348,17 +348,17 @@ impl Device {
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # ruma::{
|
||||
/// # device_id, UserId,
|
||||
/// # device_id, user_id,
|
||||
/// # events::key::verification::VerificationMethod,
|
||||
/// # }
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let device = client.get_device(&alice, device_id!("DEVICEID")).await?;
|
||||
/// let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
///
|
||||
/// if let Some(device) = device {
|
||||
/// if device.verified() {
|
||||
|
||||
@@ -36,14 +36,14 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use std::convert::TryFrom;
|
||||
//! # use matrix_sdk::{Client, ruma::{device_id, UserId}};
|
||||
//! # use matrix_sdk::{Client, ruma::{device_id, user_id}};
|
||||
//! # use url::Url;
|
||||
//! # use futures::executor::block_on;
|
||||
//! # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
//! # let alice = user_id!("@alice:example.org");
|
||||
//! # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
//! # block_on(async {
|
||||
//! # let client = Client::new(homeserver).await.unwrap();
|
||||
//! let device = client.get_device(&alice, device_id!("DEVICEID")).await?;
|
||||
//! let device = client.get_device(alice, device_id!("DEVICEID")).await?;
|
||||
//!
|
||||
//! if let Some(device) = device {
|
||||
//! // Let's request the device to be verified.
|
||||
@@ -62,14 +62,14 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use std::convert::TryFrom;
|
||||
//! # use matrix_sdk::{Client, ruma::UserId};
|
||||
//! # use matrix_sdk::{Client, ruma::user_id};
|
||||
//! # use url::Url;
|
||||
//! # use futures::executor::block_on;
|
||||
//! # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
//! # let alice = user_id!("@alice:example.org");
|
||||
//! # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
//! # block_on(async {
|
||||
//! # let client = Client::new(homeserver).await.unwrap();
|
||||
//! let user = client.get_user_identity(&alice).await?;
|
||||
//! let user = client.get_user_identity(alice).await?;
|
||||
//!
|
||||
//! if let Some(user) = user {
|
||||
//! // Let's request the user to be verified.
|
||||
|
||||
@@ -91,13 +91,13 @@ impl UserIdentity {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use matrix_sdk::{Client, ruma::user_id};
|
||||
/// # use url::Url;
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # futures::executor::block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(&alice).await?;
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// println!("This user identity belongs to {}", user.user_id().as_str());
|
||||
@@ -143,13 +143,13 @@ impl UserIdentity {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use matrix_sdk::{Client, ruma::user_id};
|
||||
/// # use url::Url;
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # futures::executor::block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(&alice).await?;
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// let verification = user.request_verification().await?;
|
||||
@@ -198,17 +198,17 @@ impl UserIdentity {
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # ruma::{
|
||||
/// # UserId,
|
||||
/// # user_id,
|
||||
/// # events::key::verification::VerificationMethod,
|
||||
/// # }
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(&alice).await?;
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
///
|
||||
/// // We don't want to support showing a QR code, we only support SAS
|
||||
/// // verification
|
||||
@@ -277,17 +277,17 @@ impl UserIdentity {
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # ruma::{
|
||||
/// # UserId,
|
||||
/// # user_id,
|
||||
/// # events::key::verification::VerificationMethod,
|
||||
/// # }
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(&alice).await?;
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// user.verify().await?;
|
||||
@@ -320,17 +320,17 @@ impl UserIdentity {
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # ruma::{
|
||||
/// # UserId,
|
||||
/// # user_id,
|
||||
/// # events::key::verification::VerificationMethod,
|
||||
/// # }
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(&alice).await?;
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// if user.verified() {
|
||||
@@ -360,17 +360,17 @@ impl UserIdentity {
|
||||
/// # use matrix_sdk::{
|
||||
/// # Client,
|
||||
/// # ruma::{
|
||||
/// # UserId,
|
||||
/// # user_id,
|
||||
/// # events::key::verification::VerificationMethod,
|
||||
/// # }
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org").unwrap();
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// let user = client.get_user_identity(&alice).await?;
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// // Let's verify the user after we confirm that the master key
|
||||
|
||||
@@ -283,6 +283,7 @@ use ruma::{
|
||||
use tracing::{debug, instrument, trace, warn};
|
||||
|
||||
use crate::{
|
||||
attachment::{AttachmentInfo, Thumbnail},
|
||||
encryption::{
|
||||
identities::{Device, UserDevices},
|
||||
verification::{SasVerification, Verification, VerificationRequest},
|
||||
@@ -367,14 +368,14 @@ impl Client {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::{device_id, UserId}};
|
||||
/// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// if let Some(device) = client.get_device(&alice, device_id!("DEVICEID")).await? {
|
||||
/// if let Some(device) = client.get_device(alice, device_id!("DEVICEID")).await? {
|
||||
/// println!("{:?}", device.verified());
|
||||
///
|
||||
/// if !device.verified() {
|
||||
@@ -407,14 +408,14 @@ impl Client {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use matrix_sdk::{Client, ruma::user_id};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let devices = client.get_user_devices(&alice).await?;
|
||||
/// let devices = client.get_user_devices(alice).await?;
|
||||
///
|
||||
/// for device in devices.devices() {
|
||||
/// println!("{:?}", device);
|
||||
@@ -446,14 +447,14 @@ impl Client {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # use matrix_sdk::{Client, ruma::UserId};
|
||||
/// # use matrix_sdk::{Client, ruma::user_id};
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # block_on(async {
|
||||
/// # let alice = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let alice = user_id!("@alice:example.org");
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// let user = client.get_user_identity(&alice).await?;
|
||||
/// let user = client.get_user_identity(alice).await?;
|
||||
///
|
||||
/// if let Some(user) = user {
|
||||
/// println!("{:?}", user.verified());
|
||||
@@ -499,14 +500,13 @@ impl Client {
|
||||
/// ```no_run
|
||||
/// # use std::{convert::TryFrom, collections::BTreeMap};
|
||||
/// # use matrix_sdk::{
|
||||
/// # ruma::{api::client::r0::uiaa, assign, UserId},
|
||||
/// # ruma::{api::client::r0::uiaa, assign},
|
||||
/// # Client,
|
||||
/// # };
|
||||
/// # use url::Url;
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use serde_json::json;
|
||||
/// # block_on(async {
|
||||
/// # let user_id = Box::<UserId>::try_from("@alice:example.org")?;
|
||||
/// # let homeserver = Url::parse("http://example.com")?;
|
||||
/// # let client = Client::new(homeserver).await?;
|
||||
/// if let Err(e) = client.bootstrap_cross_signing(None).await {
|
||||
@@ -730,14 +730,45 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Encrypt and upload the file to be read from `reader` and construct an
|
||||
/// attachment message with `body` and the specified `content_type`.
|
||||
/// attachment message with `body`, `content_type`, `info` and `thumbnail`.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub(crate) async fn prepare_encrypted_attachment_message<R: Read>(
|
||||
pub(crate) async fn prepare_encrypted_attachment_message<R: Read, T: Read>(
|
||||
&self,
|
||||
body: &str,
|
||||
content_type: &mime::Mime,
|
||||
reader: &mut R,
|
||||
info: Option<AttachmentInfo>,
|
||||
thumbnail: Option<Thumbnail<'_, T>>,
|
||||
) -> Result<ruma::events::room::message::MessageType> {
|
||||
let (thumbnail_file, thumbnail_info) =
|
||||
if let Some(thumbnail) = thumbnail {
|
||||
let mut reader = matrix_sdk_base::crypto::AttachmentEncryptor::new(thumbnail.reader);
|
||||
|
||||
let response = self.upload(thumbnail.content_type, &mut reader).await?;
|
||||
|
||||
let file: ruma::events::room::EncryptedFile = {
|
||||
let keys = reader.finish();
|
||||
ruma::events::room::EncryptedFileInit {
|
||||
url: response.content_uri,
|
||||
key: keys.web_key,
|
||||
iv: keys.iv,
|
||||
hashes: keys.hashes,
|
||||
v: keys.version,
|
||||
}
|
||||
.into()
|
||||
};
|
||||
|
||||
use ruma::events::room::ThumbnailInfo;
|
||||
let thumbnail_info = assign!(
|
||||
thumbnail.info.as_ref().map(|info| ThumbnailInfo::from(info.clone())).unwrap_or_default(),
|
||||
{ mimetype: Some(thumbnail.content_type.as_ref().to_owned()) }
|
||||
);
|
||||
|
||||
(Some(Box::new(file)), Some(Box::new(thumbnail_info)))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let mut reader = matrix_sdk_base::crypto::AttachmentEncryptor::new(reader);
|
||||
|
||||
let response = self.upload(content_type, &mut reader).await?;
|
||||
@@ -754,18 +785,66 @@ impl Client {
|
||||
.into()
|
||||
};
|
||||
|
||||
use ruma::events::room::message;
|
||||
use ruma::events::room::{self, message};
|
||||
Ok(match content_type.type_() {
|
||||
mime::IMAGE => {
|
||||
message::MessageType::Image(message::ImageMessageEventContent::encrypted(body.to_owned(), file))
|
||||
let info = assign!(
|
||||
info.map(room::ImageInfo::from).unwrap_or_default(),
|
||||
{
|
||||
mimetype: Some(content_type.as_ref().to_owned()),
|
||||
thumbnail_file,
|
||||
thumbnail_info
|
||||
}
|
||||
);
|
||||
let content = assign!(
|
||||
message::ImageMessageEventContent::encrypted(body.to_owned(), file),
|
||||
{ info: Some(Box::new(info)) }
|
||||
);
|
||||
message::MessageType::Image(content)
|
||||
}
|
||||
mime::AUDIO => {
|
||||
message::MessageType::Audio(message::AudioMessageEventContent::encrypted(body.to_owned(), file))
|
||||
let info = assign!(
|
||||
info.map(message::AudioInfo::from).unwrap_or_default(),
|
||||
{
|
||||
mimetype: Some(content_type.as_ref().to_owned()),
|
||||
}
|
||||
);
|
||||
let content = assign!(
|
||||
message::AudioMessageEventContent::encrypted(body.to_owned(), file),
|
||||
{ info: Some(Box::new(info)) }
|
||||
);
|
||||
message::MessageType::Audio(content)
|
||||
}
|
||||
mime::VIDEO => {
|
||||
message::MessageType::Video(message::VideoMessageEventContent::encrypted(body.to_owned(), file))
|
||||
let info = assign!(
|
||||
info.map(message::VideoInfo::from).unwrap_or_default(),
|
||||
{
|
||||
mimetype: Some(content_type.as_ref().to_owned()),
|
||||
thumbnail_file,
|
||||
thumbnail_info
|
||||
}
|
||||
);
|
||||
let content = assign!(
|
||||
message::VideoMessageEventContent::encrypted(body.to_owned(), file),
|
||||
{ info: Some(Box::new(info)) }
|
||||
);
|
||||
message::MessageType::Video(content)
|
||||
}
|
||||
_ => {
|
||||
let info = assign!(
|
||||
info.map(message::FileInfo::from).unwrap_or_default(),
|
||||
{
|
||||
mimetype: Some(content_type.as_ref().to_owned()),
|
||||
thumbnail_file,
|
||||
thumbnail_info
|
||||
}
|
||||
);
|
||||
let content = assign!(
|
||||
message::FileMessageEventContent::encrypted(body.to_owned(), file),
|
||||
{ info: Some(Box::new(info)) }
|
||||
);
|
||||
message::MessageType::File(content)
|
||||
}
|
||||
_ => message::MessageType::File(message::FileMessageEventContent::encrypted(body.to_owned(), file)),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,11 @@ pub enum Error {
|
||||
/// An error encountered when trying to parse a user tag name.
|
||||
#[error(transparent)]
|
||||
UserTagName(#[from] InvalidUserTagName),
|
||||
|
||||
/// An error while processing images.
|
||||
#[cfg(feature = "image_proc")]
|
||||
#[error(transparent)]
|
||||
ImageError(#[from] ImageError),
|
||||
}
|
||||
|
||||
/// Error for the room key importing functionality.
|
||||
@@ -257,3 +262,20 @@ impl From<ReqwestError> for Error {
|
||||
Error::Http(HttpError::Reqwest(e))
|
||||
}
|
||||
}
|
||||
|
||||
/// All possible errors that can happen during image processing.
|
||||
#[cfg(feature = "image_proc")]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ImageError {
|
||||
/// Error processing the image data.
|
||||
#[error(transparent)]
|
||||
Proc(#[from] image::ImageError),
|
||||
|
||||
/// The image format is not supported.
|
||||
#[error("the image format is not supported")]
|
||||
FormatNotSupported,
|
||||
|
||||
/// The thumbnail size is bigger than the original image.
|
||||
#[error("the thumbnail size is bigger than the original image size")]
|
||||
ThumbnailBiggerThanOriginal,
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ compile_error!("only one of 'native-tls' or 'rustls-tls' features can be enabled
|
||||
#[cfg(all(feature = "sso_login", target_arch = "wasm32"))]
|
||||
compile_error!("'sso_login' cannot be enabled on 'wasm32' arch");
|
||||
|
||||
#[cfg(all(feature = "image_rayon", target_arch = "wasm32"))]
|
||||
compile_error!("'image_rayon' cannot be enabled on 'wasm32' arch");
|
||||
|
||||
pub use bytes;
|
||||
pub use matrix_sdk_base::{
|
||||
media, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember, RoomType, Session,
|
||||
@@ -46,6 +49,9 @@ pub use reqwest;
|
||||
#[doc(no_inline)]
|
||||
pub use ruma;
|
||||
|
||||
mod account;
|
||||
/// Types and traits for attachments.
|
||||
pub mod attachment;
|
||||
mod client;
|
||||
pub mod config;
|
||||
mod error;
|
||||
@@ -59,7 +65,10 @@ mod sync;
|
||||
#[cfg(feature = "encryption")]
|
||||
pub mod encryption;
|
||||
|
||||
pub use account::Account;
|
||||
pub use client::{Client, LoopCtrl};
|
||||
#[cfg(feature = "image_proc")]
|
||||
pub use error::ImageError;
|
||||
pub use error::{Error, HttpError, HttpResult, Result};
|
||||
pub use http_client::HttpSend;
|
||||
pub use room_member::RoomMember;
|
||||
|
||||
@@ -523,7 +523,7 @@ pub struct MessagesOptions<'a> {
|
||||
pub limit: UInt,
|
||||
|
||||
/// A [`RoomEventFilter`] to filter returned events with.
|
||||
pub filter: Option<RoomEventFilter<'a>>,
|
||||
pub filter: RoomEventFilter<'a>,
|
||||
}
|
||||
|
||||
impl<'a> MessagesOptions<'a> {
|
||||
@@ -531,7 +531,7 @@ impl<'a> MessagesOptions<'a> {
|
||||
///
|
||||
/// All other parameters will be defaulted.
|
||||
pub fn new(from: &'a str, dir: Direction) -> Self {
|
||||
Self { from, to: None, dir, limit: uint!(10), filter: None }
|
||||
Self { from, to: None, dir, limit: uint!(10), filter: RoomEventFilter::default() }
|
||||
}
|
||||
|
||||
/// Creates `MessagesOptions` with the given start token, and `dir` set to
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#[cfg(feature = "image_proc")]
|
||||
use std::io::Cursor;
|
||||
#[cfg(feature = "encryption")]
|
||||
use std::sync::Arc;
|
||||
use std::{io::Read, ops::Deref};
|
||||
use std::{
|
||||
io::{BufReader, Read, Seek},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use matrix_sdk_common::instant::{Duration, Instant};
|
||||
#[cfg(feature = "encryption")]
|
||||
@@ -31,7 +36,14 @@ use tracing::debug;
|
||||
#[cfg(feature = "encryption")]
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::{error::HttpResult, room::Common, BaseRoom, Client, Result, RoomType};
|
||||
#[cfg(feature = "image_proc")]
|
||||
use crate::{attachment::generate_image_thumbnail, error::ImageError};
|
||||
use crate::{
|
||||
attachment::{AttachmentConfig, Thumbnail},
|
||||
error::HttpResult,
|
||||
room::Common,
|
||||
BaseRoom, Client, Result, RoomType,
|
||||
};
|
||||
|
||||
const TYPING_NOTICE_TIMEOUT: Duration = Duration::from_secs(4);
|
||||
const TYPING_NOTICE_RESEND_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
@@ -597,15 +609,13 @@ impl Joined {
|
||||
/// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
|
||||
/// media.
|
||||
///
|
||||
/// * `txn_id` - A unique ID that can be attached to a `MessageEvent`
|
||||
/// held in its unsigned field as `transaction_id`. If not given one is
|
||||
/// created for the message.
|
||||
/// * `config` - Metadata and configuration for the attachment.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::{path::PathBuf, fs::File, io::Read};
|
||||
/// # use matrix_sdk::{Client, ruma::room_id};
|
||||
/// # use matrix_sdk::{Client, ruma::room_id, attachment::AttachmentConfig};
|
||||
/// # use url::Url;
|
||||
/// # use mime;
|
||||
/// # use futures::executor::block_on;
|
||||
@@ -621,29 +631,127 @@ impl Joined {
|
||||
/// "My favorite cat",
|
||||
/// &mime::IMAGE_JPEG,
|
||||
/// &mut image,
|
||||
/// None,
|
||||
/// AttachmentConfig::new(),
|
||||
/// ).await?;
|
||||
/// }
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(()) });
|
||||
/// ```
|
||||
pub async fn send_attachment<R: Read>(
|
||||
pub async fn send_attachment<R: Read + Seek, T: Read>(
|
||||
&self,
|
||||
body: &str,
|
||||
content_type: &Mime,
|
||||
reader: &mut R,
|
||||
txn_id: Option<&TransactionId>,
|
||||
config: AttachmentConfig<'_, T>,
|
||||
) -> Result<send_message_event::Response> {
|
||||
let reader = &mut BufReader::new(reader);
|
||||
|
||||
#[cfg(feature = "image_proc")]
|
||||
let mut cursor;
|
||||
|
||||
if config.thumbnail.is_some() {
|
||||
self.prepare_and_send_attachment(body, content_type, reader, config).await
|
||||
} else {
|
||||
#[cfg(not(feature = "image_proc"))]
|
||||
let thumbnail = Thumbnail::NONE;
|
||||
|
||||
#[cfg(feature = "image_proc")]
|
||||
let thumbnail = if config.generate_thumbnail {
|
||||
match generate_image_thumbnail(content_type, reader, config.thumbnail_size) {
|
||||
Ok((thumbnail_data, thumbnail_info)) => {
|
||||
reader.rewind()?;
|
||||
|
||||
cursor = Cursor::new(thumbnail_data);
|
||||
Some(Thumbnail {
|
||||
reader: &mut cursor,
|
||||
content_type: &mime::IMAGE_JPEG,
|
||||
info: Some(thumbnail_info),
|
||||
})
|
||||
}
|
||||
Err(
|
||||
ImageError::ThumbnailBiggerThanOriginal | ImageError::FormatNotSupported,
|
||||
) => {
|
||||
reader.rewind()?;
|
||||
None
|
||||
}
|
||||
Err(error) => return Err(error.into()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let config = AttachmentConfig {
|
||||
txn_id: config.txn_id,
|
||||
info: config.info,
|
||||
thumbnail,
|
||||
#[cfg(feature = "image_proc")]
|
||||
generate_thumbnail: false,
|
||||
#[cfg(feature = "image_proc")]
|
||||
thumbnail_size: None,
|
||||
};
|
||||
|
||||
self.prepare_and_send_attachment(body, content_type, reader, config).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare and send an attachment to this room.
|
||||
///
|
||||
/// This will upload the given data that the reader produces using the
|
||||
/// [`upload()`](#method.upload) method and post an event to the given room.
|
||||
/// If the room is encrypted and the encryption feature is enabled the
|
||||
/// upload will be encrypted.
|
||||
///
|
||||
/// This is a convenience method that calls the
|
||||
/// [`Client::upload()`](#Client::method.upload) and afterwards the
|
||||
/// [`send()`](#method.send).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `body` - A textual representation of the media that is going to be
|
||||
/// uploaded. Usually the file name.
|
||||
///
|
||||
/// * `content_type` - The type of the media, this will be used as the
|
||||
/// content-type header.
|
||||
///
|
||||
/// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
|
||||
/// media.
|
||||
///
|
||||
/// * `config` - Metadata and configuration for the attachment.
|
||||
async fn prepare_and_send_attachment<R: Read, T: Read>(
|
||||
&self,
|
||||
body: &str,
|
||||
content_type: &Mime,
|
||||
reader: &mut R,
|
||||
config: AttachmentConfig<'_, T>,
|
||||
) -> Result<send_message_event::Response> {
|
||||
#[cfg(feature = "encryption")]
|
||||
let content = if self.is_encrypted() {
|
||||
self.client.prepare_encrypted_attachment_message(body, content_type, reader).await?
|
||||
self.client
|
||||
.prepare_encrypted_attachment_message(
|
||||
body,
|
||||
content_type,
|
||||
reader,
|
||||
config.info,
|
||||
config.thumbnail,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
self.client.prepare_attachment_message(body, content_type, reader).await?
|
||||
self.client
|
||||
.prepare_attachment_message(
|
||||
body,
|
||||
content_type,
|
||||
reader,
|
||||
config.info,
|
||||
config.thumbnail,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "encryption"))]
|
||||
let content = self.client.prepare_attachment_message(body, content_type, reader).await?;
|
||||
let content = self
|
||||
.client
|
||||
.prepare_attachment_message(body, content_type, reader, config.info, config.thumbnail)
|
||||
.await?;
|
||||
|
||||
self.send(RoomMessageEventContent::new(content), txn_id).await
|
||||
self.send(RoomMessageEventContent::new(content), config.txn_id).await
|
||||
}
|
||||
|
||||
/// Send a room state event to the homeserver.
|
||||
|
||||
Reference in New Issue
Block a user