use std::fs::File;
use std::io::Read;
use anyhow::anyhow;
use matrix_sdk::RoomState;
use matrix_sdk::ruma::{
OwnedRoomId,
OwnedRoomOrAliasId,
UserId,
};
use qrcode::QrCode;
use crate::prelude::*;
pub struct Location {
pub lat: f64,
pub lon: f64,
}
impl Location {
pub fn new(lat: f64, lon: f64) -> Self { Self { lat, lon } }
pub fn osm(&self, zoom: Option<u8>, marker: bool) -> String {
osm(self.lat, self.lon, zoom, marker)
}
pub fn osm_shortlink(&self, zoom: Option<u8>, marker: bool) -> String {
osm_shortlink(self.lat, self.lon, zoom, marker)
}
}
impl std::convert::TryFrom<&serde_json::Value> for Location {
type Error = anyhow::Error;
fn try_from(item: &serde_json::Value) -> Result<Self, Self::Error> {
Ok(Self {
lat: item["latitude"].as_f64().ok_or(anyhow!("schema error ({item})"))?,
lon: item["longitude"].as_f64().ok_or(anyhow!("schema error ({item})"))?,
})
}
}
fn osm(lat: f64, lon: f64, zoom: Option<u8>, marker: bool) -> String {
format!(
"https://www.openstreetmap.org/{}#map={}/{}/{}",
if marker {
format!("?mlat={}&mlon={}", lat, lon)
} else {
"".to_string()
},
zoom.unwrap_or(17),
lat,
lon,
)
}
const SHORTLINK_CHARSET: &[u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_~";
fn osm_shortlink(lat: f64, lon: f64, zoom: Option<u8>, marker: bool) -> String {
let zoom = zoom.unwrap_or(17);
let y = (((lat + 90.0) % 180.0) * 2.0_f64.powi(32) / 180.0) as u64;
let x = (((lon + 180.0) % 360.0) * 2.0_f64.powi(32) / 360.0) as u64;
let mut c = 0u64;
for i in (0u8..32).rev() {
c = (c << 1) | ((x >> i) & 1);
c = (c << 1) | ((y >> i) & 1);
}
let (d, r) = ((zoom + 8).div_ceil(3), (zoom + 8) % 3);
let mut ret = String::with_capacity((19 + d + r + if marker { 3 } else { 0 }) as usize);
ret.push_str("https://osm.org/go/");
for i in 0..d {
ret.push(SHORTLINK_CHARSET[((c >> (58 - 6 * i)) & 0x3f) as usize].into());
}
for _ in 0..r {
ret.push('-');
}
if marker {
ret.push_str("?m=")
}
ret
}
pub fn create_qrcode_png(data: &str) -> anyhow::Result<Vec<u8>> {
let qrcode = QrCode::new(data.as_bytes()).unwrap();
let image = qrcode.render::<image::Luma<u8>>().build();
let mut png: Vec<u8> = Vec::new();
image.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png)?;
Ok(png)
}
pub async fn nie_zesraj_się(
event: &OriginalSyncRoomMessageEvent,
room: &Room,
) -> anyhow::Result<()> {
let content = RoomMessageEventContent::notice_plain("Nie zesraj się.").make_reply_to(
&event.clone().into_full_event(room.room_id().into()),
ForwardThread::No,
AddMentions::Yes,
);
room.send(content).await?;
Ok(())
}
pub fn text_message_gate(
event: &OriginalSyncRoomMessageEvent,
client: &Client,
room: &Room,
) -> Option<String> {
if room.state() != RoomState::Joined || event.sender == client.user_id().unwrap() {
return None;
}
let MessageType::Text(text_content) = &event.content.msgtype else {
return None;
};
Some(text_content.body.clone())
}
pub async fn resolve_room_alias_with_default(
client: Client,
room_or_alias_id: Option<OwnedRoomOrAliasId>,
default: Option<&Room>,
) -> anyhow::Result<Option<Room>> {
if let Some(room_or_alias_id) = room_or_alias_id {
let room_id = match OwnedRoomId::try_from(room_or_alias_id) {
Ok(room_id) => room_id,
Err(room_alias_id) => client.resolve_room_alias(&room_alias_id).await?.room_id,
};
Ok(client.get_room(&room_id))
} else if let Some(default) = default {
Ok(Some(default.clone()))
} else {
Ok(None)
}
}
pub fn random() -> impl Iterator<Item = u8> {
File::open("/dev/urandom")
.expect("can't open /dev/urandom")
.bytes()
.map(|r| r.expect("failed to read from /dev/urandom"))
}
#[rustfmt::skip]
pub fn localpart_without_bridge_prefix(user_id: &UserId) -> &str {
let localpart = user_id.localpart();
[
"_discord_",
"libera_",
"slack_",
"telegram_",
].iter().filter_map(|prefix| localpart.strip_prefix(prefix)).next().unwrap_or(localpart)
}
pub async fn check_acl_room(
client: Client,
acl: &Vec<OwnedRoomOrAliasId>,
room: &Room,
) -> anyhow::Result<bool> {
tracing::debug!("check_acl_room(acl={:?}, room.room_id={:?}", &acl, room.room_id());
for room_or_alias_id in acl.iter() {
tracing::debug!(" checking {:?}", &room_or_alias_id);
if let Some(r) =
resolve_room_alias_with_default(client.clone(), Some(room_or_alias_id.clone()), None)
.await?
{
tracing::debug!(" resolved to {:?}", r.room_id());
if r.room_id() == room.room_id() {
tracing::debug!(" found");
return Ok(true);
}
}
}
tracing::debug!(" not found");
Ok(false)
}
#[cfg(test)]
mod tests {
use super::*;
use matrix_sdk::ruma::user_id;
#[test]
#[rustfmt::skip]
fn test_localpart_without_bridge_prefix() {
assert_eq!(localpart_without_bridge_prefix(
user_id!("@woju:hackerspace.pl")), "woju");
assert_eq!(localpart_without_bridge_prefix(
user_id!("@libera_woju:hackerspace.pl")), "woju");
assert_eq!(localpart_without_bridge_prefix(
user_id!("@libera_libera_woju:hackerspace.pl")), "libera_woju");
assert_eq!(localpart_without_bridge_prefix(
user_id!("@telegram_libera_woju:hackerspace.pl")), "libera_woju");
assert_eq!(localpart_without_bridge_prefix(
user_id!("@telegram_123456:hackerspace.pl")), "123456");
assert_eq!(localpart_without_bridge_prefix(
user_id!("@_discord_123456:hackerspace.pl")), "123456");
}
}