Removed Mastodon API and messaging

This commit is contained in:
Natty 2023-04-20 20:17:44 +02:00
parent c398acc9e5
commit 12f0bd4e2d
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
61 changed files with 330 additions and 4837 deletions

View File

@ -36,8 +36,8 @@
"chokidar": "^3.3.1"
},
"dependencies": {
"@bull-board/api": "^4.10.2",
"@bull-board/ui": "^4.10.2",
"@bull-board/api": "^4.12.2",
"@bull-board/ui": "^4.12.2",
"@tensorflow/tfjs": "^3.21.0",
"calckey-js": "^0.0.20",
"js-yaml": "4.1.0",

View File

@ -79,7 +79,6 @@
"koa-send": "5.0.1",
"koa-slow": "2.1.0",
"koa-views": "7.0.2",
"@cutls/megalodon": "5.1.15",
"mfm-js": "0.23.2",
"mime-types": "2.1.35",
"multer": "1.4.4-lts.1",

View File

@ -34,7 +34,6 @@ import { Hashtag } from "@/models/entities/hashtag.js";
import { NoteFavorite } from "@/models/entities/note-favorite.js";
import { AbuseUserReport } from "@/models/entities/abuse-user-report.js";
import { RegistrationTicket } from "@/models/entities/registration-tickets.js";
import { MessagingMessage } from "@/models/entities/messaging-message.js";
import { Signin } from "@/models/entities/signin.js";
import { AuthSession } from "@/models/entities/auth-session.js";
import { FollowRequest } from "@/models/entities/follow-request.js";
@ -157,7 +156,6 @@ export const entities = [
SwSubscription,
AbuseUserReport,
RegistrationTicket,
MessagingMessage,
Signin,
ModerationLog,
Clip,

View File

@ -9,8 +9,6 @@ export const kinds = [
"write:favorites",
"read:following",
"write:following",
"read:messaging",
"write:messaging",
"read:mutes",
"write:mutes",
"write:notes",

View File

@ -10,7 +10,6 @@ import {
import { packedNoteSchema } from "@/models/schema/note.js";
import { packedUserListSchema } from "@/models/schema/user-list.js";
import { packedAppSchema } from "@/models/schema/app.js";
import { packedMessagingMessageSchema } from "@/models/schema/messaging-message.js";
import { packedNotificationSchema } from "@/models/schema/notification.js";
import { packedDriveFileSchema } from "@/models/schema/drive-file.js";
import { packedDriveFolderSchema } from "@/models/schema/drive-folder.js";
@ -42,7 +41,6 @@ export const refs = {
UserList: packedUserListSchema,
UserGroup: packedUserGroupSchema,
App: packedAppSchema,
MessagingMessage: packedMessagingMessageSchema,
Note: packedNoteSchema,
NoteReaction: packedNoteReactionSchema,
NoteFavorite: packedNoteFavoriteSchema,

View File

@ -1,96 +0,0 @@
import {
PrimaryColumn,
Entity,
Index,
JoinColumn,
Column,
ManyToOne,
} from "typeorm";
import { User } from "./user.js";
import { DriveFile } from "./drive-file.js";
import { id } from "../id.js";
import { UserGroup } from "./user-group.js";
@Entity()
export class MessagingMessage {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the MessagingMessage.',
})
public createdAt: Date;
@Index()
@Column({
...id(),
comment: 'The sender user ID.',
})
public userId: User["id"];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: User | null;
@Index()
@Column({
...id(), nullable: true,
comment: 'The recipient user ID.',
})
public recipientId: User["id"] | null;
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public recipient: User | null;
@Index()
@Column({
...id(), nullable: true,
comment: 'The recipient group ID.',
})
public groupId: UserGroup["id"] | null;
@ManyToOne(type => UserGroup, {
onDelete: 'CASCADE',
})
@JoinColumn()
public group: UserGroup | null;
@Column('varchar', {
length: 4096, nullable: true,
})
public text: string | null;
@Column('boolean', {
default: false,
})
public isRead: boolean;
@Column('varchar', {
length: 512, nullable: true,
})
public uri: string | null;
@Column({
...id(),
array: true, default: '{}',
})
public reads: User["id"][];
@Column({
...id(),
nullable: true,
})
public fileId: DriveFile["id"] | null;
@ManyToOne(type => DriveFile, {
onDelete: 'CASCADE',
})
@JoinColumn()
public file: DriveFile | null;
}

View File

@ -19,7 +19,6 @@ import { DriveFolderRepository } from "./repositories/drive-folder.js";
import { AccessToken } from "./entities/access-token.js";
import { UserNotePining } from "./entities/user-note-pining.js";
import { SigninRepository } from "./repositories/signin.js";
import { MessagingMessageRepository } from "./repositories/messaging-message.js";
import { UserListRepository } from "./repositories/user-list.js";
import { UserListJoining } from "./entities/user-list-joining.js";
import { UserGroupRepository } from "./repositories/user-group.js";
@ -110,7 +109,6 @@ export const RegistrationTickets = db.getRepository(RegistrationTicket);
export const AuthSessions = AuthSessionRepository;
export const AccessTokens = db.getRepository(AccessToken);
export const Signins = SigninRepository;
export const MessagingMessages = MessagingMessageRepository;
export const Pages = PageRepository;
export const PageLikes = PageLikeRepository;
export const GalleryPosts = GalleryPostRepository;

View File

@ -1,48 +0,0 @@
import { db } from "@/db/postgre.js";
import { MessagingMessage } from "@/models/entities/messaging-message.js";
import { Users, DriveFiles, UserGroups } from "../index.js";
import type { Packed } from "@/misc/schema.js";
import type { User } from "@/models/entities/user.js";
export const MessagingMessageRepository = db
.getRepository(MessagingMessage)
.extend({
async pack(
src: MessagingMessage["id"] | MessagingMessage,
me?: { id: User["id"] } | null | undefined,
options?: {
populateRecipient?: boolean;
populateGroup?: boolean;
},
): Promise<Packed<"MessagingMessage">> {
const opts = options || {
populateRecipient: true,
populateGroup: true,
};
const message =
typeof src === "object" ? src : await this.findOneByOrFail({ id: src });
return {
id: message.id,
createdAt: message.createdAt.toISOString(),
text: message.text,
userId: message.userId,
user: await Users.pack(message.user || message.userId, me),
recipientId: message.recipientId,
recipient:
message.recipientId && opts.populateRecipient
? await Users.pack(message.recipient || message.recipientId, me)
: undefined,
groupId: message.groupId,
group:
message.groupId && opts.populateGroup
? await UserGroups.pack(message.group || message.groupId)
: undefined,
fileId: message.fileId,
file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
isRead: message.isRead,
reads: message.reads,
};
},
});

View File

@ -26,7 +26,6 @@ import {
Followings,
FollowRequests,
Instances,
MessagingMessages,
Mutings,
Notes,
NoteUnreads,
@ -174,42 +173,6 @@ export const UserRepository = db.getRepository(User).extend({
});
},
async getHasUnreadMessagingMessage(userId: User["id"]): Promise<boolean> {
const mute = await Mutings.findBy({
muterId: userId,
});
const joinings = await UserGroupJoinings.findBy({ userId: userId });
const groupQs = Promise.all(
joinings.map((j) =>
MessagingMessages.createQueryBuilder("message")
.where("message.groupId = :groupId", { groupId: j.userGroupId })
.andWhere("message.userId != :userId", { userId: userId })
.andWhere("NOT (:userId = ANY(message.reads))", { userId: userId })
.andWhere("message.createdAt > :joinedAt", { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
.getOne()
.then((x) => x != null),
),
);
const [withUser, withGroups] = await Promise.all([
MessagingMessages.count({
where: {
recipientId: userId,
isRead: false,
...(mute.length > 0
? { userId: Not(In(mute.map((x) => x.muteeId))) }
: {}),
},
take: 1,
}).then((count) => count > 0),
groupQs,
]);
return withUser || withGroups.some((x) => x);
},
async getHasUnreadAnnouncement(userId: User["id"]): Promise<boolean> {
const reads = await AnnouncementReads.findBy({
userId: userId,
@ -540,9 +503,6 @@ export const UserRepository = db.getRepository(User).extend({
hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
hasUnreadChannel: this.getHasUnreadChannel(user.id),
hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(
user.id,
),
hasUnreadNotification: this.getHasUnreadNotification(user.id),
hasPendingReceivedFollowRequest:
this.getHasPendingReceivedFollowRequest(user.id),

View File

@ -1,87 +0,0 @@
export const packedMessagingMessageSchema = {
type: "object",
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id",
example: "xxxxxxxxxx",
},
createdAt: {
type: "string",
optional: false,
nullable: false,
format: "date-time",
},
userId: {
type: "string",
optional: false,
nullable: false,
format: "id",
},
user: {
type: "object",
ref: "UserLite",
optional: true,
nullable: false,
},
text: {
type: "string",
optional: false,
nullable: true,
},
fileId: {
type: "string",
optional: true,
nullable: true,
format: "id",
},
file: {
type: "object",
optional: true,
nullable: true,
ref: "DriveFile",
},
recipientId: {
type: "string",
optional: false,
nullable: true,
format: "id",
},
recipient: {
type: "object",
optional: true,
nullable: true,
ref: "UserLite",
},
groupId: {
type: "string",
optional: false,
nullable: true,
format: "id",
},
group: {
type: "object",
optional: true,
nullable: true,
ref: "UserGroup",
},
isRead: {
type: "boolean",
optional: true,
nullable: false,
},
reads: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false,
format: "id",
},
},
},
} as const;

View File

@ -424,11 +424,6 @@ export const packedMeDetailedOnlySchema = {
nullable: false,
optional: false,
},
hasUnreadMessagingMessage: {
type: "boolean",
nullable: false,
optional: false,
},
hasUnreadNotification: {
type: "boolean",
nullable: false,

View File

@ -5,14 +5,11 @@ import type {
CacheableRemoteUser,
CacheableUser,
} from "@/models/entities/user.js";
import { User, IRemoteUser } from "@/models/entities/user.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import {
Notes,
Users,
UserPublickeys,
MessagingMessages,
} from "@/models/index.js";
import { Cache } from "@/misc/cache.js";
import { uriPersonCache, userByIdCache } from "@/services/user-cache.js";
@ -88,24 +85,6 @@ export default class DbResolver {
}
}
public async getMessageFromApId(
value: string | IObject,
): Promise<MessagingMessage | null> {
const parsed = parseUri(value);
if (parsed.local) {
if (parsed.type !== "notes") return null;
return await MessagingMessages.findOneBy({
id: parsed.id,
});
} else {
return await MessagingMessages.findOneBy({
uri: parsed.uri,
});
}
}
/**
* AP Person => Misskey User in DB
*/

View File

@ -3,7 +3,6 @@ import deleteNode from "@/services/note/delete.js";
import { apLogger } from "../../logger.js";
import DbResolver from "../../db-resolver.js";
import { getApLock } from "@/misc/app-lock.js";
import { deleteMessage } from "@/services/messages/delete.js";
const logger = apLogger;
@ -20,16 +19,7 @@ export default async function (
const note = await dbResolver.getNoteFromApId(uri);
if (note == null) {
const message = await dbResolver.getMessageFromApId(uri);
if (message == null) return "message not found";
if (message.userId !== actor.id) {
return "The user trying to delete the post is not the post author";
}
await deleteMessage(message);
return "ok: message deleted";
return "ok: note is null";
}
if (note.userId !== actor.id) {

View File

@ -25,7 +25,6 @@ import Resolver from "../resolver.js";
import create from "./create/index.js";
import performDeleteActivity from "./delete/index.js";
import performUpdateActivity from "./update/index.js";
import { performReadActivity } from "./read.js";
import follow from "./follow.js";
import undo from "./undo/index.js";
import like from "./like.js";
@ -81,8 +80,6 @@ async function performOneActivity(
await performDeleteActivity(actor, activity);
} else if (isUpdate(activity)) {
await performUpdateActivity(actor, activity);
} else if (isRead(activity)) {
await performReadActivity(actor, activity);
} else if (isFollow(activity)) {
await follow(actor, activity);
} else if (isAccept(activity)) {

View File

@ -1,33 +0,0 @@
import type { CacheableRemoteUser } from "@/models/entities/user.js";
import type { IRead } from "../type.js";
import { getApId } from "../type.js";
import { isSelfHost, extractDbHost } from "@/misc/convert-host.js";
import { MessagingMessages } from "@/models/index.js";
import { readUserMessagingMessage } from "../../../server/api/common/read-messaging-message.js";
export const performReadActivity = async (
actor: CacheableRemoteUser,
activity: IRead,
): Promise<string> => {
const id = await getApId(activity.object);
if (!isSelfHost(extractDbHost(id))) {
return `skip: Read to foreign host (${id})`;
}
const messageId = id.split("/").pop();
const message = await MessagingMessages.findOneBy({ id: messageId });
if (message == null) {
return "skip: message not found";
}
if (actor.id !== message.recipientId) {
return "skip: actor is not a message recipient";
}
await readUserMessagingMessage(message.recipientId!, message.userId, [
message.id,
]);
return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`;
};

View File

@ -15,7 +15,7 @@ import { apLogger } from "../logger.js";
import type { DriveFile } from "@/models/entities/drive-file.js";
import { deliverQuestionUpdate } from "@/services/note/polls/update.js";
import { extractDbHost, toPuny } from "@/misc/convert-host.js";
import { Emojis, Polls, MessagingMessages } from "@/models/index.js";
import { Emojis, Polls } from "@/models/index.js";
import type { Note } from "@/models/entities/note.js";
import type { IObject, IPost } from "../type.js";
import {
@ -30,7 +30,6 @@ import type { Emoji } from "@/models/entities/emoji.js";
import { genId } from "@/misc/gen-id.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { getApLock } from "@/misc/app-lock.js";
import { createMessage } from "@/services/messages/create.js";
import { parseAudience } from "../audience.js";
import { extractApMentions } from "./mention.js";
import DbResolver from "../db-resolver.js";
@ -152,6 +151,10 @@ export async function createNote(
let isTalk = note._misskey_talk && visibility === "specified";
if (isTalk) {
return null;
}
const apMentions = await extractApMentions(note.tag);
const apHashtags = await extractApHashtags(note.tag);
@ -190,17 +193,6 @@ export async function createNote(
}
})
.catch(async (e) => {
// トークだったらinReplyToのエラーは無視
const uri = getApId(note.inReplyTo);
if (uri.startsWith(`${config.url}/`)) {
const id = uri.split("/").pop();
const talk = await MessagingMessages.findOneBy({ id });
if (talk) {
isTalk = true;
return null;
}
}
logger.warn(
`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`,
);
@ -323,20 +315,6 @@ export async function createNote(
() => undefined,
);
if (isTalk) {
for (const recipient of visibleUsers) {
await createMessage(
actor,
recipient,
undefined,
text || undefined,
files && files.length > 0 ? files[0] : null,
object.id,
);
return null;
}
}
return await post(
actor,
{

View File

@ -1,12 +0,0 @@
import config from "@/config/index.js";
import type { User } from "@/models/entities/user.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
export const renderReadActivity = (
user: { id: User["id"] },
message: MessagingMessage,
) => ({
type: "Read",
actor: `${config.url}/users/${user.id}`,
object: message.uri,
});

View File

@ -22,13 +22,14 @@ import Featured from "./activitypub/featured.js";
import Following from "./activitypub/following.js";
import Followers from "./activitypub/followers.js";
import Outbox, { packActivity } from "./activitypub/outbox.js";
import {IActivity} from "@/remote/activitypub/type";
// Init router
const router = new Router();
//#region Routing
function inbox(ctx: Router.RouterContext) {
async function inbox(ctx: Router.RouterContext) {
let signature;
try {
@ -38,7 +39,7 @@ function inbox(ctx: Router.RouterContext) {
return;
}
processInbox(ctx.request.body, signature);
processInbox(ctx.request.body as IActivity, signature);
ctx.status = 202;
}

View File

@ -1,179 +0,0 @@
import {
publishMainStream,
publishGroupMessagingStream,
} from "@/services/stream.js";
import { publishMessagingStream } from "@/services/stream.js";
import { publishMessagingIndexStream } from "@/services/stream.js";
import { pushNotification } from "@/services/push-notification.js";
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";
import { In } from "typeorm";
import { IdentifiableError } from "@/misc/identifiable-error.js";
import type { UserGroup } from "@/models/entities/user-group.js";
import { toArray } from "@/prelude/array.js";
import { renderReadActivity } from "@/remote/activitypub/renderer/read.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import { deliver } from "@/queue/index.js";
import orderedCollection from "@/remote/activitypub/renderer/ordered-collection.js";
/**
* Mark messages as read
*/
export async function readUserMessagingMessage(
userId: User["id"],
otherpartyId: User["id"],
messageIds: MessagingMessage["id"][],
) {
if (messageIds.length === 0) return;
const messages = await MessagingMessages.findBy({
id: In(messageIds),
});
for (const message of messages) {
if (message.recipientId !== userId) {
throw new IdentifiableError(
"e140a4bf-49ce-4fb6-b67c-b78dadf6b52f",
"Access denied (user).",
);
}
}
// Update documents
await MessagingMessages.update(
{
id: In(messageIds),
userId: otherpartyId,
recipientId: userId,
isRead: false,
},
{
isRead: true,
},
);
// Publish event
publishMessagingStream(otherpartyId, userId, "read", messageIds);
publishMessagingIndexStream(userId, "read", messageIds);
if (!(await Users.getHasUnreadMessagingMessage(userId))) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
publishMainStream(userId, "readAllMessagingMessages");
pushNotification(userId, "readAllMessagingMessages", undefined);
} else {
// そのユーザーとのメッセージで未読がなければイベント発行
const count = await MessagingMessages.count({
where: {
userId: otherpartyId,
recipientId: userId,
isRead: false,
},
take: 1,
});
if (!count) {
pushNotification(userId, "readAllMessagingMessagesOfARoom", {
userId: otherpartyId,
});
}
}
}
/**
* Mark messages as read
*/
export async function readGroupMessagingMessage(
userId: User["id"],
groupId: UserGroup["id"],
messageIds: MessagingMessage["id"][],
) {
if (messageIds.length === 0) return;
// check joined
const joining = await UserGroupJoinings.findOneBy({
userId: userId,
userGroupId: groupId,
});
if (joining == null) {
throw new IdentifiableError(
"930a270c-714a-46b2-b776-ad27276dc569",
"Access denied (group).",
);
}
const messages = await MessagingMessages.findBy({
id: In(messageIds),
});
const reads: MessagingMessage["id"][] = [];
for (const message of messages) {
if (message.userId === userId) continue;
if (message.reads.includes(userId)) continue;
// Update document
await MessagingMessages.createQueryBuilder()
.update()
.set({
reads: (() => `array_append("reads", '${joining.userId}')`) as any,
})
.where("id = :id", { id: message.id })
.execute();
reads.push(message.id);
}
// Publish event
publishGroupMessagingStream(groupId, "read", {
ids: reads,
userId: userId,
});
publishMessagingIndexStream(userId, "read", reads);
if (!(await Users.getHasUnreadMessagingMessage(userId))) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
publishMainStream(userId, "readAllMessagingMessages");
pushNotification(userId, "readAllMessagingMessages", undefined);
} else {
// そのグループにおいて未読がなければイベント発行
const unreadExist = await MessagingMessages.createQueryBuilder("message")
.where("message.groupId = :groupId", { groupId: groupId })
.andWhere("message.userId != :userId", { userId: userId })
.andWhere("NOT (:userId = ANY(message.reads))", { userId: userId })
.andWhere("message.createdAt > :joinedAt", {
joinedAt: joining.createdAt,
}) // 自分が加入する前の会話については、未読扱いしない
.getOne()
.then((x) => x != null);
if (!unreadExist) {
pushNotification(userId, "readAllMessagingMessagesOfARoom", { groupId });
}
}
}
export async function deliverReadActivity(
user: { id: User["id"]; host: null },
recipient: IRemoteUser,
messages: MessagingMessage | MessagingMessage[],
) {
messages = toArray(messages).filter((x) => x.uri);
const contents = messages.map((x) => renderReadActivity(user, x));
if (contents.length > 1) {
const collection = orderedCollection(
null,
contents.length,
undefined,
undefined,
contents,
);
deliver(user, renderActivity(collection), recipient.inbox);
} else {
for (const content of contents) {
deliver(user, renderActivity(content), recipient.inbox);
}
}
}

View File

@ -192,7 +192,6 @@ import * as ep___i_notifications from "./endpoints/i/notifications.js";
import * as ep___i_pageLikes from "./endpoints/i/page-likes.js";
import * as ep___i_pages from "./endpoints/i/pages.js";
import * as ep___i_pin from "./endpoints/i/pin.js";
import * as ep___i_readAllMessagingMessages from "./endpoints/i/read-all-messaging-messages.js";
import * as ep___i_readAllUnreadNotes from "./endpoints/i/read-all-unread-notes.js";
import * as ep___i_readAnnouncement from "./endpoints/i/read-announcement.js";
import * as ep___i_regenerateToken from "./endpoints/i/regenerate-token.js";
@ -216,11 +215,6 @@ import * as ep___i_webhooks_show from "./endpoints/i/webhooks/show.js";
import * as ep___i_webhooks_list from "./endpoints/i/webhooks/list.js";
import * as ep___i_webhooks_update from "./endpoints/i/webhooks/update.js";
import * as ep___i_webhooks_delete from "./endpoints/i/webhooks/delete.js";
import * as ep___messaging_history from "./endpoints/messaging/history.js";
import * as ep___messaging_messages from "./endpoints/messaging/messages.js";
import * as ep___messaging_messages_create from "./endpoints/messaging/messages/create.js";
import * as ep___messaging_messages_delete from "./endpoints/messaging/messages/delete.js";
import * as ep___messaging_messages_read from "./endpoints/messaging/messages/read.js";
import * as ep___meta from "./endpoints/meta.js";
import * as ep___miauth_genToken from "./endpoints/miauth/gen-token.js";
import * as ep___mute_create from "./endpoints/mute/create.js";
@ -533,7 +527,6 @@ const eps = [
["i/page-likes", ep___i_pageLikes],
["i/pages", ep___i_pages],
["i/pin", ep___i_pin],
["i/read-all-messaging-messages", ep___i_readAllMessagingMessages],
["i/read-all-unread-notes", ep___i_readAllUnreadNotes],
["i/read-announcement", ep___i_readAnnouncement],
["i/regenerate-token", ep___i_regenerateToken],
@ -557,11 +550,6 @@ const eps = [
["i/webhooks/show", ep___i_webhooks_show],
["i/webhooks/update", ep___i_webhooks_update],
["i/webhooks/delete", ep___i_webhooks_delete],
["messaging/history", ep___messaging_history],
["messaging/messages", ep___messaging_messages],
["messaging/messages/create", ep___messaging_messages_create],
["messaging/messages/delete", ep___messaging_messages_delete],
["messaging/messages/read", ep___messaging_messages_read],
["meta", ep___meta],
["miauth/gen-token", ep___miauth_genToken],
["mute/create", ep___mute_create],

View File

@ -1,48 +0,0 @@
import { publishMainStream } from "@/services/stream.js";
import define from "../../define.js";
import { MessagingMessages, UserGroupJoinings } from "@/models/index.js";
export const meta = {
tags: ["account", "messaging"],
requireCredential: true,
kind: "write:account",
} as const;
export const paramDef = {
type: "object",
properties: {},
required: [],
} as const;
export default define(meta, paramDef, async (ps, user) => {
// Update documents
await MessagingMessages.update(
{
recipientId: user.id,
isRead: false,
},
{
isRead: true,
},
);
const joinings = await UserGroupJoinings.findBy({ userId: user.id });
await Promise.all(
joinings.map((j) =>
MessagingMessages.createQueryBuilder()
.update()
.set({
reads: (() => `array_append("reads", '${user.id}')`) as any,
})
.where("groupId = :groupId", { groupId: j.userGroupId })
.andWhere("userId != :userId", { userId: user.id })
.andWhere("NOT (:userId = ANY(reads))", { userId: user.id })
.execute(),
),
);
publishMainStream(user.id, "readAllMessagingMessages");
});

View File

@ -1,112 +0,0 @@
import { Brackets } from "typeorm";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import {
MessagingMessages,
Mutings,
UserGroupJoinings,
} from "@/models/index.js";
import define from "../../define.js";
export const meta = {
tags: ["messaging"],
requireCredential: true,
kind: "read:messaging",
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "MessagingMessage",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
group: { type: "boolean", default: false },
},
required: [],
} as const;
export default define(meta, paramDef, async (ps, user) => {
const mute = await Mutings.findBy({
muterId: user.id,
});
const groups = ps.group
? await UserGroupJoinings.findBy({
userId: user.id,
}).then((xs) => xs.map((x) => x.userGroupId))
: [];
if (ps.group && groups.length === 0) {
return [];
}
const history: MessagingMessage[] = [];
for (let i = 0; i < ps.limit; i++) {
const found = ps.group
? history.map((m) => m.groupId!)
: history.map((m) => (m.userId === user.id ? m.recipientId! : m.userId!));
const query = MessagingMessages.createQueryBuilder("message").orderBy(
"message.createdAt",
"DESC",
);
if (ps.group) {
query.where("message.groupId IN (:...groups)", { groups: groups });
if (found.length > 0) {
query.andWhere("message.groupId NOT IN (:...found)", { found: found });
}
} else {
query.where(
new Brackets((qb) => {
qb.where("message.userId = :userId", { userId: user.id }).orWhere(
"message.recipientId = :userId",
{ userId: user.id },
);
}),
);
query.andWhere("message.groupId IS NULL");
if (found.length > 0) {
query.andWhere("message.userId NOT IN (:...found)", { found: found });
query.andWhere("message.recipientId NOT IN (:...found)", {
found: found,
});
}
if (mute.length > 0) {
query.andWhere("message.userId NOT IN (:...mute)", {
mute: mute.map((m) => m.muteeId),
});
query.andWhere("message.recipientId NOT IN (:...mute)", {
mute: mute.map((m) => m.muteeId),
});
}
}
const message = await query.getOne();
if (message) {
history.push(message);
} else {
break;
}
}
return await Promise.all(
history.map((h) => MessagingMessages.pack(h.id, user)),
);
});

View File

@ -1,182 +0,0 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { getUser } from "../../common/getters.js";
import {
MessagingMessages,
UserGroups,
UserGroupJoinings,
Users,
} from "@/models/index.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { Brackets } from "typeorm";
import {
readUserMessagingMessage,
readGroupMessagingMessage,
deliverReadActivity,
} from "../../common/read-messaging-message.js";
export const meta = {
tags: ["messaging"],
requireCredential: true,
kind: "read:messaging",
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "MessagingMessage",
},
},
errors: {
noSuchUser: {
message: "No such user.",
code: "NO_SUCH_USER",
id: "11795c64-40ea-4198-b06e-3c873ed9039d",
},
noSuchGroup: {
message: "No such group.",
code: "NO_SUCH_GROUP",
id: "c4d9f88c-9270-4632-b032-6ed8cee36f7f",
},
groupAccessDenied: {
message: "You can not read messages of groups that you have not joined.",
code: "GROUP_ACCESS_DENIED",
id: "a053a8dd-a491-4718-8f87-50775aad9284",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
sinceId: { type: "string", format: "misskey:id" },
untilId: { type: "string", format: "misskey:id" },
markAsRead: { type: "boolean", default: true },
},
anyOf: [
{
properties: {
userId: { type: "string", format: "misskey:id" },
},
required: ["userId"],
},
{
properties: {
groupId: { type: "string", format: "misskey:id" },
},
required: ["groupId"],
},
],
} as const;
export default define(meta, paramDef, async (ps, user) => {
if (ps.userId != null) {
// Fetch recipient (user)
const recipient = await getUser(ps.userId).catch((e) => {
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff")
throw new ApiError(meta.errors.noSuchUser);
throw e;
});
const query = makePaginationQuery(
MessagingMessages.createQueryBuilder("message"),
ps.sinceId,
ps.untilId,
)
.andWhere(
new Brackets((qb) => {
qb.where(
new Brackets((qb) => {
qb.where("message.userId = :meId").andWhere(
"message.recipientId = :recipientId",
);
}),
).orWhere(
new Brackets((qb) => {
qb.where("message.userId = :recipientId").andWhere(
"message.recipientId = :meId",
);
}),
);
}),
)
.setParameter("meId", user.id)
.setParameter("recipientId", recipient.id);
const messages = await query.take(ps.limit).getMany();
// Mark all as read
if (ps.markAsRead) {
readUserMessagingMessage(
user.id,
recipient.id,
messages.filter((m) => m.recipientId === user.id).map((x) => x.id),
);
// リモートユーザーとのメッセージだったら既読配信
if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) {
deliverReadActivity(user, recipient, messages);
}
}
return await Promise.all(
messages.map((message) =>
MessagingMessages.pack(message, user, {
populateRecipient: false,
}),
),
);
} else if (ps.groupId != null) {
// Fetch recipient (group)
const recipientGroup = await UserGroups.findOneBy({ id: ps.groupId });
if (recipientGroup == null) {
throw new ApiError(meta.errors.noSuchGroup);
}
// check joined
const joining = await UserGroupJoinings.findOneBy({
userId: user.id,
userGroupId: recipientGroup.id,
});
if (joining == null) {
throw new ApiError(meta.errors.groupAccessDenied);
}
const query = makePaginationQuery(
MessagingMessages.createQueryBuilder("message"),
ps.sinceId,
ps.untilId,
).andWhere("message.groupId = :groupId", { groupId: recipientGroup.id });
const messages = await query.take(ps.limit).getMany();
// Mark all as read
if (ps.markAsRead) {
readGroupMessagingMessage(
user.id,
recipientGroup.id,
messages.map((x) => x.id),
);
}
return await Promise.all(
messages.map((message) =>
MessagingMessages.pack(message, user, {
populateGroup: false,
}),
),
);
}
});

View File

@ -1,165 +0,0 @@
import define from "../../../define.js";
import { ApiError } from "../../../error.js";
import { getUser } from "../../../common/getters.js";
import {
MessagingMessages,
DriveFiles,
UserGroups,
UserGroupJoinings,
Blockings,
} from "@/models/index.js";
import type { User } from "@/models/entities/user.js";
import type { UserGroup } from "@/models/entities/user-group.js";
import { createMessage } from "@/services/messages/create.js";
export const meta = {
tags: ["messaging"],
requireCredential: true,
kind: "write:messaging",
res: {
type: "object",
optional: false,
nullable: false,
ref: "MessagingMessage",
},
errors: {
recipientIsYourself: {
message: "You can not send a message to yourself.",
code: "RECIPIENT_IS_YOURSELF",
id: "17e2ba79-e22a-4cbc-bf91-d327643f4a7e",
},
noSuchUser: {
message: "No such user.",
code: "NO_SUCH_USER",
id: "11795c64-40ea-4198-b06e-3c873ed9039d",
},
noSuchGroup: {
message: "No such group.",
code: "NO_SUCH_GROUP",
id: "c94e2a5d-06aa-4914-8fa6-6a42e73d6537",
},
groupAccessDenied: {
message: "You can not send messages to groups that you have not joined.",
code: "GROUP_ACCESS_DENIED",
id: "d96b3cca-5ad1-438b-ad8b-02f931308fbd",
},
noSuchFile: {
message: "No such file.",
code: "NO_SUCH_FILE",
id: "4372b8e2-185d-4146-8749-2f68864a3e5f",
},
contentRequired: {
message: "Content required. You need to set text or fileId.",
code: "CONTENT_REQUIRED",
id: "25587321-b0e6-449c-9239-f8925092942c",
},
youHaveBeenBlocked: {
message:
"You cannot send a message because you have been blocked by this user.",
code: "YOU_HAVE_BEEN_BLOCKED",
id: "c15a5199-7422-4968-941a-2a462c478f7d",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
text: { type: "string", nullable: true, maxLength: 3000 },
fileId: { type: "string", format: "misskey:id" },
},
anyOf: [
{
properties: {
userId: { type: "string", format: "misskey:id" },
},
required: ["userId"],
},
{
properties: {
groupId: { type: "string", format: "misskey:id" },
},
required: ["groupId"],
},
],
} as const;
export default define(meta, paramDef, async (ps, user) => {
let recipientUser: User | null;
let recipientGroup: UserGroup | null;
if (ps.userId != null) {
// Myself
if (ps.userId === user.id) {
throw new ApiError(meta.errors.recipientIsYourself);
}
// Fetch recipient (user)
recipientUser = await getUser(ps.userId).catch((e) => {
if (e.id === "15348ddd-432d-49c2-8a5a-8069753becff")
throw new ApiError(meta.errors.noSuchUser);
throw e;
});
// Check blocking
const block = await Blockings.findOneBy({
blockerId: recipientUser.id,
blockeeId: user.id,
});
if (block) {
throw new ApiError(meta.errors.youHaveBeenBlocked);
}
} else if (ps.groupId != null) {
// Fetch recipient (group)
recipientGroup = await UserGroups.findOneBy({ id: ps.groupId! });
if (recipientGroup == null) {
throw new ApiError(meta.errors.noSuchGroup);
}
// check joined
const joining = await UserGroupJoinings.findOneBy({
userId: user.id,
userGroupId: recipientGroup.id,
});
if (joining == null) {
throw new ApiError(meta.errors.groupAccessDenied);
}
}
let file = null;
if (ps.fileId != null) {
file = await DriveFiles.findOneBy({
id: ps.fileId,
userId: user.id,
});
if (file == null) {
throw new ApiError(meta.errors.noSuchFile);
}
}
// テキストが無いかつ添付ファイルも無かったらエラー
if (ps.text == null && file == null) {
throw new ApiError(meta.errors.contentRequired);
}
return await createMessage(
user,
recipientUser,
recipientGroup,
ps.text,
file,
);
});

View File

@ -1,48 +0,0 @@
import define from "../../../define.js";
import { ApiError } from "../../../error.js";
import { MessagingMessages } from "@/models/index.js";
import { deleteMessage } from "@/services/messages/delete.js";
import { SECOND, HOUR } from "@/const.js";
export const meta = {
tags: ["messaging"],
requireCredential: true,
kind: "write:messaging",
limit: {
duration: HOUR,
max: 300,
minInterval: SECOND,
},
errors: {
noSuchMessage: {
message: "No such message.",
code: "NO_SUCH_MESSAGE",
id: "54b5b326-7925-42cf-8019-130fda8b56af",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
messageId: { type: "string", format: "misskey:id" },
},
required: ["messageId"],
} as const;
export default define(meta, paramDef, async (ps, user) => {
const message = await MessagingMessages.findOneBy({
id: ps.messageId,
userId: user.id,
});
if (message == null) {
throw new ApiError(meta.errors.noSuchMessage);
}
await deleteMessage(message);
});

View File

@ -1,57 +0,0 @@
import define from "../../../define.js";
import { ApiError } from "../../../error.js";
import { MessagingMessages } from "@/models/index.js";
import {
readUserMessagingMessage,
readGroupMessagingMessage,
} from "../../../common/read-messaging-message.js";
export const meta = {
tags: ["messaging"],
requireCredential: true,
kind: "write:messaging",
errors: {
noSuchMessage: {
message: "No such message.",
code: "NO_SUCH_MESSAGE",
id: "86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
messageId: { type: "string", format: "misskey:id" },
},
required: ["messageId"],
} as const;
export default define(meta, paramDef, async (ps, user) => {
const message = await MessagingMessages.findOneBy({ id: ps.messageId });
if (message == null) {
throw new ApiError(meta.errors.noSuchMessage);
}
if (message.recipientId) {
await readUserMessagingMessage(user.id, message.userId, [message.id]).catch(
(e) => {
if (e.id === "e140a4bf-49ce-4fb6-b67c-b78dadf6b52f")
throw new ApiError(meta.errors.noSuchMessage);
throw e;
},
);
} else if (message.groupId) {
await readGroupMessagingMessage(user.id, message.groupId, [
message.id,
]).catch((e) => {
if (e.id === "930a270c-714a-46b2-b776-ad27276dc569")
throw new ApiError(meta.errors.noSuchMessage);
throw e;
});
}
});

View File

@ -7,7 +7,6 @@ import Router from "@koa/router";
import multer from "@koa/multer";
import bodyParser from "koa-bodyparser";
import cors from "@koa/cors";
import { apiMastodonCompatible } from './mastodon/ApiMastodonCompatibleService.js';
import { Instances, AccessTokens, Users } from "@/models/index.js";
import config from "@/config/index.js";
import endpoints from "./endpoints.js";
@ -58,8 +57,6 @@ const upload = multer({
// Init router
const router = new Router();
apiMastodonCompatible(router);
/**
* Register endpoint handlers
*/

View File

@ -1,58 +0,0 @@
import Router from "@koa/router";
import megalodon, { MegalodonInterface } from '@cutls/megalodon';
import { apiAuthMastodon } from './endpoints/auth.js';
import { apiAccountMastodon } from './endpoints/account.js';
import { apiStatusMastodon } from './endpoints/status.js';
import { apiFilterMastodon } from './endpoints/filter.js';
import { apiTimelineMastodon } from './endpoints/timeline.js';
import { apiNotificationsMastodon } from './endpoints/notifications.js';
import { apiSearchMastodon } from './endpoints/search.js';
import { getInstance } from './endpoints/meta.js';
export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface {
const accessTokenArr = authorization?.split(' ') ?? [null];
const accessToken = accessTokenArr[accessTokenArr.length - 1];
const generator = (megalodon as any).default
const client = generator('misskey', BASE_URL, accessToken) as MegalodonInterface;
return client
}
export function apiMastodonCompatible(router: Router): void {
apiAuthMastodon(router)
apiAccountMastodon(router)
apiStatusMastodon(router)
apiFilterMastodon(router)
apiTimelineMastodon(router)
apiNotificationsMastodon(router)
apiSearchMastodon(router)
router.get('/v1/custom_emojis', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getInstanceCustomEmojis();
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.get('/v1/instance', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
// displayed without being logged in
try {
const data = await client.getInstance();
ctx.body = getInstance(data.data);
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
}

View File

@ -1,323 +0,0 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon';
import Router from "@koa/router";
import { koaBody } from 'koa-body';
import { getClient } from '../ApiMastodonCompatibleService.js';
import { toLimitToInt } from './timeline.js';
export function apiAccountMastodon(router: Router): void {
router.get('/v1/accounts/verify_credentials', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.verifyAccountCredentials();
const acct = data.data;
acct.url = `${BASE_URL}/@${acct.url}`
acct.note = ''
acct.avatar_static = acct.avatar
acct.header = acct.header || ''
acct.header_static = acct.header || ''
acct.source = {
note: acct.note,
fields: acct.fields,
privacy: 'public',
sensitive: false,
language: ''
}
ctx.body = acct
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.patch('/v1/accounts/update_credentials', async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.updateCredentials((ctx.request as any).body as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/accounts/:id', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getAccount(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/accounts/:id/statuses', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getAccountStatuses(ctx.params.id, toLimitToInt(ctx.query as any));
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/accounts/:id/followers', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getAccountFollowers(ctx.params.id, ctx.query as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/accounts/:id/following', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getAccountFollowing(ctx.params.id, ctx.query as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/accounts/:id/lists', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getAccountLists(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/accounts/:id/follow', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.followAccount(ctx.params.id);
const acct = data.data;
acct.following = true;
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/accounts/:id/unfollow', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unfollowAccount(ctx.params.id);
const acct = data.data;
acct.following = false;
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/accounts/:id/block', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.blockAccount(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/accounts/:id/unblock', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unblockAccount(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.muteAccount(ctx.params.id, (ctx.request as any).body as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/accounts/:id/unmute', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unmuteAccount(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get('/v1/accounts/relationships', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const idsRaw = (ctx.query as any)['id[]']
const ids = typeof idsRaw === 'string' ? [idsRaw] : idsRaw
const data = await client.getRelationships(ids) as any;
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get('/v1/bookmarks', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getBookmarks(ctx.query as any) as any;
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get('/v1/favourites', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getFavourites(ctx.query as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get('/v1/mutes', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getMutes(ctx.query as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get('/v1/blocks', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getBlocks(ctx.query as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.get('/v1/follow_ctxs', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getFollowRequests((ctx.query as any || { limit: 20 }).limit);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/follow_ctxs/:id/authorize', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.acceptFollowRequest(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/follow_ctxs/:id/reject', async (ctx, next) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.rejectFollowRequest(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status =(401);
ctx.body = e.response.data;
}
});
}

View File

@ -1,81 +0,0 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon';
import Router from "@koa/router";
import { koaBody } from 'koa-body';
import { getClient } from '../ApiMastodonCompatibleService.js';
const readScope = [
'read:account',
'read:drive',
'read:blocks',
'read:favorites',
'read:following',
'read:messaging',
'read:mutes',
'read:notifications',
'read:reactions',
'read:pages',
'read:page-likes',
'read:user-groups',
'read:channels',
'read:gallery',
'read:gallery-likes'
]
const writeScope = [
'write:account',
'write:drive',
'write:blocks',
'write:favorites',
'write:following',
'write:messaging',
'write:mutes',
'write:notes',
'write:notifications',
'write:reactions',
'write:votes',
'write:pages',
'write:page-likes',
'write:user-groups',
'write:channels',
'write:gallery',
'write:gallery-likes'
]
export function apiAuthMastodon(router: Router): void {
router.post('/v1/apps', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
let scope = body.scopes
console.log(body)
if (typeof scope === 'string') scope = scope.split(' ')
const pushScope = new Set<string>()
for (const s of scope) {
if (s.match(/^read/)) for (const r of readScope) pushScope.add(r)
if (s.match(/^write/)) for (const r of writeScope) pushScope.add(r)
}
const scopeArr = Array.from(pushScope)
let red = body.redirect_uris
if (red === 'urn:ietf:wg:oauth:2.0:oob') {
red = 'https://thedesk.top/hello.html'
}
const appData = await client.registerApp(body.client_name, { scopes: scopeArr, redirect_uris: red, website: body.website });
ctx.body = {
id: appData.id,
name: appData.name,
website: appData.website,
redirect_uri: red,
client_id: Buffer.from(appData.url || '').toString('base64'),
client_secret: appData.clientSecret,
}
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
}

View File

@ -1,83 +0,0 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon';
import Router from "@koa/router";
import { koaBody } from 'koa-body';
import { getClient } from '../ApiMastodonCompatibleService.js';
export function apiFilterMastodon(router: Router): void {
router.get('/v1/filters', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const data = await client.getFilters();
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.get('/v1/filters/:id', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const data = await client.getFilter(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.post('/v1/filters', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const data = await client.createFilter(body.phrase, body.context, body);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.post('/v1/filters/:id', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const data = await client.updateFilter(ctx.params.id, body.phrase, body.context);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.delete('/v1/filters/:id', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const data = await client.deleteFilter(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
}

View File

@ -1,97 +0,0 @@
import { Entity } from "@cutls/megalodon";
// TODO: add calckey features
export function getInstance(response: Entity.Instance) {
return {
uri: response.uri,
title: response.title || "",
short_description: response.description || "",
description: response.description || "",
email: response.email || "",
version: "3.0.0 compatible (Calckey)",
urls: response.urls,
stats: response.stats,
thumbnail: response.thumbnail || "",
languages: ["en", "de", "ja"],
registrations: response.registrations,
approval_required: !response.registrations,
invites_enabled: response.registrations,
configuration: {
accounts: {
max_featured_tags: 20,
},
statuses: {
max_characters: 3000,
max_media_attachments: 4,
characters_reserved_per_url: response.uri.length,
},
media_attachments: {
supported_mime_types: [
"image/jpeg",
"image/png",
"image/gif",
"image/heic",
"image/heif",
"image/webp",
"image/avif",
"video/webm",
"video/mp4",
"video/quicktime",
"video/ogg",
"audio/wave",
"audio/wav",
"audio/x-wav",
"audio/x-pn-wave",
"audio/vnd.wave",
"audio/ogg",
"audio/vorbis",
"audio/mpeg",
"audio/mp3",
"audio/webm",
"audio/flac",
"audio/aac",
"audio/m4a",
"audio/x-m4a",
"audio/mp4",
"audio/3gpp",
"video/x-ms-asf",
],
image_size_limit: 10485760,
image_matrix_limit: 16777216,
video_size_limit: 41943040,
video_frame_rate_limit: 60,
video_matrix_limit: 2304000,
},
polls: {
max_options: 8,
max_characters_per_option: 50,
min_expiration: 300,
max_expiration: 2629746,
},
},
contact_account: {
id: "1",
username: "admin",
acct: "admin",
display_name: "admin",
locked: true,
bot: true,
discoverable: false,
group: false,
created_at: "1971-01-01T00:00:00.000Z",
note: "",
url: "https://http.cat/404",
avatar: "https://http.cat/404",
avatar_static: "https://http.cat/404",
header: "https://http.cat/404",
header_static: "https://http.cat/404",
followers_count: -1,
following_count: 0,
statuses_count: 0,
last_status_at: "1971-01-01T00:00:00.000Z",
noindex: true,
emojis: [],
fields: [],
},
rules: [],
};
}

View File

@ -1,89 +0,0 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon';
import Router from "@koa/router";
import { koaBody } from 'koa-body';
import { getClient } from '../ApiMastodonCompatibleService.js';
import { toTextWithReaction } from './timeline.js';
function toLimitToInt(q: any) {
if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10)
return q
}
export function apiNotificationsMastodon(router: Router): void {
router.get('/v1/notifications', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const data = await client.getNotifications(toLimitToInt(ctx.query));
const notfs = data.data;
const ret = notfs.map((n) => {
if(n.type !== 'follow' && n.type !== 'follow_request') {
if (n.type === 'reaction') n.type = 'favourite'
n.status = toTextWithReaction(n.status ? [n.status] : [], ctx.hostname)[0]
return n
} else {
return n
}
})
ctx.body = ret;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.get('/v1/notification/:id', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const dataRaw = await client.getNotification(ctx.params.id);
const data = dataRaw.data;
if(data.type !== 'follow' && data.type !== 'follow_request') {
if (data.type === 'reaction') data.type = 'favourite'
ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0]
} else {
ctx.body = data
}
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.post('/v1/notifications/clear', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const data = await client.dismissNotifications();
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
router.post('/v1/notification/:id/dismiss', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const data = await client.dismissNotification(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
}

View File

@ -1,25 +0,0 @@
import megalodon, { MegalodonInterface } from '@cutls/megalodon';
import Router from "@koa/router";
import { koaBody } from 'koa-body';
import { getClient } from '../ApiMastodonCompatibleService.js';
export function apiSearchMastodon(router: Router): void {
router.get('/v1/search', async (ctx) => {
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body;
try {
const query: any = ctx.query
const type = query.type || ''
const data = await client.search(query.q, type, query);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = 401;
ctx.body = e.response.data;
}
});
}

View File

@ -1,403 +0,0 @@
import Router from "@koa/router";
import { koaBody } from 'koa-body';
import megalodon, { MegalodonInterface } from '@cutls/megalodon';
import { getClient } from '../ApiMastodonCompatibleService.js';
import fs from 'fs'
import { pipeline } from 'node:stream';
import { promisify } from 'node:util';
import { createTemp } from '@/misc/create-temp.js';
import { emojiRegex, emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js';
import axios from 'axios';
const pump = promisify(pipeline);
export function apiStatusMastodon(router: Router): void {
router.post('/v1/statuses', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const body: any = ctx.request.body
const text = body.status
const removed = text.replace(/@\S+/g, '').replaceAll(' ', '')
const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed)
const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed)
if (body.in_reply_to_id && isDefaultEmoji || isCustomEmoji) {
const a = await client.createEmojiReaction(body.in_reply_to_id, removed)
ctx.body = a.data
}
if (body.in_reply_to_id && removed === '/unreact') {
try {
const id = body.in_reply_to_id
const post = await client.getStatus(id)
const react = post.data.emoji_reactions.filter((e) => e.me)[0].name
const data = await client.deleteEmojiReaction(id, react);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
}
if (!body.media_ids) body.media_ids = undefined
if (body.media_ids && !body.media_ids.length) body.media_ids = undefined
const data = await client.postStatus(text, body);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/statuses/:id', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.delete<{ Params: { id: string } }>('/v1/statuses/:id', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.deleteStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
interface IReaction {
id: string
createdAt: string
user: MisskeyEntity.User,
type: string
}
router.get<{ Params: { id: string } }>('/v1/statuses/:id/context', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const id = ctx.params.id
const data = await client.getStatusContext(id, ctx.query as any);
const status = await client.getStatus(id);
const reactionsAxios = await axios.get(`${BASE_URL}/api/notes/reactions?noteId=${id}`)
const reactions: IReaction[] = reactionsAxios.data
const text = reactions.map((r) => `${r.type.replace('@.', '')} ${r.user.username}`).join('<br />')
data.data.descendants.unshift(statusModel(status.data.id, status.data.account.id, status.data.emojis, text))
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/statuses/:id/reblogged_by', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getStatusRebloggedBy(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/statuses/:id/favourited_by', async (ctx, reply) => {
ctx.body = []
});
router.post<{ Params: { id: string } }>('/v1/statuses/:id/favourite', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const react = await getFirstReaction(BASE_URL, accessTokens);
try {
const a = await client.createEmojiReaction(ctx.params.id, react) as any;
//const data = await client.favouriteStatus(ctx.params.id) as any;
ctx.body = a.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/statuses/:id/unfavourite', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
const react = await getFirstReaction(BASE_URL, accessTokens);
try {
const data = await client.deleteEmojiReaction(ctx.params.id, react);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/statuses/:id/reblog', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.reblogStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/statuses/:id/unreblog', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unreblogStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/statuses/:id/bookmark', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.bookmarkStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/statuses/:id/unbookmark', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unbookmarkStatus(ctx.params.id) as any;
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/statuses/:id/pin', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.pinStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/statuses/:id/unpin', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.unpinStatus(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post('/v1/media', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const multipartData = await ctx.file;
if (!multipartData) {
ctx.body = { error: 'No image' };
return;
}
const [path] = await createTemp();
await pump(multipartData.buffer, fs.createWriteStream(path));
const image = fs.readFileSync(path);
const data = await client.uploadMedia(image);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post('/v2/media', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const multipartData = await ctx.file;
if (!multipartData) {
ctx.body = { error: 'No image' };
return;
}
const [path] = await createTemp();
await pump(multipartData.buffer, fs.createWriteStream(path));
const image = fs.readFileSync(path);
const data = await client.uploadMedia(image);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/media/:id', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getMedia(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.put<{ Params: { id: string } }>('/v1/media/:id', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.updateMedia(ctx.params.id, ctx.request.body as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/polls/:id', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getPoll(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/polls/:id/votes', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.votePoll(ctx.params.id, (ctx.request.body as any).choices);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
ctx.status = (401);
ctx.body = e.response.data;
}
});
}
async function getFirstReaction(BASE_URL: string, accessTokens: string | undefined) {
const accessTokenArr = accessTokens?.split(' ') ?? [null];
const accessToken = accessTokenArr[accessTokenArr.length - 1];
let react = '👍'
try {
const api = await axios.post(`${BASE_URL}/api/i/registry/get-unsecure`, {
scope: ['client', 'base'],
key: 'reactions',
i: accessToken
})
const reactRaw = api.data
react = Array.isArray(reactRaw) ? api.data[0] : '👍'
console.log(api.data)
return react
} catch (e) {
return react
}
}
export function statusModel(id: string | null, acctId: string | null, emojis: MastodonEntity.Emoji[], content: string) {
const now = "1970-01-02T00:00:00.000Z"
return {
id: '9atm5frjhb',
uri: 'https://http.cat/404', // ""
url: 'https://http.cat/404', // "",
account: {
id: '9arzuvv0sw',
username: 'ReactionBot',
acct: 'ReactionBot',
display_name: 'ReactionOfThisPost',
locked: false,
created_at: now,
followers_count: 0,
following_count: 0,
statuses_count: 0,
note: '',
url: 'https://http.cat/404',
avatar: 'https://http.cat/404',
avatar_static: 'https://http.cat/404',
header: 'https://http.cat/404', // ""
header_static: 'https://http.cat/404', // ""
emojis: [],
fields: [],
moved: null,
bot: false,
},
in_reply_to_id: id,
in_reply_to_account_id: acctId,
reblog: null,
content: `<p>${content}</p>`,
plain_content: null,
created_at: now,
emojis: emojis,
replies_count: 0,
reblogs_count: 0,
favourites_count: 0,
favourited: false,
reblogged: false,
muted: false,
sensitive: false,
spoiler_text: '',
visibility: 'public' as const,
media_attachments: [],
mentions: [],
tags: [],
card: null,
poll: null,
application: null,
language: null,
pinned: false,
emoji_reactions: [],
bookmarked: false,
quote: false,
}
}

View File

@ -1,246 +0,0 @@
import Router from "@koa/router";
import { koaBody } from 'koa-body';
import megalodon, { Entity, MegalodonInterface } from '@cutls/megalodon';
import { getClient } from '../ApiMastodonCompatibleService.js'
import { statusModel } from './status.js';
import Autolinker from 'autolinker';
import { ParsedUrlQuery } from "querystring";
export function toLimitToInt(q: ParsedUrlQuery) {
if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10).toString()
return q
}
export function toTextWithReaction(status: Entity.Status[], host: string) {
return status.map((t) => {
if (!t) return statusModel(null, null, [], 'no content')
if (!t.emoji_reactions) return t
if (t.reblog) t.reblog = toTextWithReaction([t.reblog], host)[0]
const reactions = t.emoji_reactions.map((r) => `${r.name.replace('@.', '')} (${r.count}${r.me ? "* " : ''})`);
//t.emojis = getEmoji(t.content, host)
t.content = `<p>${autoLinker(t.content, host)}</p><p>${reactions.join(', ')}</p>`
return t
})
}
export function autoLinker(input: string, host: string) {
return Autolinker.link(input, {
hashtag: 'twitter',
mention: 'twitter',
email: false,
stripPrefix: false,
replaceFn : function (match) {
switch(match.type) {
case 'url':
return true
case 'mention':
console.log("Mention: ", match.getMention());
console.log("Mention Service Name: ", match.getServiceName());
return `<a href="https://${host}/@${encodeURIComponent(match.getMention())}" target="_blank">@${match.getMention()}</a>`;
case 'hashtag':
console.log("Hashtag: ", match.getHashtag());
return `<a href="https://${host}/tags/${encodeURIComponent(match.getHashtag())}" target="_blank">#${match.getHashtag()}</a>`;
}
return false
}
} );
}
export function apiTimelineMastodon(router: Router): void {
router.get('/v1/timelines/public', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const query: any = ctx.query
const data = query.local ? await client.getLocalTimeline(toLimitToInt(query)) : await client.getPublicTimeline(toLimitToInt(query));
ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { hashtag: string } }>('/v1/timelines/tag/:hashtag', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getTagTimeline(ctx.params.hashtag, toLimitToInt(ctx.query));
ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { hashtag: string } }>('/v1/timelines/home', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getHomeTimeline(toLimitToInt(ctx.query));
ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { listId: string } }>('/v1/timelines/list/:listId', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getListTimeline(ctx.params.listId, toLimitToInt(ctx.query));
ctx.body = toTextWithReaction(data.data, ctx.hostname);
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get('/v1/conversations', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getConversationTimeline(toLimitToInt(ctx.query));
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get('/v1/lists', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getLists();
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getList(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post('/v1/lists', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.createList((ctx.query as any).title);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.put<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.updateList(ctx.params.id, ctx.query as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.delete<{ Params: { id: string } }>('/v1/lists/:id', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.deleteList(ctx.params.id);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.get<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.getAccountsInList(ctx.params.id, ctx.query as any);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.post<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.addAccountsToList(ctx.params.id, (ctx.query as any).account_ids);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
router.delete<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (ctx, reply) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try {
const data = await client.deleteAccountsFromList(ctx.params.id, (ctx.query as any).account_ids);
ctx.body = data.data;
} catch (e: any) {
console.error(e)
console.error(e.response.data)
ctx.status = (401);
ctx.body = e.response.data;
}
});
}
function escapeHTML(str: string) {
if (!str) {
return ''
}
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;')
}
function nl2br(str: string) {
if (!str) {
return ''
}
str = str.replace(/\r\n/g, '<br />')
str = str.replace(/(\n|\r)/g, '<br />')
return str
}

View File

@ -8,8 +8,6 @@ import serverStats from "./server-stats.js";
import queueStats from "./queue-stats.js";
import userList from "./user-list.js";
import antenna from "./antenna.js";
import messaging from "./messaging.js";
import messagingIndex from "./messaging-index.js";
import drive from "./drive.js";
import hashtag from "./hashtag.js";
import channel from "./channel.js";
@ -26,8 +24,6 @@ export default {
queueStats,
userList,
antenna,
messaging,
messagingIndex,
drive,
hashtag,
channel,

View File

@ -1,14 +0,0 @@
import Channel from "../channel.js";
export default class extends Channel {
public readonly chName = "messagingIndex";
public static shouldShare = true;
public static requireCredential = true;
public async init(params: any) {
// Subscribe messaging index stream
this.subscriber.on(`messagingIndexStream:${this.user!.id}`, (data) => {
this.send(data);
});
}
}

View File

@ -1,130 +0,0 @@
import {
readUserMessagingMessage,
readGroupMessagingMessage,
deliverReadActivity,
} from "../../common/read-messaging-message.js";
import Channel from "../channel.js";
import { UserGroupJoinings, Users, MessagingMessages } from "@/models/index.js";
import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
import type { UserGroup } from "@/models/entities/user-group.js";
import type { StreamMessages } from "../types.js";
export default class extends Channel {
public readonly chName = "messaging";
public static shouldShare = false;
public static requireCredential = true;
private otherpartyId: string | null;
private otherparty: User | null;
private groupId: string | null;
private subCh:
| `messagingStream:${User["id"]}-${User["id"]}`
| `messagingStream:${UserGroup["id"]}`;
private typers: Record<User["id"], Date> = {};
private emitTypersIntervalId: ReturnType<typeof setInterval>;
constructor(id: string, connection: Channel["connection"]) {
super(id, connection);
this.onEvent = this.onEvent.bind(this);
this.onMessage = this.onMessage.bind(this);
this.emitTypers = this.emitTypers.bind(this);
}
public async init(params: any) {
this.otherpartyId = params.otherparty;
this.otherparty = this.otherpartyId
? await Users.findOneByOrFail({ id: this.otherpartyId })
: null;
this.groupId = params.group;
// Check joining
if (this.groupId) {
const joining = await UserGroupJoinings.findOneBy({
userId: this.user!.id,
userGroupId: this.groupId,
});
if (joining == null) {
return;
}
}
this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
this.subCh = this.otherpartyId
? `messagingStream:${this.user!.id}-${this.otherpartyId}`
: `messagingStream:${this.groupId}`;
// Subscribe messaging stream
this.subscriber.on(this.subCh, this.onEvent);
}
private onEvent(
data:
| StreamMessages["messaging"]["payload"]
| StreamMessages["groupMessaging"]["payload"],
) {
if (data.type === "typing") {
const id = data.body;
const begin = this.typers[id] == null;
this.typers[id] = new Date();
if (begin) {
this.emitTypers();
}
} else {
this.send(data);
}
}
public onMessage(type: string, body: any) {
switch (type) {
case "read":
if (this.otherpartyId) {
readUserMessagingMessage(this.user!.id, this.otherpartyId, [body.id]);
// リモートユーザーからのメッセージだったら既読配信
if (
Users.isLocalUser(this.user!) &&
Users.isRemoteUser(this.otherparty!)
) {
MessagingMessages.findOneBy({ id: body.id }).then((message) => {
if (message)
deliverReadActivity(
this.user as ILocalUser,
this.otherparty as IRemoteUser,
message,
);
});
}
} else if (this.groupId) {
readGroupMessagingMessage(this.user!.id, this.groupId, [body.id]);
}
break;
}
}
private async emitTypers() {
const now = new Date();
// Remove not typing users
for (const [userId, date] of Object.entries(this.typers)) {
if (now.getTime() - date.getTime() > 5000)
this.typers[userId] = undefined;
}
const users = await Users.packMany(Object.keys(this.typers), null, {
detail: false,
});
this.send({
type: "typers",
body: users,
});
}
public dispose() {
this.subscriber.off(this.subCh, this.onEvent);
clearInterval(this.emitTypersIntervalId);
}
}

View File

@ -4,7 +4,6 @@ import readNote from "@/services/note/read.js";
import type { User } from "@/models/entities/user.js";
import type { Channel as ChannelModel } from "@/models/entities/channel.js";
import {
Users,
Followings,
Mutings,
UserProfiles,
@ -13,20 +12,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 {
publishChannelStream,
publishGroupMessagingStream,
publishMessagingStream,
} from "@/services/stream.js";
import type { UserGroup } from "@/models/entities/user-group.js";
import type { Packed } from "@/misc/schema.js";
import { readNotification } from "../common/read-notification.js";
import channels from "./channels/index.js";
import type Channel from "./channel.js";
import type { StreamEventEmitter, StreamMessages } from "./types.js";
import { Converter } from "@cutls/megalodon";
import { getClient } from "../mastodon/ApiMastodonCompatibleService.js";
import { toTextWithReaction } from "../mastodon/endpoints/timeline.js";
/**
* Main stream connection
@ -44,27 +35,17 @@ export default class Connection {
private channels: Channel[] = [];
private subscribingNotes: any = {};
private cachedNotes: Packed<"Note">[] = [];
private isMastodonCompatible: boolean = false;
private host: string;
private accessToken: string;
private currentSubscribe: string[][] = [];
constructor(
wsConnection: websocket.connection,
subscriber: EventEmitter,
user: User | null | undefined,
token: AccessToken | null | undefined,
host: string,
accessToken: string,
prepareStream: string | undefined,
) {
console.log("constructor", prepareStream);
this.wsConnection = wsConnection;
this.subscriber = subscriber;
if (user) this.user = user;
if (token) this.token = token;
if (host) this.host = host;
if (accessToken) this.accessToken = accessToken;
this.onWsConnectionMessage = this.onWsConnectionMessage.bind(this);
this.onUserEvent = this.onUserEvent.bind(this);
@ -86,13 +67,6 @@ export default class Connection {
this.subscriber.on(`user:${this.user.id}`, this.onUserEvent);
}
console.log("prepare", prepareStream);
if (prepareStream) {
this.onWsConnectionMessage({
type: "utf8",
utf8Data: JSON.stringify({ stream: prepareStream, type: "subscribe" }),
});
}
}
private onUserEvent(data: StreamMessages["user"]["payload"]) {
@ -145,149 +119,49 @@ export default class Connection {
if (data.type !== "utf8") return;
if (data.utf8Data == null) return;
let objs: Record<string, any>[];
let obj: Record<string, any>;
try {
objs = [JSON.parse(data.utf8Data)];
obj = JSON.parse(data.utf8Data);
} catch (e) {
return;
}
const simpleObj = objs[0];
if (simpleObj.stream) {
// is Mastodon Compatible
this.isMastodonCompatible = true;
if (simpleObj.type === "subscribe") {
let forSubscribe = [];
if (simpleObj.stream === "user") {
this.currentSubscribe.push(["user"]);
objs = [
{
type: "connect",
body: {
channel: "main",
id: simpleObj.stream,
},
},
{
type: "connect",
body: {
channel: "homeTimeline",
id: simpleObj.stream,
},
},
];
const client = getClient(this.host, this.accessToken);
try {
const tl = await client.getHomeTimeline();
for (const t of tl.data) forSubscribe.push(t.id);
} catch (e: any) {
console.log(e);
console.error(e.response.data);
}
} else if (simpleObj.stream === "public:local") {
this.currentSubscribe.push(["public:local"]);
objs = [
{
type: "connect",
body: {
channel: "localTimeline",
id: simpleObj.stream,
},
},
];
const client = getClient(this.host, this.accessToken);
const tl = await client.getLocalTimeline();
for (const t of tl.data) forSubscribe.push(t.id);
} else if (simpleObj.stream === "public") {
this.currentSubscribe.push(["public"]);
objs = [
{
type: "connect",
body: {
channel: "globalTimeline",
id: simpleObj.stream,
},
},
];
const client = getClient(this.host, this.accessToken);
const tl = await client.getPublicTimeline();
for (const t of tl.data) forSubscribe.push(t.id);
} else if (simpleObj.stream === "list") {
this.currentSubscribe.push(["list", simpleObj.list]);
objs = [
{
type: "connect",
body: {
channel: "list",
id: simpleObj.stream,
params: {
listId: simpleObj.list,
},
},
},
];
const client = getClient(this.host, this.accessToken);
const tl = await client.getListTimeline(simpleObj.list);
for (const t of tl.data) forSubscribe.push(t.id);
}
for (const s of forSubscribe) {
objs.push({
type: "s",
body: {
id: s,
},
});
}
}
}
for (const obj of objs) {
const { type, body } = obj;
console.log(type, body);
switch (type) {
case "readNotification":
this.onReadNotification(body);
break;
case "subNote":
this.onSubscribeNote(body);
break;
case "s":
this.onSubscribeNote(body);
break; // alias
case "sr":
this.onSubscribeNote(body);
this.readNote(body);
break;
case "unsubNote":
this.onUnsubscribeNote(body);
break;
case "un":
this.onUnsubscribeNote(body);
break; // alias
case "connect":
this.onChannelConnectRequested(body);
break;
case "disconnect":
this.onChannelDisconnectRequested(body);
break;
case "channel":
this.onChannelMessageRequested(body);
break;
case "ch":
this.onChannelMessageRequested(body);
break; // alias
const { type, body } = obj;
switch (type) {
case "readNotification":
this.onReadNotification(body);
break;
case "subNote":
this.onSubscribeNote(body);
break;
case "s":
this.onSubscribeNote(body);
break; // alias
case "sr":
this.onSubscribeNote(body);
this.readNote(body);
break;
case "unsubNote":
this.onUnsubscribeNote(body);
break;
case "un":
this.onUnsubscribeNote(body);
break; // alias
case "connect":
this.onChannelConnectRequested(body);
break;
case "disconnect":
this.onChannelDisconnectRequested(body);
break;
case "channel":
this.onChannelMessageRequested(body);
break;
case "ch":
this.onChannelMessageRequested(body);
break; // alias
// 個々のチャンネルではなくルートレベルでこれらのメッセージを受け取る理由は、
// クライアントの事情を考慮したとき、入力フォームはノートチャンネルやメッセージのメインコンポーネントとは別
// なこともあるため、それらのコンポーネントがそれぞれ各チャンネルに接続するようにするのは面倒なため。
case "typingOnChannel":
this.typingOnChannel(body.channel);
break;
case "typingOnMessaging":
this.typingOnMessaging(body);
break;
}
}
}
@ -391,75 +265,12 @@ export default class Connection {
*
*/
public sendMessageToWs(type: string, payload: any) {
console.log(payload, this.isMastodonCompatible);
if (this.isMastodonCompatible) {
if (payload.type === "note") {
this.wsConnection.send(
JSON.stringify({
stream: [payload.id],
event: "update",
payload: JSON.stringify(
toTextWithReaction(
[Converter.note(payload.body, this.host)],
this.host,
)[0],
),
}),
);
this.onSubscribeNote({
id: payload.body.id,
});
} else if (payload.type === "reacted" || payload.type === "unreacted") {
// reaction
const client = getClient(this.host, this.accessToken);
client.getStatus(payload.id).then((data) => {
const newPost = toTextWithReaction([data.data], this.host);
for (const stream of this.currentSubscribe) {
this.wsConnection.send(
JSON.stringify({
stream,
event: "status.update",
payload: JSON.stringify(newPost[0]),
}),
);
}
});
} else if (payload.type === "deleted") {
// delete
for (const stream of this.currentSubscribe) {
this.wsConnection.send(
JSON.stringify({
stream,
event: "delete",
payload: payload.id,
}),
);
}
} else if (payload.type === "unreadNotification") {
if (payload.id === "user") {
const body = Converter.notification(payload.body, this.host);
if (body.type === "reaction") body.type = "favourite";
body.status = toTextWithReaction(
body.status ? [body.status] : [],
"",
)[0];
this.wsConnection.send(
JSON.stringify({
stream: ["user"],
event: "notification",
payload: JSON.stringify(body),
}),
);
}
}
} else {
this.wsConnection.send(
JSON.stringify({
type: type,
body: payload,
}),
);
}
this.wsConnection.send(
JSON.stringify({
type: type,
body: payload,
}),
);
}
/**
@ -518,30 +329,6 @@ export default class Connection {
}
}
private typingOnChannel(channel: ChannelModel["id"]) {
if (this.user) {
publishChannelStream(channel, "typing", this.user.id);
}
}
private typingOnMessaging(param: {
partner?: User["id"];
group?: UserGroup["id"];
}) {
if (this.user) {
if (param.partner) {
publishMessagingStream(
param.partner,
this.user.id,
"typing",
this.user.id,
);
} else if (param.group) {
publishGroupMessagingStream(param.group, "typing", this.user.id);
}
}
}
private async updateFollowing() {
const followings = await Followings.find({
where: {

View File

@ -9,7 +9,6 @@ import type { DriveFile } from "@/models/entities/drive-file.js";
import type { DriveFolder } from "@/models/entities/drive-folder.js";
import { Emoji } from "@/models/entities/emoji.js";
import type { UserList } from "@/models/entities/user-list.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import type { UserGroup } from "@/models/entities/user-group.js";
import type { AbuseUserReport } from "@/models/entities/abuse-user-report.js";
import type { Signin } from "@/models/entities/signin.js";
@ -86,9 +85,6 @@ export interface MainStreamTypes {
readAllUnreadMentions: undefined;
unreadSpecifiedNote: Note["id"];
readAllUnreadSpecifiedNotes: undefined;
readAllMessagingMessages: undefined;
messagingMessage: Packed<"MessagingMessage">;
unreadMessagingMessage: Packed<"MessagingMessage">;
readAllAntennas: undefined;
unreadAntenna: Antenna;
readAllAnnouncements: undefined;
@ -156,28 +152,6 @@ export interface AntennaStreamTypes {
note: Note;
}
export interface MessagingStreamTypes {
read: MessagingMessage["id"][];
typing: User["id"];
message: Packed<"MessagingMessage">;
deleted: MessagingMessage["id"];
}
export interface GroupMessagingStreamTypes {
read: {
ids: MessagingMessage["id"][];
userId: User["id"];
};
typing: User["id"];
message: Packed<"MessagingMessage">;
deleted: MessagingMessage["id"];
}
export interface MessagingIndexStreamTypes {
read: MessagingMessage["id"][];
message: Packed<"MessagingMessage">;
}
export interface AdminStreamTypes {
newAbuseUserReport: {
id: AbuseUserReport["id"];
@ -232,18 +206,6 @@ export type StreamMessages = {
name: `antennaStream:${Antenna["id"]}`;
payload: EventUnionFromDictionary<AntennaStreamTypes>;
};
messaging: {
name: `messagingStream:${User["id"]}-${User["id"]}`;
payload: EventUnionFromDictionary<MessagingStreamTypes>;
};
groupMessaging: {
name: `messagingStream:${UserGroup["id"]}`;
payload: EventUnionFromDictionary<GroupMessagingStreamTypes>;
};
messagingIndex: {
name: `messagingIndexStream:${User["id"]}`;
payload: EventUnionFromDictionary<MessagingIndexStreamTypes>;
};
admin: {
name: `adminStream:${User["id"]}`;
payload: EventUnionFromDictionary<AdminStreamTypes>;

View File

@ -20,8 +20,6 @@ import { createTemp } from "@/misc/create-temp.js";
import { publishMainStream } from "@/services/stream.js";
import * as Acct from "@/misc/acct.js";
import { envOption } from "@/env.js";
import { koaBody } from 'koa-body';
import megalodon, { MegalodonInterface } from '@cutls/megalodon';
import activityPub from "./activitypub.js";
import nodeinfo from "./nodeinfo.js";
import wellKnown from "./well-known.js";
@ -135,34 +133,6 @@ router.get("/verify-email/:code", async (ctx) => {
}
});
router.get("/oauth/authorize", async (ctx) => {
const client_id = ctx.request.query.client_id;
console.log(ctx.request.req);
ctx.redirect(Buffer.from(client_id?.toString() || '', 'base64').toString());
});
router.post("/oauth/token", async (ctx) => {
const body: any = ctx.request.body;
const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
const generator = (megalodon as any).default;
const client = generator('misskey', BASE_URL, null) as MegalodonInterface;
const m = body.code.match(/^[a-zA-Z0-9-]+/);
if (!m.length) return { error: 'Invalid code' }
try {
const atData = await client.fetchAccessToken(null, body.client_secret, m[0]);
ctx.body = {
access_token: atData.accessToken,
token_type: 'Bearer',
scope: 'read write follow',
created_at: new Date().getTime() / 1000
};
} catch (err: any) {
console.error(err);
ctx.status = 401;
ctx.body = err.response.data;
}
});
// Register router
app.use(router.routes());

View File

@ -1,148 +0,0 @@
import type { CacheableUser, User } from "@/models/entities/user.js";
import type { UserGroup } from "@/models/entities/user-group.js";
import type { DriveFile } from "@/models/entities/drive-file.js";
import {
MessagingMessages,
UserGroupJoinings,
Mutings,
Users,
} from "@/models/index.js";
import { genId } from "@/misc/gen-id.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import {
publishMessagingStream,
publishMessagingIndexStream,
publishMainStream,
publishGroupMessagingStream,
} from "@/services/stream.js";
import { pushNotification } from "@/services/push-notification.js";
import { Not } from "typeorm";
import type { Note } from "@/models/entities/note.js";
import renderNote from "@/remote/activitypub/renderer/note.js";
import renderCreate from "@/remote/activitypub/renderer/create.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import { deliver } from "@/queue/index.js";
export async function createMessage(
user: { id: User["id"]; host: User["host"] },
recipientUser: CacheableUser | undefined,
recipientGroup: UserGroup | undefined,
text: string | null | undefined,
file: DriveFile | null,
uri?: string,
) {
const message = {
id: genId(),
createdAt: new Date(),
fileId: file ? file.id : null,
recipientId: recipientUser ? recipientUser.id : null,
groupId: recipientGroup ? recipientGroup.id : null,
text: text ? text.trim() : null,
userId: user.id,
isRead: false,
reads: [] as any[],
uri,
} as MessagingMessage;
await MessagingMessages.insert(message);
const messageObj = await MessagingMessages.pack(message);
if (recipientUser) {
if (Users.isLocalUser(user)) {
// 自分のストリーム
publishMessagingStream(
message.userId,
recipientUser.id,
"message",
messageObj,
);
publishMessagingIndexStream(message.userId, "message", messageObj);
publishMainStream(message.userId, "messagingMessage", messageObj);
}
if (Users.isLocalUser(recipientUser)) {
// 相手のストリーム
publishMessagingStream(
recipientUser.id,
message.userId,
"message",
messageObj,
);
publishMessagingIndexStream(recipientUser.id, "message", messageObj);
publishMainStream(recipientUser.id, "messagingMessage", messageObj);
}
} else if (recipientGroup) {
// グループのストリーム
publishGroupMessagingStream(recipientGroup.id, "message", messageObj);
// メンバーのストリーム
const joinings = await UserGroupJoinings.findBy({
userGroupId: recipientGroup.id,
});
for (const joining of joinings) {
publishMessagingIndexStream(joining.userId, "message", messageObj);
publishMainStream(joining.userId, "messagingMessage", messageObj);
}
}
// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
setTimeout(async () => {
const freshMessage = await MessagingMessages.findOneBy({ id: message.id });
if (freshMessage == null) return; // メッセージが削除されている場合もある
if (recipientUser && Users.isLocalUser(recipientUser)) {
if (freshMessage.isRead) return; // 既読
//#region ただしミュートされているなら発行しない
const mute = await Mutings.findBy({
muterId: recipientUser.id,
});
if (mute.map((m) => m.muteeId).includes(user.id)) return;
//#endregion
publishMainStream(recipientUser.id, "unreadMessagingMessage", messageObj);
pushNotification(recipientUser.id, "unreadMessagingMessage", messageObj);
} else if (recipientGroup) {
const joinings = await UserGroupJoinings.findBy({
userGroupId: recipientGroup.id,
userId: Not(user.id),
});
for (const joining of joinings) {
if (freshMessage.reads.includes(joining.userId)) return; // 既読
publishMainStream(joining.userId, "unreadMessagingMessage", messageObj);
pushNotification(joining.userId, "unreadMessagingMessage", messageObj);
}
}
}, 2000);
if (
recipientUser &&
Users.isLocalUser(user) &&
Users.isRemoteUser(recipientUser)
) {
const note = {
id: message.id,
createdAt: message.createdAt,
fileIds: message.fileId ? [message.fileId] : [],
text: message.text,
userId: message.userId,
visibility: "specified",
mentions: [recipientUser].map((u) => u.id),
mentionedRemoteUsers: JSON.stringify(
[recipientUser].map((u) => ({
uri: u.uri,
username: u.username,
host: u.host,
})),
),
} as Note;
const activity = renderActivity(
renderCreate(await renderNote(note, false, true), note),
);
deliver(user, activity, recipientUser.inbox);
}
return messageObj;
}

View File

@ -1,50 +0,0 @@
import config from "@/config/index.js";
import { MessagingMessages, Users } from "@/models/index.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import {
publishGroupMessagingStream,
publishMessagingStream,
} from "@/services/stream.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderDelete from "@/remote/activitypub/renderer/delete.js";
import renderTombstone from "@/remote/activitypub/renderer/tombstone.js";
import { deliver } from "@/queue/index.js";
export async function deleteMessage(message: MessagingMessage) {
await MessagingMessages.delete(message.id);
postDeleteMessage(message);
}
async function postDeleteMessage(message: MessagingMessage) {
if (message.recipientId) {
const user = await Users.findOneByOrFail({ id: message.userId });
const recipient = await Users.findOneByOrFail({ id: message.recipientId });
if (Users.isLocalUser(user))
publishMessagingStream(
message.userId,
message.recipientId,
"deleted",
message.id,
);
if (Users.isLocalUser(recipient))
publishMessagingStream(
message.recipientId,
message.userId,
"deleted",
message.id,
);
if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) {
const activity = renderActivity(
renderDelete(
renderTombstone(`${config.url}/notes/${message.id}`),
user,
),
);
deliver(user, activity, recipient.inbox);
}
} else if (message.groupId) {
publishGroupMessagingStream(message.groupId, "deleted", message.id);
}
}

View File

@ -8,11 +8,8 @@ import { getNoteSummary } from "@/misc/get-note-summary.js";
// Defined also packages/sw/types.ts#L14-L21
type pushNotificationsTypes = {
notification: Packed<"Notification">;
unreadMessagingMessage: Packed<"MessagingMessage">;
readNotifications: { notificationIds: string[] };
readAllNotifications: undefined;
readAllMessagingMessages: undefined;
readAllMessagingMessagesOfARoom: { userId: string } | { groupId: string };
};
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します

View File

@ -13,11 +13,8 @@ import type {
BroadcastTypes,
ChannelStreamTypes,
DriveStreamTypes,
GroupMessagingStreamTypes,
InternalStreamTypes,
MainStreamTypes,
MessagingIndexStreamTypes,
MessagingStreamTypes,
NoteStreamTypes,
UserListStreamTypes,
UserStreamTypes,
@ -146,47 +143,6 @@ class Publisher {
);
};
public publishMessagingStream = <K extends keyof MessagingStreamTypes>(
userId: User["id"],
otherpartyId: User["id"],
type: K,
value?: MessagingStreamTypes[K],
): void => {
this.publish(
`messagingStream:${userId}-${otherpartyId}`,
type,
typeof value === "undefined" ? null : value,
);
};
public publishGroupMessagingStream = <
K extends keyof GroupMessagingStreamTypes,
>(
groupId: UserGroup["id"],
type: K,
value?: GroupMessagingStreamTypes[K],
): void => {
this.publish(
`messagingStream:${groupId}`,
type,
typeof value === "undefined" ? null : value,
);
};
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);
};
@ -218,9 +174,4 @@ export const publishNotesStream = publisher.publishNotesStream;
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;

View File

@ -460,15 +460,6 @@ import { getAccountFromId } from "@/scripts/get-account-from-id";
updateAccount({ hasUnreadSpecifiedNotes: false });
});
main.on("readAllMessagingMessages", () => {
updateAccount({ hasUnreadMessagingMessage: false });
});
main.on("unreadMessagingMessage", () => {
updateAccount({ hasUnreadMessagingMessage: true });
sound.play("chatBg");
});
main.on("readAllAntennas", () => {
updateAccount({ hasUnreadAntenna: false });
});

View File

@ -18,13 +18,6 @@ export const navbarItemDef = reactive({
indicated: computed(() => $i?.hasUnreadNotification),
to: "/my/notifications",
},
messaging: {
title: "messaging",
icon: "ph-chats-teardrop-bold ph-lg",
show: computed(() => $i != null),
indicated: computed(() => $i?.hasUnreadMessagingMessage),
to: "/my/messaging",
},
drive: {
title: "drive",
icon: "ph-cloud-bold ph-lg",

View File

@ -1,220 +0,0 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<div>
<MkSpacer :content-max="800">
<swiper
:modules="[Virtual]"
:space-between="20"
:virtual="true"
:allow-touch-move="!(deviceKind === 'desktop' && !defaultStore.state.swipeOnDesktop)"
@swiper="setSwiperRef"
@slide-change="onSlideChange"
>
<swiper-slide>
<div class="_content yweeujhr dms">
<MkButton primary class="start" @click="startUser"><i class="ph-plus-bold ph-lg"></i> {{ i18n.ts.startMessaging }}</MkButton>
<MkPagination v-slot="{items}" :pagination="dmsPagination">
<MkChatPreview v-for="message in items" :key="message.id" class="yweeujhr message _block" :message="message"/>
</MkPagination>
</div>
</swiper-slide>
<swiper-slide>
<div class="_content yweeujhr groups">
<div class="groupsbuttons">
<MkButton primary class="start" :link="true" to="/my/groups"><i class="ph-user-circle-gear-bold ph-lg"></i> {{ i18n.ts.manageGroups }}</MkButton>
<MkButton primary class="start" @click="startGroup"><i class="ph-plus-bold ph-lg"></i> {{ i18n.ts.startMessaging }}</MkButton>
</div>
<MkPagination v-slot="{items}" :pagination="groupsPagination">
<MkChatPreview v-for="message in items" :key="message.id" class="yweeujhr message _block" :message="message"/>
</MkPagination>
</div>
</swiper-slide>
</swiper>
</MkSpacer>
</div>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { markRaw, onMounted, onUnmounted, watch } from 'vue';
import * as Acct from 'calckey-js/built/acct';
import { Virtual } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import MkButton from '@/components/MkButton.vue';
import MkChatPreview from '@/components/MkChatPreview.vue';
import MkPagination from '@/components/MkPagination.vue';
import * as os from '@/os';
import { stream } from '@/stream';
import { useRouter } from '@/router';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { $i } from '@/account';
import { deviceKind } from '@/scripts/device-kind';
import { defaultStore } from '@/store';
import 'swiper/scss';
import 'swiper/scss/virtual';
const router = useRouter();
let messages = $ref([]);
let connection = $ref(null);
const tabs = ['dms', 'groups'];
let tab = $ref(tabs[0]);
watch($$(tab), () => (syncSlide(tabs.indexOf(tab))));
const headerActions = $computed(() => [{
asFullButton: true,
icon: 'ph-plus-bold ph-lg',
text: i18n.ts.addUser,
handler: startMenu,
}]);
const headerTabs = $computed(() => [{
key: 'dms',
title: i18n.ts._messaging.dms,
icon: 'ph-user-bold ph-lg',
}, {
key: 'groups',
title: i18n.ts._messaging.groups,
icon: 'ph-users-three-bold ph-lg',
}]);
definePageMetadata({
title: i18n.ts.messaging,
icon: 'ph-chats-teardrop-bold ph-lg',
});
const dmsPagination = {
endpoint: 'messaging/history' as const,
limit: 15,
params: {
group: false,
},
};
const groupsPagination = {
endpoint: 'messaging/history' as const,
limit: 5,
params: {
group: true,
},
};
function onMessage(message): void {
if (message.recipientId) {
messages = messages.filter(m => !(
(m.recipientId === message.recipientId && m.userId === message.userId) ||
(m.recipientId === message.userId && m.userId === message.recipientId)));
messages.unshift(message);
} else if (message.groupId) {
messages = messages.filter(m => m.groupId !== message.groupId);
messages.unshift(message);
}
}
function onRead(ids): void {
for (const id of ids) {
const found = messages.find(m => m.id === id);
if (found) {
if (found.recipientId) {
found.isRead = true;
} else if (found.groupId) {
found.reads.push($i.id);
}
}
}
}
function startMenu(ev) {
os.popupMenu([{
text: i18n.ts.messagingWithUser,
icon: 'ph-user-bold ph-lg',
action: () => { startUser(); },
}, {
text: i18n.ts.messagingWithGroup,
icon: 'ph-users-three-bold ph-lg',
action: () => { startGroup(); },
}], ev.currentTarget ?? ev.target);
}
async function startUser(): void {
os.selectUser().then(user => {
router.push(`/my/messaging/${Acct.toString(user)}`);
});
}
async function startGroup(): void {
const groups1 = await os.api('users/groups/owned');
const groups2 = await os.api('users/groups/joined');
if (groups1.length === 0 && groups2.length === 0) {
os.alert({
type: 'warning',
title: i18n.ts.youHaveNoGroups,
text: i18n.ts.joinOrCreateGroup,
});
return;
}
const { canceled, result: group } = await os.select({
title: i18n.ts.group,
items: groups1.concat(groups2).map(group => ({
value: group, text: group.name,
})),
});
if (canceled) return;
router.push(`/my/messaging/group/${group.id}`);
}
let swiperRef = null;
function setSwiperRef(swiper) {
swiperRef = swiper;
syncSlide(tabs.indexOf(tab));
}
function onSlideChange() {
tab = tabs[swiperRef.activeIndex];
}
function syncSlide(index) {
swiperRef.slideTo(index);
}
onMounted(() => {
syncSlide(tabs.indexOf(swiperRef.activeIndex));
connection = markRaw(stream.useChannel('messagingIndex'));
connection.on('message', onMessage);
connection.on('read', onRead);
os.api('messaging/history', { group: false, limit: 5 }).then(userMessages => {
os.api('messaging/history', { group: true, limit: 5 }).then(groupMessages => {
const _messages = userMessages.concat(groupMessages);
_messages.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
messages = _messages;
});
});
});
onUnmounted(() => {
if (connection) connection.dispose();
});
</script>
<style lang="scss" scoped>
.yweeujhr {
> .start {
margin: 0 auto var(--margin) auto;
}
> .groupsbuttons {
max-width: 100%;
display: flex;
justify-content: center;
margin-bottom: 1rem;
}
}
</style>

View File

@ -1,361 +0,0 @@
<template>
<div
class="pemppnzi _block"
@dragover.stop="onDragover"
@drop.stop="onDrop"
>
<textarea
ref="textEl"
v-model="text"
:placeholder="i18n.ts.inputMessageHere"
@keydown="onKeydown"
@compositionupdate="onCompositionUpdate"
@paste="onPaste"
></textarea>
<footer>
<div v-if="file" class="file" @click="file = null">{{ file.name }}</div>
<div class="buttons">
<button class="_button" @click="chooseFile"><i class="ph-upload-bold ph-lg"></i></button>
<button class="_button" @click="insertEmoji"><i class="ph-smiley-bold ph-lg"></i></button>
<button class="send _button" :disabled="!canSend || sending" :title="i18n.ts.send" @click="send">
<template v-if="!sending"><i class="ph-paper-plane-tilt-bold ph-lg"></i></template><template v-if="sending"><i class="ph-circle-notch-bold ph-lg fa-pulse ph-fw ph-lg"></i></template>
</button>
</div>
</footer>
<input ref="fileEl" type="file" @change="onChangeFile"/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, watch } from 'vue';
import * as Misskey from 'calckey-js';
import autosize from 'autosize';
//import insertTextAtCursor from 'insert-text-at-cursor';
import { throttle } from 'throttle-debounce';
import { Autocomplete } from '@/scripts/autocomplete';
import { formatTimeString } from '@/scripts/format-time-string';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
import { stream } from '@/stream';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { uploadFile } from '@/scripts/upload';
const props = defineProps<{
user?: Misskey.entities.UserDetailed | null;
group?: Misskey.entities.UserGroup | null;
}>();
let textEl = $ref<HTMLTextAreaElement>();
let fileEl = $ref<HTMLInputElement>();
let text = $ref<string>('');
let file = $ref<Misskey.entities.DriveFile | null>(null);
let sending = $ref(false);
const typing = throttle(3000, () => {
stream.send('typingOnMessaging', props.user ? { partner: props.user.id } : { group: props.group?.id });
});
let draftKey = $computed(() => props.user ? 'user:' + props.user.id : 'group:' + props.group?.id);
let canSend = $computed(() => (text != null && text !== '') || file != null);
watch([$$(text), $$(file)], saveDraft);
async function onPaste(ev: ClipboardEvent) {
if (!ev.clipboardData) return;
const clipboardData = ev.clipboardData;
const items = clipboardData.items;
if (items.length === 1) {
if (items[0].kind === 'file') {
const pastedFile = items[0].getAsFile();
if (!pastedFile) return;
const lio = pastedFile.name.lastIndexOf('.');
const ext = lio >= 0 ? pastedFile.name.slice(lio) : '';
const formatted = formatTimeString(new Date(pastedFile.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, '1') + ext;
if (formatted) upload(pastedFile, formatted);
}
} else {
if (items[0].kind === 'file') {
os.alert({
type: 'error',
text: i18n.ts.onlyOneFileCanBeAttached,
});
}
}
}
function onDragover(ev: DragEvent) {
if (!ev.dataTransfer) return;
const isFile = ev.dataTransfer.items[0].kind === 'file';
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) {
ev.preventDefault();
ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
}
}
function onDrop(ev: DragEvent): void {
if (!ev.dataTransfer) return;
//
if (ev.dataTransfer.files.length === 1) {
ev.preventDefault();
upload(ev.dataTransfer.files[0]);
return;
} else if (ev.dataTransfer.files.length > 1) {
ev.preventDefault();
os.alert({
type: 'error',
text: i18n.ts.onlyOneFileCanBeAttached,
});
return;
}
//#region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== '') {
file = JSON.parse(driveFile);
ev.preventDefault();
}
//#endregion
}
function onKeydown(ev: KeyboardEvent) {
typing();
let sendOnEnter = localStorage.getItem('enterSendsMessage') === 'true' || defaultStore.state.enterSendsMessage;
if (sendOnEnter) {
if ((ev.key === 'Enter') && (ev.ctrlKey || ev.metaKey)) {
textEl.value += '\n';
}
else if (ev.key === 'Enter' && !ev.shiftKey && !('ontouchstart' in document.documentElement) && canSend) {
ev.preventDefault();
send();
}
}
else {
if ((ev.key === 'Enter') && (ev.ctrlKey || ev.metaKey) && canSend) {
ev.preventDefault();
send();
}
}
}
function onCompositionUpdate() {
typing();
}
function chooseFile(ev: MouseEvent) {
selectFile(ev.currentTarget ?? ev.target, i18n.ts.selectFile).then(selectedFile => {
file = selectedFile;
});
}
function onChangeFile() {
if (fileEl.files![0]) upload(fileEl.files[0]);
}
function upload(fileToUpload: File, name?: string) {
uploadFile(fileToUpload, defaultStore.state.uploadFolder, name).then(res => {
file = res;
});
}
function send() {
sending = true;
os.api('messaging/messages/create', {
userId: props.user ? props.user.id : undefined,
groupId: props.group ? props.group.id : undefined,
text: text ? text : undefined,
fileId: file ? file.id : undefined,
}).then(message => {
clear();
}).catch(err => {
console.error(err);
}).then(() => {
sending = false;
});
}
function clear() {
text = '';
file = null;
deleteDraft();
}
function saveDraft() {
const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}');
drafts[draftKey] = {
updatedAt: new Date(),
data: {
text: text,
file: file,
},
};
localStorage.setItem('message_drafts', JSON.stringify(drafts));
}
function deleteDraft() {
const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}');
delete drafts[draftKey];
localStorage.setItem('message_drafts', JSON.stringify(drafts));
}
async function insertEmoji(ev: MouseEvent) {
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textEl);
}
onMounted(() => {
autosize(textEl);
// TODO: detach when unmount
new Autocomplete(textEl, $$(text));
// 稿
const draft = JSON.parse(localStorage.getItem('message_drafts') || '{}')[draftKey];
if (draft) {
text = draft.data.text;
file = draft.data.file;
}
});
defineExpose({
file,
upload,
});
</script>
<style lang="scss" scoped>
.pemppnzi {
position: relative;
margin-top: 1rem;
> textarea {
cursor: auto;
display: block;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 80px;
margin: 0;
padding: 16px 16px 0 16px;
resize: none;
font-size: 1em;
font-family: inherit;
outline: none;
border: none;
border-radius: 0;
box-shadow: none;
background: transparent;
box-sizing: border-box;
color: var(--fg);
}
footer {
position: sticky;
bottom: 0;
background: var(--panel);
> .file {
padding: 8px;
color: var(--fg);
background: transparent;
cursor: pointer;
}
}
.files {
display: block;
margin: 0;
padding: 0 8px;
list-style: none;
&:after {
content: '';
display: block;
clear: both;
}
> li {
display: block;
float: left;
margin: 4px;
padding: 0;
width: 64px;
height: 64px;
background-color: #eee;
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
cursor: move;
&:hover {
> .remove {
display: block;
}
}
> .remove {
display: none;
position: absolute;
right: -6px;
top: -6px;
margin: 0;
padding: 0;
background: transparent;
outline: none;
border: none;
border-radius: 0;
box-shadow: none;
cursor: pointer;
}
}
}
.buttons {
display: flex;
._button {
margin: 0;
padding: 16px;
font-size: 1em;
font-weight: normal;
text-decoration: none;
transition: color 0.1s ease;
&:hover {
color: var(--accent);
}
&:active {
color: var(--accentDarken);
transition: color 0s ease;
}
}
> .send {
margin-left: auto;
color: var(--accent);
&:hover {
color: var(--accentLighten);
}
&:active {
color: var(--accentDarken);
transition: color 0s ease;
}
}
}
input[type=file] {
display: none;
}
}
</style>

View File

@ -1,329 +0,0 @@
<template>
<div v-size="{ max: [400, 500] }" class="thvuemwp" :class="{ isMe }">
<MkAvatar v-if="!isMe" class="avatar" :user="message.user" :show-indicator="true"/>
<div class="content">
<div class="balloon" :class="{ noText: message.text == null }">
<button v-if="isMe" class="delete-button" :title="i18n.ts.delete" @click="del">
<img src="/client-assets/remove.png" alt="Delete"/>
</button>
<div v-if="!message.isDeleted" class="content">
<Mfm v-if="message.text" ref="text" class="text" :text="message.text" :i="$i"/>
</div>
<div v-else class="content">
<p class="is-deleted">{{ i18n.ts.deleted }}</p>
</div>
</div>
<div v-if="message.file" class="file" width="400px">
<XMediaList v-if="message.file.type.split('/')[0] == 'image' || message.file.type.split('/')[0] == 'video'" :in-dm="true" width="400px" :media-list="[message.file]" style="border-radius: 5px"/>
<a v-else :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name">
<p>{{ message.file.name }}</p>
</a>
</div>
<div></div>
<MkUrlPreview v-for="url in urls" :key="url" :url="url" style="margin: 8px 0;"/>
<footer>
<template v-if="isGroup">
<span v-if="message.reads.length > 0" class="read">{{ i18n.ts.messageRead }} {{ message.reads.length }}</span>
</template>
<template v-else>
<span v-if="isMe && message.isRead" class="read">{{ i18n.ts.messageRead }}</span>
</template>
<MkTime :time="message.createdAt"/>
<template v-if="message.is_edited"><i class="ph-pencil-bold ph-lg"></i></template>
</footer>
</div>
</div>
</template>
<script lang="ts" setup>
import { } from 'vue';
import * as mfm from 'mfm-js';
import type * as Misskey from 'calckey-js';
import XMediaList from '@/components/MkMediaList.vue';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
import * as os from '@/os';
import { $i } from '@/account';
import { i18n } from '@/i18n';
const props = defineProps<{
message: Misskey.entities.MessagingMessage;
isGroup?: boolean;
}>();
const isMe = $computed(() => props.message.userId === $i?.id);
const urls = $computed(() => props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : []);
function del(): void {
os.api('messaging/messages/delete', {
messageId: props.message.id,
});
}
</script>
<style lang="scss" scoped>
.thvuemwp {
$me-balloon-color: var(--accent);
--plyr-color-main: var(--accent);
position: relative;
background-color: transparent;
display: flex;
> .avatar {
position: sticky;
top: calc(var(--stickyTop, 0px) + 20px);
display: block;
width: 45px;
height: 45px;
transition: all 0.1s ease;
}
> .content {
min-width: 0;
> .balloon {
position: relative;
display: inline-flex;
align-items: center;
padding: 0;
min-height: 38px;
border-radius: 16px;
max-width: 100%;
& + * {
clear: both;
}
&:hover {
> .delete-button {
display: block;
}
}
> .delete-button {
display: none;
position: absolute;
z-index: 1;
top: -4px;
right: -4px;
margin: 0;
padding: 0;
cursor: pointer;
outline: none;
border: none;
border-radius: 0;
box-shadow: none;
background: transparent;
> img {
vertical-align: bottom;
width: 16px;
height: 16px;
cursor: pointer;
}
}
> .content {
max-width: 100%;
> .is-deleted {
display: block;
margin: 0;
padding: 0;
overflow: hidden;
overflow-wrap: break-word;
font-size: 1em;
color: rgba(#000, 0.5);
}
> .text {
display: block;
margin: 0;
padding: 12px 18px;
overflow: hidden;
overflow-wrap: break-word;
word-break: break-word;
font-size: 1em;
color: rgba(#000, 0.8);
& + .file {
> a {
border-radius: 0 0 16px 16px;
}
}
}
> .file {
> a {
display: block;
max-width: 100%;
border-radius: 16px;
overflow: hidden;
text-decoration: none;
&:hover {
text-decoration: none;
> p {
background: #ccc;
}
}
> * {
display: block;
margin: 0;
width: 100%;
max-height: 512px;
object-fit: contain;
box-sizing: border-box;
}
> p {
padding: 30px;
text-align: center;
color: #6e6a86;
background: #ddd;
}
}
}
}
}
> footer {
display: block;
margin: 2px 0 0 0;
font-size: 0.65em;
> .read {
margin: 0 8px;
}
> i {
margin-left: 4px;
}
}
}
&:not(.isMe) {
padding-left: var(--margin);
> .content {
padding-left: 16px;
padding-right: 32px;
> .balloon {
$color: var(--X4);
background: $color;
&.noText {
background: transparent;
}
&:not(.noText):before {
left: -14px;
border-top: solid 8px transparent;
border-right: solid 8px $color;
border-bottom: solid 8px transparent;
border-left: solid 8px transparent;
}
> .content {
> .text {
color: var(--fg);
}
}
}
> footer {
text-align: left;
}
}
}
&.isMe {
flex-direction: row-reverse;
padding-right: var(--margin);
right: var(--margin); // position: absolute使
> .content {
padding-right: 16px;
padding-left: 32px;
text-align: right;
> .balloon {
background: $me-balloon-color;
text-align: left;
::selection {
color: var(--accent);
background-color: #fff;
}
&.noText {
background: transparent;
}
&:not(.noText):before {
right: -14px;
left: auto;
border-top: solid 8px transparent;
border-right: solid 8px transparent;
border-bottom: solid 8px transparent;
border-left: solid 8px $me-balloon-color;
}
> .content {
> p.is-deleted {
color: rgba(#fff, 0.5);
}
> .text {
&, ::v-deep(*) {
color: var(--fgOnAccent) !important;
}
}
}
}
> footer {
text-align: right;
> .read {
user-select: none;
}
}
}
}
&.max-width_400px {
> .avatar {
width: 48px;
height: 48px;
}
> .content {
> .balloon {
> .content {
> .text {
font-size: 0.9em;
}
}
}
}
}
&.max-width_500px {
> .content {
> .balloon {
> .content {
> .text {
padding: 8px 16px;
}
}
}
}
}
}
</style>

View File

@ -1,400 +0,0 @@
<template>
<div
ref="rootEl"
class="_section"
@dragover.prevent.stop="onDragover"
@drop.prevent.stop="onDrop"
>
<div class="_content mk-messaging-room">
<MkSpacer :content-max="800">
<div class="body">
<MkPagination v-if="pagination" ref="pagingComponent" :key="userAcct || groupId" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img src="/static-assets/badges/info.png" class="_ghost" alt="Info"/>
<div>{{ i18n.ts.noMessagesYet }}</div>
</div>
</template>
<template #default="{ items: messages, fetching: pFetching }">
<XList
v-if="messages.length > 0"
v-slot="{ item: message }"
:class="{ messages: true, 'deny-move-transition': pFetching }"
:items="messages"
direction="up"
reversed
>
<XMessage :key="message.id" :message="message" :is-group="group != null"/>
</XList>
</template>
</MkPagination>
</div>
<footer>
<div v-if="typers.length > 0" class="typers">
<I18n :src="i18n.ts.typingUsers" text-tag="span" class="users">
<template #users>
<b v-for="typer in typers" :key="typer.id" class="user">{{ typer.username }}</b>
</template>
</I18n>
<MkEllipsis/>
</div>
<transition :name="animation ? 'fade' : ''">
<div v-show="showIndicator" class="new-message">
<button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas ph-fw ph-lg ph-arrow-circle-down-bold ph-lg"></i>{{ i18n.ts.newMessageExists }}</button>
</div>
</transition>
<XForm v-if="!fetching" ref="formEl" :user="user" :group="group" class="form"/>
</footer>
</MkSpacer>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue';
import * as Misskey from 'calckey-js';
import * as Acct from 'calckey-js/built/acct';
import XMessage from './messaging-room.message.vue';
import XForm from './messaging-room.form.vue';
import XList from '@/components/MkDateSeparatedList.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { isBottomVisible, onScrollBottom, scrollToBottom } from '@/scripts/scroll';
import * as os from '@/os';
import { stream } from '@/stream';
import * as sound from '@/scripts/sound';
import { i18n } from '@/i18n';
import { $i } from '@/account';
import { defaultStore } from '@/store';
import { definePageMetadata } from '@/scripts/page-metadata';
const props = defineProps<{
userAcct?: string;
groupId?: string;
}>();
let rootEl = $ref<HTMLDivElement>();
let formEl = $ref<InstanceType<typeof XForm>>();
let pagingComponent = $ref<InstanceType<typeof MkPagination>>();
let fetching = $ref(true);
let user: Misskey.entities.UserDetailed | null = $ref(null);
let group: Misskey.entities.UserGroup | null = $ref(null);
let typers: Misskey.entities.User[] = $ref([]);
let connection: Misskey.ChannelConnection<Misskey.Channels['messaging']> | null = $ref(null);
let showIndicator = $ref(false);
const {
animation,
} = defaultStore.reactiveState;
let pagination: Paging | null = $ref(null);
watch([() => props.userAcct, () => props.groupId], () => {
if (connection) connection.dispose();
fetch();
});
async function fetch() {
fetching = true;
if (props.userAcct) {
const acct = Acct.parse(props.userAcct);
user = await os.api('users/show', { username: acct.username, host: acct.host || undefined });
group = null;
pagination = {
endpoint: 'messaging/messages',
limit: 20,
params: {
userId: user.id,
},
reversed: true,
pageEl: $$(rootEl).value,
};
connection = stream.useChannel('messaging', {
otherparty: user.id,
});
} else {
user = null;
group = await os.api('users/groups/show', { groupId: props.groupId });
pagination = {
endpoint: 'messaging/messages',
limit: 20,
params: {
groupId: group?.id,
},
reversed: true,
pageEl: $$(rootEl).value,
};
connection = stream.useChannel('messaging', {
group: group?.id,
});
}
connection.on('message', onMessage);
connection.on('read', onRead);
connection.on('deleted', onDeleted);
connection.on('typers', _typers => {
typers = _typers.filter(u => u.id !== $i?.id);
});
document.addEventListener('visibilitychange', onVisibilitychange);
nextTick(() => {
// thisScrollToBottom();
window.setTimeout(() => {
fetching = false;
}, 300);
});
}
function onDragover(ev: DragEvent) {
if (!ev.dataTransfer) return;
const isFile = ev.dataTransfer.items[0].kind === 'file';
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) {
ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
} else {
ev.dataTransfer.dropEffect = 'none';
}
}
function onDrop(ev: DragEvent): void {
if (!ev.dataTransfer) return;
//
if (ev.dataTransfer.files.length === 1) {
formEl.upload(ev.dataTransfer.files[0]);
return;
} else if (ev.dataTransfer.files.length > 1) {
os.alert({
type: 'error',
text: i18n.ts.onlyOneFileCanBeAttached,
});
return;
}
//#region
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== '') {
const file = JSON.parse(driveFile);
formEl.file = file;
}
//#endregion
}
function onMessage(message) {
sound.play('chat');
const _isBottom = isBottomVisible(rootEl, 64);
pagingComponent.prepend(message);
if (message.userId !== $i?.id && !document.hidden) {
connection?.send('read', {
id: message.id,
});
}
if (_isBottom) {
// Scroll to bottom
nextTick(() => {
thisScrollToBottom();
});
} else if (message.userId !== $i?.id) {
// Notify
notifyNewMessage();
}
}
function onRead(x) {
if (user) {
if (!Array.isArray(x)) x = [x];
for (const id of x) {
if (pagingComponent.items.some(y => y.id === id)) {
const exist = pagingComponent.items.map(y => y.id).indexOf(id);
pagingComponent.items[exist] = {
...pagingComponent.items[exist],
isRead: true,
};
}
}
} else if (group) {
for (const id of x.ids) {
if (pagingComponent.items.some(y => y.id === id)) {
const exist = pagingComponent.items.map(y => y.id).indexOf(id);
pagingComponent.items[exist] = {
...pagingComponent.items[exist],
reads: [...pagingComponent.items[exist].reads, x.userId],
};
}
}
}
}
function onDeleted(id) {
const msg = pagingComponent.items.find(m => m.id === id);
if (msg) {
pagingComponent.items = pagingComponent.items.filter(m => m.id !== msg.id);
}
}
function thisScrollToBottom() {
if (window.location.href.includes('my/messaging/')) {
scrollToBottom($$(rootEl).value, { behavior: 'smooth' });
}
}
function onIndicatorClick() {
showIndicator = false;
thisScrollToBottom();
}
let scrollRemove: (() => void) | null = $ref(null);
function notifyNewMessage() {
showIndicator = true;
scrollRemove = onScrollBottom(rootEl, () => {
showIndicator = false;
scrollRemove = null;
});
}
function onVisibilitychange() {
if (document.hidden) return;
for (const message of pagingComponent.items) {
if (message.userId !== $i?.id && !message.isRead) {
connection?.send('read', {
id: message.id,
});
}
}
}
onMounted(() => {
fetch();
definePageMetadata(computed(() => ({
title: group != null ? group.name : user?.name,
icon: 'ph-chats-teardrop-bold ph-lg',
})));
});
onBeforeUnmount(() => {
connection?.dispose();
document.removeEventListener('visibilitychange', onVisibilitychange);
if (scrollRemove) scrollRemove();
});
</script>
<style lang="scss" scoped>
XMessage:last-of-type {
margin-bottom: 4rem;
}
.mk-messaging-room {
position: relative;
overflow: auto;
> .body {
.more {
display: block;
margin: 16px auto;
padding: 0 12px;
line-height: 24px;
color: #fff;
background: rgba(#000, 0.3);
border-radius: 12px;
&:hover {
background: rgba(#000, 0.4);
}
&:active {
background: rgba(#000, 0.5);
}
&.fetching {
cursor: wait;
}
> i {
margin-right: 4px;
}
}
.messages {
padding: 8px 0;
> ::v-deep(*) {
margin-bottom: 16px;
}
}
}
> footer {
width: 100%;
position: sticky;
z-index: 2;
bottom: 0;
padding-top: 8px;
bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
> .new-message {
width: 100%;
padding-bottom: 8px;
text-align: center;
> button {
display: inline-block;
margin: 0;
padding: 0 12px;
line-height: 32px;
font-size: 12px;
border-radius: 16px;
> i {
display: inline-block;
margin-right: 8px;
}
}
}
> .typers {
position: absolute;
bottom: 100%;
padding: 0 8px 0 8px;
font-size: 0.9em;
color: var(--fgTransparentWeak);
> .users {
> .user + .user:before {
content: ", ";
font-weight: normal;
}
> .user:last-of-type:after {
content: " ";
}
}
}
> .form {
max-height: 12em;
overflow-y: scroll;
border-top: solid 0.5px var(--divider);
}
}
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.1s;
}
.fade-enter-from, .fade-leave-to {
transition: opacity 0.5s;
opacity: 0;
}
</style>

View File

@ -542,22 +542,6 @@ export const routes = [
component: page(() => import("./pages/favorites.vue")),
loginRequired: true,
},
{
name: "messaging",
path: "/my/messaging",
component: page(() => import("./pages/messaging/index.vue")),
loginRequired: true,
},
{
path: "/my/messaging/:userAcct",
component: page(() => import("./pages/messaging/messaging-room.vue")),
loginRequired: true,
},
{
path: "/my/messaging/group/:groupId",
component: page(() => import("./pages/messaging/messaging-room.vue")),
loginRequired: true,
},
{
path: "/my/drive/folder/:folder",
component: page(() => import("./pages/drive.vue")),

View File

@ -1,6 +1,5 @@
import { defineAsyncComponent, Ref, inject } from "vue";
import { defineAsyncComponent, Ref } from "vue";
import * as misskey from "calckey-js";
import { pleaseLogin } from "./please-login";
import { $i } from "@/account";
import { i18n } from "@/i18n";
import { instance } from "@/instance";

View File

@ -232,15 +232,6 @@ export function getUserMenu(user, router: Router = mainRouter) {
os.post({ specified: user });
},
},
meId !== user.id
? {
type: "link",
icon: "ph-chats-teardrop-bold ph-lg",
text: i18n.ts.startMessaging,
to: `/my/messaging/${Acct.toString(user)}`,
}
: undefined,
null,
{
icon: "ph-list-bullets-bold ph-lg",
text: i18n.ts.addToList,

View File

@ -86,7 +86,6 @@ export const defaultStore = markRaw(
"notifications",
undefined,
"followRequests",
"messaging",
"explore",
"favorites",
"channels",

View File

@ -305,33 +305,6 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(
default:
return null;
}
case "unreadMessagingMessage":
if (data.body.groupId === null) {
return [
t("_notification.youGotMessagingMessageFromUser", {
name: getUserName(data.body.user),
}),
{
icon: data.body.user.avatarUrl,
badge: iconUrl("comments"),
tag: `messaging:user:${data.body.userId}`,
data,
renotify: true,
},
];
}
return [
t("_notification.youGotMessagingMessageFromGroup", {
name: data.body.group.name,
}),
{
icon: data.body.user.avatarUrl,
badge: iconUrl("comments"),
tag: `messaging:group:${data.body.groupId}`,
data,
renotify: true,
},
];
default:
return null;
}

View File

@ -6,7 +6,6 @@ declare var self: ServiceWorkerGlobalScope;
import * as Misskey from "calckey-js";
import { SwMessage, swMessageOrderType } from "@/types";
import { acct as getAcct } from "@/filters/user";
import { getAccountFromId } from "@/scripts/get-account-from-id";
import { getUrlWithLoginId } from "@/scripts/login-id";
@ -36,18 +35,6 @@ export function openNote(noteId: string, loginId: string) {
return openClient("push", `/notes/${noteId}`, loginId, { noteId });
}
export async function openChat(body: any, loginId: string) {
if (body.groupId === null) {
return openClient("push", `/my/messaging/${getAcct(body.user)}`, loginId, {
body,
});
} else {
return openClient("push", `/my/messaging/group/${body.groupId}`, loginId, {
body,
});
}
}
// post-formのオプションから投稿フォームを開く
export async function openPost(options: any, loginId: string) {
// クエリを作成しておく

View File

@ -2,7 +2,6 @@ declare var self: ServiceWorkerGlobalScope;
import {
createEmptyNotification,
createNotification,
} from "@/scripts/create-notification";
import { swLang } from "@/scripts/lang";
import { swNotificationRead } from "@/scripts/notification-read";
@ -65,25 +64,11 @@ self.addEventListener("push", (ev) => {
switch (data.type) {
// case 'driveFileCreated':
case "notification":
case "unreadMessagingMessage":
// 1日以上経過している場合は無視
if (new Date().getTime() - data.dateTime > 1000 * 60 * 60 * 24)
break;
// クライアントがあったらストリームに接続しているということなので通知しない
if (clients.length !== 0) break;
return createNotification(data);
case "readAllNotifications":
for (const n of await self.registration.getNotifications()) {
if (n?.data?.type === "notification") n.close();
}
break;
case "readAllMessagingMessages":
for (const n of await self.registration.getNotifications()) {
if (n?.data?.type === "unreadMessagingMessage") n.close();
}
break;
case "readNotifications":
for (const n of await self.registration.getNotifications()) {
if (data.body?.notificationIds?.includes(n.data.body.id)) {
@ -91,18 +76,6 @@ self.addEventListener("push", (ev) => {
}
}
break;
case "readAllMessagingMessagesOfARoom":
for (const n of await self.registration.getNotifications()) {
if (
n.data.type === "unreadMessagingMessage" &&
("userId" in data.body
? data.body.userId === n.data.body.userId
: data.body.groupId === n.data.body.groupId)
) {
n.close();
}
}
break;
}
return createEmptyNotification();
@ -210,9 +183,6 @@ self.addEventListener(
}
}
break;
case "unreadMessagingMessage":
client = await swos.openChat(data.body, id);
break;
}
if (client) {

View File

@ -13,11 +13,8 @@ export type SwMessage = {
// Defined also @/services/push-notification.ts#L7-L14
type pushNotificationDataSourceMap = {
notification: Misskey.entities.Notification;
unreadMessagingMessage: Misskey.entities.MessagingMessage;
readNotifications: { notificationIds: string[] };
readAllNotifications: undefined;
readAllMessagingMessages: undefined;
readAllMessagingMessagesOfARoom: { userId: string } | { groupId: string };
};
export type pushNotificationData<

View File

@ -7,8 +7,8 @@ importers:
.:
specifiers:
'@bull-board/api': ^4.10.2
'@bull-board/ui': ^4.10.2
'@bull-board/api': ^4.12.2
'@bull-board/ui': ^4.12.2
'@tensorflow/tfjs': ^3.21.0
'@types/gulp': 4.0.10
'@types/gulp-rename': 2.0.1
@ -29,8 +29,8 @@ importers:
start-server-and-test: 1.15.2
typescript: 4.9.4
dependencies:
'@bull-board/api': 4.10.2
'@bull-board/ui': 4.10.2
'@bull-board/api': 4.12.2
'@bull-board/ui': 4.12.2
'@tensorflow/tfjs': 3.21.0_seedrandom@3.0.5
calckey-js: 0.0.20
js-yaml: 4.1.0
@ -672,6 +672,12 @@ packages:
redis-info: 3.1.0
dev: false
/@bull-board/api/4.12.2:
resolution: {integrity: sha512-efF8K1pvfEEft2ELQwCBe/a225vHufCjM2hfcyvmIG/SUX6TlKQFUFZ1+KV0wenD9wxvUVb5wxUu6on+HrZiVg==}
dependencies:
redis-info: 3.1.0
dev: false
/@bull-board/koa/4.10.2_6tybghmia4wsnt33xeid7y4rby:
resolution: {integrity: sha512-gabPtsMOt2SQHkS5VcY1q/FCpbBRFiFrbWbcouZ7zWKg413J8nG+yErz3pc0rbmp23kbKX6wTG/diWKhE7EWbA==}
dependencies:
@ -746,6 +752,12 @@ packages:
'@bull-board/api': 4.10.2
dev: false
/@bull-board/ui/4.12.2:
resolution: {integrity: sha512-jB/OOEhg+DUL6ssmtQYTK+iYd3iy68bbozOp+q2xVUC4V7zeFmYF25sIApYFTNfbjuUMesAVOiX4u0gNEo/J7w==}
dependencies:
'@bull-board/api': 4.12.2
dev: false
/@colors/colors/1.5.0:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
@ -1191,6 +1203,15 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
'@jridgewell/trace-mapping': 0.3.17
/@jridgewell/gen-mapping/0.3.3:
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.18
dev: true
/@jridgewell/resolve-uri/3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
@ -1206,15 +1227,33 @@ packages:
'@jridgewell/trace-mapping': 0.3.17
dev: true
/@jridgewell/source-map/0.3.3:
resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==}
dependencies:
'@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.18
dev: true
/@jridgewell/sourcemap-codec/1.4.14:
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
/@jridgewell/sourcemap-codec/1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping/0.3.17:
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
/@jridgewell/trace-mapping/0.3.18:
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@jridgewell/trace-mapping/0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
@ -1865,7 +1904,7 @@ packages:
'@types/webgl-ext': 0.0.30
'@webgpu/types': 0.1.16
long: 4.0.0
node-fetch: 2.6.8
node-fetch: 2.6.9
seedrandom: 3.0.5
transitivePeerDependencies:
- encoding
@ -1894,8 +1933,8 @@ packages:
seedrandom: ^3.0.5
dependencies:
'@tensorflow/tfjs-core': 3.21.0
'@types/node-fetch': 2.6.2
node-fetch: 2.6.8
'@types/node-fetch': 2.6.3
node-fetch: 2.6.9
seedrandom: 3.0.5
string_decoder: 1.3.0
transitivePeerDependencies:
@ -1965,7 +2004,7 @@ packages:
'@tensorflow/tfjs-layers': 3.21.0_aipmo6igpprgzt4umpaa3m6sn4
argparse: 1.0.10
chalk: 4.1.2
core-js: 3.27.1
core-js: 3.30.1
regenerator-runtime: 0.13.11
yargs: 16.2.0
transitivePeerDependencies:
@ -2138,11 +2177,18 @@ packages:
'@types/node': 18.11.18
dev: false
/@types/glob-stream/6.1.1:
resolution: {integrity: sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==}
/@types/glob-stream/6.1.2:
resolution: {integrity: sha512-EIJSLP/nGyMzD8aFhJljO9nv3EmQ10xk1+dl+i15AITrcWLhhTTPmNMFK0TWcGRvVYuSlA1VPi1fe8tbgDsUhg==}
dependencies:
'@types/glob': 8.0.0
'@types/node': 18.11.18
'@types/glob': 7.2.0
'@types/node': 18.15.12
dev: true
/@types/glob/7.2.0:
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 18.15.12
dev: true
/@types/glob/8.0.0:
@ -2152,10 +2198,17 @@ packages:
'@types/node': 18.11.18
dev: true
/@types/glob/8.1.0:
resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==}
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 18.15.12
dev: true
/@types/gulp-rename/2.0.1:
resolution: {integrity: sha512-9ZjeS2RHEnmBmTcyi2+oeye3BgCsWhvi4uv3qCnAg8i6plOuRdaeNxjOves0ELysEXYLBl7bCl5fbVs7AZtgTA==}
dependencies:
'@types/node': 18.11.18
'@types/node': 18.15.12
'@types/vinyl': 2.0.7
dev: true
@ -2163,7 +2216,7 @@ packages:
resolution: {integrity: sha512-spgZHJFqiEJGwqGlf7T/k4nkBpBcLgP7T0EfN6G2vvnhUfvd4uO1h8RwpXOE8x/54DVYUs1XCAtBHkX/R3axAQ==}
dependencies:
'@types/undertaker': 1.2.8
'@types/vinyl-fs': 2.4.12
'@types/vinyl-fs': 3.0.1
chokidar: 3.5.3
dev: true
@ -2382,6 +2435,13 @@ packages:
form-data: 3.0.1
dev: false
/@types/node-fetch/2.6.3:
resolution: {integrity: sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==}
dependencies:
'@types/node': 18.15.12
form-data: 3.0.1
dev: false
/@types/node-fetch/3.0.3:
resolution: {integrity: sha512-HhggYPH5N+AQe/OmN6fmhKmRRt2XuNJow+R3pQwJxOOF9GuwM7O2mheyGeIrs5MOIeNjDEdgdoyHBOrFeJBR3g==}
deprecated: This is a stub types definition. node-fetch provides its own type definitions, so you do not need this installed.
@ -2391,10 +2451,18 @@ packages:
/@types/node/14.18.36:
resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==}
dev: false
/@types/node/14.18.42:
resolution: {integrity: sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==}
dev: true
/@types/node/18.11.18:
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
/@types/node/18.15.12:
resolution: {integrity: sha512-Wha1UwsB3CYdqUm2PPzh/1gujGCNtWVUYF0mB00fJFoR4gTyWTDPjSm+zBF787Ahw8vSGgBja90MkgFwvB86Dg==}
/@types/nodemailer/6.4.7:
resolution: {integrity: sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==}
dependencies:
@ -2464,6 +2532,13 @@ packages:
dependencies:
'@types/node': 18.11.18
/@types/rimraf/2.0.5:
resolution: {integrity: sha512-YyP+VfeaqAyFmXoTh3HChxOQMyjByRMsHU7kc5KOJkSlXudhMhQIALbYV7rHh/l8d2lX3VUQzprrcAgWdRuU8g==}
dependencies:
'@types/glob': 8.1.0
'@types/node': 18.15.12
dev: true
/@types/sanitize-html/2.8.0:
resolution: {integrity: sha512-Uih6caOm3DsBYnVGOYn0A9NoTNe1c4aPStmHC/YA2JrpP9kx//jzaRcIklFvSpvVQEcpl/ZCr4DgISSf/YxTvg==}
dependencies:
@ -2539,7 +2614,7 @@ packages:
/@types/undertaker/1.2.8:
resolution: {integrity: sha512-gW3PRqCHYpo45XFQHJBhch7L6hytPsIe0QeLujlnFsjHPnXLhJcPdN6a9368d7aIQgH2I/dUTPFBlGeSNA3qOg==}
dependencies:
'@types/node': 18.11.18
'@types/node': 18.15.12
'@types/undertaker-registry': 1.0.1
async-done: 1.3.2
dev: true
@ -2548,11 +2623,12 @@ packages:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
dev: true
/@types/vinyl-fs/2.4.12:
resolution: {integrity: sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==}
/@types/vinyl-fs/3.0.1:
resolution: {integrity: sha512-me2Gcxw23pZp62oqPoiTDDMz/txEmtEZzXM/D/VTr+xUX4LiNA+nQPs38SSPu5KHnsaEER4HEtzWU5qJRXigfA==}
dependencies:
'@types/glob-stream': 6.1.1
'@types/node': 18.11.18
'@types/glob-stream': 6.1.2
'@types/node': 18.15.12
'@types/rimraf': 2.0.5
'@types/vinyl': 2.0.7
dev: true
@ -2560,7 +2636,7 @@ packages:
resolution: {integrity: sha512-4UqPv+2567NhMQuMLdKAyK4yzrfCqwaTt6bLhHEs8PFcxbHILsrxaY63n4wgE/BRLDWDQeI+WcTmkXKExh9hQg==}
dependencies:
'@types/expect': 1.20.4
'@types/node': 18.11.18
'@types/node': 18.15.12
dev: true
/@types/web-push/3.3.2:
@ -2599,7 +2675,7 @@ packages:
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
requiresBuild: true
dependencies:
'@types/node': 18.11.18
'@types/node': 14.18.42
dev: true
optional: true
@ -2885,6 +2961,12 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
/acorn/8.8.2:
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/adm-zip/0.5.10:
resolution: {integrity: sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==}
engines: {node: '>=6.0'}
@ -3288,7 +3370,7 @@ packages:
resolution: {integrity: sha512-WKExI/eSGgGAkWAO+wMVdFObZV7hQen54UpD1kCCTN3tvlL3W1jL4+lPP/M7MwoP7Q4RHzKtO3JQ4HxYEcd+xQ==}
dependencies:
browserslist: 1.7.7
caniuse-db: 1.0.30001443
caniuse-db: 1.0.30001480
normalize-range: 0.1.2
num2fraction: 1.2.2
postcss: 5.2.18
@ -3343,7 +3425,7 @@ packages:
/axios/0.25.0_debug@4.3.4:
resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
dependencies:
follow-redirects: 1.15.2
follow-redirects: 1.15.2_debug@4.3.4
transitivePeerDependencies:
- debug
dev: true
@ -3600,8 +3682,8 @@ packages:
deprecated: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.
hasBin: true
dependencies:
caniuse-db: 1.0.30001443
electron-to-chromium: 1.4.284
caniuse-db: 1.0.30001480
electron-to-chromium: 1.4.368
dev: true
/browserslist/4.21.4:
@ -3826,13 +3908,13 @@ packages:
autobind-decorator: 2.4.0
eventemitter3: 4.0.7
reconnecting-websocket: 4.4.0
semver: 7.3.8
semver: 7.5.0
/call-bind/1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
dependencies:
function-bind: 1.1.1
get-intrinsic: 1.1.3
get-intrinsic: 1.2.0
/callsites/3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
@ -3857,13 +3939,13 @@ packages:
resolution: {integrity: sha512-SBTl70K0PkDUIebbkXrxWqZlHNs0wRgRD6QZ8guctShjbh63gEPfF+Wj0Yw+75f5Y8tSzqAI/NcisYv/cCah2Q==}
dependencies:
browserslist: 1.7.7
caniuse-db: 1.0.30001443
caniuse-db: 1.0.30001480
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
dev: true
/caniuse-db/1.0.30001443:
resolution: {integrity: sha512-4KKthVYyooNIOhO1w0OJ13EhEwOGECMrZdkeyDydhvYXaTDA3WdhR8amoJnAgpSgcCR26aOAWk6N9ANVYlv2oQ==}
/caniuse-db/1.0.30001480:
resolution: {integrity: sha512-DVj/w8brn4D//RjICBR23GP/4y28fk2Bsb3PDN1orV3/7rEB8czfr0TxYBY0weRPwUsj6n9+kMoPxUD7wyvcJg==}
dev: true
/caniuse-lite/1.0.30001443:
@ -4045,8 +4127,8 @@ packages:
engines: {node: '>=6.0'}
dev: true
/ci-info/3.7.1:
resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
/ci-info/3.8.0:
resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==}
engines: {node: '>=8'}
dev: true
@ -4176,7 +4258,7 @@ packages:
dependencies:
inherits: 2.0.4
process-nextick-args: 2.0.1
readable-stream: 2.3.7
readable-stream: 2.3.8
dev: true
/cluster-key-slot/1.1.1:
@ -4294,8 +4376,8 @@ packages:
resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
dev: false
/colorette/2.0.19:
resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
/colorette/2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
dev: true
/colormin/1.1.2:
@ -4372,7 +4454,7 @@ packages:
dependencies:
buffer-from: 1.1.2
inherits: 2.0.4
readable-stream: 2.3.7
readable-stream: 2.3.8
typedarray: 0.0.6
/condense-newlines/0.2.1:
@ -4780,6 +4862,11 @@ packages:
resolution: {integrity: sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==}
requiresBuild: true
/core-js/3.30.1:
resolution: {integrity: sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==}
requiresBuild: true
dev: false
/core-util-is/1.0.2:
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
@ -4960,7 +5047,7 @@ packages:
dependencies:
'@cypress/request': 2.88.11
'@cypress/xvfb': 1.2.4_supports-color@8.1.1
'@types/node': 14.18.36
'@types/node': 14.18.42
'@types/sinonjs__fake-timers': 8.1.1
'@types/sizzle': 2.3.3
arch: 2.2.0
@ -4990,12 +5077,12 @@ packages:
listr2: 3.14.0_enquirer@2.3.6
lodash: 4.17.21
log-symbols: 4.1.0
minimist: 1.2.7
minimist: 1.2.8
ospath: 1.2.2
pretty-bytes: 5.6.0
proxy-from-env: 1.0.0
request-progress: 3.0.0
semver: 7.3.8
semver: 7.5.0
supports-color: 8.1.1
tmp: 0.2.1
untildify: 4.0.0
@ -5241,8 +5328,8 @@ packages:
engines: {node: '>=10'}
dev: false
/define-properties/1.1.4:
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
/define-properties/1.2.0:
resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==}
engines: {node: '>= 0.4'}
dependencies:
has-property-descriptors: 1.0.0
@ -5460,7 +5547,7 @@ packages:
dependencies:
end-of-stream: 1.4.4
inherits: 2.0.4
readable-stream: 2.3.7
readable-stream: 2.3.8
stream-shift: 1.0.1
dev: true
@ -5508,6 +5595,10 @@ packages:
resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
dev: true
/electron-to-chromium/1.4.368:
resolution: {integrity: sha512-e2aeCAixCj9M7nJxdB/wDjO6mbYX+lJJxSJCXDzlr5YPGYVofuJwGN9nKg2o6wWInjX6XmxRinn3AeJMK81ltw==}
dev: true
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -6298,7 +6389,7 @@ packages:
resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==}
dependencies:
inherits: 2.0.4
readable-stream: 2.3.7
readable-stream: 2.3.8
dev: true
/follow-redirects/1.15.2:
@ -6309,6 +6400,19 @@ packages:
peerDependenciesMeta:
debug:
optional: true
dev: false
/follow-redirects/1.15.2_debug@4.3.4:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dependencies:
debug: 4.3.4
dev: true
/for-each/0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@ -6416,7 +6520,7 @@ packages:
engines: {node: '>=10'}
dependencies:
at-least-node: 1.0.0
graceful-fs: 4.2.10
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: true
@ -6439,7 +6543,7 @@ packages:
resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==}
engines: {node: '>= 0.10'}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
through2: 2.0.5
dev: true
@ -6520,6 +6624,14 @@ packages:
function-bind: 1.1.1
has: 1.0.3
has-symbols: 1.0.3
dev: false
/get-intrinsic/1.2.0:
resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==}
dependencies:
function-bind: 1.1.1
has: 1.0.3
has-symbols: 1.0.3
/get-paths/0.0.7:
resolution: {integrity: sha512-0wdJt7C1XKQxuCgouqd+ZvLJ56FQixKoki9MrFaO4EriqzXOiH9gbukaDE1ou08S8Ns3/yDzoBAISNPqj6e6tA==}
@ -6641,7 +6753,7 @@ packages:
is-negated-glob: 1.0.0
ordered-read-streams: 1.0.1
pumpify: 1.5.1
readable-stream: 2.3.7
readable-stream: 2.3.8
remove-trailing-separator: 1.1.0
to-absolute-glob: 2.0.2
unique-stream: 2.3.1
@ -6827,6 +6939,10 @@ packages:
/graceful-fs/4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
/graceful-fs/4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: true
/grapheme-splitter/1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
@ -6876,7 +6992,7 @@ packages:
resolution: {integrity: sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==}
engines: {node: '>=10'}
dependencies:
'@types/node': 18.11.18
'@types/node': 18.15.12
'@types/vinyl': 2.0.7
istextorbinary: 3.3.0
replacestream: 4.0.3
@ -6888,7 +7004,7 @@ packages:
engines: {node: '>=10'}
dependencies:
plugin-error: 1.0.1
terser: 5.16.1
terser: 5.17.1
through2: 4.0.2
vinyl-sourcemaps-apply: 0.2.1
dev: true
@ -6956,7 +7072,7 @@ packages:
/has-property-descriptors/1.0.0:
resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
dependencies:
get-intrinsic: 1.1.3
get-intrinsic: 1.2.0
dev: true
/has-symbol-support-x/1.4.2:
@ -7464,7 +7580,7 @@ packages:
resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
hasBin: true
dependencies:
ci-info: 3.7.1
ci-info: 3.8.0
dev: true
/is-core-module/2.11.0:
@ -7472,6 +7588,12 @@ packages:
dependencies:
has: 1.0.3
/is-core-module/2.12.0:
resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==}
dependencies:
has: 1.0.3
dev: true
/is-data-descriptor/0.1.4:
resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==}
engines: {node: '>=0.10.0'}
@ -7802,8 +7924,8 @@ packages:
engines: {node: '>= 0.6.0'}
dev: false
/joi/17.7.0:
resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==}
/joi/17.9.1:
resolution: {integrity: sha512-FariIi9j6QODKATGBrEX7HZcja8Bsh3rfdGYy/Sb65sGlZWK/QWesU1ghk7aJWDj95knjXlQfSmzFSPPkLVsfw==}
dependencies:
'@hapi/hoek': 9.3.0
'@hapi/topo': 5.1.0
@ -7985,7 +8107,7 @@ packages:
dependencies:
universalify: 2.0.0
optionalDependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
dev: true
/jsonld/6.0.0:
@ -8416,7 +8538,7 @@ packages:
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
engines: {node: '>= 0.6.3'}
dependencies:
readable-stream: 2.3.7
readable-stream: 2.3.8
/lcid/1.0.0:
resolution: {integrity: sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==}
@ -8458,7 +8580,7 @@ packages:
is-plain-object: 2.0.4
object.map: 1.0.1
rechoir: 0.6.2
resolve: 1.22.1
resolve: 1.22.2
transitivePeerDependencies:
- supports-color
dev: true
@ -8477,7 +8599,7 @@ packages:
optional: true
dependencies:
cli-truncate: 2.1.0
colorette: 2.0.19
colorette: 2.0.20
enquirer: 2.3.6
log-update: 4.0.0
p-map: 4.0.0
@ -8512,7 +8634,7 @@ packages:
resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==}
engines: {node: '>=0.10.0'}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
parse-json: 2.2.0
pify: 2.3.0
pinkie-promise: 2.0.1
@ -8780,7 +8902,7 @@ packages:
dependencies:
findup-sync: 2.0.0
micromatch: 3.1.10
resolve: 1.22.1
resolve: 1.22.2
stack-trace: 0.0.10
transitivePeerDependencies:
- supports-color
@ -8906,6 +9028,9 @@ packages:
/minimist/1.2.7:
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
/minimist/1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
/minipass-collect/1.0.2:
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
engines: {node: '>= 8'}
@ -9005,7 +9130,7 @@ packages:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
dependencies:
minimist: 1.2.7
minimist: 1.2.8
/mkdirp/1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
@ -9247,6 +9372,18 @@ packages:
whatwg-url: 5.0.0
dev: false
/node-fetch/2.6.9:
resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: false
/node-fetch/3.3.0:
resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -9319,7 +9456,7 @@ packages:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
hosted-git-info: 2.8.9
resolve: 1.22.1
resolve: 1.22.2
semver: 5.7.1
validate-npm-package-license: 3.0.4
dev: true
@ -9503,7 +9640,7 @@ packages:
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.2
define-properties: 1.1.4
define-properties: 1.2.0
has-symbols: 1.0.3
object-keys: 1.1.1
dev: true
@ -9615,7 +9752,7 @@ packages:
/ordered-read-streams/1.0.1:
resolution: {integrity: sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==}
dependencies:
readable-stream: 2.3.7
readable-stream: 2.3.8
dev: true
/os-filter-obj/2.0.0:
@ -9870,7 +10007,7 @@ packages:
resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==}
engines: {node: '>=0.10.0'}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
pify: 2.3.0
pinkie-promise: 2.0.1
dev: true
@ -10539,6 +10676,10 @@ packages:
resolution: {integrity: sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==}
engines: {node: '>=6'}
/punycode/2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'}
/pureimage/0.3.15:
resolution: {integrity: sha512-QpQYEV8nxVb84en7D0nKXwG0bdmwmlsSg9QnqxpEOExvUXdbmo6Lw/UoxSXD9z+ryvWDkgWqZsIM3iPCAh4dXg==}
engines: {node: '>=0.8'}
@ -10733,6 +10874,17 @@ packages:
string_decoder: 1.1.1
util-deprecate: 1.0.2
/readable-stream/2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
/readable-stream/3.6.0:
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
engines: {node: '>= 6'}
@ -10740,6 +10892,16 @@ packages:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/readable-stream/3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: true
/readable-web-to-node-stream/3.0.2:
resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==}
@ -10764,7 +10926,7 @@ packages:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
dependencies:
resolve: 1.22.1
resolve: 1.22.2
dev: true
/reconnecting-websocket/4.4.0:
@ -10893,7 +11055,7 @@ packages:
dependencies:
escape-string-regexp: 1.0.5
object-assign: 4.1.1
readable-stream: 2.3.7
readable-stream: 2.3.8
dev: true
/request-progress/3.0.0:
@ -10997,6 +11159,15 @@ packages:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
/resolve/1.22.2:
resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
hasBin: true
dependencies:
is-core-module: 2.12.0
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true
/responselike/1.0.2:
resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==}
dependencies:
@ -11106,7 +11277,7 @@ packages:
/rxjs/7.8.0:
resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
dependencies:
tslib: 2.4.1
tslib: 2.5.0
dev: true
/s-age/1.1.2:
@ -11229,6 +11400,13 @@ packages:
dependencies:
lru-cache: 6.0.0
/semver/7.5.0:
resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
/serialize-javascript/6.0.0:
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
dependencies:
@ -11314,7 +11492,7 @@ packages:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
call-bind: 1.0.2
get-intrinsic: 1.1.3
get-intrinsic: 1.2.0
object-inspect: 1.12.3
/sigmund/1.0.1:
@ -11496,11 +11674,11 @@ packages:
engines: {node: '>= 0.10'}
dev: true
/spdx-correct/3.1.1:
resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
/spdx-correct/3.2.0:
resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
dependencies:
spdx-expression-parse: 3.0.1
spdx-license-ids: 3.0.12
spdx-license-ids: 3.0.13
dev: true
/spdx-exceptions/2.3.0:
@ -11511,11 +11689,11 @@ packages:
resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
dependencies:
spdx-exceptions: 2.3.0
spdx-license-ids: 3.0.12
spdx-license-ids: 3.0.13
dev: true
/spdx-license-ids/3.0.12:
resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==}
/spdx-license-ids/3.0.13:
resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==}
dev: true
/speakeasy/2.0.0:
@ -11980,6 +12158,17 @@ packages:
source-map-support: 0.5.21
dev: true
/terser/5.17.1:
resolution: {integrity: sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==}
engines: {node: '>=10'}
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.3
acorn: 8.8.2
commander: 2.20.3
source-map-support: 0.5.21
dev: true
/tesseract.js-core/3.0.2:
resolution: {integrity: sha512-2fD76ka9nO/C616R0fq+M9Zu91DA3vEfyozp0jlxaJOBmpfeprtgRP3cqVweZh2darE1kK/DazoxZ65g7WU99Q==}
dev: false
@ -12060,14 +12249,14 @@ packages:
/through2/2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
dependencies:
readable-stream: 2.3.7
readable-stream: 2.3.8
xtend: 4.0.2
dev: true
/through2/4.0.2:
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
dependencies:
readable-stream: 3.6.0
readable-stream: 3.6.2
dev: true
/time-stamp/1.1.0:
@ -12163,7 +12352,7 @@ packages:
engines: {node: '>=0.8'}
dependencies:
psl: 1.9.0
punycode: 2.2.0
punycode: 2.3.0
/tough-cookie/4.1.2:
resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==}
@ -12270,6 +12459,11 @@ packages:
/tslib/2.4.1:
resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
dev: false
/tslib/2.5.0:
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
dev: true
/tsscmp/1.0.6:
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
@ -12675,7 +12869,7 @@ packages:
/validate-npm-package-license/3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
dependencies:
spdx-correct: 3.1.1
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
dev: true
@ -12711,13 +12905,13 @@ packages:
dependencies:
fs-mkdirp-stream: 1.0.0
glob-stream: 6.1.0
graceful-fs: 4.2.10
graceful-fs: 4.2.11
is-valid-glob: 1.0.0
lazystream: 1.0.1
lead: 1.0.0
object.assign: 4.1.4
pumpify: 1.5.1
readable-stream: 2.3.7
readable-stream: 2.3.8
remove-bom-buffer: 3.0.0
remove-bom-stream: 1.2.0
resolve-options: 1.1.0
@ -12734,7 +12928,7 @@ packages:
dependencies:
append-buffer: 1.0.2
convert-source-map: 1.9.0
graceful-fs: 4.2.10
graceful-fs: 4.2.11
normalize-path: 2.1.1
now-and-later: 2.0.1
remove-bom-buffer: 3.0.0
@ -12858,9 +13052,9 @@ packages:
hasBin: true
dependencies:
axios: 0.25.0_debug@4.3.4
joi: 17.7.0
joi: 17.9.1
lodash: 4.17.21
minimist: 1.2.7
minimist: 1.2.8
rxjs: 7.8.0
transitivePeerDependencies:
- debug