mirror of
https://github.com/chatmail/core.git
synced 2026-05-25 01:36:31 +03:00
Generate a QR code
This commit is contained in:
@@ -35,7 +35,7 @@ const VCARD_SCHEME: &str = "BEGIN:VCARD";
|
|||||||
const SMTP_SCHEME: &str = "SMTP:";
|
const SMTP_SCHEME: &str = "SMTP:";
|
||||||
const HTTP_SCHEME: &str = "http://";
|
const HTTP_SCHEME: &str = "http://";
|
||||||
const HTTPS_SCHEME: &str = "https://";
|
const HTTPS_SCHEME: &str = "https://";
|
||||||
const DCBACKUP_SCHEME: &str = "DCBACKUP:";
|
pub(crate) const DCBACKUP_SCHEME: &str = "DCBACKUP:";
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Qr {
|
pub enum Qr {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use base64::Engine as _;
|
use base64::Engine as _;
|
||||||
use qrcodegen::{QrCode, QrCodeEcc};
|
use qrcodegen::{QrCode, QrCodeEcc};
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ use crate::{
|
|||||||
config::Config,
|
config::Config,
|
||||||
contact::{Contact, ContactId},
|
contact::{Contact, ContactId},
|
||||||
context::Context,
|
context::Context,
|
||||||
|
qr::{Qr, DCBACKUP_SCHEME},
|
||||||
securejoin, stock_str,
|
securejoin, stock_str,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,6 +44,36 @@ async fn generate_join_group_qr_code(context: &Context, chat_id: ChatId) -> Resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_verification_qr(context: &Context) -> Result<String> {
|
async fn generate_verification_qr(context: &Context) -> Result<String> {
|
||||||
|
let (avatar, displayname, addr, color) = self_info(context).await?;
|
||||||
|
|
||||||
|
inner_generate_secure_join_qr_code(
|
||||||
|
&stock_str::setup_contact_qr_description(context, &displayname, &addr).await,
|
||||||
|
&securejoin::get_securejoin_qr(context, None).await?,
|
||||||
|
&color,
|
||||||
|
avatar,
|
||||||
|
displayname.chars().next().unwrap_or('#'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn generate_backup_qr(context: &Context, qr: Qr) -> Result<String> {
|
||||||
|
let Qr::Backup { ticket } = qr else {
|
||||||
|
bail!("QR code not a backup");
|
||||||
|
};
|
||||||
|
let content = format!("{DCBACKUP_SCHEME}{}", ticket.to_string());
|
||||||
|
let (avatar, displayname, _addr, color) = self_info(context).await?;
|
||||||
|
let description = "Scan to setup second device"; // TODO: translation!
|
||||||
|
|
||||||
|
inner_generate_secure_join_qr_code(
|
||||||
|
description,
|
||||||
|
&content,
|
||||||
|
&color,
|
||||||
|
avatar,
|
||||||
|
displayname.chars().next().unwrap_or('#'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `(avatar, displayname, addr, color) of the configured account.
|
||||||
|
async fn self_info(context: &Context) -> Result<(Option<Vec<u8>>, String, String, String)> {
|
||||||
let contact = Contact::get_by_id(context, ContactId::SELF).await?;
|
let contact = Contact::get_by_id(context, ContactId::SELF).await?;
|
||||||
|
|
||||||
let avatar = match contact.get_profile_image(context).await? {
|
let avatar = match contact.get_profile_image(context).await? {
|
||||||
@@ -55,16 +86,11 @@ async fn generate_verification_qr(context: &Context) -> Result<String> {
|
|||||||
|
|
||||||
let displayname = match context.get_config(Config::Displayname).await? {
|
let displayname = match context.get_config(Config::Displayname).await? {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => contact.get_addr().to_owned(),
|
None => contact.get_addr().to_string(),
|
||||||
};
|
};
|
||||||
|
let addr = contact.get_addr().to_string();
|
||||||
inner_generate_secure_join_qr_code(
|
let color = color_int_to_hex_string(contact.get_color());
|
||||||
&stock_str::setup_contact_qr_description(context, &displayname, contact.get_addr()).await,
|
Ok((avatar, displayname, addr, color))
|
||||||
&securejoin::get_securejoin_qr(context, None).await?,
|
|
||||||
&color_int_to_hex_string(contact.get_color()),
|
|
||||||
avatar,
|
|
||||||
displayname.chars().next().unwrap_or('#'),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner_generate_secure_join_qr_code(
|
fn inner_generate_secure_join_qr_code(
|
||||||
@@ -268,6 +294,11 @@ fn inner_generate_secure_join_qr_code(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use testdir::testdir;
|
||||||
|
|
||||||
|
use crate::imex::BackupProvider;
|
||||||
|
use crate::test_utils::TestContextManager;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
@@ -282,4 +313,19 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(svg.contains("descr123 " < > &"))
|
assert!(svg.contains("descr123 " < > &"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_generate_backup_qr() {
|
||||||
|
let dir = testdir!();
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let ctx = tcm.alice().await;
|
||||||
|
let provider = BackupProvider::prepare(&ctx, &dir).await.unwrap();
|
||||||
|
let qr = provider.qr();
|
||||||
|
|
||||||
|
let rendered = generate_backup_qr(&ctx, qr).await.unwrap();
|
||||||
|
tokio::fs::write(dir.join("qr.svg"), &rendered)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(&rendered[..4], "<svg");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user