Removed Mastodon API and messaging
This commit is contained in:
parent
c398acc9e5
commit
12f0bd4e2d
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -9,8 +9,6 @@ export const kinds = [
|
|||
"write:favorites",
|
||||
"read:following",
|
||||
"write:following",
|
||||
"read:messaging",
|
||||
"write:messaging",
|
||||
"read:mutes",
|
||||
"write:mutes",
|
||||
"write:notes",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
|
@ -424,11 +424,6 @@ export const packedMeDetailedOnlySchema = {
|
|||
nullable: false,
|
||||
optional: false,
|
||||
},
|
||||
hasUnreadMessagingMessage: {
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
optional: false,
|
||||
},
|
||||
hasUnreadNotification: {
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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})`;
|
||||
};
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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],
|
||||
|
|
|
@ -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");
|
||||
});
|
|
@ -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)),
|
||||
);
|
||||
});
|
|
@ -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,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
|
@ -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,
|
||||
);
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -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: [],
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''')
|
||||
}
|
||||
function nl2br(str: string) {
|
||||
if (!str) {
|
||||
return ''
|
||||
}
|
||||
str = str.replace(/\r\n/g, '<br />')
|
||||
str = str.replace(/(\n|\r)/g, '<br />')
|
||||
return str
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,106 +119,16 @@ 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);
|
||||
|
@ -278,16 +162,6 @@ export default class Connection {
|
|||
this.onChannelMessageRequested(body);
|
||||
break; // alias
|
||||
|
||||
// 個々のチャンネルではなくルートレベルでこれらのメッセージを受け取る理由は、
|
||||
// クライアントの事情を考慮したとき、入力フォームはノートチャンネルやメッセージのメインコンポーネントとは別
|
||||
// なこともあるため、それらのコンポーネントがそれぞれ各チャンネルに接続するようにするのは面倒なため。
|
||||
case "typingOnChannel":
|
||||
this.typingOnChannel(body.channel);
|
||||
break;
|
||||
case "typingOnMessaging":
|
||||
this.typingOnMessaging(body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,68 +265,6 @@ 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,
|
||||
|
@ -460,7 +272,6 @@ export default class Connection {
|
|||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* チャンネルに接続
|
||||
|
@ -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: {
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 };
|
||||
};
|
||||
|
||||
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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")),
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -86,7 +86,6 @@ export const defaultStore = markRaw(
|
|||
"notifications",
|
||||
undefined,
|
||||
"followRequests",
|
||||
"messaging",
|
||||
"explore",
|
||||
"favorites",
|
||||
"channels",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
// クエリを作成しておく
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<
|
||||
|
|
358
pnpm-lock.yaml
358
pnpm-lock.yaml
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue