Removed calckey-js and removed channels
ci/woodpecker/push/ociImageTag Pipeline failed Details
ci/woodpecker/tag/ociImageTag Pipeline is pending Details

This commit is contained in:
Natty 2023-07-20 22:57:13 +02:00
parent b58917aabb
commit b04076fbf0
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
163 changed files with 582 additions and 27206 deletions

View File

@ -8,7 +8,6 @@ RUN apk add --no-cache --no-progress git alpine-sdk python3 nodejs-current npm v
# Copy only the dependency-related files first, to cache efficiently
COPY package.json pnpm*.yaml ./
COPY packages/backend/package.json packages/backend/package.json
COPY packages/calckey-js/package.json packages/calckey-js/package.json
# Configure corepack and pnpm, and install dev mode dependencies for compilation
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile
@ -32,7 +31,6 @@ COPY . ./
# Copy node modules
COPY --from=build /calckey/node_modules /calckey/node_modules
COPY --from=build /calckey/packages/backend/node_modules /calckey/packages/backend/node_modules
COPY --from=build /calckey/packages/calckey-js/node_modules /calckey/packages/calckey-js/node_modules
# Copy the finished compiled files
COPY --from=build /calckey/packages/backend/built /calckey/packages/backend/built

View File

@ -36,7 +36,6 @@
"cross-env": "7.0.3",
"execa": "5.1.1",
"install-peers": "^1.0.4",
"rome": "^12.1.3",
"start-server-and-test": "1.15.2",
"typescript": "4.9.4"
}

View File

@ -1,9 +0,0 @@
# 📦 Packages
This directory contains all of the packages Calckey uses.
- `backend`: Main backend code written in TypeScript for NodeJS
- `backend/native-utils`: Backend code written in Rust, bound to NodeJS by [NAPI-RS](https://napi.rs/)
- `client`: Web interface written in Vue3 and TypeScript
- `sw`: Web [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) written in TypeScript
- `calckey-js`: TypeScript SDK for both backend and client, also published on [NPM](https://www.npmjs.com/package/calckey-js) for public use

View File

@ -7,10 +7,7 @@
"start": "pnpm node ./built/index.js",
"start:test": "NODE_ENV=test pnpm node ./built/index.js",
"build": "pnpm swc src -d built -D",
"watch": "pnpm swc src -d built -D -w",
"lint": "pnpm rome check --apply *",
"test": "pnpm run mocha",
"format": "pnpm rome format * --write"
"watch": "pnpm swc src -d built -D -w"
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11"
@ -41,7 +38,6 @@
"blurhash": "1.1.5",
"bull": "4.10.4",
"cacheable-lookup": "7.0.0",
"calckey-js": "workspace:*",
"cbor": "8.1.0",
"chalk": "5.2.0",
"chalk-template": "0.4.0",

View File

@ -130,7 +130,6 @@ export type MeilisearchNote = {
userId: string;
userHost: string;
userName: string;
channelId: string;
mediaAttachment: string;
createdAt: number;
};
@ -346,7 +345,6 @@ export default hasConfig
note.userHost !== ""
? note.userHost
: url.parse(config.host).host,
channelId: note.channelId ? note.channelId : "",
mediaAttachment: attachmentType,
userName: note.user?.username ?? "UNKNOWN",
createdAt: note.createdAt.getTime() / 1000, // division by 1000 is necessary because Node returns in ms-accuracy

View File

@ -60,9 +60,6 @@ import {PromoNote} from "@/models/entities/promo-note.js";
import {PromoRead} from "@/models/entities/promo-read.js";
import {Relay} from "@/models/entities/relay.js";
import {MutedNote} from "@/models/entities/muted-note.js";
import {Channel} from "@/models/entities/channel.js";
import {ChannelFollowing} from "@/models/entities/channel-following.js";
import {ChannelNotePining} from "@/models/entities/channel-note-pining.js";
import {RegistryItem} from "@/models/entities/registry-item.js";
import {Ad} from "@/models/entities/ad.js";
import {PasswordResetRequest} from "@/models/entities/password-reset-request.js";
@ -169,9 +166,6 @@ export const entities = [
PromoRead,
Relay,
MutedNote,
Channel,
ChannelFollowing,
ChannelNotePining,
RegistryItem,
Ad,
PasswordResetRequest,

View File

@ -23,8 +23,6 @@ export const kinds = [
"read:page-likes",
"read:user-groups",
"write:user-groups",
"read:channels",
"write:channels",
"read:gallery",
"write:gallery",
"read:gallery-likes",

View File

@ -1,10 +1,10 @@
import {
packedUserLiteSchema,
packedUserDetailedNotMeOnlySchema,
packedMeDetailedOnlySchema,
packedUserDetailedNotMeSchema,
packedMeDetailedSchema,
packedUserDetailedNotMeOnlySchema,
packedUserDetailedNotMeSchema,
packedUserDetailedSchema,
packedUserLiteSchema,
packedUserSchema,
} from "@/models/schema/user.js";
import {packedNoteSchema} from "@/models/schema/note.js";
@ -22,7 +22,6 @@ import { packedHashtagSchema } from "@/models/schema/hashtag.js";
import {packedPageSchema} from "@/models/schema/page.js";
import {packedUserGroupSchema} from "@/models/schema/user-group.js";
import {packedNoteFavoriteSchema} from "@/models/schema/note-favorite.js";
import { packedChannelSchema } from "@/models/schema/channel.js";
import {packedAntennaSchema} from "@/models/schema/antenna.js";
import {packedClipSchema} from "@/models/schema/clip.js";
import {packedFederationInstanceSchema} from "@/models/schema/federation-instance.js";
@ -56,7 +55,6 @@ export const refs = {
Blocking: packedBlockingSchema,
Hashtag: packedHashtagSchema,
Page: packedPageSchema,
Channel: packedChannelSchema,
QueueCount: packedQueueCountSchema,
Antenna: packedAntennaSchema,
Clip: packedClipSchema,

View File

@ -1,50 +0,0 @@
import {
PrimaryColumn,
Entity,
Index,
JoinColumn,
Column,
ManyToOne,
} from "typeorm";
import { User } from "./user.js";
import { id } from "../id.js";
import { Channel } from "./channel.js";
@Entity()
@Index(["followerId", "followeeId"], { unique: true })
export class ChannelFollowing {
@PrimaryColumn(id())
public id: string;
@Index()
@Column("timestamp with time zone", {
comment: "The created date of the ChannelFollowing.",
})
public createdAt: Date;
@Index()
@Column({
...id(),
comment: "The followee channel ID.",
})
public followeeId: Channel["id"];
@ManyToOne((type) => Channel, {
onDelete: "CASCADE",
})
@JoinColumn()
public followee: Channel | null;
@Index()
@Column({
...id(),
comment: "The follower user ID.",
})
public followerId: User["id"];
@ManyToOne((type) => User, {
onDelete: "CASCADE",
})
@JoinColumn()
public follower: User | null;
}

View File

@ -1,42 +0,0 @@
import {
PrimaryColumn,
Entity,
Index,
JoinColumn,
Column,
ManyToOne,
} from "typeorm";
import { Note } from "./note.js";
import { Channel } from "./channel.js";
import { id } from "../id.js";
@Entity()
@Index(["channelId", "noteId"], { unique: true })
export class ChannelNotePining {
@PrimaryColumn(id())
public id: string;
@Column("timestamp with time zone", {
comment: "The created date of the ChannelNotePining.",
})
public createdAt: Date;
@Index()
@Column(id())
public channelId: Channel["id"];
@ManyToOne((type) => Channel, {
onDelete: "CASCADE",
})
@JoinColumn()
public channel: Channel | null;
@Column(id())
public noteId: Note["id"];
@ManyToOne((type) => Note, {
onDelete: "CASCADE",
})
@JoinColumn()
public note: Note | null;
}

View File

@ -1,83 +0,0 @@
import {
PrimaryColumn,
Entity,
Index,
JoinColumn,
Column,
ManyToOne,
} from "typeorm";
import { User } from "./user.js";
import { id } from "../id.js";
import { DriveFile } from "./drive-file.js";
@Entity()
export class Channel {
@PrimaryColumn(id())
public id: string;
@Index()
@Column("timestamp with time zone", {
comment: "The created date of the Channel.",
})
public createdAt: Date;
@Index()
@Column("timestamp with time zone", {
nullable: true,
})
public lastNotedAt: Date | null;
@Index()
@Column({
...id(),
nullable: true,
comment: "The owner ID.",
})
public userId: User["id"] | null;
@ManyToOne((type) => User, {
onDelete: "SET NULL",
})
@JoinColumn()
public user: User | null;
@Column("varchar", {
length: 128,
comment: "The name of the Channel.",
})
public name: string;
@Column("varchar", {
length: 2048,
nullable: true,
comment: "The description of the Channel.",
})
public description: string | null;
@Column({
...id(),
nullable: true,
comment: "The ID of banner Channel.",
})
public bannerId: DriveFile["id"] | null;
@ManyToOne((type) => DriveFile, {
onDelete: "SET NULL",
})
@JoinColumn()
public banner: DriveFile | null;
@Index()
@Column("integer", {
default: 0,
comment: "The count of notes.",
})
public notesCount: number;
@Index()
@Column("integer", {
default: 0,
comment: "The count of users.",
})
public usersCount: number;
}

View File

@ -1,4 +1,4 @@
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from "typeorm";
import {Column, Entity, JoinColumn, ManyToOne, PrimaryColumn} from "typeorm";
import {id} from "../id.js";
import {User} from "./user.js";
import type {Clip} from "./clip.js";
@ -143,7 +143,7 @@ export class Meta {
@Column("varchar", {
length: 512,
array: true,
default: "{/featured,/channels,/explore,/pages,/about-calckey}",
default: "{/featured,/explore,/pages,/about-calckey}",
})
public pinnedPages: string[];

View File

@ -1,15 +1,7 @@
import {
PrimaryColumn,
Entity,
Index,
JoinColumn,
Column,
ManyToOne,
} from "typeorm";
import {Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn,} from "typeorm";
import {User} from "./user.js";
import {Note} from "./note.js";
import {id} from "../id.js";
import type { Channel } from "./channel.js";
@Entity()
@Index(["userId", "noteId"], { unique: true })
@ -58,13 +50,5 @@ export class NoteUnread {
comment: "[Denormalized]",
})
public noteUserId: User["id"];
@Index()
@Column({
...id(),
nullable: true,
comment: "[Denormalized]",
})
public noteChannelId: Channel["id"] | null;
//#endregion
}

View File

@ -1,16 +1,8 @@
import {
Entity,
Index,
JoinColumn,
Column,
PrimaryColumn,
ManyToOne,
} from "typeorm";
import {Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn,} from "typeorm";
import {User} from "./user.js";
import type {DriveFile} from "./drive-file.js";
import {id} from "../id.js";
import {noteVisibilities} from "../../types.js";
import { Channel } from "./channel.js";
@Entity()
@Index("IDX_NOTE_TAGS", { synchronize: false })
@ -200,20 +192,6 @@ export class Note {
})
public hasPoll: boolean;
@Index()
@Column({
...id(),
nullable: true,
comment: "The ID of source channel.",
})
public channelId: Channel["id"] | null;
@ManyToOne((type) => Channel, {
onDelete: "CASCADE",
})
@JoinColumn()
public channel: Channel | null;
//#region Denormalized fields
@Index()
@Column("varchar", {

View File

@ -1,9 +1,7 @@
import {} from "typeorm";
import {db} from "@/db/postgre.js";
import {Announcement} from "./entities/announcement.js";
import {AnnouncementRead} from "./entities/announcement-read.js";
import { Instance } from "./entities/instance.js";
import {Poll} from "./entities/poll.js";
import {PollVote} from "./entities/poll-vote.js";
import {Meta} from "./entities/meta.js";
@ -55,10 +53,7 @@ import { PromoNote } from "./entities/promo-note.js";
import {PromoRead} from "./entities/promo-read.js";
import {EmojiRepository} from "./repositories/emoji.js";
import {RelayRepository} from "./repositories/relay.js";
import { ChannelRepository } from "./repositories/channel.js";
import {MutedNote} from "./entities/muted-note.js";
import { ChannelFollowing } from "./entities/channel-following.js";
import { ChannelNotePining } from "./entities/channel-note-pining.js";
import {RegistryItem} from "./entities/registry-item.js";
import {Ad} from "./entities/ad.js";
import {PasswordResetRequest} from "./entities/password-reset-request.js";
@ -126,9 +121,6 @@ export const PromoNotes = db.getRepository(PromoNote);
export const PromoReads = db.getRepository(PromoRead);
export const Relays = RelayRepository;
export const MutedNotes = db.getRepository(MutedNote);
export const Channels = ChannelRepository;
export const ChannelFollowings = db.getRepository(ChannelFollowing);
export const ChannelNotePinings = db.getRepository(ChannelNotePining);
export const RegistryItems = db.getRepository(RegistryItem);
export const Webhooks = db.getRepository(Webhook);
export const Ads = db.getRepository(Ad);

View File

@ -1,55 +0,0 @@
import { db } from "@/db/postgre.js";
import { Channel } from "@/models/entities/channel.js";
import type { Packed } from "@/misc/schema.js";
import { DriveFiles, ChannelFollowings, NoteUnreads } from "../index.js";
import type { User } from "@/models/entities/user.js";
export const ChannelRepository = db.getRepository(Channel).extend({
async pack(
src: Channel["id"] | Channel,
me?: { id: User["id"] } | null | undefined,
): Promise<Packed<"Channel">> {
const channel =
typeof src === "object" ? src : await this.findOneByOrFail({ id: src });
const meId = me ? me.id : null;
const banner = channel.bannerId
? await DriveFiles.findOneBy({ id: channel.bannerId })
: null;
const hasUnreadNote = meId
? (await NoteUnreads.findOneBy({
noteChannelId: channel.id,
userId: meId,
})) != null
: undefined;
const following = meId
? await ChannelFollowings.findOneBy({
followerId: meId,
followeeId: channel.id,
})
: null;
return {
id: channel.id,
createdAt: channel.createdAt.toISOString(),
lastNotedAt: channel.lastNotedAt
? channel.lastNotedAt.toISOString()
: null,
name: channel.name,
description: channel.description,
userId: channel.userId,
bannerUrl: banner ? DriveFiles.getPublicUrl(banner, false) : null,
usersCount: channel.usersCount,
notesCount: channel.notesCount,
...(me
? {
isFollowing: following != null,
hasUnreadNote,
}
: {}),
};
},
});

View File

@ -2,29 +2,13 @@ import { In } from "typeorm";
import * as mfm from "mfm-js";
import {Note} from "@/models/entities/note.js";
import type {User} from "@/models/entities/user.js";
import {
Users,
PollVotes,
DriveFiles,
NoteReactions,
Followings,
Polls,
Channels,
} from "../index.js";
import {DriveFiles, Followings, NoteReactions, Polls, PollVotes, Users,} from "../index.js";
import type {Packed} from "@/misc/schema.js";
import {nyaize} from "@/misc/nyaize.js";
import {awaitAll} from "@/prelude/await-all.js";
import {
convertLegacyReaction,
convertLegacyReactions,
decodeReaction,
} from "@/misc/reaction-lib.js";
import {convertLegacyReaction, convertLegacyReactions, decodeReaction,} from "@/misc/reaction-lib.js";
import type {NoteReaction} from "@/models/entities/note-reaction.js";
import {
aggregateNoteEmojis,
populateEmojis,
prefetchEmojis,
} from "@/misc/populate-emojis.js";
import {aggregateNoteEmojis, populateEmojis, prefetchEmojis,} from "@/misc/populate-emojis.js";
import {db} from "@/db/postgre.js";
import {IdentifiableError} from "@/misc/identifiable-error.js";
@ -186,12 +170,6 @@ export const NoteRepository = db.getRepository(Note).extend({
}`;
}
const channel = note.channelId
? note.channel
? note.channel
: await Channels.findOneBy({ id: note.channelId })
: null;
const reactionEmojiNames = Object.keys(note.reactions)
.filter((x) => x?.startsWith(":"))
.map((x) => decodeReaction(x).reaction)
@ -225,13 +203,6 @@ export const NoteRepository = db.getRepository(Note).extend({
files: DriveFiles.packMany(note.fileIds),
replyId: note.replyId,
renoteId: note.renoteId,
channelId: note.channelId || undefined,
channel: channel
? {
id: channel.id,
name: channel.name,
}
: undefined,
mentions: note.mentions.length > 0 ? note.mentions : undefined,
uri: note.uri || undefined,
url: note.url || undefined,

View File

@ -1,4 +1,3 @@
import { URL } from "url";
import {In, Not} from "typeorm";
import Ajv from "ajv";
import type {ILocalUser, IRemoteUser} from "@/models/entities/user.js";
@ -12,7 +11,7 @@ import { getAntennas } from "@/misc/antenna-cache.js";
import {USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD} from "@/const.js";
import {Cache} from "@/misc/cache.js";
import {db} from "@/db/postgre.js";
import { isActor, getApId } from "@/remote/activitypub/type.js";
import {getApId, isActor} from "@/remote/activitypub/type.js";
import DbResolver from "@/remote/activitypub/db-resolver.js";
import Resolver from "@/remote/activitypub/resolver.js";
import {createPerson} from "@/remote/activitypub/models/person.js";
@ -21,18 +20,16 @@ import {
Announcements,
AntennaNotes,
Blockings,
ChannelFollowings,
DriveFiles,
Followings,
FollowRequests,
Instances,
Mutings,
RenoteMutings,
Notes,
NoteUnreads,
Notifications,
Pages,
UserGroupJoinings,
RenoteMutings,
UserNotePinings,
UserProfiles,
UserSecurityKeys,
@ -238,20 +235,6 @@ export const UserRepository = db.getRepository(User).extend({
}
},
async getHasUnreadChannel(userId: User["id"]): Promise<boolean> {
const channels = await ChannelFollowings.findBy({ followerId: userId });
const unread =
channels.length > 0
? await NoteUnreads.findOneBy({
userId: userId,
noteChannelId: In(channels.map((x) => x.followeeId)),
})
: null;
return unread != null;
},
async getHasUnreadNotification(userId: User["id"]): Promise<boolean> {
const mute = await Mutings.findBy({
muterId: userId,
@ -518,7 +501,6 @@ export const UserRepository = db.getRepository(User).extend({
}).then((count) => count > 0),
hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
hasUnreadChannel: this.getHasUnreadChannel(user.id),
hasUnreadNotification: this.getHasUnreadNotification(user.id),
hasPendingReceivedFollowRequest:
this.getHasPendingReceivedFollowRequest(user.id),

View File

@ -1,61 +0,0 @@
export const packedChannelSchema = {
type: "object",
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id",
example: "xxxxxxxxxx",
},
createdAt: {
type: "string",
optional: false,
nullable: false,
format: "date-time",
},
lastNotedAt: {
type: "string",
optional: false,
nullable: true,
format: "date-time",
},
name: {
type: "string",
optional: false,
nullable: false,
},
description: {
type: "string",
nullable: true,
optional: false,
},
bannerUrl: {
type: "string",
format: "url",
nullable: true,
optional: false,
},
notesCount: {
type: "number",
nullable: false,
optional: false,
},
usersCount: {
type: "number",
nullable: false,
optional: false,
},
isFollowing: {
type: "boolean",
optional: true,
nullable: false,
},
userId: {
type: "string",
nullable: true,
optional: false,
format: "id",
},
},
} as const;

View File

@ -126,35 +126,6 @@ export const packedNoteSchema = {
optional: true,
nullable: true,
},
channelId: {
type: "string",
optional: true,
nullable: true,
format: "id",
example: "xxxxxxxxxx",
},
channel: {
type: "object",
optional: true,
nullable: true,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
},
name: {
type: "string",
optional: false,
nullable: true,
},
},
},
},
localOnly: {
type: "boolean",
optional: true,

View File

@ -54,7 +54,6 @@ export async function importMastoPost(
localOnly: false,
visibility: "hidden",
visibleUsers: [],
channel: null,
apMentions: new Array(0),
apHashtags: undefined,
apEmojis: undefined,

View File

@ -1,35 +0,0 @@
import type { User } from "@/models/entities/user.js";
import { ChannelFollowings } from "@/models/index.js";
import type { SelectQueryBuilder } from "typeorm";
import { Brackets } from "typeorm";
export function generateChannelQuery(
q: SelectQueryBuilder<any>,
me?: { id: User["id"] } | null,
) {
if (me == null) {
q.andWhere("note.channelId IS NULL");
} else {
q.leftJoinAndSelect("note.channel", "channel");
const channelFollowingQuery = ChannelFollowings.createQueryBuilder(
"channelFollowing",
)
.select("channelFollowing.followeeId")
.where("channelFollowing.followerId = :followerId", {
followerId: me.id,
});
q.andWhere(
new Brackets((qb) => {
qb
// チャンネルのノートではない
.where("note.channelId IS NULL")
// または自分がフォローしているチャンネルのノート
.orWhere(`note.channelId IN (${channelFollowingQuery.getQuery()})`);
}),
);
q.setParameters(channelFollowingQuery.getParameters());
}
}

View File

@ -86,16 +86,6 @@ import * as ep___auth_session_userkey from "./endpoints/auth/session/userkey.js"
import * as ep___blocking_create from "./endpoints/blocking/create.js";
import * as ep___blocking_delete from "./endpoints/blocking/delete.js";
import * as ep___blocking_list from "./endpoints/blocking/list.js";
import * as ep___channels_create from "./endpoints/channels/create.js";
import * as ep___channels_featured from "./endpoints/channels/featured.js";
import * as ep___channels_follow from "./endpoints/channels/follow.js";
import * as ep___channels_followed from "./endpoints/channels/followed.js";
import * as ep___channels_owned from "./endpoints/channels/owned.js";
import * as ep___channels_search from "./endpoints/channels/search.js";
import * as ep___channels_show from "./endpoints/channels/show.js";
import * as ep___channels_timeline from "./endpoints/channels/timeline.js";
import * as ep___channels_unfollow from "./endpoints/channels/unfollow.js";
import * as ep___channels_update from "./endpoints/channels/update.js";
import * as ep___charts_activeUsers from "./endpoints/charts/active-users.js";
import * as ep___charts_apRequest from "./endpoints/charts/ap-request.js";
import * as ep___charts_drive from "./endpoints/charts/drive.js";
@ -432,16 +422,6 @@ const eps = [
["blocking/create", ep___blocking_create],
["blocking/delete", ep___blocking_delete],
["blocking/list", ep___blocking_list],
["channels/create", ep___channels_create],
["channels/featured", ep___channels_featured],
["channels/follow", ep___channels_follow],
["channels/followed", ep___channels_followed],
["channels/owned", ep___channels_owned],
["channels/search", ep___channels_search],
["channels/show", ep___channels_show],
["channels/timeline", ep___channels_timeline],
["channels/unfollow", ep___channels_unfollow],
["channels/update", ep___channels_update],
["charts/active-users", ep___charts_activeUsers],
["charts/ap-request", ep___charts_apRequest],
["charts/drive", ep___charts_drive],

View File

@ -1,68 +0,0 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Channels, DriveFiles } from "@/models/index.js";
import type { Channel } from "@/models/entities/channel.js";
import { genId } from "@/misc/gen-id.js";
export const meta = {
tags: ["channels"],
requireCredential: true,
kind: "write:channels",
res: {
type: "object",
optional: false,
nullable: false,
ref: "Channel",
},
errors: {
noSuchFile: {
message: "No such file.",
code: "NO_SUCH_FILE",
id: "cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
name: { type: "string", minLength: 1, maxLength: 128 },
description: {
type: "string",
nullable: true,
minLength: 1,
maxLength: 2048,
},
bannerId: { type: "string", format: "misskey:id", nullable: true },
},
required: ["name"],
} as const;
export default define(meta, paramDef, async (ps, user) => {
let banner = null;
if (ps.bannerId != null) {
banner = await DriveFiles.findOneBy({
id: ps.bannerId,
userId: user.id,
});
if (banner == null) {
throw new ApiError(meta.errors.noSuchFile);
}
}
const channel = await Channels.insert({
id: genId(),
createdAt: new Date(),
userId: user.id,
name: ps.name,
description: ps.description || null,
bannerId: banner ? banner.id : null,
} as Channel).then((x) => Channels.findOneByOrFail(x.identifiers[0]));
return await Channels.pack(channel, user);
});

View File

@ -1,37 +0,0 @@
import define from "../../define.js";
import { Channels } from "@/models/index.js";
export const meta = {
tags: ["channels"],
requireCredential: false,
requireCredentialPrivateMode: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Channel",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {},
required: [],
} as const;
export default define(meta, paramDef, async (ps, me) => {
const query = Channels.createQueryBuilder("channel")
.where("channel.lastNotedAt IS NOT NULL")
.orderBy("channel.lastNotedAt", "DESC");
const channels = await query.take(10).getMany();
return await Promise.all(channels.map((x) => Channels.pack(x, me)));
});

View File

@ -1,48 +0,0 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Channels, ChannelFollowings } from "@/models/index.js";
import { genId } from "@/misc/gen-id.js";
import { publishUserEvent } from "@/services/stream.js";
export const meta = {
tags: ["channels"],
requireCredential: true,
kind: "write:channels",
errors: {
noSuchChannel: {
message: "No such channel.",
code: "NO_SUCH_CHANNEL",
id: "c0031718-d573-4e85-928e-10039f1fbb68",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
channelId: { type: "string", format: "misskey:id" },
},
required: ["channelId"],
} as const;
export default define(meta, paramDef, async (ps, user) => {
const channel = await Channels.findOneBy({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
await ChannelFollowings.insert({
id: genId(),
createdAt: new Date(),
followerId: user.id,
followeeId: channel.id,
});
publishUserEvent(user.id, "followChannel", channel);
});

View File

@ -1,59 +0,0 @@
import define from "../../define.js";
import { Channels, ChannelFollowings } from "@/models/index.js";
export const meta = {
tags: ["channels", "account"],
requireCredential: true,
kind: "read:channels",
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Channel",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
sinceId: { type: "string", format: "misskey:id" },
untilId: { type: "string", format: "misskey:id" },
limit: { type: "integer", minimum: 1, maximum: 100, default: 5 },
},
required: [],
} as const;
export default define(meta, paramDef, async (ps, me) => {
const query = ChannelFollowings.createQueryBuilder("following").andWhere({
followerId: me.id,
});
if (ps.sinceId) {
query.andWhere('following."followeeId" > :sinceId', {
sinceId: ps.sinceId,
});
}
if (ps.untilId) {
query.andWhere('following."followeeId" < :untilId', {
untilId: ps.untilId,
});
}
if (ps.sinceId && !ps.untilId) {
query.orderBy('following."followeeId"', "ASC");
} else {
query.orderBy('following."followeeId"', "DESC");
}
const followings = await query.take(ps.limit).getMany();
return await Promise.all(
followings.map((x) => Channels.pack(x.followeeId, me)),
);
});

View File

@ -1,45 +0,0 @@
import define from "../../define.js";
import { Channels } from "@/models/index.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
export const meta = {
tags: ["channels", "account"],
requireCredential: true,
kind: "read:channels",
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Channel",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
sinceId: { type: "string", format: "misskey:id" },
untilId: { type: "string", format: "misskey:id" },
limit: { type: "integer", minimum: 1, maximum: 100, default: 5 },
},
required: [],
} as const;
export default define(meta, paramDef, async (ps, me) => {
const query = makePaginationQuery(
Channels.createQueryBuilder(),
ps.sinceId,
ps.untilId,
).andWhere({ userId: me.id });
const channels = await query.take(ps.limit).getMany();
return await Promise.all(channels.map((x) => Channels.pack(x, me)));
});

View File

@ -1,69 +0,0 @@
import define from "../../define.js";
import { Brackets } from "typeorm";
import { Endpoint } from "@/server/api/endpoint-base.js";
import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js";
import { Channels } from "@/models/index.js";
import { DI } from "@/di-symbols.js";
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
export const meta = {
tags: ["channels"],
requireCredential: false,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Channel",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
query: { type: "string" },
type: {
type: "string",
enum: ["nameAndDescription", "nameOnly"],
default: "nameAndDescription",
},
sinceId: { type: "string", format: "misskey:id" },
untilId: { type: "string", format: "misskey:id" },
limit: { type: "integer", minimum: 1, maximum: 100, default: 5 },
},
required: ["query"],
} as const;
export default define(meta, paramDef, async (ps, me) => {
const query = makePaginationQuery(
Channels.createQueryBuilder("channel"),
ps.sinceId,
ps.untilId,
);
if (ps.type === "nameAndDescription") {
query.andWhere(
new Brackets((qb) => {
qb.where("channel.name ILIKE :q", {
q: `%${sqlLikeEscape(ps.query)}%`,
}).orWhere("channel.description ILIKE :q", {
q: `%${sqlLikeEscape(ps.query)}%`,
});
}),
);
} else {
query.andWhere("channel.name ILIKE :q", {
q: `%${sqlLikeEscape(ps.query)}%`,
});
}
const channels = await query.take(ps.limit).getMany();
return await Promise.all(channels.map((x) => Channels.pack(x, me)));
});

View File

@ -1,45 +0,0 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Channels } from "@/models/index.js";
export const meta = {
tags: ["channels"],
requireCredential: false,
requireCredentialPrivateMode: true,
res: {
type: "object",
optional: false,
nullable: false,
ref: "Channel",
},
errors: {
noSuchChannel: {
message: "No such channel.",
code: "NO_SUCH_CHANNEL",
id: "6f6c314b-7486-4897-8966-c04a66a02923",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
channelId: { type: "string", format: "misskey:id" },
},
required: ["channelId"],
} as const;
export default define(meta, paramDef, async (ps, me) => {
const channel = await Channels.findOneBy({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
return await Channels.pack(channel, me);
});

View File

@ -1,84 +0,0 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Notes, Channels } from "@/models/index.js";
import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { activeUsersChart } from "@/services/chart/index.js";
export const meta = {
tags: ["notes", "channels"],
requireCredential: false,
requireCredentialPrivateMode: true,
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
ref: "Note",
},
},
errors: {
noSuchChannel: {
message: "No such channel.",
code: "NO_SUCH_CHANNEL",
id: "4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
channelId: { type: "string", format: "misskey:id" },
limit: { type: "integer", minimum: 1, maximum: 100, default: 10 },
sinceId: { type: "string", format: "misskey:id" },
untilId: { type: "string", format: "misskey:id" },
sinceDate: { type: "integer" },
untilDate: { type: "integer" },
},
required: ["channelId"],
} as const;
export default define(meta, paramDef, async (ps, user) => {
const channel = await Channels.findOneBy({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
//#region Construct query
const query = makePaginationQuery(
Notes.createQueryBuilder("note"),
ps.sinceId,
ps.untilId,
ps.sinceDate,
ps.untilDate,
)
.andWhere("note.channelId = :channelId", { channelId: channel.id })
.innerJoinAndSelect("note.user", "user")
.leftJoinAndSelect("user.avatar", "avatar")
.leftJoinAndSelect("user.banner", "banner")
.leftJoinAndSelect("note.reply", "reply")
.leftJoinAndSelect("note.renote", "renote")
.leftJoinAndSelect("reply.user", "replyUser")
.leftJoinAndSelect("replyUser.avatar", "replyUserAvatar")
.leftJoinAndSelect("replyUser.banner", "replyUserBanner")
.leftJoinAndSelect("renote.user", "renoteUser")
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner")
.leftJoinAndSelect("note.channel", "channel");
//#endregion
const timeline = await query.take(ps.limit).getMany();
if (user) activeUsersChart.read(user);
return await Notes.packMany(timeline, user);
});

View File

@ -1,45 +0,0 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Channels, ChannelFollowings } from "@/models/index.js";
import { publishUserEvent } from "@/services/stream.js";
export const meta = {
tags: ["channels"],
requireCredential: true,
kind: "write:channels",
errors: {
noSuchChannel: {
message: "No such channel.",
code: "NO_SUCH_CHANNEL",
id: "19959ee9-0153-4c51-bbd9-a98c49dc59d6",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
channelId: { type: "string", format: "misskey:id" },
},
required: ["channelId"],
} as const;
export default define(meta, paramDef, async (ps, user) => {
const channel = await Channels.findOneBy({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
await ChannelFollowings.delete({
followerId: user.id,
followeeId: channel.id,
});
publishUserEvent(user.id, "unfollowChannel", channel);
});

View File

@ -1,90 +0,0 @@
import define from "../../define.js";
import { ApiError } from "../../error.js";
import { Channels, DriveFiles } from "@/models/index.js";
export const meta = {
tags: ["channels"],
requireCredential: true,
kind: "write:channels",
res: {
type: "object",
optional: false,
nullable: false,
ref: "Channel",
},
errors: {
noSuchChannel: {
message: "No such channel.",
code: "NO_SUCH_CHANNEL",
id: "f9c5467f-d492-4c3c-9a8d-a70dacc86512",
},
accessDenied: {
message: "You do not have edit privilege of the channel.",
code: "ACCESS_DENIED",
id: "1fb7cb09-d46a-4fdf-b8df-057788cce513",
},
noSuchFile: {
message: "No such file.",
code: "NO_SUCH_FILE",
id: "e86c14a4-0da2-4032-8df3-e737a04c7f3b",
},
},
} as const;
export const paramDef = {
type: "object",
properties: {
channelId: { type: "string", format: "misskey:id" },
name: { type: "string", minLength: 1, maxLength: 128 },
description: {
type: "string",
nullable: true,
minLength: 1,
maxLength: 2048,
},
bannerId: { type: "string", format: "misskey:id", nullable: true },
},
required: ["channelId"],
} as const;
export default define(meta, paramDef, async (ps, me) => {
const channel = await Channels.findOneBy({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
if (channel.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied);
}
let banner = undefined;
if (ps.bannerId != null) {
banner = await DriveFiles.findOneBy({
id: ps.bannerId,
userId: me.id,
});
if (banner == null) {
throw new ApiError(meta.errors.noSuchFile);
}
} else if (ps.bannerId === null) {
banner = null;
}
await Channels.update(channel.id, {
...(ps.name !== undefined ? { name: ps.name } : {}),
...(ps.description !== undefined ? { description: ps.description } : {}),
...(banner ? { bannerId: banner.id } : {}),
});
return await Channels.pack(channel.id, me);
});

View File

@ -1,21 +1,13 @@
import {In} from "typeorm";
import create from "@/services/note/create.js";
import type {User} from "@/models/entities/user.js";
import {
Users,
DriveFiles,
Notes,
Channels,
Blockings,
} from "@/models/index.js";
import {Blockings, DriveFiles, Notes, Users,} from "@/models/index.js";
import type {DriveFile} from "@/models/entities/drive-file.js";
import type {Note} from "@/models/entities/note.js";
import type { Channel } from "@/models/entities/channel.js";
import { MAX_NOTE_TEXT_LENGTH } from "@/const.js";
import {HOUR, MAX_NOTE_TEXT_LENGTH} from "@/const.js";
import {noteVisibilities} from "../../../../types.js";
import {ApiError} from "../../error.js";
import define from "../../define.js";
import { HOUR } from "@/const.js";
import {getNote} from "../../common/getters.js";
export const meta = {
@ -268,16 +260,6 @@ export default define(meta, paramDef, async (ps, user) => {
ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter;
}
}
let channel: Channel | null = null;
if (ps.channelId != null) {
channel = await Channels.findOneBy({ id: ps.channelId });
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
}
// Create a post
const note = await create(user, {
createdAt: new Date(),
@ -296,7 +278,6 @@ export default define(meta, paramDef, async (ps, user) => {
localOnly: ps.localOnly,
visibility: ps.visibility,
visibleUsers,
channel,
apMentions: ps.noExtractMentions ? [] : undefined,
apHashtags: ps.noExtractHashtags ? [] : undefined,
apEmojis: ps.noExtractEmojis ? [] : undefined,

View File

@ -1,31 +1,19 @@
import {In} from "typeorm";
import create, { index } from "@/services/note/create.js";
import {extractMentionedUsers, index} from "@/services/note/create.js";
import type {IRemoteUser, User} from "@/models/entities/user.js";
import {
Users,
DriveFiles,
Notes,
Channels,
Blockings,
UserProfiles,
Polls,
NoteEdits,
} from "@/models/index.js";
import {Blockings, DriveFiles, NoteEdits, Notes, Polls, UserProfiles, Users,} from "@/models/index.js";
import type {DriveFile} from "@/models/entities/drive-file.js";
import type {IMentionedRemoteUsers, Note} from "@/models/entities/note.js";
import type { Channel } from "@/models/entities/channel.js";
import { MAX_NOTE_TEXT_LENGTH } from "@/const.js";
import {HOUR, MAX_NOTE_TEXT_LENGTH} from "@/const.js";
import {noteVisibilities} from "../../../../types.js";
import {ApiError} from "../../error.js";
import define from "../../define.js";
import { HOUR } from "@/const.js";
import {getNote} from "../../common/getters.js";
import {Poll} from "@/models/entities/poll.js";
import * as mfm from "mfm-js";
import {concat} from "@/prelude/array.js";
import {extractHashtags} from "@/misc/extract-hashtags.js";
import {extractCustomEmojisFromMfm} from "@/misc/extract-custom-emojis-from-mfm.js";
import { extractMentionedUsers } from "@/services/note/create.js";
import {genId} from "@/misc/gen-id.js";
import {publishNoteStream} from "@/services/stream.js";
import DeliverManager from "@/remote/activitypub/deliver-manager.js";
@ -320,15 +308,6 @@ export default define(meta, paramDef, async (ps, user) => {
}
}
let channel: Channel | null = null;
if (ps.channelId != null) {
channel = await Channels.findOneBy({ id: ps.channelId });
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
}
// enforce silent clients on server
if (user.isSilenced && ps.visibility === "public" && ps.channelId == null) {
ps.visibility = "home";
@ -546,9 +525,6 @@ export default define(meta, paramDef, async (ps, user) => {
update.mentions = mentionedUserIds;
update.mentionedRemoteUsers = JSON.stringify(mentionedRemoteUsers);
}
if (ps.channelId !== note.channelId) {
update.channelId = ps.channelId;
}
if (ps.replyId !== note.replyId) {
update.replyId = ps.replyId;
}

View File

@ -79,7 +79,6 @@ export default define(meta, paramDef, async (ps, user) => {
ps.untilDate,
)
.andWhere("note.visibility = 'public'")
.andWhere("note.channelId IS NULL")
.innerJoinAndSelect("note.user", "user")
.leftJoinAndSelect("user.avatar", "avatar")
.leftJoinAndSelect("user.banner", "banner")

View File

@ -9,7 +9,6 @@ import { generateVisibilityQuery } from "../../common/generate-visibility-query.
import {generateMutedUserQuery} from "../../common/generate-muted-user-query.js";
import {generateRepliesQuery} from "../../common/generate-replies-query.js";
import {generateMutedNoteQuery} from "../../common/generate-muted-note-query.js";
import { generateChannelQuery } from "../../common/generate-channel-query.js";
import {generateBlockedUserQuery} from "../../common/generate-block-query.js";
import {generateMutedUserRenotesQueryForNotes} from "../../common/generated-muted-renote-query.js";
@ -108,7 +107,6 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner")
.setParameters(followingQuery.getParameters());
generateChannelQuery(query, user);
generateRepliesQuery(query, ps.withReplies, user);
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);

View File

@ -1,6 +1,6 @@
import {Brackets} from "typeorm";
import {fetchMeta} from "@/misc/fetch-meta.js";
import { Notes, Users } from "@/models/index.js";
import {Notes} from "@/models/index.js";
import {activeUsersChart} from "@/services/chart/index.js";
import define from "../../define.js";
import {ApiError} from "../../error.js";
@ -9,7 +9,6 @@ import { makePaginationQuery } from "../../common/make-pagination-query.js";
import {generateVisibilityQuery} from "../../common/generate-visibility-query.js";
import {generateRepliesQuery} from "../../common/generate-replies-query.js";
import {generateMutedNoteQuery} from "../../common/generate-muted-note-query.js";
import { generateChannelQuery } from "../../common/generate-channel-query.js";
import {generateBlockedUserQuery} from "../../common/generate-block-query.js";
import {generateMutedUserRenotesQueryForNotes} from "../../common/generated-muted-renote-query.js";
@ -101,7 +100,6 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner");
generateChannelQuery(query, user);
generateRepliesQuery(query, ps.withReplies, user);
generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user);

View File

@ -9,7 +9,6 @@ import { makePaginationQuery } from "../../common/make-pagination-query.js";
import {generateVisibilityQuery} from "../../common/generate-visibility-query.js";
import {generateRepliesQuery} from "../../common/generate-replies-query.js";
import {generateMutedNoteQuery} from "../../common/generate-muted-note-query.js";
import { generateChannelQuery } from "../../common/generate-channel-query.js";
import {generateBlockedUserQuery} from "../../common/generate-block-query.js";
import {generateMutedUserRenotesQueryForNotes} from "../../common/generated-muted-renote-query.js";
@ -104,7 +103,6 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar")
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner");
generateChannelQuery(query, user);
generateRepliesQuery(query, ps.withReplies, user);
generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user);

View File

@ -1,5 +1,5 @@
import {Brackets} from "typeorm";
import { Notes, Followings } from "@/models/index.js";
import {Followings, Notes} from "@/models/index.js";
import {activeUsersChart} from "@/services/chart/index.js";
import define from "../../define.js";
import {makePaginationQuery} from "../../common/make-pagination-query.js";
@ -7,7 +7,6 @@ import { generateVisibilityQuery } from "../../common/generate-visibility-query.
import {generateMutedUserQuery} from "../../common/generate-muted-user-query.js";
import {generateRepliesQuery} from "../../common/generate-replies-query.js";
import {generateMutedNoteQuery} from "../../common/generate-muted-note-query.js";
import { generateChannelQuery } from "../../common/generate-channel-query.js";
import {generateBlockedUserQuery} from "../../common/generate-block-query.js";
import {generateMutedUserRenotesQueryForNotes} from "../../common/generated-muted-renote-query.js";
import {ApiError} from "../../error.js";
@ -104,7 +103,6 @@ export default define(meta, paramDef, async (ps, user) => {
.leftJoinAndSelect("renoteUser.banner", "renoteUserBanner")
.setParameters(followingQuery.getParameters());
generateChannelQuery(query, user);
generateRepliesQuery(query, ps.withReplies, user);
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);

View File

@ -8,11 +8,16 @@ import { IdentifiableError } from "@/misc/identifiable-error.js";
* Stream channel
*/
export default abstract class Channel {
protected connection: Connection;
public id: string;
public abstract readonly chName: string;
public static readonly shouldShare: boolean;
public static readonly requireCredential: boolean;
public id: string;
public abstract readonly chName: string;
protected connection: Connection;
constructor(id: string, connection: Connection) {
this.id = id;
this.connection = connection;
}
protected get user() {
return this.connection.user;
@ -38,19 +43,10 @@ export default abstract class Channel {
return this.connection.blocking;
}
protected get followingChannels() {
return this.connection.followingChannels;
}
protected get subscriber() {
return this.connection.subscriber;
}
constructor(id: string, connection: Connection) {
this.id = id;
this.connection = connection;
}
public send(typeOrPayload: any, payload?: any) {
const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
const body = payload === undefined ? typeOrPayload.body : payload;
@ -62,6 +58,12 @@ export default abstract class Channel {
});
}
public abstract init(params: any): void;
public dispose?(): void;
public onMessage?(type: string, body: any): void;
protected withPackedNote(
callback: (note: Packed<"Note">) => void,
): (Note) => void {
@ -74,7 +76,6 @@ export default abstract class Channel {
note.reply = undefined;
note.renote = undefined;
note.user = undefined;
note.channel = undefined;
const packed = await Notes.pack(note, this.user, { detail: true });
@ -92,8 +93,4 @@ export default abstract class Channel {
}
};
}
public abstract init(params: any): void;
public dispose?(): void;
public onMessage?(type: string, body: any): void;
}

View File

@ -1,84 +0,0 @@
import Channel from "../channel.js";
import { Users } from "@/models/index.js";
import { isUserRelated } from "@/misc/is-user-related.js";
import type { User } from "@/models/entities/user.js";
import type { StreamMessages } from "../types.js";
import type { Packed } from "@/misc/schema.js";
export default class extends Channel {
public readonly chName = "channel";
public static shouldShare = false;
public static requireCredential = false;
private channelId: string;
private typers: Map<User["id"], Date> = new Map();
private emitTypersIntervalId: ReturnType<typeof setInterval>;
constructor(id: string, connection: Channel["connection"]) {
super(id, connection);
this.onNote = this.withPackedNote(this.onNote.bind(this));
this.emitTypers = this.emitTypers.bind(this);
}
public async init(params: any) {
this.channelId = params.channelId as string;
// Subscribe stream
this.subscriber.on("notesStream", this.onNote);
this.subscriber.on(`channelStream:${this.channelId}`, this.onEvent);
this.emitTypersIntervalId = setInterval(this.emitTypers, 5000);
}
private async onNote(note: Packed<"Note">) {
if (note.visibility === "hidden") return;
if (note.channelId !== this.channelId) return;
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
if (note.renote && !note.text && this.renoteMuting.has(note.userId)) return;
this.connection.cacheNote(note);
this.send("note", note);
}
private onEvent(data: StreamMessages["channel"]["payload"]) {
if (data.type === "typing") {
const id = data.body;
const begin = !this.typers.has(id);
this.typers.set(id, new Date());
if (begin) {
this.emitTypers();
}
}
}
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.delete(userId);
}
const userIds = Array.from(this.typers.keys());
const users = await Users.packMany(userIds, null, {
detail: false,
});
this.send({
type: "typers",
body: users,
});
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
this.subscriber.off(`channelStream:${this.channelId}`, this.onEvent);
clearInterval(this.emitTypersIntervalId);
}
}

View File

@ -6,9 +6,9 @@ import { isUserRelated } from "@/misc/is-user-related.js";
import type {Packed} from "@/misc/schema.js";
export default class extends Channel {
public readonly chName = "globalTimeline";
public static shouldShare = true;
public static requireCredential = false;
public readonly chName = "globalTimeline";
private withReplies: boolean;
constructor(id: string, connection: Channel["connection"]) {
@ -29,9 +29,13 @@ export default class extends Channel {
this.subscriber.on("notesStream", this.onNote);
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
}
private async onNote(note: Packed<"Note">) {
if (note.visibility !== "public") return;
if (note.channelId != null) return;
// 関係ない返信は除外
if (note.reply && !this.withReplies) {
@ -76,9 +80,4 @@ export default class extends Channel {
this.send("note", note);
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
}
}

View File

@ -5,9 +5,9 @@ import { isInstanceMuted } from "@/misc/is-instance-muted.js";
import type {Packed} from "@/misc/schema.js";
export default class extends Channel {
public readonly chName = "homeTimeline";
public static shouldShare = true;
public static requireCredential = true;
public readonly chName = "homeTimeline";
private withReplies: boolean;
constructor(id: string, connection: Channel["connection"]) {
@ -22,15 +22,13 @@ export default class extends Channel {
this.subscriber.on("notesStream", this.onNote);
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
}
private async onNote(note: Packed<"Note">) {
if (note.visibility === "hidden") return;
if (note.channelId) {
if (!this.followingChannels.has(note.channelId)) return;
} else {
// その投稿のユーザーをフォローしていなかったら弾く
if (this.user!.id !== note.userId && !this.following.has(note.userId))
return;
}
// Ignore notes from instances the user has muted
if (
@ -75,9 +73,4 @@ export default class extends Channel {
this.send("note", note);
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
}
}

View File

@ -6,9 +6,9 @@ import { isInstanceMuted } from "@/misc/is-instance-muted.js";
import type {Packed} from "@/misc/schema.js";
export default class extends Channel {
public readonly chName = "hybridTimeline";
public static shouldShare = true;
public static requireCredential = true;
public readonly chName = "hybridTimeline";
private withReplies: boolean;
constructor(id: string, connection: Channel["connection"]) {
@ -31,22 +31,13 @@ export default class extends Channel {
this.subscriber.on("notesStream", this.onNote);
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
}
private async onNote(note: Packed<"Note">) {
if (note.visibility === "hidden") return;
// チャンネルの投稿ではなく、自分自身の投稿 または
// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
// チャンネルの投稿ではなく、全体公開のローカルの投稿 または
// フォローしているチャンネルの投稿 の場合だけ
if (
!(
(note.channelId == null && this.user!.id === note.userId) ||
(note.channelId == null && this.following.has(note.userId)) ||
(note.channelId == null &&
note.user.host == null &&
note.visibility === "public") ||
(note.channelId != null && this.followingChannels.has(note.channelId))
)
)
if (note.visibility === "hidden")
return;
// Ignore notes from instances the user has muted
@ -92,9 +83,4 @@ export default class extends Channel {
this.send("note", note);
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
}
}

View File

@ -10,7 +10,6 @@ import userList from "./user-list.js";
import antenna from "./antenna.js";
import drive from "./drive.js";
import hashtag from "./hashtag.js";
import channel from "./channel.js";
import admin from "./admin.js";
export default {
@ -26,6 +25,5 @@ export default {
antenna,
drive,
hashtag,
channel,
admin,
};

View File

@ -5,9 +5,9 @@ import { isUserRelated } from "@/misc/is-user-related.js";
import type {Packed} from "@/misc/schema.js";
export default class extends Channel {
public readonly chName = "localTimeline";
public static shouldShare = true;
public static requireCredential = false;
public readonly chName = "localTimeline";
private withReplies: boolean;
constructor(id: string, connection: Channel["connection"]) {
@ -28,11 +28,14 @@ export default class extends Channel {
this.subscriber.on("notesStream", this.onNote);
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
}
private async onNote(note: Packed<"Note">) {
if (note.user.host !== null) return;
if (note.visibility !== "public") return;
if (note.channelId != null && !this.followingChannels.has(note.channelId))
return;
// 関係ない返信は除外
if (note.reply && !this.withReplies) {
@ -68,9 +71,4 @@ export default class extends Channel {
this.send("note", note);
}
public dispose() {
// Unsubscribe events
this.subscriber.off("notesStream", this.onNote);
}
}

View File

@ -1,19 +1,9 @@
import type {EventEmitter} from "events";
import type * as websocket from "websocket";
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 {
Followings,
Mutings,
RenoteMutings,
UserProfiles,
ChannelFollowings,
Blockings,
} from "@/models/index.js";
import {Blockings, Followings, Mutings, RenoteMutings, UserProfiles,} from "@/models/index.js";
import type {AccessToken} from "@/models/entities/access-token.js";
import type {UserProfile} from "@/models/entities/user-profile.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";
@ -30,14 +20,12 @@ export default class Connection {
public muting: Set<User["id"]> = new Set();
public renoteMuting: Set<User["id"]> = new Set();
public blocking: Set<User["id"]> = new Set(); // "被"blocking
public followingChannels: Set<ChannelModel["id"]> = new Set();
public token?: AccessToken;
private wsConnection: websocket.connection;
public subscriber: StreamEventEmitter;
private wsConnection: websocket.connection;
private channels: Channel[] = [];
private subscribingNotes: Map<string, number> = new Map();
private cachedNotes: Packed<"Note">[] = [];
private isMastodonCompatible: boolean = false;
private host: string;
private accessToken: string;
private currentSubscribe: string[][] = [];
@ -75,7 +63,6 @@ export default class Connection {
this.updateMuting();
this.updateRenoteMuting();
this.updateBlocking();
this.updateFollowingChannels();
this.updateUserProfile();
this.subscriber.on(`user:${this.user.id}`, this.onUserEvent);
@ -89,6 +76,91 @@ export default class Connection {
}
}
public cacheNote(note: Packed<"Note">) {
const add = (note: Packed<"Note">) => {
const existIndex = this.cachedNotes.findIndex((n) => n.id === note.id);
if (existIndex > -1) {
this.cachedNotes[existIndex] = note;
return;
}
this.cachedNotes.unshift(note);
if (this.cachedNotes.length > 32) {
this.cachedNotes.splice(32);
}
};
add(note);
if (note.reply) add(note.reply);
if (note.renote) add(note.renote);
}
/**
*
*/
public sendMessageToWs(type: string, payload: any) {
this.wsConnection.send(
JSON.stringify({
type: type,
body: payload,
}),
);
}
/**
*
*/
public connectChannel(
id: string,
params: any,
channel: string,
pong = false,
) {
if ((channels as any)[channel].requireCredential && this.user == null) {
return;
}
// 共有可能チャンネルに接続しようとしていて、かつそのチャンネルに既に接続していたら無意味なので無視
if (
(channels as any)[channel].shouldShare &&
this.channels.some((c) => c.chName === channel)
) {
return;
}
const ch: Channel = new (channels as any)[channel](id, this);
this.channels.push(ch);
ch.init(params);
if (pong) {
this.sendMessageToWs("connected", {
id: id,
});
}
}
/**
*
* @param id ID
*/
public disconnectChannel(id: string) {
const channel = this.channels.find((c) => c.id === id);
if (channel) {
if (channel.dispose) channel.dispose();
this.channels = this.channels.filter((c) => c.id !== id);
}
}
/**
*
*/
public dispose() {
for (const c of this.channels.filter((c) => c.dispose)) {
if (c.dispose) c.dispose();
}
}
private onUserEvent(data: StreamMessages["user"]["payload"]) {
// { type, body }と展開するとそれぞれ型が分離してしまう
switch (data.type) {
@ -111,14 +183,6 @@ export default class Connection {
// TODO: renote mute events
// TODO: block events
case "followChannel":
this.followingChannels.add(data.body.id);
break;
case "unfollowChannel":
this.followingChannels.delete(data.body.id);
break;
case "updateUserProfile":
this.userProfile = data.body;
break;
@ -162,7 +226,6 @@ export default class Connection {
break; // alias
case "sr":
this.onSubscribeNote(body);
this.readNote(body);
break;
case "unsubNote":
this.onUnsubscribeNote(body);
@ -190,39 +253,6 @@ export default class Connection {
this.sendMessageToWs(data.type, data.body);
}
public cacheNote(note: Packed<"Note">) {
const add = (note: Packed<"Note">) => {
const existIndex = this.cachedNotes.findIndex((n) => n.id === note.id);
if (existIndex > -1) {
this.cachedNotes[existIndex] = note;
return;
}
this.cachedNotes.unshift(note);
if (this.cachedNotes.length > 32) {
this.cachedNotes.splice(32);
}
};
add(note);
if (note.reply) add(note.reply);
if (note.renote) add(note.renote);
}
private readNote(body: any) {
const id = body.id;
const note = this.cachedNotes.find((n) => n.id === id);
if (note == null) return;
if (this.user && note.userId !== this.user.id) {
readNote(this.user.id, [note], {
following: this.following,
followingChannels: this.followingChannels,
});
}
}
private onReadNotification(payload: any) {
if (!payload.id) return;
readNotification(this.user!.id, [payload.id]);
@ -281,63 +311,6 @@ export default class Connection {
this.disconnectChannel(id);
}
/**
*
*/
public sendMessageToWs(type: string, payload: any) {
this.wsConnection.send(
JSON.stringify({
type: type,
body: payload,
}),
);
}
/**
*
*/
public connectChannel(
id: string,
params: any,
channel: string,
pong = false,
) {
if ((channels as any)[channel].requireCredential && this.user == null) {
return;
}
// 共有可能チャンネルに接続しようとしていて、かつそのチャンネルに既に接続していたら無意味なので無視
if (
(channels as any)[channel].shouldShare &&
this.channels.some((c) => c.chName === channel)
) {
return;
}
const ch: Channel = new (channels as any)[channel](id, this);
this.channels.push(ch);
ch.init(params);
if (pong) {
this.sendMessageToWs("connected", {
id: id,
});
}
}
/**
*
* @param id ID
*/
public disconnectChannel(id: string) {
const channel = this.channels.find((c) => c.id === id);
if (channel) {
if (channel.dispose) channel.dispose();
this.channels = this.channels.filter((c) => c.id !== id);
}
}
/**
*
* @param data
@ -394,31 +367,9 @@ export default class Connection {
this.blocking = new Set<string>(blockings.map((x) => x.blockerId));
}
private async updateFollowingChannels() {
const followings = await ChannelFollowings.find({
where: {
followerId: this.user!.id,
},
select: ["followeeId"],
});
this.followingChannels = new Set<string>(
followings.map((x) => x.followeeId),
);
}
private async updateUserProfile() {
this.userProfile = await UserProfiles.findOneBy({
userId: this.user!.id,
});
}
/**
*
*/
public dispose() {
for (const c of this.channels.filter((c) => c.dispose)) {
if (c.dispose) c.dispose();
}
}
}

View File

@ -1,6 +1,5 @@
import type {EventEmitter} from "events";
import type Emitter from "strict-event-emitter-types";
import type { Channel } from "@/models/entities/channel.js";
import type {User} from "@/models/entities/user.js";
import type {UserProfile} from "@/models/entities/user-profile.js";
import type {Note} from "@/models/entities/note.js";
@ -8,7 +7,6 @@ import type { Antenna } from "@/models/entities/antenna.js";
import type {DriveFile} from "@/models/entities/drive-file.js";
import type {DriveFolder} from "@/models/entities/drive-folder.js";
import type {UserList} from "@/models/entities/user-list.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";
import type {Page} from "@/models/entities/page.js";
@ -56,8 +54,6 @@ export interface BroadcastTypes {
export interface UserStreamTypes {
terminate: Record<string, unknown>;
followChannel: Channel;
unfollowChannel: Channel;
updateUserProfile: UserProfile;
mute: User;
unmute: User;
@ -207,10 +203,6 @@ export type StreamMessages = {
name: `noteStream:${Note["id"]}`;
payload: EventUnionFromDictionary<NoteStreamEventTypes>;
};
channel: {
name: `channelStream:${Channel["id"]}`;
payload: EventUnionFromDictionary<ChannelStreamTypes>;
};
userList: {
name: `userListStream:${UserList["id"]}`;
payload: EventUnionFromDictionary<UserListStreamTypes>;

View File

@ -1,11 +1,7 @@
import * as mfm from "mfm-js";
import es from "../../db/elasticsearch.js";
import sonic from "../../db/sonic.js";
import {
publishMainStream,
publishNotesStream,
publishNoteStream,
} from "@/services/stream.js";
import {publishMainStream, publishNotesStream, publishNoteStream,} from "@/services/stream.js";
import DeliverManager from "@/remote/activitypub/deliver-manager.js";
import renderNote from "@/remote/activitypub/renderer/note.js";
import renderCreate from "@/remote/activitypub/renderer/create.js";
@ -23,31 +19,21 @@ import { extractHashtags } from "@/misc/extract-hashtags.js";
import type {IMentionedRemoteUsers} from "@/models/entities/note.js";
import {Note} from "@/models/entities/note.js";
import {
Mutings,
Users,
NoteWatchings,
Notes,
Instances,
UserProfiles,
Antennas,
Followings,
MutedNotes,
Channels,
ChannelFollowings,
Blockings,
Mutings,
Notes,
NoteThreadMutings,
NoteWatchings,
UserProfiles,
Users,
} from "@/models/index.js";
import type {DriveFile} from "@/models/entities/drive-file.js";
import type {App} from "@/models/entities/app.js";
import { Not, In, IsNull } from "typeorm";
import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
import {In, Not} from "typeorm";
import type {ILocalUser, IRemoteUser, User} from "@/models/entities/user.js";
import {genId} from "@/misc/gen-id.js";
import {
notesChart,
perUserNotesChart,
activeUsersChart,
instanceChart,
} from "@/services/chart/index.js";
import {activeUsersChart, instanceChart, notesChart, perUserNotesChart,} from "@/services/chart/index.js";
import type {IPoll} from "@/models/entities/poll.js";
import {Poll} from "@/models/entities/poll.js";
import {createNotification} from "../create-notification.js";
@ -57,7 +43,6 @@ import { getWordHardMute } from "@/misc/check-word-mute.js";
import {addNoteToAntenna} from "../add-note-to-antenna.js";
import {countSameRenotes} from "@/misc/count-same-renotes.js";
import {deliverToRelays, getCachedRelays} from "../relay.js";
import type { Channel } from "@/models/entities/channel.js";
import {normalizeForSearch} from "@/misc/normalize-for-search.js";
import {getAntennas} from "@/misc/antenna-cache.js";
import {endedPollNotificationQueue} from "@/queue/queues.js";
@ -149,7 +134,6 @@ type Option = {
cw?: string | null;
visibility?: string;
visibleUsers?: MinimumUser[] | null;
channel?: Channel | null;
apMentions?: MinimumUser[] | null;
apHashtags?: string[] | null;
apEmojis?: string[] | null;
@ -176,39 +160,15 @@ export default async (
const dontFederateInitially =
data.localOnly || data.visibility === "hidden";
// If you reply outside the channel, match the scope of the target.
// TODO (I think it's a process that could be done on the client side, but it's server side for now.)
if (
data.reply &&
data.channel &&
data.reply.channelId !== data.channel.id
) {
if (data.reply.channelId) {
data.channel = await Channels.findOneBy({ id: data.reply.channelId });
} else {
data.channel = null;
}
}
// When you reply in a channel, match the scope of the target
// TODO (I think it's a process that could be done on the client side, but it's server side for now.)
if (data.reply && data.channel == null && data.reply.channelId) {
data.channel = await Channels.findOneBy({ id: data.reply.channelId });
}
if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = "public";
if (data.localOnly == null) data.localOnly = false;
if (data.channel != null) data.visibility = "public";
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;
if (data.visibility === "hidden") data.visibility = "public";
// enforce silent clients on server
if (
user.isSilenced &&
data.visibility === "public" &&
data.channel == null
data.visibility === "public"
) {
data.visibility = "home";
}
@ -256,12 +216,12 @@ export default async (
}
// Renote local only if you Renote local only.
if (data.renote?.localOnly && data.channel == null) {
if (data.renote?.localOnly) {
data.localOnly = true;
}
// If you reply to local only, make it local only.
if (data.reply?.localOnly && data.channel == null) {
if (data.reply?.localOnly) {
data.localOnly = true;
}
@ -386,20 +346,6 @@ export default async (
});
}
// Channel
if (note.channelId) {
ChannelFollowings.findBy({ followeeId: note.channelId }).then(
(followings) => {
for (const following of followings) {
insertNoteUnread(following.followerId, note, {
isSpecified: false,
isMentioned: false,
});
}
},
);
}
if (data.reply) {
saveReply(data.reply, note);
}
@ -644,24 +590,6 @@ export default async (
//#endregion
}
if (data.channel) {
Channels.increment({ id: data.channel.id }, "notesCount", 1);
Channels.update(data.channel.id, {
lastNotedAt: new Date(),
});
await Notes.countBy({
userId: user.id,
channelId: data.channel.id,
}).then((count) => {
// この処理が行われるのはノート作成後なので、ノートが一つしかなかったら最初の投稿だと判断できる
// TODO: とはいえノートを削除して何回も投稿すればその分だけインクリメントされる雑さもあるのでどうにかしたい
if (count === 1 && data.channel != null) {
Channels.increment({ id: data.channel.id }, "usersCount", 1);
}
});
}
// Register to search database
await index(note, false);
});
@ -712,7 +640,6 @@ async function insertNote(
fileIds: data.files ? data.files.map((file) => file.id) : [],
replyId: data.reply ? data.reply.id : null,
renoteId: data.renote ? data.renote.id : null,
channelId: data.channel ? data.channel.id : null,
threadId: data.reply
? data.reply.threadId
? data.reply.threadId
@ -837,8 +764,7 @@ export async function index(note: Note, reindexing: boolean): Promise<void> {
JSON.stringify({
id: note.id,
userId: note.userId,
userHost: note.userHost,
channelId: note.channelId,
userHost: note.userHost
}),
note.text,
);

View File

@ -1,15 +1,8 @@
import {publishMainStream} from "@/services/stream.js";
import type {Note} from "@/models/entities/note.js";
import type {User} from "@/models/entities/user.js";
import {
NoteUnreads,
AntennaNotes,
Users,
Followings,
ChannelFollowings,
} from "@/models/index.js";
import { Not, IsNull, In } from "typeorm";
import type { Channel } from "@/models/entities/channel.js";
import {AntennaNotes, Followings, NoteUnreads, Users} from "@/models/index.js";
import {In} from "typeorm";
import {checkHitAntenna} from "@/misc/check-hit-antenna.js";
import {getAntennas} from "@/misc/antenna-cache.js";
import {readNotificationByQuery} from "@/server/api/common/read-notification.js";
@ -23,7 +16,6 @@ export default async function (
notes: (Note | Packed<"Note">)[],
info?: {
following: Set<User["id"]>;
followingChannels: Set<Channel["id"]>;
},
) {
const following = info?.following
@ -38,23 +30,10 @@ export default async function (
})
).map((x) => x.followeeId),
);
const followingChannels = info?.followingChannels
? info.followingChannels
: new Set<string>(
(
await ChannelFollowings.find({
where: {
followerId: userId,
},
select: ["followeeId"],
})
).map((x) => x.followeeId),
);
const myAntennas = (await getAntennas()).filter((a) => a.userId === userId);
const readMentions: (Note | Packed<"Note">)[] = [];
const readSpecifiedNotes: (Note | Packed<"Note">)[] = [];
const readChannelNotes: (Note | Packed<"Note">)[] = [];
const readAntennaNotes: (Note | Packed<"Note">)[] = [];
for (const note of notes) {
@ -64,10 +43,6 @@ export default async function (
readSpecifiedNotes.push(note);
}
if (note.channelId && followingChannels.has(note.channelId)) {
readChannelNotes.push(note);
}
if (note.user != null) {
// たぶんnullになることは無いはずだけど一応
for (const antenna of myAntennas) {
@ -88,8 +63,7 @@ export default async function (
if (
readMentions.length > 0 ||
readSpecifiedNotes.length > 0 ||
readChannelNotes.length > 0
readSpecifiedNotes.length > 0
) {
// Remove the record
await NoteUnreads.delete({
@ -97,7 +71,6 @@ export default async function (
noteId: In([
...readMentions.map((n) => n.id),
...readSpecifiedNotes.map((n) => n.id),
...readChannelNotes.map((n) => n.id),
]),
});
@ -123,16 +96,6 @@ export default async function (
}
});
NoteUnreads.countBy({
userId: userId,
noteChannelId: Not(IsNull()),
}).then((channelNoteCount) => {
if (channelNoteCount === 0) {
// 全て既読になったイベントを発行
publishMainStream(userId, "readAllChannels");
}
});
readNotificationByQuery(userId, {
noteId: In([
...readMentions.map((n) => n.id),

View File

@ -2,46 +2,22 @@ import { redisClient } from "../db/redis.js";
import type {User} from "@/models/entities/user.js";
import type {Note} from "@/models/entities/note.js";
import type {UserList} from "@/models/entities/user-list.js";
import type { UserGroup } from "@/models/entities/user-group.js";
import config from "@/config/index.js";
import type {Antenna} from "@/models/entities/antenna.js";
import type { Channel } from "@/models/entities/channel.js";
import type {
StreamChannels,
AdminStreamTypes,
AntennaStreamTypes,
BroadcastTypes,
ChannelStreamTypes,
DriveStreamTypes,
InternalStreamTypes,
MainStreamTypes,
NoteStreamTypes,
StreamChannels,
UserListStreamTypes,
UserStreamTypes,
} from "@/server/api/stream/types.js";
class Publisher {
private publish = (
channel: StreamChannels,
type: string | null,
value?: any,
): void => {
const message =
type == null
? value
: value == null
? { type: type, body: null }
: { type: type, body: value };
redisClient.publish(
config.host,
JSON.stringify({
channel: channel,
message: message,
}),
);
};
public publishInternalEvent = <K extends keyof InternalStreamTypes>(
type: K,
value?: InternalStreamTypes[K],
@ -107,18 +83,6 @@ class Publisher {
});
};
public publishChannelStream = <K extends keyof ChannelStreamTypes>(
channelId: Channel["id"],
type: K,
value?: ChannelStreamTypes[K],
): void => {
this.publish(
`channelStream:${channelId}`,
type,
typeof value === "undefined" ? null : value,
);
};
public publishUserListStream = <K extends keyof UserListStreamTypes>(
listId: UserList["id"],
type: K,
@ -158,6 +122,27 @@ class Publisher {
typeof value === "undefined" ? null : value,
);
};
private publish = (
channel: StreamChannels,
type: string | null,
value?: any,
): void => {
const message =
type == null
? value
: value == null
? { type: type, body: null }
: { type: type, body: value };
redisClient.publish(
config.host,
JSON.stringify({
channel: channel,
message: message,
}),
);
};
}
const publisher = new Publisher();
@ -171,7 +156,6 @@ export const publishMainStream = publisher.publishMainStream;
export const publishDriveStream = publisher.publishDriveStream;
export const publishNoteStream = publisher.publishNoteStream;
export const publishNotesStream = publisher.publishNotesStream;
export const publishChannelStream = publisher.publishChannelStream;
export const publishUserListStream = publisher.publishUserListStream;
export const publishAntennaStream = publisher.publishAntennaStream;
export const publishAdminStream = publisher.publishAdminStream;

View File

@ -1,7 +0,0 @@
root = true
[*]
indent_style = tab
indent_size = 2
charset = utf-8
insert_final_newline = true

View File

@ -1,113 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# App
temp
# built
# Yarn
.yarn/*
.pnp.cjs
.pnp.loader.mjs

View File

@ -1,20 +0,0 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "typescript",
"dynamicImport": true,
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2022"
},
"minify": false,
"module": {
"type": "commonjs",
"strict": true
}
}

View File

@ -1,29 +0,0 @@
# 0.0.14
- remove needless Object.freeze()
# 0.0.13
- expose ChannelConnection and Channels types
# 0.0.12
- fix a bug that cannot connect to streaming
# 0.0.11
- update user type
- add missing main stream types
# 0.0.10
- add consts
# 0.0.9
- add list of api permission
- Update Note type
# 0.0.8
- add type definition for `messagingMessage` event to main stream channel
- Update Note type
# 0.0.7
- Notificationsの型を修正
- MessagingMessageの型を修正
- UserLiteの型を修正
- apiでネイティブfetchを格納する際に無名関数でラップするように

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021-2022 syuilo and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,9 +0,0 @@
# Calckey.js
Fork of Misskey.js for Calckey
https://www.npmjs.com/package/calckey-js
To fully build, run `pnpm i && pnpm run render`.
![Parody of the Javascript logo with "CK" instead of "JS"](https://codeberg.org/repo-avatars/80771-4d86135f67b9a460cdd1be9e91648e5f)

View File

@ -1,364 +0,0 @@
/**
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
/**
* Optionally specifies another JSON config file that this file extends from. This provides a way for
* standard settings to be shared across multiple projects.
*
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
* resolved using NodeJS require().
*
* SUPPORTED TOKENS: none
* DEFAULT VALUE: ""
*/
// "extends": "./shared/api-extractor-base.json"
// "extends": "my-package/include/api-extractor-base.json"
/**
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
*
* The path is resolved relative to the folder of the config file that contains the setting.
*
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
* will be reported.
*
* SUPPORTED TOKENS: <lookup>
* DEFAULT VALUE: "<lookup>"
*/
// "projectFolder": "..",
/**
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
* analyzes the symbols exported by this module.
*
* The file extension must be ".d.ts" and not ".ts".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
*/
"mainEntryPointFilePath": "<projectFolder>/built/index.d.ts",
/**
* A list of NPM package names whose exports should be treated as part of this package.
*
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
* imports library2. To avoid this, we can specify:
*
* "bundledPackages": [ "library2" ],
*
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
* local files for library1.
*/
"bundledPackages": [],
/**
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
*/
"compiler": {
/**
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* Note: This setting will be ignored if "overrideTsconfig" is used.
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
*/
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
/**
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
* The object must conform to the TypeScript tsconfig schema:
*
* http://json.schemastore.org/tsconfig
*
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
*
* DEFAULT VALUE: no overrideTsconfig section
*/
// "overrideTsconfig": {
// . . .
// }
/**
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
*
* DEFAULT VALUE: false
*/
// "skipLibCheck": true,
},
/**
* Configures how the API report file (*.api.md) will be generated.
*/
"apiReport": {
/**
* (REQUIRED) Whether to generate an API report.
*/
"enabled": true
/**
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
* a full file path.
*
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
*
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
*/
// "reportFileName": "<unscopedPackageName>.api.md",
/**
* Specifies the folder where the API report file is written. The file name portion is determined by
* the "reportFileName" setting.
*
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
* e.g. for an API review.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/etc/"
*/
// "reportFolder": "<projectFolder>/etc/",
/**
* Specifies the folder where the temporary report file is written. The file name portion is determined by
* the "reportFileName" setting.
*
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
* If they are different, a production build will fail.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/"
*/
// "reportTempFolder": "<projectFolder>/temp/"
},
/**
* Configures how the doc model file (*.api.json) will be generated.
*/
"docModel": {
/**
* (REQUIRED) Whether to generate a doc model file.
*/
"enabled": true
/**
* The output path for the doc model file. The file extension should be ".api.json".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
*/
// "apiJsonFilePath": "<projectFolder>/temp/<unscopedPackageName>.api.json"
},
/**
* Configures how the .d.ts rollup file will be generated.
*/
"dtsRollup": {
/**
* (REQUIRED) Whether to generate the .d.ts rollup file.
*/
"enabled": false
/**
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
* This file will include all declarations that are exported by the main entry point.
*
* If the path is an empty string, then this file will not be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
*/
// "untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
* This file will include only declarations that are marked as "@public" or "@beta".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
* This file will include only declarations that are marked as "@public".
*
* If the path is an empty string, then this file will not be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
/**
* When a declaration is trimmed, by default it will be replaced by a code comment such as
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
* declaration completely.
*
* DEFAULT VALUE: false
*/
// "omitTrimmingComments": true
},
/**
* Configures how the tsdoc-metadata.json file will be generated.
*/
"tsdocMetadata": {
/**
* Whether to generate the tsdoc-metadata.json file.
*
* DEFAULT VALUE: true
*/
// "enabled": true,
/**
* Specifies where the TSDoc metadata file should be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
* falls back to "tsdoc-metadata.json" in the package folder.
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<lookup>"
*/
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
},
/**
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
* To use the OS's default newline kind, specify "os".
*
* DEFAULT VALUE: "crlf"
*/
// "newlineKind": "crlf",
/**
* Configures how API Extractor reports error and warning messages produced during analysis.
*
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
*/
"messages": {
/**
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
* the input .d.ts files.
*
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
*
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
*/
"compilerMessageReporting": {
/**
* Configures the default routing for messages that don't match an explicit rule in this table.
*/
"default": {
/**
* Specifies whether the message should be written to the the tool's output log. Note that
* the "addToApiReportFile" property may supersede this option.
*
* Possible values: "error", "warning", "none"
*
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
* the "--local" option), the warning is displayed but the build will not fail.
*
* DEFAULT VALUE: "warning"
*/
"logLevel": "warning"
/**
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
* then the message will be written inside that file; otherwise, the message is instead logged according to
* the "logLevel" option.
*
* DEFAULT VALUE: false
*/
// "addToApiReportFile": false
}
// "TS2551": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
},
/**
* Configures handling of messages reported by API Extractor during its analysis.
*
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
*
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
*/
"extractorMessageReporting": {
"default": {
"logLevel": "none"
// "addToApiReportFile": false
}
// "ae-extra-release-tag": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
},
/**
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
*
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
*
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
*/
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
// "addToApiReportFile": false
}
// "tsdoc-link-tag-unescaped-text": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
}
}
}

View File

@ -1,2 +0,0 @@
codecov:
token: d55e1270-f20a-4727-aa05-2bd57793315a

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,195 +0,0 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/en/configuration.html
*/
export default {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "C:\\Users\\ai\\AppData\\Local\\Temp\\jest",
// Automatically clear mock calls and instances between every test
// clearMocks: false,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: "v8",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "json",
// "jsx",
// "ts",
// "tsx",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
roots: ["<rootDir>"],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[tj]s?(x)",
"<rootDir>/test/**/*",
],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "\\\\node_modules\\\\",
// "\\.pnp\\.[^\\\\]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};

View File

@ -1,14 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [Acct](./calckey-js.acct.md)
## Acct type
**Signature:**
```typescript
export declare type Acct = {
username: string;
host: string | null;
};
```

View File

@ -1,24 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [APIClient](./calckey-js.api.apiclient.md) &gt; [(constructor)](./calckey-js.api.apiclient._constructor_.md)
## api.APIClient.(constructor)
Constructs a new instance of the `APIClient` class
**Signature:**
```typescript
constructor(opts: {
origin: APIClient["origin"];
credential?: APIClient["credential"];
fetch?: APIClient["fetch"] | null | undefined;
});
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| opts | { origin: [APIClient](./calckey-js.api.apiclient.md)<!-- -->\["origin"\]; credential?: [APIClient](./calckey-js.api.apiclient.md)<!-- -->\["credential"\]; fetch?: [APIClient](./calckey-js.api.apiclient.md)<!-- -->\["fetch"\] \| null \| undefined; } | |

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [APIClient](./calckey-js.api.apiclient.md) &gt; [credential](./calckey-js.api.apiclient.credential.md)
## api.APIClient.credential property
**Signature:**
```typescript
credential: string | null | undefined;
```

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [APIClient](./calckey-js.api.apiclient.md) &gt; [fetch](./calckey-js.api.apiclient.fetch.md)
## api.APIClient.fetch property
**Signature:**
```typescript
fetch: FetchLike;
```

View File

@ -1,32 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [APIClient](./calckey-js.api.apiclient.md)
## api.APIClient class
**Signature:**
```typescript
export declare class APIClient
```
## Constructors
| Constructor | Modifiers | Description |
| --- | --- | --- |
| [(constructor)(opts)](./calckey-js.api.apiclient._constructor_.md) | | Constructs a new instance of the <code>APIClient</code> class |
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [credential](./calckey-js.api.apiclient.credential.md) | | string \| null \| undefined | |
| [fetch](./calckey-js.api.apiclient.fetch.md) | | [FetchLike](./calckey-js.api.fetchlike.md) | |
| [origin](./calckey-js.api.apiclient.origin.md) | | string | |
## Methods
| Method | Modifiers | Description |
| --- | --- | --- |
| [request(endpoint, params, credential)](./calckey-js.api.apiclient.request.md) | | |

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [APIClient](./calckey-js.api.apiclient.md) &gt; [origin](./calckey-js.api.apiclient.origin.md)
## api.APIClient.origin property
**Signature:**
```typescript
origin: string;
```

View File

@ -1,57 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [APIClient](./calckey-js.api.apiclient.md) &gt; [request](./calckey-js.api.apiclient.request.md)
## api.APIClient.request() method
**Signature:**
```typescript
request<E extends keyof Endpoints, P extends Endpoints[E]["req"]>(
endpoint: E,
params?: P,
credential?: string | null | undefined,
): Promise<
Endpoints[E]["res"] extends {
$switch: {
$cases: [any, any][];
$default: any;
};
}
? IsCaseMatched<E, P, 0> extends true
? GetCaseResult<E, P, 0>
: IsCaseMatched<E, P, 1> extends true
? GetCaseResult<E, P, 1>
: IsCaseMatched<E, P, 2> extends true
? GetCaseResult<E, P, 2>
: IsCaseMatched<E, P, 3> extends true
? GetCaseResult<E, P, 3>
: IsCaseMatched<E, P, 4> extends true
? GetCaseResult<E, P, 4>
: IsCaseMatched<E, P, 5> extends true
? GetCaseResult<E, P, 5>
: IsCaseMatched<E, P, 6> extends true
? GetCaseResult<E, P, 6>
: IsCaseMatched<E, P, 7> extends true
? GetCaseResult<E, P, 7>
: IsCaseMatched<E, P, 8> extends true
? GetCaseResult<E, P, 8>
: IsCaseMatched<E, P, 9> extends true
? GetCaseResult<E, P, 9>
: Endpoints[E]["res"]["$switch"]["$default"]
: Endpoints[E]["res"]
>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| endpoint | E | |
| params | P | _(Optional)_ |
| credential | string \| null \| undefined | _(Optional)_ |
**Returns:**
Promise&lt; [Endpoints](./calckey-js.endpoints.md)<!-- -->\[E\]\["res"\] extends { $switch: { $cases: \[any, any\]\[\]; $default: any; }; } ? IsCaseMatched&lt;E, P, 0&gt; extends true ? GetCaseResult&lt;E, P, 0&gt; : IsCaseMatched&lt;E, P, 1&gt; extends true ? GetCaseResult&lt;E, P, 1&gt; : IsCaseMatched&lt;E, P, 2&gt; extends true ? GetCaseResult&lt;E, P, 2&gt; : IsCaseMatched&lt;E, P, 3&gt; extends true ? GetCaseResult&lt;E, P, 3&gt; : IsCaseMatched&lt;E, P, 4&gt; extends true ? GetCaseResult&lt;E, P, 4&gt; : IsCaseMatched&lt;E, P, 5&gt; extends true ? GetCaseResult&lt;E, P, 5&gt; : IsCaseMatched&lt;E, P, 6&gt; extends true ? GetCaseResult&lt;E, P, 6&gt; : IsCaseMatched&lt;E, P, 7&gt; extends true ? GetCaseResult&lt;E, P, 7&gt; : IsCaseMatched&lt;E, P, 8&gt; extends true ? GetCaseResult&lt;E, P, 8&gt; : IsCaseMatched&lt;E, P, 9&gt; extends true ? GetCaseResult&lt;E, P, 9&gt; : [Endpoints](./calckey-js.endpoints.md)<!-- -->\[E\]\["res"\]\["$switch"\]\["$default"\] : [Endpoints](./calckey-js.endpoints.md)<!-- -->\[E\]\["res"\] &gt;

View File

@ -1,17 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [APIError](./calckey-js.api.apierror.md)
## api.APIError type
**Signature:**
```typescript
export declare type APIError = {
id: string;
code: string;
message: string;
kind: "client" | "server";
info: Record<string, any>;
};
```

View File

@ -1,22 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [FetchLike](./calckey-js.api.fetchlike.md)
## api.FetchLike type
**Signature:**
```typescript
export declare type FetchLike = (
input: string,
init?: {
method?: string;
body?: string;
credentials?: RequestCredentials;
cache?: RequestCache;
},
) => Promise<{
status: number;
json(): Promise<any>;
}>;
```

View File

@ -1,22 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md) &gt; [isAPIError](./calckey-js.api.isapierror.md)
## api.isAPIError() function
**Signature:**
```typescript
export declare function isAPIError(reason: any): reason is APIError;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| reason | any | |
**Returns:**
reason is [APIError](./calckey-js.api.apierror.md)

View File

@ -1,25 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [api](./calckey-js.api.md)
## api namespace
## Classes
| Class | Description |
| --- | --- |
| [APIClient](./calckey-js.api.apiclient.md) | |
## Functions
| Function | Description |
| --- | --- |
| [isAPIError(reason)](./calckey-js.api.isapierror.md) | |
## Type Aliases
| Type Alias | Description |
| --- | --- |
| [APIError](./calckey-js.api.apierror.md) | |
| [FetchLike](./calckey-js.api.fetchlike.md) | |

View File

@ -1,22 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [(constructor)](./calckey-js.channelconnection._constructor_.md)
## ChannelConnection.(constructor)
Constructs a new instance of the `Connection` class
**Signature:**
```typescript
constructor(stream: Stream, channel: string, name?: string);
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| stream | [Stream](./calckey-js.stream.md) | |
| channel | string | |
| name | string | _(Optional)_ |

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [channel](./calckey-js.channelconnection.channel.md)
## ChannelConnection.channel property
**Signature:**
```typescript
channel: string;
```

View File

@ -1,15 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [dispose](./calckey-js.channelconnection.dispose.md)
## ChannelConnection.dispose() method
**Signature:**
```typescript
abstract dispose(): void;
```
**Returns:**
void

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [id](./calckey-js.channelconnection.id.md)
## ChannelConnection.id property
**Signature:**
```typescript
abstract id: string;
```

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [inCount](./calckey-js.channelconnection.incount.md)
## ChannelConnection.inCount property
**Signature:**
```typescript
inCount: number;
```

View File

@ -1,39 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md)
## ChannelConnection class
**Signature:**
```typescript
export declare abstract class Connection<
Channel extends AnyOf<Channels> = any,
> extends EventEmitter<Channel["events"]>
```
**Extends:** EventEmitter&lt;Channel\["events"\]&gt;
## Constructors
| Constructor | Modifiers | Description |
| --- | --- | --- |
| [(constructor)(stream, channel, name)](./calckey-js.channelconnection._constructor_.md) | | Constructs a new instance of the <code>Connection</code> class |
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [channel](./calckey-js.channelconnection.channel.md) | | string | |
| [id](./calckey-js.channelconnection.id.md) | <code>abstract</code> | string | |
| [inCount](./calckey-js.channelconnection.incount.md) | | number | |
| [name?](./calckey-js.channelconnection.name.md) | | string | _(Optional)_ |
| [outCount](./calckey-js.channelconnection.outcount.md) | | number | |
| [stream](./calckey-js.channelconnection.stream.md) | <code>protected</code> | [Stream](./calckey-js.stream.md) | |
## Methods
| Method | Modifiers | Description |
| --- | --- | --- |
| [dispose()](./calckey-js.channelconnection.dispose.md) | <code>abstract</code> | |
| [send(type, body)](./calckey-js.channelconnection.send.md) | | |

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [name](./calckey-js.channelconnection.name.md)
## ChannelConnection.name property
**Signature:**
```typescript
name?: string;
```

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [outCount](./calckey-js.channelconnection.outcount.md)
## ChannelConnection.outCount property
**Signature:**
```typescript
outCount: number;
```

View File

@ -1,26 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [send](./calckey-js.channelconnection.send.md)
## ChannelConnection.send() method
**Signature:**
```typescript
send<T extends keyof Channel["receives"]>(
type: T,
body: Channel["receives"][T],
): void;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| type | T | |
| body | Channel\["receives"\]\[T\] | |
**Returns:**
void

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [ChannelConnection](./calckey-js.channelconnection.md) &gt; [stream](./calckey-js.channelconnection.stream.md)
## ChannelConnection.stream property
**Signature:**
```typescript
protected stream: Stream;
```

View File

@ -1,143 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [Channels](./calckey-js.channels.md)
## Channels type
**Signature:**
```typescript
export declare type Channels = {
main: {
params: null;
events: {
notification: (payload: Notification) => void;
mention: (payload: Note) => void;
reply: (payload: Note) => void;
renote: (payload: Note) => void;
follow: (payload: User) => void;
followed: (payload: User) => void;
unfollow: (payload: User) => void;
meUpdated: (payload: MeDetailed) => void;
pageEvent: (payload: PageEvent) => void;
urlUploadFinished: (payload: {
marker: string;
file: DriveFile;
}) => void;
readAllNotifications: () => void;
unreadNotification: (payload: Notification) => void;
unreadMention: (payload: Note["id"]) => void;
readAllUnreadMentions: () => void;
unreadSpecifiedNote: (payload: Note["id"]) => void;
readAllUnreadSpecifiedNotes: () => void;
readAllMessagingMessages: () => void;
messagingMessage: (payload: MessagingMessage) => void;
unreadMessagingMessage: (payload: MessagingMessage) => void;
readAllAntennas: () => void;
unreadAntenna: (payload: Antenna) => void;
readAllAnnouncements: () => void;
readAllChannels: () => void;
unreadChannel: (payload: Note["id"]) => void;
myTokenRegenerated: () => void;
reversiNoInvites: () => void;
reversiInvited: (payload: FIXME) => void;
signin: (payload: FIXME) => void;
registryUpdated: (payload: {
scope?: string[];
key: string;
value: any | null;
}) => void;
driveFileCreated: (payload: DriveFile) => void;
readAntenna: (payload: Antenna) => void;
};
receives: null;
};
homeTimeline: {
params: null;
events: {
note: (payload: Note) => void;
};
receives: null;
};
localTimeline: {
params: null;
events: {
note: (payload: Note) => void;
};
receives: null;
};
hybridTimeline: {
params: null;
events: {
note: (payload: Note) => void;
};
receives: null;
};
recommendedTimeline: {
params: null;
events: {
note: (payload: Note) => void;
};
receives: null;
};
globalTimeline: {
params: null;
events: {
note: (payload: Note) => void;
};
receives: null;
};
antenna: {
params: {
antennaId: Antenna["id"];
};
events: {
note: (payload: Note) => void;
};
receives: null;
};
messaging: {
params: {
otherparty?: User["id"] | null;
group?: UserGroup["id"] | null;
};
events: {
message: (payload: MessagingMessage) => void;
deleted: (payload: MessagingMessage["id"]) => void;
read: (payload: MessagingMessage["id"][]) => void;
typers: (payload: User[]) => void;
};
receives: {
read: {
id: MessagingMessage["id"];
};
};
};
serverStats: {
params: null;
events: {
stats: (payload: FIXME) => void;
};
receives: {
requestLog: {
id: string | number;
length: number;
};
};
};
queueStats: {
params: null;
events: {
stats: (payload: FIXME) => void;
};
receives: {
requestLog: {
id: string | number;
length: number;
};
};
};
};
```
**References:** [Note](./calckey-js.entities.note.md)<!-- -->, [User](./calckey-js.entities.user.md)<!-- -->, [MeDetailed](./calckey-js.entities.medetailed.md)<!-- -->, [PageEvent](./calckey-js.entities.pageevent.md)<!-- -->, [DriveFile](./calckey-js.entities.drivefile.md)<!-- -->, [MessagingMessage](./calckey-js.entities.messagingmessage.md)<!-- -->, [Antenna](./calckey-js.entities.antenna.md)<!-- -->, [UserGroup](./calckey-js.entities.usergroup.md)

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [Ad](./calckey-js.entities.ad.md)
## entities.Ad type
**Signature:**
```typescript
export declare type Ad = TODO;
```

View File

@ -1,21 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [Announcement](./calckey-js.entities.announcement.md)
## entities.Announcement type
**Signature:**
```typescript
export declare type Announcement = {
id: ID;
createdAt: DateString;
updatedAt: DateString | null;
text: string;
title: string;
imageUrl: string | null;
isRead?: boolean;
};
```
**References:** [ID](./calckey-js.entities.id.md)<!-- -->, [DateString](./calckey-js.entities.datestring.md)

View File

@ -1,29 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [Antenna](./calckey-js.entities.antenna.md)
## entities.Antenna type
**Signature:**
```typescript
export declare type Antenna = {
id: ID;
createdAt: DateString;
name: string;
keywords: string[][];
excludeKeywords: string[][];
src: "home" | "all" | "users" | "list" | "group" | "instances";
userListId: ID | null;
userGroupId: ID | null;
users: string[];
instances: string[];
caseSensitive: boolean;
notify: boolean;
withReplies: boolean;
withFile: boolean;
hasUnreadNote: boolean;
};
```
**References:** [ID](./calckey-js.entities.id.md)<!-- -->, [DateString](./calckey-js.entities.datestring.md)

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [App](./calckey-js.entities.app.md)
## entities.App type
**Signature:**
```typescript
export declare type App = TODO;
```

View File

@ -1,17 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [AuthSession](./calckey-js.entities.authsession.md)
## entities.AuthSession type
**Signature:**
```typescript
export declare type AuthSession = {
id: ID;
app: App;
token: string;
};
```
**References:** [ID](./calckey-js.entities.id.md)<!-- -->, [App](./calckey-js.entities.app.md)

View File

@ -1,18 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [Blocking](./calckey-js.entities.blocking.md)
## entities.Blocking type
**Signature:**
```typescript
export declare type Blocking = {
id: ID;
createdAt: DateString;
blockeeId: User["id"];
blockee: UserDetailed;
};
```
**References:** [ID](./calckey-js.entities.id.md)<!-- -->, [DateString](./calckey-js.entities.datestring.md)<!-- -->, [User](./calckey-js.entities.user.md)<!-- -->, [UserDetailed](./calckey-js.entities.userdetailed.md)

View File

@ -1,15 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [Channel](./calckey-js.entities.channel.md)
## entities.Channel type
**Signature:**
```typescript
export declare type Channel = {
id: ID;
};
```
**References:** [ID](./calckey-js.entities.id.md)

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [Clip](./calckey-js.entities.clip.md)
## entities.Clip type
**Signature:**
```typescript
export declare type Clip = TODO;
```

View File

@ -1,17 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [CustomEmoji](./calckey-js.entities.customemoji.md)
## entities.CustomEmoji type
**Signature:**
```typescript
export declare type CustomEmoji = {
id: string;
name: string;
url: string;
category: string;
aliases: string[];
};
```

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [DateString](./calckey-js.entities.datestring.md)
## entities.DateString type
**Signature:**
```typescript
export declare type DateString = string;
```

View File

@ -1,15 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [DetailedInstanceMetadata](./calckey-js.entities.detailedinstancemetadata.md)
## entities.DetailedInstanceMetadata type
**Signature:**
```typescript
export declare type DetailedInstanceMetadata = LiteInstanceMetadata & {
features: Record<string, any>;
};
```
**References:** [LiteInstanceMetadata](./calckey-js.entities.liteinstancemetadata.md)

View File

@ -1,26 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [DriveFile](./calckey-js.entities.drivefile.md)
## entities.DriveFile type
**Signature:**
```typescript
export declare type DriveFile = {
id: ID;
createdAt: DateString;
isSensitive: boolean;
name: string;
thumbnailUrl: string;
url: string;
type: string;
size: number;
md5: string;
blurhash: string;
comment: string | null;
properties: Record<string, any>;
};
```
**References:** [ID](./calckey-js.entities.id.md)<!-- -->, [DateString](./calckey-js.entities.datestring.md)

View File

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [calckey-js](./calckey-js.md) &gt; [entities](./calckey-js.entities.md) &gt; [DriveFolder](./calckey-js.entities.drivefolder.md)
## entities.DriveFolder type
**Signature:**
```typescript
export declare type DriveFolder = TODO;
```

Some files were not shown because too many files have changed in this diff Show More