Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (99)
Showing
with 1551 additions and 326 deletions
Rocket.toml Rocket.toml
/target /target
/target_backup
**/*.rs.bk **/*.rs.bk
.mongo .mongo
.env .env
......
...@@ -646,12 +646,6 @@ version = "1.0.4" ...@@ -646,12 +646,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "dotenv" name = "dotenv"
version = "0.15.0" version = "0.15.0"
...@@ -1389,6 +1383,15 @@ version = "0.5.4" ...@@ -1389,6 +1383,15 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "linkify"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1986921c3c13e81df623c66a298d4b130c061bcb98a01f5b2d3ac402b1649a7f"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.3.4" version = "0.3.4"
...@@ -1596,6 +1599,15 @@ dependencies = [ ...@@ -1596,6 +1599,15 @@ dependencies = [
"rand 0.6.5", "rand 0.6.5",
] ]
[[package]]
name = "nanoid"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8"
dependencies = [
"rand 0.8.3",
]
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.7" version = "0.2.7"
...@@ -2329,8 +2341,8 @@ dependencies = [ ...@@ -2329,8 +2341,8 @@ dependencies = [
[[package]] [[package]]
name = "rauth" name = "rauth"
version = "0.2.5-alpha.0" version = "0.2.7-patch.0"
source = "git+https://gitlab.insrt.uk/insert/rauth?rev=c52758a5087cd035b0ed9c6eacc942ba5468d2ce#c52758a5087cd035b0ed9c6eacc942ba5468d2ce" source = "git+https://gitlab.insrt.uk/insert/rauth?rev=00d3c3dff51cf3242a7d4adda4c5184c97fa2a03#00d3c3dff51cf3242a7d4adda4c5184c97fa2a03"
dependencies = [ dependencies = [
"chrono", "chrono",
"handlebars", "handlebars",
...@@ -2338,7 +2350,7 @@ dependencies = [ ...@@ -2338,7 +2350,7 @@ dependencies = [
"lazy_static", "lazy_static",
"lettre", "lettre",
"mongodb", "mongodb",
"nanoid", "nanoid 0.3.0",
"regex", "regex",
"reqwest", "reqwest",
"rocket", "rocket",
...@@ -2346,7 +2358,6 @@ dependencies = [ ...@@ -2346,7 +2358,6 @@ dependencies = [
"rust-argon2", "rust-argon2",
"serde", "serde",
"serde_json", "serde_json",
"snafu",
"tokio", "tokio",
"ulid", "ulid",
"validator", "validator",
...@@ -2475,7 +2486,7 @@ dependencies = [ ...@@ -2475,7 +2486,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt" name = "revolt"
version = "0.4.1-alpha.2" version = "0.0.0"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-tungstenite", "async-tungstenite",
...@@ -2490,10 +2501,12 @@ dependencies = [ ...@@ -2490,10 +2501,12 @@ dependencies = [
"impl_ops", "impl_ops",
"lazy_static", "lazy_static",
"lettre", "lettre",
"linkify",
"log", "log",
"many-to-many", "many-to-many",
"md5", "md5",
"mongodb", "mongodb",
"nanoid 0.4.0",
"num_enum", "num_enum",
"once_cell", "once_cell",
"rand 0.7.3", "rand 0.7.3",
...@@ -2506,7 +2519,6 @@ dependencies = [ ...@@ -2506,7 +2519,6 @@ dependencies = [
"rocket_prometheus", "rocket_prometheus",
"serde", "serde",
"serde_json", "serde_json",
"snafu",
"time 0.2.25", "time 0.2.25",
"ulid", "ulid",
"urlencoding", "urlencoding",
...@@ -2914,27 +2926,6 @@ version = "1.6.1" ...@@ -2914,27 +2926,6 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "snafu"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
dependencies = [
"doc-comment",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.9",
"syn 1.0.60",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.3.19" version = "0.3.19"
......
[package] [package]
name = "revolt" name = "revolt"
version = "0.4.1-alpha.2" # To help optimise CI and Docker builds.
# Version here is left as 0.0.0, please
# adjust and run ./set_version.sh instead.
version = "0.0.0"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"
...@@ -13,7 +16,9 @@ log = "0.4.11" ...@@ -13,7 +16,9 @@ log = "0.4.11"
ulid = "0.4.1" ulid = "0.4.1"
rand = "0.7.3" rand = "0.7.3"
time = "0.2.16" time = "0.2.16"
nanoid = "0.4.0"
base64 = "0.13.0" base64 = "0.13.0"
linkify = "0.6.0"
dotenv = "0.15.0" dotenv = "0.15.0"
futures = "0.3.8" futures = "0.3.8"
chrono = "0.4.15" chrono = "0.4.15"
...@@ -28,7 +33,6 @@ lazy_static = "1.4.0" ...@@ -28,7 +33,6 @@ lazy_static = "1.4.0"
urlencoding = "1.1.1" urlencoding = "1.1.1"
many-to-many = "0.1.2" many-to-many = "0.1.2"
lettre = "0.10.0-alpha.1" lettre = "0.10.0-alpha.1"
snafu = { version = "0.6.9" }
reqwest = { version = "0.10.8", features = ["json"] } reqwest = { version = "0.10.8", features = ["json"] }
serde = { version = "1.0.115", features = ["derive"] } serde = { version = "1.0.115", features = ["derive"] }
validator = { version = "0.11", features = ["derive"] } validator = { version = "0.11", features = ["derive"] }
...@@ -38,7 +42,7 @@ async-std = { version = "1.8.0", features = ["tokio02", "attributes"] } ...@@ -38,7 +42,7 @@ async-std = { version = "1.8.0", features = ["tokio02", "attributes"] }
async-tungstenite = { version = "0.10.0", features = ["async-std-runtime"] } async-tungstenite = { version = "0.10.0", features = ["async-std-runtime"] }
rocket_cors = { git = "https://github.com/insertish/rocket_cors", branch = "master" } rocket_cors = { git = "https://github.com/insertish/rocket_cors", branch = "master" }
mongodb = { version = "1.1.1", features = ["tokio-runtime"], default-features = false } mongodb = { version = "1.1.1", features = ["tokio-runtime"], default-features = false }
rauth = { git = "https://gitlab.insrt.uk/insert/rauth", rev = "c52758a5087cd035b0ed9c6eacc942ba5468d2ce" } rauth = { git = "https://gitlab.insrt.uk/insert/rauth", rev = "00d3c3dff51cf3242a7d4adda4c5184c97fa2a03" }
rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", rev = "031948c1daaa146128d8a435be116476f2adde00" } rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", rev = "031948c1daaa146128d8a435be116476f2adde00" }
rocket_prometheus = { git = "https://github.com/insertish/rocket_prometheus", rev = "3d825aedb42793246c306a81fe67c5b187948983" } rocket_prometheus = { git = "https://github.com/insertish/rocket_prometheus", rev = "3d825aedb42793246c306a81fe67c5b187948983" }
rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "031948c1daaa146128d8a435be116476f2adde00", default-features = false } rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "031948c1daaa146128d8a435be116476f2adde00", default-features = false }
...@@ -6,6 +6,9 @@ WORKDIR /home/rust/src ...@@ -6,6 +6,9 @@ WORKDIR /home/rust/src
RUN USER=root cargo new --bin revolt RUN USER=root cargo new --bin revolt
WORKDIR /home/rust/src/revolt WORKDIR /home/rust/src/revolt
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
COPY src/bin/dummy.rs ./src/bin/dummy.rs
RUN cargo build --release --bin dummy
COPY assets/templates ./assets/templates COPY assets/templates ./assets/templates
COPY src ./src COPY src ./src
RUN cargo build --release RUN cargo build --release
......
This diff is collapsed.
...@@ -4,57 +4,58 @@ ...@@ -4,57 +4,58 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Reset your password.</title> <title>Reset your password.</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style type="text/css"> <style type="text/css">
a[x-apple-data-detectors] {color: inherit !important;} a[x-apple-data-detectors] {color: inherit !important;}
</style> </style>
</head> </head>
<body style="margin: 0; padding: 0;"> <body style="margin: 0; padding: 0;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%"> <table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr> <tr>
<td style="padding: 20px 0 30px 0;"> <td style="padding: 20px 0 30px 0;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;"> <table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;">
<tr> <tr>
<td align="center" bgcolor="#ff4654"> <td align="center" bgcolor="#ff4654">
<img src="https://revolt.chat/header.png" alt="REVOLT logo" width="600" height="168" style="display: block;" /> <img src="https://revolt.chat/header.png" alt="Revolt logo" width="600" height="168" style="display: block;" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;"> <td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr> <tr>
<td style="color: #153643; font-family: Arial, sans-serif;"> <td style="color: #153643; font-family: Arial, sans-serif;">
<h1 style="font-size: 24px; margin: 0;">Reset your password!</h1> <h1 style="font-size: 24px; margin: 0;">Reset your password!</h1>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;"> <td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;">
<p style="margin: 0;"> <p>
Reset your password by <a href="{{url}}">clicking here</a>. You requested a password reset, if you didn't perform this action you can safely ignore this email.
</p> </p>
<p> <p style="margin: 0;">
Or by manually navigating to the URL: {{url}} Reset your password by navigating to <a href="{{url}}">{{url}}</a>.
</p> </p>
</td> </td>
</tr> </tr>
</table> </table>
</td> </td>
</tr> </tr>
<tr> <tr>
<td bgcolor="#ff4654" style="padding: 30px 30px;"> <td bgcolor="#ff4654" style="padding: 30px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr> <tr>
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;"> <td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;">
<p style="margin: 0;">Sent by REVOLT. &middot; <a style="color: white;" href="https://revolt.chat">Website</a> &middot; <a style="color: white;" href="https://gitlab.insrt.uk/revolt">Source Code</a></p> <p style="margin: 0;">Sent by Revolt. &middot; Website: <a style="color: white;" href="https://revolt.chat">https://revolt.chat</a></p>
</td> <p>Revolt is a user-first chat platform built with modern web technologies.</p>
</tr> </td>
</table> </tr>
</td> </table>
</tr> </td>
</table> </tr>
</table>
</td> </td>
</tr> </tr>
</table> </table>
......
<h2>Reset your password.</h2>
<p>
You requested a password reset, if you did not perform this action you can safely ignore this email.
</p>
<p>
Reset your password here: <a href="{{url}}">{{url}}</a>
</p>
<br/>
<p>
Sent by Revolt. · Website: <a href="https://revolt.chat">https://revolt.chat</a>
</p>
<p>
Revolt is a user-first chat platform built with modern web technologies.
</p>
\ No newline at end of file
...@@ -4,57 +4,58 @@ ...@@ -4,57 +4,58 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Verify your account.</title> <title>Verify your account.</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style type="text/css"> <style type="text/css">
a[x-apple-data-detectors] {color: inherit !important;} a[x-apple-data-detectors] {color: inherit !important;}
</style> </style>
</head> </head>
<body style="margin: 0; padding: 0;"> <body style="margin: 0; padding: 0;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%"> <table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr> <tr>
<td style="padding: 20px 0 30px 0;"> <td style="padding: 20px 0 30px 0;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;"> <table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;">
<tr> <tr>
<td align="center" bgcolor="#ff4654"> <td align="center" bgcolor="#ff4654">
<img src="https://revolt.chat/header.png" alt="REVOLT logo" width="600" height="168" style="display: block;" /> <img src="https://revolt.chat/header.png" alt="Revolt logo" width="600" height="168" style="display: block;" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;"> <td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr> <tr>
<td style="color: #153643; font-family: Arial, sans-serif;"> <td style="color: #153643; font-family: Arial, sans-serif;">
<h1 style="font-size: 24px; margin: 0;">Verify your account!</h1> <h1 style="font-size: 24px; margin: 0;">You're almost there!</h1>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;"> <td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;">
<p style="margin: 0;"> <p>
Please verify your account by <a href="{{url}}">clicking here</a>. Verify your account to be able to log into the platform. If you didn't perform this action you can safely ignore this email.
</p> </p>
<p> <p style="margin: 0;">
Or by manually navigating to the URL: {{url}} Please verify your account by navigating to <a href="{{url}}">{{url}}</a>.
</p> </p>
</td> </td>
</tr> </tr>
</table> </table>
</td> </td>
</tr> </tr>
<tr> <tr>
<td bgcolor="#ff4654" style="padding: 30px 30px;"> <td bgcolor="#ff4654" style="padding: 30px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr> <tr>
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;"> <td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;">
<p style="margin: 0;">Sent by REVOLT. &middot; <a style="color: white;" href="https://revolt.chat">Website</a> &middot; <a style="color: white;" href="https://gitlab.insrt.uk/revolt">Source Code</a></p> <p style="margin: 0;">Sent by Revolt. &middot; Website: <a style="color: white;" href="https://revolt.chat">https://revolt.chat</a></p>
</td> <p>Revolt is a user-first chat platform built with modern web technologies.</p>
</tr> </td>
</table> </tr>
</td> </table>
</tr> </td>
</table> </tr>
</table>
</td> </td>
</tr> </tr>
</table> </table>
......
<h2>You're almost there!</h2>
<p>
Verify your account to be able to log into the platform.<br/>
If you did not perform this action you can safely ignore this email.
</p>
<p>
Please verify your account here: <a href="{{url}}">{{url}}</a>
</p>
<br/>
<p>
Sent by Revolt. · Website: <a href="https://revolt.chat">https://revolt.chat</a>
</p>
<p>
Revolt is a user-first chat platform built with modern web technologies.
</p>
\ No newline at end of file
#!/bin/bash
source set_version.sh
docker build -t revoltchat/server:${version} . &&
docker tag revoltchat/server:${version} revoltchat/server:latest &&
docker push revoltchat/server:${version} &&
docker push revoltchat/server:latest
#!/bin/bash
export version=0.5.1-alpha.21
echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
fn main() {}
use std::collections::HashMap;
use crate::database::*; use crate::database::*;
use crate::notifications::events::ClientboundNotification; use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result}; use crate::util::result::{Error, Result};
use mongodb::{bson::{Document, doc, from_document, to_document}, options::FindOptions}; use futures::StreamExt;
use mongodb::bson::Bson;
use mongodb::{
bson::{doc, to_document, Document},
options::FindOptions,
};
use rocket_contrib::json::JsonValue; use rocket_contrib::json::JsonValue;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use futures::StreamExt;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LastMessage { pub struct LastMessage {
...@@ -25,6 +31,7 @@ pub enum Channel { ...@@ -25,6 +31,7 @@ pub enum Channel {
DirectMessage { DirectMessage {
#[serde(rename = "_id")] #[serde(rename = "_id")]
id: String, id: String,
active: bool, active: bool,
recipients: Vec<String>, recipients: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
...@@ -35,12 +42,59 @@ pub enum Channel { ...@@ -35,12 +42,59 @@ pub enum Channel {
id: String, id: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>, nonce: Option<String>,
name: String, name: String,
owner: String, owner: String,
description: String, #[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
recipients: Vec<String>, recipients: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
last_message: Option<LastMessage>, last_message: Option<LastMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
permissions: Option<i32>,
},
TextChannel {
#[serde(rename = "_id")]
id: String,
server: String,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
last_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
default_permissions: Option<i32>,
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
role_permissions: HashMap<String, i32>
},
VoiceChannel {
#[serde(rename = "_id")]
id: String,
server: String,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<File>,
#[serde(skip_serializing_if = "Option::is_none")]
default_permissions: Option<i32>,
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
role_permissions: HashMap<String, i32>
}, },
} }
...@@ -49,24 +103,19 @@ impl Channel { ...@@ -49,24 +103,19 @@ impl Channel {
match self { match self {
Channel::SavedMessages { id, .. } Channel::SavedMessages { id, .. }
| Channel::DirectMessage { id, .. } | Channel::DirectMessage { id, .. }
| Channel::Group { id, .. } => id, | Channel::Group { id, .. }
| Channel::TextChannel { id, .. }
| Channel::VoiceChannel { id, .. } => id,
} }
} }
pub fn has_messaging(&self) -> Result<()> {
pub async fn get(id: &str) -> Result<Channel> { match self {
let doc = get_collection("channels") Channel::SavedMessages { .. }
.find_one(doc! { "_id": id }, None) | Channel::DirectMessage { .. }
.await | Channel::Group { .. }
.map_err(|_| Error::DatabaseError { | Channel::TextChannel { .. } => Ok(()),
operation: "find_one", Channel::VoiceChannel { .. } => Err(Error::InvalidOperation)
with: "channel", }
})?
.ok_or_else(|| Error::UnknownChannel)?;
from_document::<Channel>(doc).map_err(|_| Error::DatabaseError {
operation: "from_document",
with: "channel",
})
} }
pub async fn publish(self) -> Result<()> { pub async fn publish(self) -> Result<()> {
...@@ -85,10 +134,7 @@ impl Channel { ...@@ -85,10 +134,7 @@ impl Channel {
})?; })?;
let channel_id = self.id().to_string(); let channel_id = self.id().to_string();
ClientboundNotification::ChannelCreate(self) ClientboundNotification::ChannelCreate(self).publish(channel_id);
.publish(channel_id)
.await
.ok();
Ok(()) Ok(())
} }
...@@ -98,23 +144,51 @@ impl Channel { ...@@ -98,23 +144,51 @@ impl Channel {
ClientboundNotification::ChannelUpdate { ClientboundNotification::ChannelUpdate {
id: id.clone(), id: id.clone(),
data, data,
clear: None,
} }
.publish(id) .publish(id);
.await
.ok();
Ok(()) Ok(())
} }
pub async fn delete(&self) -> Result<()> { pub async fn delete_associated_objects(id: Bson) -> Result<()> {
let id = self.id(); get_collection("channel_invites")
.delete_many(
doc! {
"channel": id
},
None,
)
.await
.map(|_| ())
.map_err(|_| Error::DatabaseError {
operation: "delete_many",
with: "channel_invites",
})
}
pub async fn delete_messages(id: Bson) -> Result<()> {
let messages = get_collection("messages"); let messages = get_collection("messages");
// Delete any unreads.
get_collection("channel_unreads")
.delete_many(
doc! {
"_id.channel": &id
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "delete_many",
with: "channel_unreads",
})?;
// Check if there are any attachments we need to delete. // Check if there are any attachments we need to delete.
let message_ids = messages let message_ids = messages
.find( .find(
doc! { doc! {
"channel": id, "channel": &id,
"attachment": { "attachment": {
"$exists": 1 "$exists": 1
} }
...@@ -132,7 +206,7 @@ impl Channel { ...@@ -132,7 +206,7 @@ impl Channel {
.into_iter() .into_iter()
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string())) .filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
.collect::<Vec<String>>(); .collect::<Vec<String>>();
// If we found any, mark them as deleted. // If we found any, mark them as deleted.
if message_ids.len() > 0 { if message_ids.len() > 0 {
get_collection("attachments") get_collection("attachments")
...@@ -147,7 +221,7 @@ impl Channel { ...@@ -147,7 +221,7 @@ impl Channel {
"deleted": true "deleted": true
} }
}, },
None None,
) )
.await .await
.map_err(|_| Error::DatabaseError { .map_err(|_| Error::DatabaseError {
...@@ -165,11 +239,88 @@ impl Channel { ...@@ -165,11 +239,88 @@ impl Channel {
None, None,
) )
.await .await
.map(|_| ())
.map_err(|_| Error::DatabaseError { .map_err(|_| Error::DatabaseError {
operation: "delete_many", operation: "delete_many",
with: "messages", with: "messages",
})?; })
}
pub async fn delete(&self) -> Result<()> {
let id = self.id();
// Delete any invites.
Channel::delete_associated_objects(Bson::String(id.to_string())).await?;
// Delete messages.
match &self {
Channel::VoiceChannel { .. } => {},
_ => {
Channel::delete_messages(Bson::String(id.to_string())).await?;
}
}
// Remove from server object.
match &self {
Channel::TextChannel { server, .. }
| Channel::VoiceChannel { server, .. } => {
let server = Ref::from_unchecked(server.clone()).fetch_server().await?;
let mut update = doc! {
"$pull": {
"channels": id
}
};
if let Some(sys) = &server.system_messages {
let mut unset = doc! {};
if let Some(cid) = &sys.user_joined {
if id == cid {
unset.insert("system_messages.user_joined", 1);
}
}
if let Some(cid) = &sys.user_left {
if id == cid {
unset.insert("system_messages.user_left", 1);
}
}
if let Some(cid) = &sys.user_kicked {
if id == cid {
unset.insert("system_messages.user_kicked", 1);
}
}
if let Some(cid) = &sys.user_banned {
if id == cid {
unset.insert("system_messages.user_banned", 1);
}
}
if unset.len() > 0 {
update.insert("$unset", unset);
}
}
get_collection("servers")
.update_one(
doc! {
"_id": server.id
},
update,
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "servers",
})?;
},
_ => {}
}
// Finally, delete the channel object.
get_collection("channels") get_collection("channels")
.delete_one( .delete_one(
doc! { doc! {
...@@ -183,10 +334,13 @@ impl Channel { ...@@ -183,10 +334,13 @@ impl Channel {
with: "channel", with: "channel",
})?; })?;
ClientboundNotification::ChannelDelete { id: id.to_string() } ClientboundNotification::ChannelDelete { id: id.to_string() }.publish(id.to_string());
.publish(id.to_string())
.await if let Channel::Group { icon, .. } = self {
.ok(); if let Some(attachment) = icon {
attachment.delete().await?;
}
}
Ok(()) Ok(())
} }
......
// use serde::{Deserialize, Serialize};
/*#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MemberCompositeKey {
pub guild: String,
pub user: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Member {
#[serde(rename = "_id")]
pub id: MemberCompositeKey,
pub nickname: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Invite {
pub code: String,
pub creator: String,
pub channel: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Ban {
pub id: String,
pub reason: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Guild {
#[serde(rename = "_id")]
pub id: String,
// pub nonce: String, used internally
pub name: String,
pub description: String,
pub owner: String,
pub channels: Vec<String>,
pub invites: Vec<Invite>,
pub bans: Vec<Ban>,
pub default_permissions: u32,
}*/
use mongodb::bson::doc;
use mongodb::bson::from_document;
use mongodb::bson::to_document;
use serde::{Deserialize, Serialize};
use crate::database::get_collection;
use crate::util::result::Error;
use crate::util::result::Result;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
pub enum Invite {
Server {
#[serde(rename = "_id")]
code: String,
server: String,
creator: String,
channel: String,
},
Group {
#[serde(rename = "_id")]
code: String,
creator: String,
channel: String,
}, /* User {
code: String,
user: String
} */
}
impl Invite {
pub fn code(&self) -> &String {
match &self {
Invite::Server { code, .. } => code,
Invite::Group { code, .. } => code,
}
}
pub fn creator(&self) -> &String {
match &self {
Invite::Server { creator, .. } => creator,
Invite::Group { creator, .. } => creator,
}
}
pub async fn get(code: &str) -> Result<Invite> {
let doc = get_collection("channel_invites")
.find_one(doc! { "_id": code }, None)
.await
.map_err(|_| Error::DatabaseError {
operation: "find_one",
with: "invite",
})?
.ok_or_else(|| Error::UnknownServer)?;
from_document::<Invite>(doc).map_err(|_| Error::DatabaseError {
operation: "from_document",
with: "invite",
})
}
pub async fn save(self) -> Result<()> {
get_collection("channel_invites")
.insert_one(
to_document(&self).map_err(|_| Error::DatabaseError {
operation: "to_bson",
with: "invite",
})?,
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "insert_one",
with: "invite",
})?;
Ok(())
}
pub async fn delete(&self) -> Result<()> {
get_collection("channel_invites")
.delete_one(
doc! {
"_id": self.code()
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "delete_one",
with: "invite",
})?;
Ok(())
}
}
use crate::util::variables::VAPID_PRIVATE_KEY; use crate::util::variables::{USE_JANUARY, VAPID_PRIVATE_KEY};
use crate::{ use crate::{
database::*, database::*,
notifications::{events::ClientboundNotification, websocket::is_online}, notifications::{events::ClientboundNotification, websocket::is_online},
...@@ -6,6 +6,7 @@ use crate::{ ...@@ -6,6 +6,7 @@ use crate::{
}; };
use futures::StreamExt; use futures::StreamExt;
use mongodb::options::UpdateOptions;
use mongodb::{ use mongodb::{
bson::{doc, to_bson, DateTime}, bson::{doc, to_bson, DateTime},
options::FindOptions, options::FindOptions,
...@@ -17,12 +18,6 @@ use web_push::{ ...@@ -17,12 +18,6 @@ use web_push::{
ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder, ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder,
}; };
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
pub enum MessageEmbed {
Dummy,
}
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum SystemMessage { pub enum SystemMessage {
...@@ -32,10 +27,20 @@ pub enum SystemMessage { ...@@ -32,10 +27,20 @@ pub enum SystemMessage {
UserAdded { id: String, by: String }, UserAdded { id: String, by: String },
#[serde(rename = "user_remove")] #[serde(rename = "user_remove")]
UserRemove { id: String, by: String }, UserRemove { id: String, by: String },
#[serde(rename = "user_joined")]
UserJoined { id: String },
#[serde(rename = "user_left")] #[serde(rename = "user_left")]
UserLeft { id: String }, UserLeft { id: String },
#[serde(rename = "user_kicked")]
UserKicked { id: String },
#[serde(rename = "user_banned")]
UserBanned { id: String },
#[serde(rename = "channel_renamed")] #[serde(rename = "channel_renamed")]
ChannelRenamed { name: String, by: String }, ChannelRenamed { name: String, by: String },
#[serde(rename = "channel_description_changed")]
ChannelDescriptionChanged { by: String },
#[serde(rename = "channel_icon_changed")]
ChannelIconChanged { by: String },
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
...@@ -45,6 +50,20 @@ pub enum Content { ...@@ -45,6 +50,20 @@ pub enum Content {
SystemMessage(SystemMessage), SystemMessage(SystemMessage),
} }
impl Content {
pub async fn send_as_system(self, target: &Channel) -> Result<()> {
Message::create(
"00000000000000000000000000".to_string(),
target.id().to_string(),
self,
None,
None
)
.publish(&target, false)
.await
}
}
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Message { pub struct Message {
#[serde(rename = "_id")] #[serde(rename = "_id")]
...@@ -56,15 +75,25 @@ pub struct Message { ...@@ -56,15 +75,25 @@ pub struct Message {
pub content: Content, pub content: Content,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub attachment: Option<File>, pub attachments: Option<Vec<File>>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub edited: Option<DateTime>, pub edited: Option<DateTime>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub embeds: Option<MessageEmbed>, pub embeds: Option<Vec<Embed>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mentions: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub replies: Option<Vec<String>>
} }
impl Message { impl Message {
pub fn create(author: String, channel: String, content: Content) -> Message { pub fn create(
author: String,
channel: String,
content: Content,
mentions: Option<Vec<String>>,
replies: Option<Vec<String>>,
) -> Message {
Message { Message {
id: Ulid::new().to_string(), id: Ulid::new().to_string(),
nonce: None, nonce: None,
...@@ -72,13 +101,15 @@ impl Message { ...@@ -72,13 +101,15 @@ impl Message {
author, author,
content, content,
attachment: None, attachments: None,
edited: None, edited: None,
embeds: None, embeds: None,
mentions,
replies
} }
} }
pub async fn publish(self, channel: &Channel) -> Result<()> { pub async fn publish(self, channel: &Channel, process_embeds: bool) -> Result<()> {
get_collection("messages") get_collection("messages")
.insert_one(to_bson(&self).unwrap().as_document().unwrap().clone(), None) .insert_one(to_bson(&self).unwrap().as_document().unwrap().clone(), None)
.await .await
...@@ -87,39 +118,30 @@ impl Message { ...@@ -87,39 +118,30 @@ impl Message {
with: "message", with: "message",
})?; })?;
let mut set = if let Content::Text(text) = &self.content { // ! FIXME: all this code is legitimately crap
doc! { // ! rewrite when can be asked
"last_message": {
"_id": self.id.clone(), let ss = self.clone();
"author": self.author.clone(), let c_clone = channel.clone();
"short": text.chars().take(24).collect::<String>() async_std::task::spawn(async move {
let mut set = if let Content::Text(text) = &ss.content {
doc! {
"last_message": {
"_id": ss.id.clone(),
"author": ss.author.clone(),
"short": text.chars().take(128).collect::<String>()
}
} }
} } else {
} else { doc! {}
doc! {} };
};
// ! FIXME: temp code // ! MARK AS ACTIVE
let channels = get_collection("channels"); // ! FIXME: temp code
match &channel { let channels = get_collection("channels");
Channel::DirectMessage { id, .. } => { match &c_clone {
set.insert("active", true); Channel::DirectMessage { id, .. } => {
channels set.insert("active", true);
.update_one(
doc! { "_id": id },
doc! {
"$set": set
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "channel",
})?;
}
Channel::Group { id, .. } => {
if let Content::Text(_) = &self.content {
channels channels
.update_one( .update_one(
doc! { "_id": id }, doc! { "_id": id },
...@@ -129,114 +151,237 @@ impl Message { ...@@ -129,114 +151,237 @@ impl Message {
None, None,
) )
.await .await
.map_err(|_| Error::DatabaseError { /*.map_err(|_| Error::DatabaseError {
operation: "update_one", operation: "update_one",
with: "channel", with: "channel",
})?; })?;*/
.unwrap();
} }
Channel::Group { id, .. } => {
if let Content::Text(_) = &ss.content {
channels
.update_one(
doc! { "_id": id },
doc! {
"$set": set
},
None,
)
.await
/*.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "channel",
})?;*/
.unwrap();
}
}
Channel::TextChannel { id, .. } => {
if let Content::Text(_) = &ss.content {
channels
.update_one(
doc! { "_id": id },
doc! {
"$set": {
"last_message": &ss.id
}
},
None,
)
.await
/*.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "channel",
})?;*/
.unwrap();
}
}
_ => {}
} }
_ => {} });
// ! FIXME: also temp code
// ! THIS ADDS ANY MENTIONS
if let Some(mentions) = &self.mentions {
let message = self.id.clone();
let channel = self.channel.clone();
let mentions = mentions.clone();
async_std::task::spawn(async move {
get_collection("channel_unreads")
.update_many(
doc! {
"_id.channel": channel,
"_id.user": {
"$in": mentions
}
},
doc! {
"$push": {
"mentions": message
}
},
UpdateOptions::builder().upsert(true).build(),
)
.await
/*.map_err(|_| Error::DatabaseError {
operation: "update_many",
with: "channel_unreads",
})?;*/
.unwrap();
});
}
if process_embeds {
self.process_embed();
} }
let mentions = self.mentions.clone();
let enc = serde_json::to_string(&self).unwrap(); let enc = serde_json::to_string(&self).unwrap();
ClientboundNotification::Message(self) ClientboundNotification::Message(self).publish(channel.id().to_string());
.publish(channel.id().to_string())
.await
.unwrap();
/* /*
Web Push Test Code Web Push Test Code
! FIXME: temp code
*/ */
let c_clone = channel.clone();
// Find all offline users. async_std::task::spawn(async move {
let mut target_ids = vec![]; // Find all offline users.
match &channel { let mut target_ids = vec![];
Channel::DirectMessage { recipients, .. } | Channel::Group { recipients, .. } => { match &c_clone {
for recipient in recipients { Channel::DirectMessage { recipients, .. } | Channel::Group { recipients, .. } => {
if !is_online(recipient) { for recipient in recipients {
target_ids.push(recipient.clone()); if !is_online(recipient) {
target_ids.push(recipient.clone());
}
} }
} }
Channel::TextChannel { .. } => {
if let Some(mut mentions) = mentions {
target_ids.append(&mut mentions);
}
}
_ => {}
} }
_ => {}
}
// Fetch their corresponding sessions. // Fetch their corresponding sessions.
if let Ok(mut cursor) = get_collection("accounts") if let Ok(mut cursor) = get_collection("accounts")
.find( .find(
doc! { doc! {
"_id": { "_id": {
"$in": target_ids "$in": target_ids
},
"sessions.subscription": {
"$exists": true
}
}, },
"sessions.subscription": { FindOptions::builder()
"$exists": true .projection(doc! { "sessions": 1 })
} .build(),
}, )
FindOptions::builder() .await
.projection(doc! { "sessions": 1 }) {
.build(), let mut subscriptions = vec![];
) while let Some(result) = cursor.next().await {
.await if let Ok(doc) = result {
{ if let Ok(sessions) = doc.get_array("sessions") {
let mut subscriptions = vec![]; for session in sessions {
while let Some(result) = cursor.next().await { if let Some(doc) = session.as_document() {
if let Ok(doc) = result { if let Ok(sub) = doc.get_document("subscription") {
if let Ok(sessions) = doc.get_array("sessions") { let endpoint = sub.get_str("endpoint").unwrap().to_string();
for session in sessions { let p256dh = sub.get_str("p256dh").unwrap().to_string();
if let Some(doc) = session.as_document() { let auth = sub.get_str("auth").unwrap().to_string();
if let Ok(sub) = doc.get_document("subscription") {
let endpoint = sub.get_str("endpoint").unwrap().to_string();
let p256dh = sub.get_str("p256dh").unwrap().to_string();
let auth = sub.get_str("auth").unwrap().to_string();
subscriptions subscriptions
.push(SubscriptionInfo::new(endpoint, p256dh, auth)); .push(SubscriptionInfo::new(endpoint, p256dh, auth));
}
} }
} }
} }
} }
} }
}
if subscriptions.len() > 0 { if subscriptions.len() > 0 {
let client = WebPushClient::new(); let client = WebPushClient::new();
let key = let key =
base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap(); base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap();
for subscription in subscriptions { for subscription in subscriptions {
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap(); let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
let sig_builder = let sig_builder = VapidSignatureBuilder::from_pem(
VapidSignatureBuilder::from_pem(std::io::Cursor::new(&key), &subscription) std::io::Cursor::new(&key),
.unwrap(); &subscription,
let signature = sig_builder.build().unwrap(); )
builder.set_vapid_signature(signature); .unwrap();
builder.set_payload(ContentEncoding::AesGcm, enc.as_bytes()); let signature = sig_builder.build().unwrap();
let m = builder.build().unwrap(); builder.set_vapid_signature(signature);
client.send(m).await.ok(); builder.set_payload(ContentEncoding::AesGcm, enc.as_bytes());
let m = builder.build().unwrap();
client.send(m).await.ok();
}
} }
} }
} });
Ok(()) Ok(())
} }
pub async fn publish_update(&self, data: JsonValue) -> Result<()> { pub async fn publish_update(self, data: JsonValue) -> Result<()> {
let channel = self.channel.clone(); let channel = self.channel.clone();
ClientboundNotification::MessageUpdate { ClientboundNotification::MessageUpdate {
id: self.id.clone(), id: self.id.clone(),
channel: self.channel.clone(),
data, data,
} }
.publish(channel) .publish(channel);
.await self.process_embed();
.ok();
Ok(()) Ok(())
} }
pub fn process_embed(&self) {
if !*USE_JANUARY {
return;
}
if let Content::Text(text) = &self.content {
// ! FIXME: re-write this at some point,
// ! or just before we allow user generated embeds
let id = self.id.clone();
let content = text.clone();
let channel = self.channel.clone();
async_std::task::spawn(async move {
if let Ok(embeds) = Embed::generate(content).await {
if let Ok(bson) = to_bson(&embeds) {
if let Ok(_) = get_collection("messages")
.update_one(
doc! {
"_id": &id
},
doc! {
"$set": {
"embeds": bson
}
},
None,
)
.await
{
ClientboundNotification::MessageUpdate {
id,
channel: channel.clone(),
data: json!({ "embeds": embeds }),
}
.publish(channel);
}
}
}
});
}
}
pub async fn delete(&self) -> Result<()> { pub async fn delete(&self) -> Result<()> {
if let Some(attachment) = &self.attachment { if let Some(attachments) = &self.attachments {
attachment.delete().await?; for attachment in attachments {
attachment.delete().await?;
}
} }
get_collection("messages") get_collection("messages")
...@@ -255,23 +400,26 @@ impl Message { ...@@ -255,23 +400,26 @@ impl Message {
let channel = self.channel.clone(); let channel = self.channel.clone();
ClientboundNotification::MessageDelete { ClientboundNotification::MessageDelete {
id: self.id.clone(), id: self.id.clone(),
channel: self.channel.clone(),
} }
.publish(channel) .publish(channel);
.await
.ok();
if let Some(attachment) = &self.attachment { if let Some(attachments) = &self.attachments {
let attachment_ids: Vec<String> =
attachments.iter().map(|f| f.id.to_string()).collect();
get_collection("attachments") get_collection("attachments")
.update_one( .update_one(
doc! { doc! {
"_id": &attachment.id "_id": {
"$in": attachment_ids
}
}, },
doc! { doc! {
"$set": { "$set": {
"deleted": true "deleted": true
} }
}, },
None None,
) )
.await .await
.map_err(|_| Error::DatabaseError { .map_err(|_| Error::DatabaseError {
......
use mongodb::bson::{doc, from_document}; use mongodb::bson::{doc, from_document};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::util::result::{Error, Result};
use crate::database::*; use crate::database::*;
use crate::util::result::{Error, Result};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")] #[serde(tag = "type")]
...@@ -23,7 +23,7 @@ pub struct File { ...@@ -23,7 +23,7 @@ pub struct File {
metadata: Metadata, metadata: Metadata,
content_type: String, content_type: String,
size: isize, size: isize,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
deleted: Option<bool>, deleted: Option<bool>,
...@@ -38,7 +38,12 @@ pub struct File { ...@@ -38,7 +38,12 @@ pub struct File {
} }
impl File { impl File {
pub async fn find_and_use(attachment_id: &str, tag: &str, parent_type: &str, parent_id: &str) -> Result<File> { pub async fn find_and_use(
attachment_id: &str,
tag: &str,
parent_type: &str,
parent_id: &str,
) -> Result<File> {
let attachments = get_collection("attachments"); let attachments = get_collection("attachments");
let key = format!("{}_id", parent_type); let key = format!("{}_id", parent_type);
if let Some(doc) = attachments if let Some(doc) = attachments
......
use crate::util::{
result::{Error, Result},
variables::JANUARY_URL,
};
use linkify::{LinkFinder, LinkKind};
use regex::Regex;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ImageSize {
Large,
Preview,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Image {
pub url: String,
pub width: isize,
pub height: isize,
pub size: ImageSize,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Video {
pub url: String,
pub width: isize,
pub height: isize,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum TwitchType {
Channel,
Video,
Clip,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum BandcampType {
Album,
Track,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
pub enum Special {
None,
YouTube {
id: String,
},
Twitch {
content_type: TwitchType,
id: String,
},
Spotify {
content_type: String,
id: String,
},
Soundcloud,
Bandcamp {
content_type: BandcampType,
id: String,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Metadata {
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
special: Option<Special>,
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
image: Option<Image>,
#[serde(skip_serializing_if = "Option::is_none")]
video: Option<Video>,
// #[serde(skip_serializing_if = "Option::is_none")]
// opengraph_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
site_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
color: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
pub enum Embed {
Website(Metadata),
Image(Image),
None,
}
impl Embed {
pub async fn generate(content: String) -> Result<Vec<Embed>> {
lazy_static! {
static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap();
}
// Ignore code blocks.
let content = RE_CODE.replace_all(&content, "");
let content = content
// Ignore quoted lines.
.split("\n")
.map(|v| {
if let Some(c) = v.chars().next() {
if c == '>' {
return "";
}
}
v
})
.collect::<Vec<&str>>()
.join("\n");
// ! FIXME: allow multiple links
// ! FIXME: prevent generation if link is surrounded with < >
let mut finder = LinkFinder::new();
finder.kinds(&[LinkKind::Url]);
let links: Vec<_> = finder.links(&content).collect();
if links.len() == 0 {
return Err(Error::LabelMe);
}
let link = &links[0];
let client = reqwest::Client::new();
let result = client
.get(&format!("{}/embed", *JANUARY_URL))
.query(&[("url", link.as_str())])
.send()
.await;
match result {
Err(_) => return Err(Error::LabelMe),
Ok(result) => match result.status() {
reqwest::StatusCode::OK => {
let res: Embed = result.json().await.map_err(|_| Error::InvalidOperation)?;
Ok(vec![res])
}
_ => return Err(Error::LabelMe),
},
}
}
}
pub mod autumn;
pub mod january;
mod autumn;
mod channel; mod channel;
mod guild; mod invites;
mod message; mod message;
mod microservice;
mod server;
mod sync;
mod user; mod user;
use microservice::*;
pub use autumn::*; pub use autumn::*;
pub use channel::*; pub use channel::*;
pub use guild::*; pub use invites::*;
pub use january::*;
pub use message::*; pub use message::*;
pub use server::*;
pub use sync::*;
pub use user::*; pub use user::*;