Merge branch 'develop' into iceshrimp_mastodon

This commit is contained in:
naskya 2024-05-30 07:11:44 +09:00
commit 6209fdd242
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
45 changed files with 391 additions and 253 deletions

View file

@ -5,6 +5,6 @@ These are the extensions to ActivityPub that Firefish implements. This page uses
## speakAsCat
- Compact IRI: `firefish:speakAsCat`
- Canonical IRI: `https://firefish.dev/ns#speakascat`
- Canonical IRI: `https://firefish.dev/ns#speakAsCat`
Used on actors to indicate that they not only identify as a cat, but also want to have their text be transformed to speak like one, expressed as a boolean value. If this property is set to true, displaying the actors posts will make them speak with “nya” instead of “na” and other cat-related text mannerisms. Used in combination with [misskey:isCat](https://misskey-hub.net/ns/#iscat).

View file

@ -46,7 +46,7 @@
"@biomejs/cli-darwin-x64": "1.7.3",
"@biomejs/cli-linux-arm64": "1.7.3",
"@biomejs/cli-linux-x64": "1.7.3",
"@types/node": "20.12.12",
"@types/node": "20.12.13",
"execa": "9.1.0",
"pnpm": "9.1.3",
"typescript": "5.4.5"

View file

@ -11,8 +11,9 @@ export const USER_ONLINE_THRESHOLD: number
export const USER_ACTIVE_THRESHOLD: number
/**
* List of file types allowed to be viewed directly in the browser
*
* Anything not included here will be responded as application/octet-stream
* SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly
* SVG is not allowed because it generates XSS (TODO: fix this and later allow it to be viewed directly)
* * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
* * <https://github.com/sindresorhus/file-type/blob/main/core.js>
* * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
@ -207,6 +208,7 @@ export interface Acct {
}
export function stringToAcct(acct: string): Acct
export function acctToString(acct: Acct): string
/** Fetches and returns the NodeInfo of a remote server. */
export function fetchNodeinfo(host: string): Promise<Nodeinfo>
export function nodeinfo_2_1(): Promise<any>
export function nodeinfo_2_0(): Promise<any>
@ -307,31 +309,86 @@ export interface Users {
activeHalfyear: number | null
activeMonth: number | null
}
/** Prints the greeting message and the Firefish version to stdout. */
export function greet(): void
/** Initializes the [tracing] logger. */
export function initializeRustLogger(): void
/** Prints the server hardware information as the server info log. */
export function showServerInfo(): void
/**
* Checks if a server is blocked.
*
* ## Argument
* # Argument
* `host` - punycoded instance host
*
* # Example
* ```no_run
* # use backend_rs::misc::check_server_block::is_blocked_server;
* # async fn f() -> Result<(), Box<dyn std::error::Error>> {
* assert_eq!(true, is_blocked_server("blocked.com").await?);
* assert_eq!(false, is_blocked_server("not-blocked.com").await?);
* assert_eq!(true, is_blocked_server("subdomain.of.blocked.com").await?);
* assert_eq!(true, is_blocked_server("xn--l8jegik.blocked.com").await?);
* # Ok(())
* # }
* ```
*/
export function isBlockedServer(host: string): Promise<boolean>
/**
* Checks if a server is silenced.
*
* ## Argument
* # Argument
* `host` - punycoded instance host
*
* # Example
* ```no_run
* # use backend_rs::misc::check_server_block::is_silenced_server;
* # async fn f() -> Result<(), Box<dyn std::error::Error>> {
* assert_eq!(true, is_silenced_server("silenced.com").await?);
* assert_eq!(false, is_silenced_server("not-silenced.com").await?);
* assert_eq!(true, is_silenced_server("subdomain.of.silenced.com").await?);
* assert_eq!(true, is_silenced_server("xn--l8jegik.silenced.com").await?);
* # Ok(())
* # }
* ```
*/
export function isSilencedServer(host: string): Promise<boolean>
/**
* Checks if a server is allowlisted.
* Returns `Ok(true)` if private mode is disabled.
*
* ## Argument
* # Argument
* `host` - punycoded instance host
*
* # Example
* ```no_run
* # use backend_rs::misc::check_server_block::is_allowed_server;
* # async fn f() -> Result<(), Box<dyn std::error::Error>> {
* assert_eq!(true, is_allowed_server("allowed.com").await?);
* assert_eq!(false, is_allowed_server("not-allowed.com").await?);
* assert_eq!(false, is_allowed_server("subdomain.of.allowed.com").await?);
* assert_eq!(false, is_allowed_server("xn--l8jegik.allowed.com").await?);
* # Ok(())
* # }
* ```
*/
export function isAllowedServer(host: string): Promise<boolean>
/**
* Returns whether `note` should be hard-muted.
*
* More specifically, this function returns `Ok(true)`
* if and only if one or more of these conditions are met:
*
* * the note (text or CW) contains any of the words/patterns
* * the "parent" note(s) (reply, quote) contain any of the words/patterns
* * the alt text of the attached files contains any of the words/patterns
*
* # Arguments
*
* * `note` : [NoteLike] object
* * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition)
* * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions
*/
export function checkWordMute(note: NoteLike, mutedWords: Array<string>, mutedPatterns: Array<string>): Promise<boolean>
export function getFullApAccount(username: string, host?: string | undefined | null): string
export function isSelfHost(host?: string | undefined | null): boolean
@ -339,10 +396,12 @@ export function isSameOrigin(uri: string): boolean
export function extractHost(uri: string): string
export function toPuny(host: string): string
export function isUnicodeEmoji(s: string): boolean
/** Escapes `%` and `\` in the given string. */
export function sqlLikeEscape(src: string): string
export function sqlRegexEscape(src: string): string
/** Returns `true` if `src` does not contain suspicious characters like `%`. */
export function safeForSql(src: string): boolean
/** Convert milliseconds to a human readable string */
/** Converts milliseconds to a human readable string. */
export function formatMilliseconds(milliseconds: number): string
export interface ImageSize {
width: number
@ -372,6 +431,7 @@ export interface NoteLikeForIsQuote {
}
export function isQuote(note: NoteLikeForIsQuote): boolean
export function isSafeUrl(url: string): boolean
/** Returns the latest Firefish version. */
export function latestVersion(): Promise<string>
export function fetchMeta(useCache: boolean): Promise<Meta>
export interface PugArgs {
@ -386,9 +446,31 @@ export interface PugArgs {
privateMode: boolean | null
}
export function metaToPugArgs(meta: Meta): PugArgs
/**
* Converts the given text into the cat language.
*
* refs:
* * <https://misskey-hub.net/ns/#isCat>
* * <https://firefish.dev/ns#speakAsCat>
*
* # Arguments
*
* * `text` : original text
* * `lang` : language code (e.g., `Some("en")`, `Some("en-US")`, `Some("uk-UA")`, `None`)
*
* # Example
*
* ```
* # use backend_rs::misc::nyaify::nyaify;
* assert_eq!(nyaify("I'll take a nap.", Some("en")), "I'll take a nyap.");
* ```
*/
export function nyaify(text: string, lang?: string | undefined | null): string
/** Hashes the given password using [Argon2] algorithm. */
export function hashPassword(password: string): string
/** Checks whether the given password and hash match. */
export function verifyPassword(password: string, hash: string): boolean
/** Returns whether the [bcrypt] algorithm is used for the password hash. */
export function isOldPasswordAlgorithm(hash: string): boolean
export interface DecodedReaction {
reaction: string
@ -398,7 +480,7 @@ export interface DecodedReaction {
export function decodeReaction(reaction: string): DecodedReaction
export function countReactions(reactions: Record<string, number>): Record<string, number>
export function toDbReaction(reaction?: string | undefined | null, host?: string | undefined | null): Promise<string>
/** Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago */
/** Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago */
export function removeOldAttestationChallenges(): Promise<void>
export interface Cpu {
model: string
@ -1344,6 +1426,6 @@ export function getTimestamp(id: string): number
export function genId(): string
/** Generate an ID using a specific datetime */
export function genIdAt(date: Date): string
/** Generate random string based on [thread_rng] and [Alphanumeric]. */
/** Generates a random string based on [thread_rng] and [Alphanumeric]. */
export function generateSecureRandomString(length: number): string
export function generateUserToken(): string

View file

@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, sqlRegexEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isQuote, isSafeUrl, latestVersion, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
module.exports.SECOND = SECOND
module.exports.MINUTE = MINUTE

View file

@ -1,24 +1,25 @@
#[crate::export]
#[crate::ts_export]
pub const SECOND: i32 = 1000;
#[crate::export]
#[crate::ts_export]
pub const MINUTE: i32 = 60 * SECOND;
#[crate::export]
#[crate::ts_export]
pub const HOUR: i32 = 60 * MINUTE;
#[crate::export]
#[crate::ts_export]
pub const DAY: i32 = 24 * HOUR;
#[crate::export]
#[crate::ts_export]
pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE;
#[crate::export]
#[crate::ts_export]
pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
/// List of file types allowed to be viewed directly in the browser
///
/// Anything not included here will be responded as application/octet-stream
/// SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly
/// SVG is not allowed because it generates XSS (TODO: fix this and later allow it to be viewed directly)
/// * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
/// * <https://github.com/sindresorhus/file-type/blob/main/core.js>
/// * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
#[crate::export]
#[crate::ts_export]
pub const FILE_TYPE_BROWSERSAFE: [&str; 41] = [
// Images
"image/png",

View file

@ -1,3 +1,5 @@
//! Server configurations and environment variables
pub use server::CONFIG;
pub mod constant;

View file

@ -1,3 +1,5 @@
//! Utilities for using Redis cache
use crate::database::{redis_conn, redis_key, RedisConnError};
use redis::{AsyncCommands, RedisError};
use serde::{Deserialize, Serialize};

View file

@ -1,11 +1,10 @@
//! Interfaces for accessing PostgreSQL and Redis
pub use postgresql::db_conn;
pub use redis::key as redis_key;
pub use redis::redis_conn;
pub use redis::RedisConnError;
/// Utilities for using Redis cache
pub mod cache;
/// PostgreSQL interface
pub mod postgresql;
/// Redis interface
pub mod redis;

View file

@ -1,3 +1,5 @@
//! PostgreSQL interface
use crate::config::CONFIG;
use once_cell::sync::OnceCell;
use sea_orm::{ConnectOptions, Database, DbConn, DbErr};
@ -5,7 +7,7 @@ use tracing::log::LevelFilter;
static DB_CONN: OnceCell<DbConn> = OnceCell::new();
async fn init_database() -> Result<&'static DbConn, DbErr> {
async fn init_conn() -> Result<&'static DbConn, DbErr> {
let database_uri = format!(
"postgres://{}:{}@{}:{}/{}",
CONFIG.db.user,
@ -24,10 +26,11 @@ async fn init_database() -> Result<&'static DbConn, DbErr> {
Ok(DB_CONN.get_or_init(move || conn))
}
/// Returns an async PostgreSQL connection that can be used with [sea_orm] utilities.
pub async fn db_conn() -> Result<&'static DbConn, DbErr> {
match DB_CONN.get() {
Some(conn) => Ok(conn),
None => init_database().await,
None => init_conn().await,
}
}

View file

@ -1,18 +1,20 @@
//! Redis interface
use crate::config::CONFIG;
use async_trait::async_trait;
use bb8::{ManageConnection, Pool, PooledConnection, RunError};
use redis::{aio::MultiplexedConnection, Client, ErrorKind, IntoConnectionInfo, RedisError};
use tokio::sync::OnceCell;
/// A `bb8::ManageConnection` for `redis::Client::get_multiplexed_async_connection`.
/// A [bb8::ManageConnection] for [redis::Client::get_multiplexed_async_connection].
#[derive(Clone, Debug)]
pub struct RedisConnectionManager {
client: Client,
}
impl RedisConnectionManager {
/// Create a new `RedisConnectionManager`.
/// See `redis::Client::open` for a description of the parameter types.
/// Creates a new [RedisConnectionManager].
/// See [redis::Client::open] for a description of the parameter types.
pub fn new<T: IntoConnectionInfo>(info: T) -> Result<Self, RedisError> {
Ok(Self {
client: Client::open(info.into_connection_info()?)?,
@ -85,6 +87,7 @@ pub enum RedisConnError {
Bb8Pool(RunError<RedisError>),
}
/// Returns an async [redis] connection managed by a [bb8] connection pool.
pub async fn redis_conn(
) -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError> {
if !CONN_POOL.initialized() {
@ -103,7 +106,7 @@ pub async fn redis_conn(
.map_err(RedisConnError::Bb8Pool)
}
/// prefix redis key
/// prefix Redis key
#[inline]
pub fn key(key: impl ToString) -> String {
format!("{}:{}", CONFIG.redis_key_prefix, key.to_string())

View file

@ -1,2 +1,4 @@
//! Services used to federate with other servers
pub mod acct;
pub mod nodeinfo;

View file

@ -1,3 +1,5 @@
//! NodeInfo fetcher
use crate::federation::nodeinfo::schema::*;
use crate::util::http_client;
use isahc::AsyncReadResponseExt;
@ -83,6 +85,7 @@ async fn fetch_nodeinfo_impl(nodeinfo_link: &str) -> Result<Nodeinfo20, Error> {
// for napi export
type Nodeinfo = Nodeinfo20;
/// Fetches and returns the NodeInfo of a remote server.
#[crate::export]
pub async fn fetch_nodeinfo(host: &str) -> Result<Nodeinfo, Error> {
tracing::info!("fetching from {}", host);

View file

@ -1,3 +1,5 @@
//! NodeInfo generator
use crate::config::CONFIG;
use crate::database::{cache, db_conn};
use crate::federation::nodeinfo::schema::*;
@ -112,6 +114,7 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
})
}
/// Returns NodeInfo (version 2.1) of the local server.
pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
const NODEINFO_2_1_CACHE_KEY: &str = "nodeinfo_2_1";
@ -126,6 +129,7 @@ pub async fn nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
}
}
/// Returns NodeInfo (version 2.0) of the local server.
pub async fn nodeinfo_2_0() -> Result<Nodeinfo20, Error> {
Ok(nodeinfo_2_1().await?.into())
}

View file

@ -1,3 +1,5 @@
//! NodeInfo handler
pub mod fetch;
pub mod generate;
mod schema;

View file

@ -1,9 +1,11 @@
//! Schema definitions of NodeInfo version 2.0 and 2.1
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
// TODO: I want to use these macros but they don't work with rmp_serde
// - #[serde(skip_serializing_if = "Option::is_none")] (https://github.com/3Hren/msgpack-rust/issues/86)
// - #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318)
// * #[serde(skip_serializing_if = "Option::is_none")] (https://github.com/3Hren/msgpack-rust/issues/86)
// * #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318)
/// NodeInfo schema version 2.1. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1>
#[derive(Deserialize, Serialize, Debug, PartialEq)]

View file

@ -11,6 +11,7 @@ const GREETING_MESSAGE: &str = "\
If you like Firefish, please consider contributing to the repo. https://firefish.dev/firefish/firefish
";
/// Prints the greeting message and the Firefish version to stdout.
#[crate::export]
pub fn greet() {
println!("{}", GREETING_MESSAGE);

View file

@ -2,6 +2,7 @@ use crate::config::CONFIG;
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
/// Initializes the [tracing] logger.
#[crate::export(js_name = "initializeRustLogger")]
pub fn initialize_logger() {
let mut builder = FmtSubscriber::builder();

View file

@ -1,3 +1,5 @@
//! Initializers
pub mod greet;
pub mod log;
pub mod system_info;

View file

@ -5,10 +5,21 @@ pub type SysinfoPoisonError = PoisonError<MutexGuard<'static, System>>;
static SYSTEM_INFO: OnceLock<Mutex<System>> = OnceLock::new();
pub fn system_info() -> &'static std::sync::Mutex<sysinfo::System> {
/// Gives an access to the shared static [System] object.
///
/// # Example
///
/// ```
/// # use backend_rs::init::system_info::{system_info, SysinfoPoisonError};
/// let system_info = system_info().lock()?;
/// println!("The number of CPU threads is {}.", system_info.cpus().len());
/// # Ok::<(), SysinfoPoisonError>(())
/// ```
pub fn system_info() -> &'static std::sync::Mutex<System> {
SYSTEM_INFO.get_or_init(|| Mutex::new(System::new_all()))
}
/// Prints the server hardware information as the server info log.
#[crate::export]
pub fn show_server_info() -> Result<(), SysinfoPoisonError> {
let system_info = system_info().lock()?;

View file

@ -1,18 +1,10 @@
use macro_rs::{export, ts_export};
/// Server configurations and environment variables
pub mod config;
/// Interfaces for accessing PostgreSQL and Redis
pub mod database;
/// Services used to federate with other servers
pub mod federation;
/// Initializers
pub mod init;
/// Miscellaneous utilities
pub mod misc;
/// Database structure, auto-generated by [sea_orm]
pub mod model;
/// Services provided for local users
pub mod service;
/// Basic utilities such as ID generator and HTTP client
pub mod util;

View file

@ -1,13 +1,26 @@
use crate::misc::meta::fetch_meta;
use sea_orm::DbErr;
//! This module is used in the TypeScript backend only.
// We may want to (re)implement these functions in the `federation` module
// in a Rusty way (e.g., traits of server type) if needed.
/// Checks if a server is blocked.
///
/// ## Argument
/// # Argument
/// `host` - punycoded instance host
#[crate::export]
pub async fn is_blocked_server(host: &str) -> Result<bool, DbErr> {
Ok(fetch_meta(true)
///
/// # Example
/// ```no_run
/// # use backend_rs::misc::check_server_block::is_blocked_server;
/// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
/// assert_eq!(true, is_blocked_server("blocked.com").await?);
/// assert_eq!(false, is_blocked_server("not-blocked.com").await?);
/// assert_eq!(true, is_blocked_server("subdomain.of.blocked.com").await?);
/// assert_eq!(true, is_blocked_server("xn--l8jegik.blocked.com").await?);
/// # Ok(())
/// # }
/// ```
#[crate::ts_export]
pub async fn is_blocked_server(host: &str) -> Result<bool, sea_orm::DbErr> {
Ok(crate::misc::meta::fetch_meta(true)
.await?
.blocked_hosts
.iter()
@ -18,11 +31,23 @@ pub async fn is_blocked_server(host: &str) -> Result<bool, DbErr> {
/// Checks if a server is silenced.
///
/// ## Argument
/// # Argument
/// `host` - punycoded instance host
#[crate::export]
pub async fn is_silenced_server(host: &str) -> Result<bool, DbErr> {
Ok(fetch_meta(true)
///
/// # Example
/// ```no_run
/// # use backend_rs::misc::check_server_block::is_silenced_server;
/// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
/// assert_eq!(true, is_silenced_server("silenced.com").await?);
/// assert_eq!(false, is_silenced_server("not-silenced.com").await?);
/// assert_eq!(true, is_silenced_server("subdomain.of.silenced.com").await?);
/// assert_eq!(true, is_silenced_server("xn--l8jegik.silenced.com").await?);
/// # Ok(())
/// # }
/// ```
#[crate::ts_export]
pub async fn is_silenced_server(host: &str) -> Result<bool, sea_orm::DbErr> {
Ok(crate::misc::meta::fetch_meta(true)
.await?
.silenced_hosts
.iter()
@ -34,11 +59,23 @@ pub async fn is_silenced_server(host: &str) -> Result<bool, DbErr> {
/// Checks if a server is allowlisted.
/// Returns `Ok(true)` if private mode is disabled.
///
/// ## Argument
/// # Argument
/// `host` - punycoded instance host
#[crate::export]
pub async fn is_allowed_server(host: &str) -> Result<bool, DbErr> {
let meta = fetch_meta(true).await?;
///
/// # Example
/// ```no_run
/// # use backend_rs::misc::check_server_block::is_allowed_server;
/// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
/// assert_eq!(true, is_allowed_server("allowed.com").await?);
/// assert_eq!(false, is_allowed_server("not-allowed.com").await?);
/// assert_eq!(false, is_allowed_server("subdomain.of.allowed.com").await?);
/// assert_eq!(false, is_allowed_server("xn--l8jegik.allowed.com").await?);
/// # Ok(())
/// # }
/// ```
#[crate::ts_export]
pub async fn is_allowed_server(host: &str) -> Result<bool, sea_orm::DbErr> {
let meta = crate::misc::meta::fetch_meta(true).await?;
if !meta.private_mode.unwrap_or(false) {
return Ok(true);

View file

@ -26,6 +26,20 @@ fn check_word_mute_impl(
})
}
/// Returns whether `note` should be hard-muted.
///
/// More specifically, this function returns `Ok(true)`
/// if and only if one or more of these conditions are met:
///
/// * the note (text or CW) contains any of the words/patterns
/// * the "parent" note(s) (reply, quote) contain any of the words/patterns
/// * the alt text of the attached files contains any of the words/patterns
///
/// # Arguments
///
/// * `note` : [NoteLike] object
/// * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition)
/// * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions
#[crate::export]
pub async fn check_word_mute(
note: NoteLike,

View file

@ -1,4 +1,6 @@
use crate::config::CONFIG;
//! This module is used in the TypeScript backend only.
// We may want to (re)implement these functions in the `federation` module
// in a Rusty way (e.g., traits of actor type) if needed.
#[derive(thiserror::Error, Debug)]
pub enum Error {
@ -10,28 +12,28 @@ pub enum Error {
NoHostname,
}
#[crate::export]
#[crate::ts_export]
pub fn get_full_ap_account(username: &str, host: Option<&str>) -> Result<String, Error> {
Ok(match host {
Some(host) => format!("{}@{}", username, to_puny(host)?),
None => format!("{}@{}", username, extract_host(&CONFIG.url)?),
None => format!("{}@{}", username, extract_host(&crate::config::CONFIG.url)?),
})
}
#[crate::export]
#[crate::ts_export]
pub fn is_self_host(host: Option<&str>) -> Result<bool, Error> {
Ok(match host {
Some(host) => extract_host(&CONFIG.url)? == to_puny(host)?,
Some(host) => extract_host(&crate::config::CONFIG.url)? == to_puny(host)?,
None => true,
})
}
#[crate::export]
#[crate::ts_export]
pub fn is_same_origin(uri: &str) -> Result<bool, Error> {
Ok(url::Url::parse(uri)?.origin().ascii_serialization() == CONFIG.url)
Ok(url::Url::parse(uri)?.origin().ascii_serialization() == crate::config::CONFIG.url)
}
#[crate::export]
#[crate::ts_export]
pub fn extract_host(uri: &str) -> Result<String, Error> {
url::Url::parse(uri)?
.host_str()
@ -39,29 +41,7 @@ pub fn extract_host(uri: &str) -> Result<String, Error> {
.and_then(|v| Ok(to_puny(v)?))
}
#[crate::export]
#[crate::ts_export]
pub fn to_puny(host: &str) -> Result<String, idna::Errors> {
idna::domain_to_ascii(host)
}
#[cfg(test)]
mod unit_test {
use super::{extract_host, to_puny};
use pretty_assertions::assert_eq;
#[test]
fn extract_host_test() {
assert_eq!(
extract_host("https://firefish.dev/firefish/firefish.git").unwrap(),
"firefish.dev"
);
}
#[test]
fn to_puny_test() {
assert_eq!(
to_puny("何もかも.owari.shop").unwrap(),
"xn--u8jyfb5762a.owari.shop"
);
}
}

View file

@ -1,3 +1,5 @@
//! This module is used in the TypeScript backend only.
#[crate::ts_export]
pub fn is_unicode_emoji(s: &str) -> bool {
emojis::get(s).is_some()

View file

@ -1,6 +1,7 @@
use once_cell::sync::Lazy;
use regex::Regex;
/// Escapes `%` and `\` in the given string.
#[crate::export]
pub fn sql_like_escape(src: &str) -> String {
src.replace('%', r"\%").replace('_', r"\_")
@ -12,6 +13,7 @@ pub fn sql_regex_escape(src: &str) -> String {
RE.replace_all(src, r"\$1").to_string()
}
/// Returns `true` if `src` does not contain suspicious characters like `%`.
#[crate::export]
pub fn safe_for_sql(src: &str) -> bool {
!src.contains([

View file

@ -1,4 +1,4 @@
/// Convert milliseconds to a human readable string
/// Converts milliseconds to a human readable string.
#[crate::export]
pub fn format_milliseconds(milliseconds: u32) -> String {
let mut seconds = milliseconds / 1000;

View file

@ -18,8 +18,8 @@ pub struct NoteLike {
///
/// ## Arguments
///
/// * `note` - [NoteLike] object
/// * `include_parent` - whether to take the reply-to post and quoted post into account
/// * `note` : [NoteLike] object
/// * `include_parent` : whether to take the reply-to post and quoted post into account
pub async fn all_texts(note: NoteLike, include_parent: bool) -> Result<Vec<String>, DbErr> {
let db = db_conn().await?;

View file

@ -1,3 +1,5 @@
//! Fetch latest Firefish version from the Firefish repository
use crate::database::cache;
use crate::util::http_client;
use isahc::ReadResponseExt;
@ -43,6 +45,7 @@ async fn get_latest_version() -> Result<String, Error> {
Ok(res_parsed.version)
}
/// Returns the latest Firefish version.
#[crate::export]
pub async fn latest_version() -> Result<String, Error> {
let version: Option<String> =

View file

@ -1,3 +1,5 @@
//! Miscellaneous utilities
pub mod check_server_block;
pub mod check_word_mute;
pub mod convert_host;

View file

@ -1,6 +1,25 @@
//! Cat language converter
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
/// Converts the given text into the cat language.
///
/// refs:
/// * <https://misskey-hub.net/ns/#isCat>
/// * <https://firefish.dev/ns#speakAsCat>
///
/// # Arguments
///
/// * `text` : original text
/// * `lang` : language code (e.g., `Some("en")`, `Some("en-US")`, `Some("uk-UA")`, `None`)
///
/// # Example
///
/// ```
/// # use backend_rs::misc::nyaify::nyaify;
/// assert_eq!(nyaify("I'll take a nap.", Some("en")), "I'll take a nyap.");
/// ```
#[crate::export]
pub fn nyaify(text: &str, lang: Option<&str>) -> String {
let mut to_return = text.to_owned();

View file

@ -1,9 +1,12 @@
//! Utilities for password hash generation and verification
use argon2::{
password_hash,
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2,
};
/// Hashes the given password using [Argon2] algorithm.
#[crate::export]
pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Error> {
let salt = SaltString::generate(&mut OsRng);
@ -13,7 +16,7 @@ pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Er
}
#[derive(thiserror::Error, Debug)]
pub enum VerifyError {
pub enum Error {
#[error("An error occured while bcrypt verification: {0}")]
Bcrypt(#[from] bcrypt::BcryptError),
#[error("Invalid argon2 password hash: {0}")]
@ -22,8 +25,9 @@ pub enum VerifyError {
Argon2(#[from] argon2::Error),
}
/// Checks whether the given password and hash match.
#[crate::export]
pub fn verify_password(password: &str, hash: &str) -> Result<bool, VerifyError> {
pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
if is_old_password_algorithm(hash) {
Ok(bcrypt::verify(password, hash)?)
} else {
@ -34,6 +38,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result<bool, VerifyError>
}
}
/// Returns whether the [bcrypt] algorithm is used for the password hash.
#[inline]
#[crate::export]
pub fn is_old_password_algorithm(hash: &str) -> bool {

View file

@ -1,5 +1,4 @@
use crate::database::db_conn;
use crate::misc::convert_host::to_puny;
use crate::misc::meta::fetch_meta;
use crate::model::entity::emoji;
use once_cell::sync::Lazy;
@ -87,7 +86,7 @@ pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Resul
if let Some(host) = host {
// remote emoji
let ascii_host = to_puny(host)?;
let ascii_host = idna::domain_to_ascii(host)?;
// TODO: Does SeaORM have the `exists` method?
if emoji::Entity::find()

View file

@ -5,7 +5,7 @@ use crate::model::entity::attestation_challenge;
use chrono::{Duration, Utc};
use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter};
/// Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago
/// Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago
#[crate::export]
pub async fn remove_old_attestation_challenges() -> Result<(), DbErr> {
let res = attestation_challenge::Entity::delete_many()

View file

@ -1,3 +1,5 @@
//! Utilities to check hardware information such as cpu, memory, storage usage
use crate::init::system_info::{system_info, SysinfoPoisonError};
use sysinfo::{Disks, MemoryRefreshKind};

View file

@ -1 +1,3 @@
//! Database structure, auto-generated by [sea_orm]
pub mod entity;

View file

@ -1,3 +1,5 @@
//! Services provided for local users
pub mod antenna;
pub mod note;
pub mod push_notification;

View file

@ -1,3 +1,5 @@
//! Shared [isahc] HTTP client
use crate::config::CONFIG;
use isahc::{config::*, HttpClient};
use once_cell::sync::OnceCell;

View file

@ -1,3 +1,5 @@
//! ID generation utility based on [cuid2]
use crate::config::CONFIG;
use basen::BASE36;
use chrono::{DateTime, NaiveDateTime, Utc};

View file

@ -1,6 +1,5 @@
/// Shared [isahc] HTTP client
//! Basic utilities such as ID generator and HTTP client
pub mod http_client;
/// ID generation utility based on [cuid2]
pub mod id;
/// Secure random string generator
pub mod random;

View file

@ -1,6 +1,8 @@
//! Secure random string generator
use rand::{distributions::Alphanumeric, thread_rng, Rng};
/// Generate random string based on [thread_rng] and [Alphanumeric].
/// Generates a random string based on [thread_rng] and [Alphanumeric].
#[crate::export]
pub fn generate_secure_random_string(length: u16) -> String {
thread_rng()

View file

@ -37,8 +37,8 @@
"ajv": "8.14.0",
"archiver": "7.0.1",
"async-lock": "1.4.0",
"async-mutex": "^0.5.0",
"aws-sdk": "2.1629.0",
"async-mutex": "0.5.0",
"aws-sdk": "2.1630.0",
"axios": "1.7.2",
"backend-rs": "workspace:*",
"blurhash": "2.0.5",
@ -146,10 +146,10 @@
"@types/koa__multer": "2.0.7",
"@types/koa__router": "12.0.4",
"@types/mocha": "10.0.6",
"@types/node": "20.12.12",
"@types/node": "20.12.13",
"@types/node-fetch": "2.6.11",
"@types/nodemailer": "6.4.15",
"@types/oauth": "0.9.4",
"@types/oauth": "0.9.5",
"@types/opencc-js": "1.0.3",
"@types/pg": "8.11.6",
"@types/probe-image-size": "7.2.4",

View file

@ -94,6 +94,7 @@ export default async (ctx: Koa.Context) => {
// Compare passwords
const same = verifyPassword(password, profile.password!);
// Update the password hashing algorithm
if (same && isOldPasswordAlgorithm(profile.password!)) {
profile.password = hashPassword(password);
await UserProfiles.save(profile);

View file

@ -25,7 +25,7 @@
"@swc/core": "1.5.7",
"@swc/types": "0.1.7",
"@types/jest": "29.5.12",
"@types/node": "20.12.12",
"@types/node": "20.12.13",
"jest": "29.7.0",
"jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.5.0",

View file

@ -97,16 +97,16 @@ pub fn ts_export(
/// ```
/// #[macro_rs::napi(object)]
/// struct Person {
/// id: i32,
/// name: String,
/// id: i32,
/// name: String,
/// }
/// ```
/// simply becomes
/// ```
/// #[napi_derive::napi(use_nullable = true, object)]
/// struct Person {
/// id: i32,
/// name: String,
/// id: i32,
/// name: String,
/// }
/// ```
///

View file

@ -28,8 +28,8 @@ importers:
specifier: 1.7.3
version: 1.7.3
'@types/node':
specifier: 20.12.12
version: 20.12.12
specifier: 20.12.13
version: 20.12.13
execa:
specifier: 9.1.0
version: 9.1.0
@ -88,11 +88,11 @@ importers:
specifier: 1.4.0
version: 1.4.0
async-mutex:
specifier: ^0.5.0
specifier: 0.5.0
version: 0.5.0
aws-sdk:
specifier: 2.1629.0
version: 2.1629.0
specifier: 2.1630.0
version: 2.1630.0
axios:
specifier: 1.7.2
version: 1.7.2
@ -323,7 +323,7 @@ importers:
version: 0.2.3
typeorm:
specifier: 0.3.20
version: 0.3.20(ioredis@5.4.1)(pg@8.11.5)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
version: 0.3.20(ioredis@5.4.1)(pg@8.11.5)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
ulid:
specifier: 2.3.0
version: 2.3.0
@ -414,8 +414,8 @@ importers:
specifier: 10.0.6
version: 10.0.6
'@types/node':
specifier: 20.12.12
version: 20.12.12
specifier: 20.12.13
version: 20.12.13
'@types/node-fetch':
specifier: 2.6.11
version: 2.6.11
@ -423,8 +423,8 @@ importers:
specifier: 6.4.15
version: 6.4.15
'@types/oauth':
specifier: 0.9.4
version: 0.9.4
specifier: 0.9.5
version: 0.9.5
'@types/opencc-js':
specifier: 1.0.3
version: 1.0.3
@ -505,7 +505,7 @@ importers:
version: 9.5.1(typescript@5.4.5)(webpack@5.91.0(@swc/core@1.5.7))
ts-node:
specifier: 10.9.2
version: 10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)
version: 10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)
tsconfig-paths:
specifier: 4.2.0
version: 4.2.0
@ -595,7 +595,7 @@ importers:
version: 9.0.8
'@vitejs/plugin-vue':
specifier: 5.0.4
version: 5.0.4(vite@5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0))(vue@3.4.27(typescript@5.4.5))
version: 5.0.4(vite@5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0))(vue@3.4.27(typescript@5.4.5))
'@vue/runtime-core':
specifier: 3.4.27
version: 3.4.27
@ -748,10 +748,10 @@ importers:
version: 9.0.1
vite:
specifier: 5.2.12
version: 5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)
version: 5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)
vite-plugin-compression:
specifier: 0.5.1
version: 0.5.1(vite@5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0))
version: 0.5.1(vite@5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0))
vue:
specifier: 3.4.27
version: 3.4.27(typescript@5.4.5)
@ -794,11 +794,11 @@ importers:
specifier: 29.5.12
version: 29.5.12
'@types/node':
specifier: 20.12.12
version: 20.12.12
specifier: 20.12.13
version: 20.12.13
jest:
specifier: 29.7.0
version: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
version: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
jest-fetch-mock:
specifier: 3.0.3
version: 3.0.3
@ -810,10 +810,10 @@ importers:
version: 9.3.1
ts-jest:
specifier: 29.1.4
version: 29.1.4(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)))(typescript@5.4.5)
version: 29.1.4(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)))(typescript@5.4.5)
ts-node:
specifier: 10.9.2
version: 10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)
version: 10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)
tsd:
specifier: 0.31.0
version: 0.31.0
@ -831,10 +831,10 @@ importers:
version: 6.2.1
vite:
specifier: 5.2.12
version: 5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)
version: 5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)
vite-plugin-compression:
specifier: 0.5.1
version: 0.5.1(vite@5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0))
version: 0.5.1(vite@5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0))
packages:
@ -2346,8 +2346,8 @@ packages:
'@types/node-fetch@2.6.11':
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
'@types/node@20.12.12':
resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==}
'@types/node@20.12.13':
resolution: {integrity: sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==}
'@types/nodemailer@6.4.15':
resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==}
@ -2355,8 +2355,8 @@ packages:
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
'@types/oauth@0.9.4':
resolution: {integrity: sha512-qk9orhti499fq5XxKCCEbd0OzdPZuancneyse3KtR+vgMiHRbh+mn8M4G6t64ob/Fg+GZGpa565MF/2dKWY32A==}
'@types/oauth@0.9.5':
resolution: {integrity: sha512-+oQ3C2Zx6ambINOcdIARF5Z3Tu3x//HipE889/fqo3sgpQZbe9c6ExdQFtN6qlhpR7p83lTZfPJt0tCAW29dog==}
'@types/opencc-js@1.0.3':
resolution: {integrity: sha512-TENp7YkI2hNlc4dplhivSHj0hU4DORCK56VY7rniaSfA5f87uD3uv+kPIRuH9h64TGv976iVFi4gEHZZtS2y8Q==}
@ -2885,8 +2885,8 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
aws-sdk@2.1629.0:
resolution: {integrity: sha512-fxhru9iLRqwsYK9BJgawomKAyxpsWVP5Unwa//rnQXCeX5pfYCaixKyV/B6U1x4LMcz3uN83tYvVah8uHxhTqA==}
aws-sdk@2.1630.0:
resolution: {integrity: sha512-Lu1+jzBExiAoD88A1XnY+nztUPCE1TRU/hZHgcyDsc8TYyQ0JjsJoVo+BsnshBNt4j+OvdNg3ytk4+lRJTB04w==}
engines: {node: '>= 10.0.0'}
axios@0.24.0:
@ -6622,11 +6622,6 @@ packages:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rndstr@1.0.0:
resolution: {integrity: sha512-3KN+BHTiHcsyW1qjRw3Xhms8TQfTIN4fUVgqqJpj6FnmuCnto5/lLyppSmGfdTmOiKDWeuXU4XPp58I9fsoWFQ==}
@ -7162,30 +7157,6 @@ packages:
peerDependencies:
typescript: '>=4.2.0'
ts-jest@29.1.3:
resolution: {integrity: sha512-6L9qz3ginTd1NKhOxmkP0qU3FyKjj5CPoY+anszfVn6Pmv/RIKzhiMCsH7Yb7UvJR9I2A64rm4zQl531s2F1iw==}
engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@babel/core': '>=7.0.0-beta.0 <8'
'@jest/transform': ^29.0.0
'@jest/types': ^29.0.0
babel-jest: ^29.0.0
esbuild: '*'
jest: ^29.0.0
typescript: '>=4.3 <6'
peerDependenciesMeta:
'@babel/core':
optional: true
'@jest/transform':
optional: true
'@jest/types':
optional: true
babel-jest:
optional: true
esbuild:
optional: true
ts-jest@29.1.4:
resolution: {integrity: sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==}
engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
@ -8633,27 +8604,27 @@ snapshots:
'@jest/console@29.7.0':
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
chalk: 4.1.2
jest-message-util: 29.7.0
jest-util: 29.7.0
slash: 3.0.0
'@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))':
'@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))':
dependencies:
'@jest/console': 29.7.0
'@jest/reporters': 29.7.0
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
jest-config: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@ -8678,7 +8649,7 @@ snapshots:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
jest-mock: 29.7.0
'@jest/expect-utils@29.7.0':
@ -8696,7 +8667,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
'@types/node': 20.12.12
'@types/node': 20.12.13
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@ -8718,7 +8689,7 @@ snapshots:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.25
'@types/node': 20.12.12
'@types/node': 20.12.13
chalk: 4.1.2
collect-v8-coverage: 1.0.2
exit: 0.1.2
@ -8788,7 +8759,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/yargs': 17.0.32
chalk: 4.1.2
@ -9205,11 +9176,11 @@ snapshots:
'@types/accepts@1.3.7':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/adm-zip@0.5.5':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/async-lock@1.4.0': {}
@ -9239,18 +9210,18 @@ snapshots:
'@types/body-parser@1.19.5':
dependencies:
'@types/connect': 3.4.38
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/cacheable-request@6.0.3':
dependencies:
'@types/http-cache-semantics': 4.0.4
'@types/keyv': 3.1.4
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/responselike': 1.0.3
'@types/co-body@6.1.3':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/qs': 6.9.15
'@types/color-convert@2.0.3':
@ -9261,7 +9232,7 @@ snapshots:
'@types/connect@3.4.38':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/content-disposition@0.5.8': {}
@ -9270,7 +9241,7 @@ snapshots:
'@types/connect': 3.4.38
'@types/express': 4.17.21
'@types/keygrip': 1.0.6
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/disposable-email-domains@1.0.6': {}
@ -9295,7 +9266,7 @@ snapshots:
'@types/express-serve-static-core@4.19.0':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/qs': 6.9.15
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
@ -9309,20 +9280,20 @@ snapshots:
'@types/fluent-ffmpeg@2.1.24':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/formidable@2.0.6':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/glob@8.1.0':
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/graceful-fs@4.1.9':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/http-assert@1.5.5': {}
@ -9349,7 +9320,7 @@ snapshots:
'@types/jsdom@21.1.6':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/tough-cookie': 4.0.5
parse5: 7.1.2
@ -9367,7 +9338,7 @@ snapshots:
'@types/keyv@3.1.4':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/koa-bodyparser@4.3.12':
dependencies:
@ -9406,7 +9377,7 @@ snapshots:
'@types/http-errors': 2.0.4
'@types/keygrip': 1.0.6
'@types/koa-compose': 3.2.8
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/koa__cors@5.0.0':
dependencies:
@ -9436,32 +9407,32 @@ snapshots:
'@types/needle@3.3.0':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/node-fetch@2.6.11':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
form-data: 4.0.0
'@types/node@20.12.12':
'@types/node@20.12.13':
dependencies:
undici-types: 5.26.5
'@types/nodemailer@6.4.15':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/normalize-package-data@2.4.4': {}
'@types/oauth@0.9.4':
'@types/oauth@0.9.5':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/opencc-js@1.0.3': {}
'@types/pg@8.11.6':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
pg-protocol: 1.6.1
pg-types: 4.0.2
@ -9470,7 +9441,7 @@ snapshots:
'@types/probe-image-size@7.2.4':
dependencies:
'@types/needle': 3.3.0
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/pug@2.0.10': {}
@ -9478,7 +9449,7 @@ snapshots:
'@types/qrcode@1.5.5':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/qs@6.9.15': {}
@ -9492,7 +9463,7 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/sanitize-html@2.11.0':
dependencies:
@ -9505,12 +9476,12 @@ snapshots:
'@types/send@0.17.4':
dependencies:
'@types/mime': 1.3.5
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/serve-static@1.15.7':
dependencies:
'@types/http-errors': 2.0.4
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/send': 0.17.4
'@types/sinonjs__fake-timers@8.1.5': {}
@ -9537,11 +9508,11 @@ snapshots:
'@types/websocket@1.0.10':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/ws@8.5.10':
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
'@types/yargs-parser@21.0.3': {}
@ -9721,11 +9692,9 @@ snapshots:
'@typescript-eslint/types': 6.21.0
eslint-visitor-keys: 3.4.3
'@ungap/structured-clone@1.2.0': {}
'@vitejs/plugin-vue@5.0.4(vite@5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0))(vue@3.4.27(typescript@5.4.5))':
'@vitejs/plugin-vue@5.0.4(vite@5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0))(vue@3.4.27(typescript@5.4.5))':
dependencies:
vite: 5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)
vite: 5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)
vue: 3.4.27(typescript@5.4.5)
'@volar/language-core@2.2.4':
@ -10094,7 +10063,7 @@ snapshots:
dependencies:
possible-typed-array-names: 1.0.0
aws-sdk@2.1629.0:
aws-sdk@2.1630.0:
dependencies:
buffer: 4.9.2
events: 1.1.1
@ -10677,13 +10646,13 @@ snapshots:
crc-32: 1.2.2
readable-stream: 4.5.2
create-jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)):
create-jest@29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)):
dependencies:
'@jest/types': 29.6.3
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
jest-config: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@ -10880,7 +10849,7 @@ snapshots:
deepl-node@1.13.0:
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
axios: 1.7.2
form-data: 3.0.1
loglevel: 1.9.1
@ -12497,7 +12466,7 @@ snapshots:
'@jest/expect': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
chalk: 4.1.2
co: 4.6.0
dedent: 1.5.3
@ -12517,16 +12486,16 @@ snapshots:
- babel-plugin-macros
- supports-color
jest-cli@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)):
jest-cli@29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)):
dependencies:
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
create-jest: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
create-jest: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
exit: 0.1.2
import-local: 3.1.0
jest-config: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
jest-config: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@ -12536,7 +12505,7 @@ snapshots:
- supports-color
- ts-node
jest-config@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)):
jest-config@29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)):
dependencies:
'@babel/core': 7.24.5
'@jest/test-sequencer': 29.7.0
@ -12561,8 +12530,8 @@ snapshots:
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 20.12.12
ts-node: 10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)
'@types/node': 20.12.13
ts-node: 10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@ -12591,7 +12560,7 @@ snapshots:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
jest-mock: 29.7.0
jest-util: 29.7.0
@ -12608,7 +12577,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
'@types/node': 20.12.12
'@types/node': 20.12.13
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@ -12647,7 +12616,7 @@ snapshots:
jest-mock@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
jest-util: 29.7.0
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@ -12682,7 +12651,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.11
@ -12710,7 +12679,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
chalk: 4.1.2
cjs-module-lexer: 1.3.1
collect-v8-coverage: 1.0.2
@ -12756,7 +12725,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@ -12775,7 +12744,7 @@ snapshots:
dependencies:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.12
'@types/node': 20.12.13
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@ -12789,23 +12758,23 @@ snapshots:
jest-worker@27.5.1:
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
merge-stream: 2.0.0
supports-color: 8.1.1
jest-worker@29.7.0:
dependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)):
jest@29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)):
dependencies:
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
'@jest/types': 29.6.3
import-local: 3.1.0
jest-cli: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
jest-cli: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@ -14924,29 +14893,11 @@ snapshots:
dependencies:
typescript: 5.4.5
ts-jest@29.1.3(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@18.11.18)(ts-node@10.9.2(@types/node@18.11.18)(typescript@4.9.4)))(typescript@4.9.4):
ts-jest@29.1.4(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)))(typescript@5.4.5):
dependencies:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@18.11.18)(ts-node@10.9.2(@types/node@18.11.18)(typescript@4.9.4))
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
make-error: 1.3.6
semver: 7.6.2
typescript: 4.9.4
yargs-parser: 21.1.1
optionalDependencies:
'@babel/core': 7.24.5
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.24.5)
ts-jest@29.1.4(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)))(typescript@5.4.5):
dependencies:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5))
jest: 29.7.0(@types/node@20.12.13)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5))
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
@ -14970,14 +14921,14 @@ snapshots:
typescript: 5.4.5
webpack: 5.91.0(@swc/core@1.5.7)
ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5):
ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.12.12
'@types/node': 20.12.13
acorn: 8.11.3
acorn-walk: 8.3.2
arg: 4.1.3
@ -15090,7 +15041,7 @@ snapshots:
typedarray@0.0.6: {}
typeorm@0.3.20(ioredis@5.4.1)(pg@8.11.5)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)):
typeorm@0.3.20(ioredis@5.4.1)(pg@8.11.5)(ts-node@10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)):
dependencies:
'@sqltools/formatter': 1.2.5
app-root-path: 3.1.0
@ -15110,7 +15061,7 @@ snapshots:
optionalDependencies:
ioredis: 5.4.1
pg: 8.11.5
ts-node: 10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.12)(typescript@5.4.5)
ts-node: 10.9.2(@swc/core@1.5.7)(@swc/wasm@1.2.130)(@types/node@20.12.13)(typescript@5.4.5)
transitivePeerDependencies:
- supports-color
@ -15228,22 +15179,22 @@ snapshots:
core-util-is: 1.0.2
extsprintf: 1.3.0
vite-plugin-compression@0.5.1(vite@5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)):
vite-plugin-compression@0.5.1(vite@5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)):
dependencies:
chalk: 4.1.2
debug: 4.3.4(supports-color@8.1.1)
fs-extra: 10.1.0
vite: 5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)
vite: 5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0)
transitivePeerDependencies:
- supports-color
vite@5.2.12(@types/node@20.12.12)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0):
vite@5.2.12(@types/node@20.12.13)(sass@1.77.2)(stylus@0.57.0)(terser@5.31.0):
dependencies:
esbuild: 0.20.2
postcss: 8.4.38
rollup: 4.17.2
optionalDependencies:
'@types/node': 20.12.12
'@types/node': 20.12.13
fsevents: 2.3.3
sass: 1.77.2
stylus: 0.57.0