Merge branch 'develop' into refactor/push-notification

This commit is contained in:
naskya 2024-04-26 13:59:24 +09:00
commit 56f06bae56
No known key found for this signature in database
GPG Key ID: 712D413B3A9FED5C
31 changed files with 411 additions and 147 deletions

38
Cargo.lock generated
View File

@ -224,6 +224,7 @@ dependencies = [
"napi-derive",
"nom-exif",
"once_cell",
"openssl",
"pretty_assertions",
"rand",
"redis",
@ -1823,9 +1824,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
@ -2207,6 +2208,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "300.2.3+3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.102"
@ -2215,6 +2225,7 @@ checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
@ -2290,9 +2301,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
name = "parking_lot"
version = "0.12.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [
"lock_api",
"parking_lot_core",
@ -2300,15 +2311,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.9"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"redox_syscall 0.5.1",
"smallvec",
"windows-targets 0.48.5",
"windows-targets 0.52.5",
]
[[package]]
@ -2766,6 +2777,15 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags 2.5.0",
]
[[package]]
name = "regex"
version = "1.10.4"
@ -4435,7 +4455,7 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
dependencies = [
"redox_syscall",
"redox_syscall 0.4.1",
"wasite",
]

View File

@ -20,6 +20,7 @@ idna = "0.5.0"
image = "0.25.1"
nom-exif = "1.2.0"
once_cell = "1.19.0"
openssl = "0.10.64"
pretty_assertions = "1.4.0"
proc-macro2 = "1.0.79"
quote = "1.0.36"

View File

@ -1,6 +1,7 @@
BEGIN;
DELETE FROM "migrations" WHERE name IN (
'AlterAkaType1714099399879',
'AddDriveFileUsage1713451569342',
'ConvertCwVarcharToText1713225866247',
'FixChatFileConstraint1712855579316',
@ -24,6 +25,13 @@ DELETE FROM "migrations" WHERE name IN (
'RemoveNativeUtilsMigration1705877093218'
);
-- alter-aka-type
ALTER TABLE "user" RENAME COLUMN "alsoKnownAs" TO "alsoKnownAsOld";
ALTER TABLE "user" ADD COLUMN "alsoKnownAs" text;
UPDATE "user" SET "alsoKnownAs" = array_to_string("alsoKnownAsOld", ',');
COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too';
ALTER TABLE "user" DROP COLUMN "alsoKnownAsOld";
-- AddDriveFileUsage
ALTER TABLE "drive_file" DROP COLUMN "usageHint";
DROP TYPE "drive_file_usage_hint_enum";

View File

@ -19,7 +19,7 @@ deleteAndEditConfirm: Сигурни ли сте, че искате да изт
copyUsername: Копиране на потребителското име
searchUser: Търсене на потребител
reply: Отговор
showMore: Покажи още
showMore: Показване на повече
loadMore: Зареди още
followRequestAccepted: Заявката за последване е приета
importAndExport: Импорт/експорт на данни
@ -336,6 +336,10 @@ _pages:
title: Заглавие
my: Моите страници
pageSetting: Настройки на страницата
url: Адрес на страницата
summary: Кратко обобщение
alignCenter: Центриране на елементите
variables: Променливи
_deck:
_columns:
notifications: Известия
@ -398,7 +402,7 @@ sendMessage: Изпращане на съобщение
jumpToPrevious: Премини към предишно
newer: по-ново
older: по-старо
showLess: Покажи по-малко
showLess: Показване на по-малко
youGotNewFollower: те последва
receiveFollowRequest: Заявка за последване получена
mention: Споменаване
@ -754,7 +758,7 @@ _feeds:
general: Общи
metadata: Метаданни
disk: Диск
featured: Препоръчани
featured: Препоръчано
yearsOld: на {age} години
reload: Опресняване
invites: Покани
@ -940,3 +944,11 @@ showGapBetweenNotesInTimeline: Показване на празнина межд
lookup: Поглеждане
media: Мултимедия
welcomeBackWithName: Добре дошли отново, {name}
reduceUiAnimation: Намаляване на UI анимациите
clickToFinishEmailVerification: Моля, натиснете [{ok}], за да завършите потвърждаването
на ел. поща.
_cw:
show: Показване на съдържанието
remoteFollow: Отдалечено последване
messagingUnencryptedInfo: Чатовете във Firefish не са шифровани от край до край. Не
споделяйте чувствителна информация през Firefish.

View File

@ -1159,9 +1159,9 @@ addRe: "Add \"re:\" at the beginning of comment in reply to a post with a conten
confirm: "Confirm"
importZip: "Import ZIP"
exportZip: "Export ZIP"
getQrCode: "Get QR code"
getQrCode: "Show QR code"
remoteFollow: "Remote follow"
remoteFollowUrl: "Remote follow URL"
copyRemoteFollowUrl: "Copy remote follow URL"
emojiPackCreator: "Emoji pack creator"
indexable: "Indexable"
indexableDescription: "Allow built-in search to show your public posts"

View File

@ -1978,7 +1978,7 @@ importZip: 导入 ZIP
exportZip: 导出 ZIP
getQrCode: "获取二维码"
remoteFollow: "远程关注"
remoteFollowUrl: "远程关注 URL"
copyRemoteFollowUrl: "复制远程关注 URL"
emojiPackCreator: 表情包创建工具
objectStorageS3ForcePathStyleDesc: 打开此选项可构建格式为 "s3.amazonaws.com/<bucket>/" 而非 "<bucket>.s3.amazonaws.com"
的端点 URL。

View File

@ -27,6 +27,7 @@ idna = { workspace = true }
image = { workspace = true }
nom-exif = { workspace = true }
once_cell = { workspace = true }
openssl = { workspace = true, features = ["vendored"] }
rand = { workspace = true }
redis = { workspace = true }
regex = { workspace = true }

View File

@ -1017,10 +1017,10 @@ export interface User {
isDeleted: boolean
driveCapacityOverrideMb: number | null
movedToUri: string | null
alsoKnownAs: string | null
speakAsCat: boolean
emojiModPerm: UserEmojimodpermEnum
isIndexable: boolean
alsoKnownAs: Array<string> | null
}
export interface UserGroup {
id: string
@ -1158,6 +1158,7 @@ export enum PushNotificationKind {
ReadAllNotifications = 'readAllNotifications'
}
export function sendPushNotification(receiverUserId: string, kind: PushNotificationKind, content: any): Promise<void>
export function publishToChannelStream(channelId: string, userId: string): void
export enum ChatEvent {
Message = 'message',
Read = 'read',
@ -1165,6 +1166,30 @@ export enum ChatEvent {
Typing = 'typing'
}
export function publishToChatStream(senderUserId: string, receiverUserId: string, kind: ChatEvent, object: any): void
export enum ChatIndexEvent {
Message = 'message',
Read = 'read'
}
export function publishToChatIndexStream(userId: string, kind: ChatIndexEvent, object: any): void
export interface PackedEmoji {
id: string
aliases: Array<string>
name: string
category: string | null
host: string | null
url: string
license: string | null
width: number | null
height: number | null
}
export function publishToBroadcastStream(emoji: PackedEmoji): void
export interface AbuseUserReportLike {
id: string
targetUserId: string
reporterId: string
comment: string
}
export function publishToModerationStream(moderatorId: string, report: AbuseUserReportLike): void
export function getTimestamp(id: string): number
/**
* The generated ID results in the form of `[8 chars timestamp] + [cuid2]`.

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, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initializeRustLogger, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, ChatEvent, publishToChatStream, getTimestamp, genId, genIdAt, secureRndstr } = nativeBinding
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initializeRustLogger, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToModerationStream, getTimestamp, genId, genIdAt, secureRndstr } = nativeBinding
module.exports.SECOND = SECOND
module.exports.MINUTE = MINUTE
@ -367,8 +367,13 @@ module.exports.watchNote = watchNote
module.exports.unwatchNote = unwatchNote
module.exports.PushNotificationKind = PushNotificationKind
module.exports.sendPushNotification = sendPushNotification
module.exports.publishToChannelStream = publishToChannelStream
module.exports.ChatEvent = ChatEvent
module.exports.publishToChatStream = publishToChatStream
module.exports.ChatIndexEvent = ChatIndexEvent
module.exports.publishToChatIndexStream = publishToChatIndexStream
module.exports.publishToBroadcastStream = publishToBroadcastStream
module.exports.publishToModerationStream = publishToModerationStream
module.exports.getTimestamp = getTimestamp
module.exports.genId = genId
module.exports.genIdAt = genIdAt

View File

@ -71,14 +71,14 @@ pub struct Model {
pub drive_capacity_override_mb: Option<i32>,
#[sea_orm(column_name = "movedToUri")]
pub moved_to_uri: Option<String>,
#[sea_orm(column_name = "alsoKnownAs", column_type = "Text", nullable)]
pub also_known_as: Option<String>,
#[sea_orm(column_name = "speakAsCat")]
pub speak_as_cat: bool,
#[sea_orm(column_name = "emojiModPerm")]
pub emoji_mod_perm: UserEmojimodpermEnum,
#[sea_orm(column_name = "isIndexable")]
pub is_indexable: bool,
#[sea_orm(column_name = "alsoKnownAs")]
pub also_known_as: Option<Vec<String>>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@ -13,7 +13,7 @@ pub fn initialize_logger() {
"info" => Level::INFO,
"debug" => Level::DEBUG,
"trace" => Level::TRACE,
_ => Level::INFO,
_ => Level::INFO, // Fallback
});
} else if let Some(levels) = &CONFIG.log_level {
// `logLevel` config is Deprecated
@ -27,13 +27,25 @@ pub fn initialize_logger() {
builder = builder.with_max_level(Level::WARN);
} else if levels.contains(&"error".to_string()) {
builder = builder.with_max_level(Level::ERROR);
} else {
// Fallback
builder = builder.with_max_level(Level::INFO);
}
} else {
// Fallback
builder = builder.with_max_level(Level::INFO);
};
let subscriber = builder.with_level(true).pretty().finish();
let subscriber = builder
.without_time()
.with_level(true)
.with_ansi(true)
.with_target(true)
.with_thread_names(true)
.with_line_number(true)
.log_internal_errors(true)
.compact()
.finish();
tracing::subscriber::set_global_default(subscriber).expect("Failed to initialize the logger");
}

View File

@ -1,5 +1,9 @@
pub mod antenna;
pub mod channel;
pub mod chat;
pub mod chat_index;
pub mod custom_emoji;
pub mod moderation;
use crate::config::CONFIG;
use crate::database::redis_conn;
@ -10,9 +14,9 @@ pub enum Stream {
#[strum(serialize = "internal")]
Internal,
#[strum(serialize = "broadcast")]
Broadcast,
#[strum(to_string = "adminStream:{user_id}")]
Admin { user_id: String },
CustomEmoji,
#[strum(to_string = "adminStream:{moderator_id}")]
Moderation { moderator_id: String },
#[strum(to_string = "user:{user_id}")]
User { user_id: String },
#[strum(to_string = "channelStream:{channel_id}")]
@ -37,7 +41,7 @@ pub enum Stream {
#[strum(to_string = "messagingStream:{group_id}")]
GroupChat { group_id: String },
#[strum(to_string = "messagingIndexStream:{user_id}")]
MessagingIndex { user_id: String },
ChatIndex { user_id: String },
}
#[derive(thiserror::Error, Debug)]
@ -57,7 +61,7 @@ pub fn publish_to_stream(
) -> Result<(), Error> {
let message = if let Some(kind) = kind {
format!(
"{{ \"type\": \"{}\", \"body\": {} }}",
"{{\"type\":\"{}\",\"body\":{}}}",
kind,
value.unwrap_or("null".to_string()),
)
@ -67,10 +71,7 @@ pub fn publish_to_stream(
redis_conn()?.publish(
&CONFIG.host,
format!(
"{{ \"channel\": \"{}\", \"message\": {} }}",
stream, message,
),
format!("{{\"channel\":\"{}\",\"message\":{}}}", stream, message),
)?;
Ok(())
@ -84,10 +85,10 @@ mod unit_test {
#[test]
fn channel_to_string() {
assert_eq!(Stream::Internal.to_string(), "internal");
assert_eq!(Stream::Broadcast.to_string(), "broadcast");
assert_eq!(Stream::CustomEmoji.to_string(), "broadcast");
assert_eq!(
Stream::Admin {
user_id: "9tb42br63g5apjcq".to_string()
Stream::Moderation {
moderator_id: "9tb42br63g5apjcq".to_string()
}
.to_string(),
"adminStream:9tb42br63g5apjcq"

View File

@ -0,0 +1,10 @@
use crate::service::stream::{publish_to_stream, Error, Stream};
#[crate::export(js_name = "publishToChannelStream")]
pub fn publish(channel_id: String, user_id: String) -> Result<(), Error> {
publish_to_stream(
&Stream::Channel { channel_id },
Some("typing".to_string()),
Some(format!("\"{}\"", user_id)),
)
}

View File

@ -13,12 +13,15 @@ pub enum ChatEvent {
Typing,
}
// We want to merge `kind` and `object` into a single enum
// https://github.com/napi-rs/napi-rs/issues/2036
#[crate::export(js_name = "publishToChatStream")]
pub fn publish(
sender_user_id: String,
receiver_user_id: String,
kind: ChatEvent,
object: &serde_json::Value, // TODO?: change this to enum
object: &serde_json::Value,
) -> Result<(), Error> {
publish_to_stream(
&Stream::Chat {

View File

@ -0,0 +1,26 @@
use crate::service::stream::{publish_to_stream, Error, Stream};
#[derive(strum::Display)]
#[crate::export(string_enum = "camelCase")]
pub enum ChatIndexEvent {
#[strum(serialize = "message")]
Message,
#[strum(serialize = "read")]
Read,
}
// We want to merge `kind` and `object` into a single enum
// https://github.com/napi-rs/napi-rs/issues/2036
#[crate::export(js_name = "publishToChatIndexStream")]
pub fn publish(
user_id: String,
kind: ChatIndexEvent,
object: &serde_json::Value,
) -> Result<(), Error> {
publish_to_stream(
&Stream::ChatIndex { user_id },
Some(kind.to_string()),
Some(serde_json::to_string(object)?),
)
}

View File

@ -0,0 +1,27 @@
use crate::service::stream::{publish_to_stream, Error, Stream};
use serde::{Deserialize, Serialize};
// TODO: define schema type in other place
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object)]
pub struct PackedEmoji {
pub id: String,
pub aliases: Vec<String>,
pub name: String,
pub category: Option<String>,
pub host: Option<String>,
pub url: String,
pub license: Option<String>,
pub width: Option<i32>,
pub height: Option<i32>,
}
#[crate::export(js_name = "publishToBroadcastStream")]
pub fn publish(emoji: &PackedEmoji) -> Result<(), Error> {
publish_to_stream(
&Stream::CustomEmoji,
Some("emojiAdded".to_string()),
Some(format!("{{\"emoji\":{}}}", serde_json::to_string(emoji)?)),
)
}

View File

@ -0,0 +1,21 @@
use crate::service::stream::{publish_to_stream, Error, Stream};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object)]
pub struct AbuseUserReportLike {
pub id: String,
pub target_user_id: String,
pub reporter_id: String,
pub comment: String,
}
#[crate::export(js_name = "publishToModerationStream")]
pub fn publish(moderator_id: String, report: &AbuseUserReportLike) -> Result<(), Error> {
publish_to_stream(
&Stream::Moderation { moderator_id },
Some("newAbuseUserReport".to_string()),
Some(serde_json::to_string(report)?),
)
}

View File

@ -0,0 +1,36 @@
import type { MigrationInterface, QueryRunner } from "typeorm";
export class AlterAkaType1714099399879 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" RENAME COLUMN "alsoKnownAs" TO "alsoKnownAsOld"`,
);
await queryRunner.query(
`ALTER TABLE "user" ADD COLUMN "alsoKnownAs" character varying(512)[]`,
);
await queryRunner.query(
`UPDATE "user" SET "alsoKnownAs" = string_to_array("alsoKnownAsOld", ',')::character varying[]`,
);
await queryRunner.query(
`UPDATE "user" SET "alsoKnownAs" = NULL WHERE "alsoKnownAs" = '{}'`,
);
await queryRunner.query(
`COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too'`,
);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "alsoKnownAsOld"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" RENAME COLUMN "alsoKnownAs" TO "alsoKnownAsOld"`,
);
await queryRunner.query(`ALTER TABLE "user" ADD COLUMN "alsoKnownAs" text`);
await queryRunner.query(
`UPDATE "user" SET "alsoKnownAs" = array_to_string("alsoKnownAsOld", ',')`,
);
await queryRunner.query(
`COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too'`,
);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "alsoKnownAsOld"`);
}
}

View File

@ -88,7 +88,9 @@ export class User {
})
public movedToUri: string | null;
@Column("simple-array", {
@Column("varchar", {
length: 512,
array: true,
nullable: true,
comment: "URIs the user is known as too",
})

View File

@ -2,9 +2,14 @@ import {
publishMainStream,
publishGroupMessagingStream,
} from "@/services/stream.js";
import { publishToChatStream, ChatEvent } from "backend-rs";
import { publishMessagingIndexStream } from "@/services/stream.js";
import { sendPushNotification, PushNotificationKind } from "backend-rs";
import {
publishToChatStream,
publishToChatIndexStream,
sendPushNotification,
ChatEvent,
ChatIndexEvent,
PushNotificationKind,
} from "backend-rs";
import type { User, IRemoteUser } from "@/models/entities/user.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import { MessagingMessages, UserGroupJoinings, Users } from "@/models/index.js";
@ -55,7 +60,7 @@ export async function readUserMessagingMessage(
// Publish event
publishToChatStream(otherpartyId, userId, ChatEvent.Read, messageIds);
publishMessagingIndexStream(userId, "read", messageIds);
publishToChatIndexStream(userId, ChatIndexEvent.Read, messageIds);
if (!(await Users.getHasUnreadMessagingMessage(userId))) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
@ -129,7 +134,7 @@ export async function readGroupMessagingMessage(
ids: reads,
userId: userId,
});
publishMessagingIndexStream(userId, "read", reads);
publishToChatIndexStream(userId, ChatIndexEvent.Read, reads);
if (!(await Users.getHasUnreadMessagingMessage(userId))) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行

View File

@ -1,10 +1,14 @@
import define from "@/server/api/define.js";
import { Emojis, DriveFiles } from "@/models/index.js";
import { type ImageSize, genId, getImageSizeFromUrl } from "backend-rs";
import {
type ImageSize,
genId,
getImageSizeFromUrl,
publishToBroadcastStream,
} from "backend-rs";
import { insertModerationLog } from "@/services/insert-moderation-log.js";
import { ApiError } from "@/server/api/error.js";
import rndstr from "rndstr";
import { publishBroadcastStream } from "@/services/stream.js";
import { db } from "@/db/postgre.js";
import { apiLogger } from "@/server/api/logger.js";
import { inspect } from "node:util";
@ -75,9 +79,7 @@ export default define(meta, paramDef, async (ps, me) => {
await db.queryResultCache!.remove(["meta_emojis"]);
publishBroadcastStream("emojiAdded", {
emoji: await Emojis.pack(emoji.id),
});
publishToBroadcastStream(await Emojis.pack(emoji));
insertModerationLog(me, "addEmoji", {
emojiId: emoji.id,

View File

@ -1,10 +1,14 @@
import define from "@/server/api/define.js";
import { Emojis } from "@/models/index.js";
import { type ImageSize, genId, getImageSizeFromUrl } from "backend-rs";
import {
type ImageSize,
genId,
getImageSizeFromUrl,
publishToBroadcastStream,
} from "backend-rs";
import { ApiError } from "@/server/api/error.js";
import type { DriveFile } from "@/models/entities/drive-file.js";
import { uploadFromUrl } from "@/services/drive/upload-from-url.js";
import { publishBroadcastStream } from "@/services/stream.js";
import { db } from "@/db/postgre.js";
import { apiLogger } from "@/server/api/logger.js";
import { inspect } from "node:util";
@ -102,9 +106,7 @@ export default define(meta, paramDef, async (ps, me) => {
await db.queryResultCache!.remove(["meta_emojis"]);
publishBroadcastStream("emojiAdded", {
emoji: await Emojis.pack(copied.id),
});
publishToBroadcastStream(await Emojis.pack(copied));
return {
id: copied.id,

View File

@ -1,10 +1,8 @@
import * as mfm from "mfm-js";
import sanitizeHtml from "sanitize-html";
import { publishAdminStream } from "@/services/stream.js";
import { AbuseUserReports, UserProfiles, Users } from "@/models/index.js";
import { genId } from "backend-rs";
import { genId, publishToModerationStream } from "backend-rs";
import { sendEmail } from "@/services/send-email.js";
import { fetchMeta } from "backend-rs";
import { getUser } from "@/server/api/common/getters.js";
import { ApiError } from "@/server/api/error.js";
import define from "@/server/api/define.js";
@ -86,9 +84,8 @@ export default define(meta, paramDef, async (ps, me) => {
],
});
const meta = await fetchMeta(true);
for (const moderator of moderators) {
publishAdminStream(moderator.id, "newAbuseUserReport", {
publishToModerationStream(moderator.id, {
id: report.id,
targetUserId: report.targetUserId,
reporterId: report.reporterId,

View File

@ -14,11 +14,12 @@ import {
} from "@/models/index.js";
import type { AccessToken } from "@/models/entities/access-token.js";
import type { UserProfile } from "@/models/entities/user-profile.js";
import { publishGroupMessagingStream } from "@/services/stream.js";
import {
publishChannelStream,
publishGroupMessagingStream,
} from "@/services/stream.js";
import { publishToChatStream, ChatEvent } from "backend-rs";
publishToChannelStream,
publishToChatStream,
ChatEvent,
} from "backend-rs";
import type { UserGroup } from "@/models/entities/user-group.js";
import type { Packed } from "@/misc/schema.js";
import { readNotification } from "@/server/api/common/read-notification.js";
@ -512,9 +513,9 @@ export default class Connection {
}
}
private typingOnChannel(channel: ChannelModel["id"]) {
private typingOnChannel(channelId: ChannelModel["id"]) {
if (this.user) {
publishChannelStream(channel, "typing", this.user.id);
publishToChannelStream(channelId, this.user.id);
}
}

View File

@ -11,13 +11,14 @@ import {
genId,
sendPushNotification,
publishToChatStream,
publishToChatIndexStream,
toPuny,
PushNotificationKind,
ChatEvent,
ChatIndexEvent,
PushNotificationKind,
} from "backend-rs";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import {
publishMessagingIndexStream,
publishMainStream,
publishGroupMessagingStream,
} from "@/services/stream.js";
@ -63,7 +64,11 @@ export async function createMessage(
ChatEvent.Message,
messageObj,
);
publishMessagingIndexStream(message.userId, "message", messageObj);
publishToChatIndexStream(
message.userId,
ChatIndexEvent.Message,
messageObj,
);
publishMainStream(message.userId, "messagingMessage", messageObj);
}
@ -75,7 +80,11 @@ export async function createMessage(
ChatEvent.Message,
messageObj,
);
publishMessagingIndexStream(recipientUser.id, "message", messageObj);
publishToChatIndexStream(
recipientUser.id,
ChatIndexEvent.Message,
messageObj,
);
publishMainStream(recipientUser.id, "messagingMessage", messageObj);
}
} else if (recipientGroup) {
@ -87,7 +96,11 @@ export async function createMessage(
userGroupId: recipientGroup.id,
});
for (const joining of joinings) {
publishMessagingIndexStream(joining.userId, "message", messageObj);
publishToChatIndexStream(
joining.userId,
ChatIndexEvent.Message,
messageObj,
);
publishMainStream(joining.userId, "messagingMessage", messageObj);
}
}

View File

@ -5,18 +5,18 @@ import type { UserList } from "@/models/entities/user-list.js";
import type { UserGroup } from "@/models/entities/user-group.js";
import { config } from "@/config.js";
// import type { Antenna } from "@/models/entities/antenna.js";
import type { Channel } from "@/models/entities/channel.js";
// import type { Channel } from "@/models/entities/channel.js";
import type {
StreamChannels,
AdminStreamTypes,
// AdminStreamTypes,
// AntennaStreamTypes,
BroadcastTypes,
ChannelStreamTypes,
// BroadcastTypes,
// ChannelStreamTypes,
DriveStreamTypes,
GroupMessagingStreamTypes,
InternalStreamTypes,
MainStreamTypes,
MessagingIndexStreamTypes,
// MessagingIndexStreamTypes,
// MessagingStreamTypes,
NoteStreamTypes,
UserListStreamTypes,
@ -64,16 +64,17 @@ class Publisher {
);
};
public publishBroadcastStream = <K extends keyof BroadcastTypes>(
type: K,
value?: BroadcastTypes[K],
): void => {
this.publish(
"broadcast",
type,
typeof value === "undefined" ? null : value,
);
};
/* ported to backend-rs */
// public publishBroadcastStream = <K extends keyof BroadcastTypes>(
// type: K,
// value?: BroadcastTypes[K],
// ): void => {
// this.publish(
// "broadcast",
// type,
// typeof value === "undefined" ? null : value,
// );
// };
public publishMainStream = <K extends keyof MainStreamTypes>(
userId: User["id"],
@ -110,17 +111,18 @@ class Publisher {
});
};
public publishChannelStream = <K extends keyof ChannelStreamTypes>(
channelId: Channel["id"],
type: K,
value?: ChannelStreamTypes[K],
): void => {
this.publish(
`channelStream:${channelId}`,
type,
typeof value === "undefined" ? null : value,
);
};
/* ported to backend-rs */
// public publishChannelStream = <K extends keyof ChannelStreamTypes>(
// channelId: Channel["id"],
// type: K,
// value?: ChannelStreamTypes[K],
// ): void => {
// this.publish(
// `channelStream:${channelId}`,
// type,
// typeof value === "undefined" ? null : value,
// );
// };
public publishUserListStream = <K extends keyof UserListStreamTypes>(
listId: UserList["id"],
@ -175,35 +177,37 @@ class Publisher {
);
};
public publishMessagingIndexStream = <
K extends keyof MessagingIndexStreamTypes,
>(
userId: User["id"],
type: K,
value?: MessagingIndexStreamTypes[K],
): void => {
this.publish(
`messagingIndexStream:${userId}`,
type,
typeof value === "undefined" ? null : value,
);
};
/* ported to backend-rs */
// public publishMessagingIndexStream = <
// K extends keyof MessagingIndexStreamTypes,
// >(
// userId: User["id"],
// type: K,
// value?: MessagingIndexStreamTypes[K],
// ): void => {
// this.publish(
// `messagingIndexStream:${userId}`,
// type,
// typeof value === "undefined" ? null : value,
// );
// };
public publishNotesStream = (note: Note): void => {
this.publish("notesStream", null, note);
};
public publishAdminStream = <K extends keyof AdminStreamTypes>(
userId: User["id"],
type: K,
value?: AdminStreamTypes[K],
): void => {
this.publish(
`adminStream:${userId}`,
type,
typeof value === "undefined" ? null : value,
);
};
/* ported to backend-rs */
// public publishAdminStream = <K extends keyof AdminStreamTypes>(
// userId: User["id"],
// type: K,
// value?: AdminStreamTypes[K],
// ): void => {
// this.publish(
// `adminStream:${userId}`,
// type,
// typeof value === "undefined" ? null : value,
// );
// };
}
const publisher = new Publisher();
@ -212,17 +216,16 @@ export default publisher;
export const publishInternalEvent = publisher.publishInternalEvent;
export const publishUserEvent = publisher.publishUserEvent;
export const publishBroadcastStream = publisher.publishBroadcastStream;
// export const publishBroadcastStream = publisher.publishBroadcastStream;
export const publishMainStream = publisher.publishMainStream;
export const publishDriveStream = publisher.publishDriveStream;
export const publishNoteStream = publisher.publishNoteStream;
export const publishNotesStream = publisher.publishNotesStream;
export const publishChannelStream = publisher.publishChannelStream;
// export const publishChannelStream = publisher.publishChannelStream;
export const publishUserListStream = publisher.publishUserListStream;
// export const publishAntennaStream = publisher.publishAntennaStream;
// export const publishMessagingStream = publisher.publishMessagingStream;
export const publishGroupMessagingStream =
publisher.publishGroupMessagingStream;
export const publishMessagingIndexStream =
publisher.publishMessagingIndexStream;
export const publishAdminStream = publisher.publishAdminStream;
// export const publishMessagingIndexStream = publisher.publishMessagingIndexStream;
// export const publishAdminStream = publisher.publishAdminStream;

View File

@ -113,6 +113,7 @@
:detailed="true"
:detailed-view="detailedView"
:parent-id="appearNote.id"
:is-long-judger="isLongJudger"
@push="(e) => router.push(notePage(e))"
@focusfooter="footerEl!.focus()"
@expanded="(e) => setPostExpanded(e)"
@ -325,6 +326,7 @@ const props = defineProps<{
collapsedReply?: boolean;
hideFooter?: boolean;
hideEmojiViewer?: boolean;
isLongJudger?: (note: entities.Note) => boolean;
}>();
const inChannel = inject("inChannel", null);

View File

@ -60,12 +60,14 @@
class="content"
:note="removeReplyTo(notification.note.renote)"
:hide-emoji-viewer="true"
:is-long-judger="isLongJudger"
/>
<XNote
v-else
class="content"
:note="removeReplyTo(notification.note)"
:hide-emoji-viewer="true"
:is-long-judger="isLongJudger"
/>
</div>
</template>
@ -120,6 +122,15 @@ const userleft = ref(props.notification.users.length - users.value.length);
let readObserver: IntersectionObserver | undefined;
let connection: Connection<Channels["main"]> | null = null;
function isLongJudger(note: entities.Note) {
return (
note.text != null &&
(note.text.split("\n").length > 5 ||
note.text.length > 300 ||
note.files.length > 4)
);
}
function getText() {
let res = "";
switch (props.notification.type) {

View File

@ -196,13 +196,26 @@ import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
import icon from "@/scripts/icon";
const props = defineProps<{
note: entities.Note;
parentId?: string;
conversation?: entities.Note[];
detailed?: boolean;
detailedView?: boolean;
}>();
const props = withDefaults(
defineProps<{
note: entities.Note;
parentId?: string;
conversation?: entities.Note[];
detailed?: boolean;
detailedView?: boolean;
isLongJudger?: (note: entities.Note) => boolean;
}>(),
{
isLongJudger: (note: entities.Note) => {
return (
note.text != null &&
(note.text.split("\n").length > 10 ||
note.text.length > 800 ||
note.files.length > 4)
);
},
},
);
const emit = defineEmits<{
(ev: "push", v): void;
@ -216,10 +229,7 @@ const showMoreButton = ref<HTMLElement>();
const isLong =
!props.detailedView &&
props.note.cw == null &&
((props.note.text != null &&
(props.note.text.split("\n").length > 10 ||
props.note.text.length > 800)) ||
props.note.files.length > 4);
props.isLongJudger(props.note);
const collapsed = ref(props.note.cw == null && isLong);
const urls = props.note.text
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)

View File

@ -57,7 +57,8 @@ if (!remoteAccountId.result) {
.then((response) => response.json())
.then((data) => {
const subscribeUri = data.links.find(
(link: { rel: string; }) => link.rel === "http://ostatus.org/schema/1.0/subscribe",
(link: { rel: string }) =>
link.rel === "http://ostatus.org/schema/1.0/subscribe",
).template;
window.location.href = subscribeUri.replace(
"{uri}",

View File

@ -255,6 +255,27 @@ export function getUserMenu(user, router: Router = mainRouter) {
router.push(`/user-info/${user.id}`);
},
},
{
icon: `${icon("ph-share")}`,
text: i18n.ts.share,
type: "parent",
children: [
{
icon: "ph-qr-code ph-bold ph-lg",
text: i18n.ts.getQrCode,
action: () => {
os.displayQrCode(`https://${host}/follow-me?acct=${user.username}`);
},
},
{
icon: `${icon("ph-hand-waving")}`,
text: i18n.ts.copyRemoteFollowUrl,
action: () => {
copyToClipboard(`https://${host}/follow-me?acct=${user.username}`);
},
},
],
},
{
icon: `${icon("ph-newspaper")}`,
text: i18n.ts._feeds.copyFeed,
@ -281,13 +302,6 @@ export function getUserMenu(user, router: Router = mainRouter) {
copyToClipboard(`https://${host}/@${user.username}.json`);
},
},
{
icon: `${icon("ph-hand-waving")}`,
text: i18n.ts.remoteFollowUrl,
action: () => {
copyToClipboard(`https://${host}/follow-me?acct=${user.username}`);
},
},
],
},
{
@ -306,13 +320,6 @@ export function getUserMenu(user, router: Router = mainRouter) {
},
}
: undefined,
{
icon: "ph-qr-code ph-bold ph-lg",
text: i18n.ts.getQrCode,
action: () => {
os.displayQrCode(`https://${host}/follow-me?acct=${user.username}`);
},
},
isSignedIn(me) && me.id !== user.id
? {
type: "link",