Allow setting an avatar image for the bot's account
This commit is contained in:
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -371,6 +371,9 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"log",
|
"log",
|
||||||
"matrix-sdk",
|
"matrix-sdk",
|
||||||
|
"mime",
|
||||||
|
"mime-sniffer",
|
||||||
|
"mime_guess2",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -1826,12 +1829,34 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime-sniffer"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b8b2a64cd735f1d5f17ff6701ced3cc3c54851f9448caf454cd9c923d812408"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime2ext"
|
name = "mime2ext"
|
||||||
version = "0.1.54"
|
version = "0.1.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc"
|
checksum = "cbf6f36070878c42c5233846cd3de24cf9016828fd47bc22957a687298bb21fc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess2"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"phf",
|
||||||
|
"phf_shared",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -2034,6 +2059,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2043,6 +2069,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@@ -34,6 +34,9 @@ futures = "0.3"
|
|||||||
http = "1.3"
|
http = "1.3"
|
||||||
log = { version = "0.4", features = ["std"] }
|
log = { version = "0.4", features = ["std"] }
|
||||||
matrix-sdk = { version = "0.13", features = ["anyhow", "markdown", "rustls-tls"], default-features = false }
|
matrix-sdk = { version = "0.13", features = ["anyhow", "markdown", "rustls-tls"], default-features = false }
|
||||||
|
mime = "0.3"
|
||||||
|
mime-sniffer = "0.1"
|
||||||
|
mime_guess2 = "2"
|
||||||
quick-xml = { version = "0.38", features = ["serialize"] }
|
quick-xml = { version = "0.38", features = ["serialize"] }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["charset", "http2", "gzip", "rustls-tls-native-roots", "system-proxy"] }
|
reqwest = { version = "0.12", default-features = false, features = ["charset", "http2", "gzip", "rustls-tls-native-roots", "system-proxy"] }
|
||||||
|
@@ -6,6 +6,8 @@ bind_port: 3000
|
|||||||
user_id: "@mybebot:example.com"
|
user_id: "@mybebot:example.com"
|
||||||
# Password for Matrix user.
|
# Password for Matrix user.
|
||||||
password: "secret-matrix-account-password"
|
password: "secret-matrix-account-password"
|
||||||
|
# Optional path to an image file to use as the bot account's avatar.
|
||||||
|
avatar_image_path: "/path/to/avatar.jpg"
|
||||||
# All Gitlab-specific settings are under here.
|
# All Gitlab-specific settings are under here.
|
||||||
gitlab_webhook:
|
gitlab_webhook:
|
||||||
# Optional prefix to serve the webhook path under (default is empty string).
|
# Optional prefix to serve the webhook path under (default is empty string).
|
||||||
|
@@ -87,6 +87,7 @@ pub struct Config {
|
|||||||
#[serde(deserialize_with = "crate::matrix::deser_user_id")]
|
#[serde(deserialize_with = "crate::matrix::deser_user_id")]
|
||||||
pub user_id: OwnedUserId,
|
pub user_id: OwnedUserId,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
pub avatar_image_path: Option<PathBuf>,
|
||||||
pub gitlab_webhook: Option<GitlabWebhookConfig>,
|
pub gitlab_webhook: Option<GitlabWebhookConfig>,
|
||||||
pub mail_archive: Option<MailArchiveConfig>,
|
pub mail_archive: Option<MailArchiveConfig>,
|
||||||
}
|
}
|
||||||
|
18
src/main.rs
18
src/main.rs
@@ -41,13 +41,25 @@ async fn run() -> anyhow::Result<()> {
|
|||||||
.ok_or_else(|| anyhow!("Config file should be passed as only parameter"))?;
|
.ok_or_else(|| anyhow!("Config file should be passed as only parameter"))?;
|
||||||
let mut config = config::load(config_path).await?;
|
let mut config = config::load(config_path).await?;
|
||||||
|
|
||||||
let matrix_client = matrix::connect(&config).await.context("Failed to connect to Matrix")?;
|
let mut join_handles = Vec::default();
|
||||||
|
|
||||||
let handles = if let Some(mail_archive) = config.mail_archive.take() {
|
let matrix_client = matrix::connect(&config).await.context("Failed to connect to Matrix")?;
|
||||||
|
if let Some(avatar_path) = config.avatar_image_path {
|
||||||
|
let matrix_client = matrix_client.clone();
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
if let Err(err) = matrix::set_avatar_if_needed(&matrix_client, avatar_path).await {
|
||||||
|
warn!("Failed to set matrix avatar: {err}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
join_handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mail_join_handles = if let Some(mail_archive) = config.mail_archive.take() {
|
||||||
mail_archive::start_polling(mail_archive, matrix_client.clone())?
|
mail_archive::start_polling(mail_archive, matrix_client.clone())?
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
join_handles.extend(mail_join_handles);
|
||||||
|
|
||||||
if let Some(gitlab_webhook) = config.gitlab_webhook.take() {
|
if let Some(gitlab_webhook) = config.gitlab_webhook.take() {
|
||||||
let gitlab = gitlab_webhook::build_route(gitlab_webhook, matrix_client.clone());
|
let gitlab = gitlab_webhook::build_route(gitlab_webhook, matrix_client.clone());
|
||||||
@@ -60,7 +72,7 @@ async fn run() -> anyhow::Result<()> {
|
|||||||
axum::serve(listener, gitlab).await?;
|
axum::serve(listener, gitlab).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
join_all(handles).await;
|
join_all(join_handles).await;
|
||||||
|
|
||||||
error!("No functionality is configured; exiting");
|
error!("No functionality is configured; exiting");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
@@ -14,16 +14,19 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::{fmt, process::exit, time::Duration};
|
use std::{fmt, path::Path, process::exit, time::Duration};
|
||||||
|
|
||||||
|
use futures::TryFutureExt;
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
config::SyncSettings,
|
config::SyncSettings,
|
||||||
|
media::MediaFormat,
|
||||||
room::Room,
|
room::Room,
|
||||||
ruma::{OwnedRoomOrAliasId, OwnedUserId, RoomOrAliasId, UserId},
|
ruma::{OwnedRoomOrAliasId, OwnedUserId, RoomOrAliasId, UserId},
|
||||||
BaseRoom, Client,
|
BaseRoom, Client,
|
||||||
};
|
};
|
||||||
|
use mime_sniffer::MimeTypeSnifferExt;
|
||||||
use serde::de;
|
use serde::de;
|
||||||
use tokio::time::sleep;
|
use tokio::{fs, time::sleep};
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
|
||||||
@@ -60,6 +63,34 @@ pub async fn connect(config: &Config) -> anyhow::Result<Client> {
|
|||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn set_avatar_if_needed<P: AsRef<Path>>(client: &Client, avatar_path: P) -> anyhow::Result<()> {
|
||||||
|
let new_avatar = fs::read(&avatar_path).await?;
|
||||||
|
|
||||||
|
let account = client.account();
|
||||||
|
let cur_avatar = account.get_avatar(MediaFormat::File).await?;
|
||||||
|
|
||||||
|
if cur_avatar.as_ref() != Some(&new_avatar) {
|
||||||
|
// Falling back to extension matching because for some reason mime-sniffer can't handle SVG
|
||||||
|
// files, even though it seems like it should be able to.
|
||||||
|
if let Some(media_type) = new_avatar
|
||||||
|
.as_slice()
|
||||||
|
.sniff_mime_type_ext()
|
||||||
|
.or_else(|| mime_guess2::from_path(&avatar_path).first())
|
||||||
|
{
|
||||||
|
info!("Setting avatar image from '{}'", avatar_path.as_ref().to_string_lossy());
|
||||||
|
account
|
||||||
|
.upload_avatar(&media_type, new_avatar)
|
||||||
|
.map_err(Into::into)
|
||||||
|
.map_ok(|_| ())
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Cannot determine media type of avatar image"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn ensure_room_joined(matrix_client: &Client, room_id: &OwnedRoomOrAliasId) -> anyhow::Result<Room> {
|
pub async fn ensure_room_joined(matrix_client: &Client, room_id: &OwnedRoomOrAliasId) -> anyhow::Result<Room> {
|
||||||
fn room_matches(a_room: &BaseRoom, our_room: &OwnedRoomOrAliasId) -> bool {
|
fn room_matches(a_room: &BaseRoom, our_room: &OwnedRoomOrAliasId) -> bool {
|
||||||
let our_room_str = our_room.as_str();
|
let our_room_str = our_room.as_str();
|
||||||
|
Reference in New Issue
Block a user