Merge branch 'develop' into pizzax-indexeddb
This commit is contained in:
21
packages/backend/.eslintrc.cjs
Normal file
21
packages/backend/.eslintrc.cjs
Normal file
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
],
|
||||
rules: {
|
||||
'import/order': ['warn', {
|
||||
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
'pathGroups': [
|
||||
{
|
||||
'pattern': '@/**',
|
||||
'group': 'external',
|
||||
'position': 'after'
|
||||
}
|
||||
],
|
||||
}]
|
||||
},
|
||||
};
|
@@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
],
|
||||
};
|
4
packages/backend/.vscode/settings.json
vendored
4
packages/backend/.vscode/settings.json
vendored
@@ -2,5 +2,9 @@
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"path-intellisense.mappings": {
|
||||
"@": "${workspaceRoot}/packages/backend/src/"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true
|
||||
}
|
||||
}
|
||||
|
19
packages/backend/migration/1648548247382-webhook.js
Normal file
19
packages/backend/migration/1648548247382-webhook.js
Normal file
@@ -0,0 +1,19 @@
|
||||
export class webhook1648548247382 {
|
||||
name = 'webhook1648548247382'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "webhook" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "on" character varying(128) array NOT NULL DEFAULT '{}', "url" character varying(1024) NOT NULL, "secret" character varying(1024) NOT NULL, "active" boolean NOT NULL DEFAULT true, CONSTRAINT "PK_e6765510c2d078db49632b59020" PRIMARY KEY ("id")); COMMENT ON COLUMN "webhook"."createdAt" IS 'The created date of the Antenna.'; COMMENT ON COLUMN "webhook"."userId" IS 'The owner ID.'; COMMENT ON COLUMN "webhook"."name" IS 'The name of the Antenna.'`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f272c8c8805969e6a6449c77b3" ON "webhook" ("userId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8063a0586ed1dfbe86e982d961" ON "webhook" ("on") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_5a056076f76b2efe08216ba655" ON "webhook" ("active") `);
|
||||
await queryRunner.query(`ALTER TABLE "webhook" ADD CONSTRAINT "FK_f272c8c8805969e6a6449c77b3c" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "webhook" DROP CONSTRAINT "FK_f272c8c8805969e6a6449c77b3c"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_5a056076f76b2efe08216ba655"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_8063a0586ed1dfbe86e982d961"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_f272c8c8805969e6a6449c77b3"`);
|
||||
await queryRunner.query(`DROP TABLE "webhook"`);
|
||||
}
|
||||
}
|
14
packages/backend/migration/1648816172177-webhook-2.js
Normal file
14
packages/backend/migration/1648816172177-webhook-2.js
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
export class webhook21648816172177 {
|
||||
name = 'webhook21648816172177'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "webhook" ADD "latestSentAt" TIMESTAMP WITH TIME ZONE`);
|
||||
await queryRunner.query(`ALTER TABLE "webhook" ADD "latestStatus" integer`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "latestStatus"`);
|
||||
await queryRunner.query(`ALTER TABLE "webhook" DROP COLUMN "latestSentAt"`);
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
||||
"watch": "node watch.mjs",
|
||||
"lint": "eslint --quiet src/**/*.ts",
|
||||
"lint": "eslint --quiet \"src/**/*.ts\"",
|
||||
"mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
|
||||
"test": "npm run mocha"
|
||||
},
|
||||
@@ -14,6 +14,7 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bull-board/koa": "3.10.4",
|
||||
"@discordapp/twemoji": "13.1.1",
|
||||
"@elastic/elasticsearch": "7.11.0",
|
||||
"@koa/cors": "3.1.0",
|
||||
@@ -21,65 +22,19 @@
|
||||
"@koa/router": "9.0.1",
|
||||
"@sinonjs/fake-timers": "9.1.1",
|
||||
"@syuilo/aiscript": "0.11.1",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/bull": "3.15.8",
|
||||
"@types/cbor": "6.0.0",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
"@types/is-url": "1.2.30",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/jsdom": "16.2.14",
|
||||
"@types/jsonld": "1.5.6",
|
||||
"@types/koa": "2.13.4",
|
||||
"@types/koa-bodyparser": "4.3.7",
|
||||
"@types/koa-cors": "0.0.2",
|
||||
"@types/koa-favicon": "2.0.21",
|
||||
"@types/koa-logger": "3.1.2",
|
||||
"@types/koa-mount": "4.0.1",
|
||||
"@types/koa-send": "4.1.3",
|
||||
"@types/koa-views": "7.0.0",
|
||||
"@types/koa__cors": "3.1.1",
|
||||
"@types/koa__multer": "2.0.4",
|
||||
"@types/koa__router": "8.0.11",
|
||||
"@types/mocha": "9.1.0",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.4",
|
||||
"@types/oauth": "0.9.1",
|
||||
"@types/parse5": "6.0.3",
|
||||
"@types/portscanner": "2.1.1",
|
||||
"@types/pug": "2.0.6",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/qrcode": "1.4.2",
|
||||
"@types/random-seed": "0.3.3",
|
||||
"@types/ratelimiter": "3.4.3",
|
||||
"@types/redis": "4.0.11",
|
||||
"@types/rename": "1.0.4",
|
||||
"@types/sanitize-html": "2.6.2",
|
||||
"@types/sharp": "0.30.0",
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/speakeasy": "2.0.7",
|
||||
"@types/throttle-debounce": "2.1.0",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/tmp": "0.2.3",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@types/web-push": "3.3.2",
|
||||
"@types/websocket": "1.0.5",
|
||||
"@types/ws": "8.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.16.0",
|
||||
"@typescript-eslint/parser": "5.16.0",
|
||||
"@bull-board/koa": "3.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.20.0",
|
||||
"@typescript-eslint/parser": "5.20.0",
|
||||
"abort-controller": "3.0.0",
|
||||
"ajv": "8.11.0",
|
||||
"archiver": "5.3.0",
|
||||
"archiver": "5.3.1",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autwh": "0.1.0",
|
||||
"aws-sdk": "2.1100.0",
|
||||
"aws-sdk": "2.1120.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "1.1.5",
|
||||
"broadcast-channel": "4.10.0",
|
||||
"bull": "4.8.1",
|
||||
"broadcast-channel": "4.11.0",
|
||||
"bull": "4.8.2",
|
||||
"cacheable-lookup": "6.0.4",
|
||||
"cafy": "15.2.1",
|
||||
"cbor": "8.1.0",
|
||||
"chalk": "5.0.1",
|
||||
"chalk-template": "0.4.0",
|
||||
@@ -89,22 +44,22 @@
|
||||
"date-fns": "2.28.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"escape-regexp": "0.0.1",
|
||||
"eslint": "8.12.0",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
"eslint": "8.14.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "17.1.1",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"got": "12.0.3",
|
||||
"hpagent": "0.1.2",
|
||||
"http-signature": "1.3.6",
|
||||
"ip-cidr": "3.0.4",
|
||||
"ip-cidr": "3.0.7",
|
||||
"is-svg": "4.3.2",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "19.0.0",
|
||||
"json5": "2.2.1",
|
||||
"json5-loader": "4.0.1",
|
||||
"jsonld": "5.2.0",
|
||||
"jsrsasign": "8.0.20",
|
||||
"jsrsasign": "10.5.19",
|
||||
"koa": "2.13.4",
|
||||
"koa-bodyparser": "4.3.0",
|
||||
"koa-favicon": "2.1.0",
|
||||
@@ -145,16 +100,15 @@
|
||||
"rndstr": "1.0.0",
|
||||
"s-age": "1.1.2",
|
||||
"sanitize-html": "2.7.0",
|
||||
"semver": "7.3.5",
|
||||
"sharp": "0.30.3",
|
||||
"semver": "7.3.7",
|
||||
"sharp": "0.30.4",
|
||||
"speakeasy": "2.0.0",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"style-loader": "3.3.1",
|
||||
"summaly": "2.5.0",
|
||||
"syslog-pro": "1.0.0",
|
||||
"systeminformation": "5.11.9",
|
||||
"throttle-debounce": "3.0.1",
|
||||
"systeminformation": "5.11.14",
|
||||
"tinycolor2": "1.4.2",
|
||||
"tmp": "0.2.1",
|
||||
"ts-loader": "9.2.8",
|
||||
@@ -162,7 +116,7 @@
|
||||
"tsc-alias": "1.4.1",
|
||||
"tsconfig-paths": "3.14.1",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typeorm": "0.3.4",
|
||||
"typeorm": "0.3.6",
|
||||
"typescript": "4.6.3",
|
||||
"ulid": "2.3.0",
|
||||
"unzipper": "0.10.11",
|
||||
@@ -170,11 +124,56 @@
|
||||
"web-push": "3.4.5",
|
||||
"websocket": "1.0.34",
|
||||
"ws": "8.5.0",
|
||||
"xev": "2.0.1"
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@redocly/openapi-core": "1.0.0-beta.90",
|
||||
"@redocly/openapi-core": "1.0.0-beta.93",
|
||||
"@types/semver": "7.3.9",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/bull": "3.15.8",
|
||||
"@types/cbor": "6.0.0",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
"@types/fluent-ffmpeg": "2.1.20",
|
||||
"@types/is-url": "1.2.30",
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/jsdom": "16.2.14",
|
||||
"@types/jsonld": "1.5.6",
|
||||
"@types/jsrsasign": "10.2.1",
|
||||
"@types/koa": "2.13.4",
|
||||
"@types/koa-bodyparser": "4.3.7",
|
||||
"@types/koa-cors": "0.0.2",
|
||||
"@types/koa-favicon": "2.0.21",
|
||||
"@types/koa-logger": "3.1.2",
|
||||
"@types/koa-mount": "4.0.1",
|
||||
"@types/koa-send": "4.1.3",
|
||||
"@types/koa-views": "7.0.0",
|
||||
"@types/koa__cors": "3.1.1",
|
||||
"@types/koa__multer": "2.0.4",
|
||||
"@types/koa__router": "8.0.11",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/node": "17.0.25",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.4",
|
||||
"@types/oauth": "0.9.1",
|
||||
"@types/parse5": "6.0.3",
|
||||
"@types/portscanner": "2.1.1",
|
||||
"@types/pug": "2.0.6",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/qrcode": "1.4.2",
|
||||
"@types/random-seed": "0.3.3",
|
||||
"@types/ratelimiter": "3.4.3",
|
||||
"@types/redis": "4.0.11",
|
||||
"@types/rename": "1.0.4",
|
||||
"@types/sanitize-html": "2.6.2",
|
||||
"@types/sharp": "0.30.2",
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/speakeasy": "2.0.7",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/tmp": "0.2.3",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@types/web-push": "3.3.2",
|
||||
"@types/websocket": "1.0.5",
|
||||
"@types/ws": "8.5.3",
|
||||
"cross-env": "7.0.3",
|
||||
"execa": "6.1.0"
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
declare module 'http-signature' {
|
||||
import { IncomingMessage, ClientRequest } from 'http';
|
||||
import { IncomingMessage, ClientRequest } from 'node:http';
|
||||
|
||||
interface ISignature {
|
||||
keyId: string;
|
||||
|
800
packages/backend/src/@types/jsrsasign.d.ts
vendored
800
packages/backend/src/@types/jsrsasign.d.ts
vendored
@@ -1,800 +0,0 @@
|
||||
// Attention: Partial Type Definition
|
||||
|
||||
declare module 'jsrsasign' {
|
||||
//// HELPER TYPES
|
||||
|
||||
/**
|
||||
* Attention: The value might be changed by the function.
|
||||
*/
|
||||
type Mutable<T> = T;
|
||||
|
||||
/**
|
||||
* Deprecated: The function might be deleted in future release.
|
||||
*/
|
||||
type Deprecated<T> = T;
|
||||
|
||||
//// COMMON TYPES
|
||||
|
||||
/**
|
||||
* byte number
|
||||
*/
|
||||
type ByteNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255;
|
||||
|
||||
/**
|
||||
* hexadecimal string /[0-9A-F]/
|
||||
*/
|
||||
type HexString = string;
|
||||
|
||||
/**
|
||||
* binary string /[01]/
|
||||
*/
|
||||
type BinString = string;
|
||||
|
||||
/**
|
||||
* base64 string /[A-Za-z0-9+/]=+/
|
||||
*/
|
||||
type Base64String = string;
|
||||
|
||||
/**
|
||||
* base64 URL encoded string /[A-Za-z0-9_-]/
|
||||
*/
|
||||
type Base64URLString = string;
|
||||
|
||||
/**
|
||||
* time value (ex. "151231235959Z")
|
||||
*/
|
||||
type TimeValue = string;
|
||||
|
||||
/**
|
||||
* OID string (ex. '1.2.3.4.567')
|
||||
*/
|
||||
type OID = string;
|
||||
|
||||
/**
|
||||
* OID name
|
||||
*/
|
||||
type OIDName = string;
|
||||
|
||||
/**
|
||||
* PEM formatted string
|
||||
*/
|
||||
type PEM = string;
|
||||
|
||||
//// ASN1 TYPES
|
||||
|
||||
class ASN1Object {
|
||||
public isModified: boolean;
|
||||
|
||||
public hTLV: ASN1TLV;
|
||||
|
||||
public hT: ASN1T;
|
||||
|
||||
public hL: ASN1L;
|
||||
|
||||
public hV: ASN1V;
|
||||
|
||||
public getLengthHexFromValue(): HexString;
|
||||
|
||||
public getEncodedHex(): ASN1TLV;
|
||||
|
||||
public getValueHex(): ASN1V;
|
||||
|
||||
public getFreshValueHex(): ASN1V;
|
||||
}
|
||||
|
||||
class DERAbstractStructured extends ASN1Object {
|
||||
constructor(params?: Partial<Record<'array', ASN1Object[]>>);
|
||||
|
||||
public setByASN1ObjectArray(asn1ObjectArray: ASN1Object[]): void;
|
||||
|
||||
public appendASN1Object(asn1Object: ASN1Object): void;
|
||||
}
|
||||
|
||||
class DERSequence extends DERAbstractStructured {
|
||||
constructor(params?: Partial<Record<'array', ASN1Object[]>>);
|
||||
|
||||
public getFreshValueHex(): ASN1V;
|
||||
}
|
||||
|
||||
//// ASN1HEX TYPES
|
||||
|
||||
/**
|
||||
* ASN.1 DER encoded data (hexadecimal string)
|
||||
*/
|
||||
type ASN1S = HexString;
|
||||
|
||||
/**
|
||||
* index of something
|
||||
*/
|
||||
type Idx<T extends { [idx: string]: unknown } | { [idx: number]: unknown }> = ASN1S extends { [idx: string]: unknown } ? string : ASN1S extends { [idx: number]: unknown } ? number : never;
|
||||
|
||||
/**
|
||||
* byte length of something
|
||||
*/
|
||||
type ByteLength<T extends { length: unknown }> = T['length'];
|
||||
|
||||
/**
|
||||
* ASN.1 L(length) (hexadecimal string)
|
||||
*/
|
||||
type ASN1L = HexString;
|
||||
|
||||
/**
|
||||
* ASN.1 T(tag) (hexadecimal string)
|
||||
*/
|
||||
type ASN1T = HexString;
|
||||
|
||||
/**
|
||||
* ASN.1 V(value) (hexadecimal string)
|
||||
*/
|
||||
type ASN1V = HexString;
|
||||
|
||||
/**
|
||||
* ASN.1 TLV (hexadecimal string)
|
||||
*/
|
||||
type ASN1TLV = HexString;
|
||||
|
||||
/**
|
||||
* ASN.1 object string
|
||||
*/
|
||||
type ASN1ObjectString = string;
|
||||
|
||||
/**
|
||||
* nth
|
||||
*/
|
||||
type Nth = number;
|
||||
|
||||
/**
|
||||
* ASN.1 DER encoded OID value (hexadecimal string)
|
||||
*/
|
||||
type ASN1OIDV = HexString;
|
||||
|
||||
class ASN1HEX {
|
||||
public static getLblen(s: ASN1S, idx: Idx<ASN1S>): ByteLength<ASN1L>;
|
||||
|
||||
public static getL(s: ASN1S, idx: Idx<ASN1S>): ASN1L;
|
||||
|
||||
public static getVblen(s: ASN1S, idx: Idx<ASN1S>): ByteLength<ASN1V>;
|
||||
|
||||
public static getVidx(s: ASN1S, idx: Idx<ASN1S>): Idx<ASN1V>;
|
||||
|
||||
public static getV(s: ASN1S, idx: Idx<ASN1S>): ASN1V;
|
||||
|
||||
public static getTLV(s: ASN1S, idx: Idx<ASN1S>): ASN1TLV;
|
||||
|
||||
public static getNextSiblingIdx(s: ASN1S, idx: Idx<ASN1S>): Idx<ASN1ObjectString>;
|
||||
|
||||
public static getChildIdx(h: ASN1S, pos: Idx<ASN1S>): Idx<ASN1ObjectString>[];
|
||||
|
||||
public static getNthChildIdx(h: ASN1S, idx: Idx<ASN1S>, nth: Nth): Idx<ASN1ObjectString>;
|
||||
|
||||
public static getIdxbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): Idx<Mutable<Nth[]>>;
|
||||
|
||||
public static getTLVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): ASN1TLV;
|
||||
|
||||
// eslint:disable-next-line:bool-param-default
|
||||
public static getVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string, removeUnusedbits?: boolean): ASN1V;
|
||||
|
||||
public static hextooidstr(hex: ASN1OIDV): OID;
|
||||
|
||||
public static dump(hexOrObj: ASN1S | ASN1Object, flags?: Record<string, unknown>, idx?: Idx<ASN1S>, indent?: string): string;
|
||||
|
||||
public static isASN1HEX(hex: string): hex is HexString;
|
||||
|
||||
public static oidname(oidDotOrHex: OID | ASN1OIDV): OIDName;
|
||||
}
|
||||
|
||||
//// BIG INTEGER TYPES (PARTIAL)
|
||||
|
||||
class BigInteger {
|
||||
constructor(a: null);
|
||||
|
||||
constructor(a: number, b: SecureRandom);
|
||||
|
||||
constructor(a: number, b: number, c: SecureRandom);
|
||||
|
||||
constructor(a: unknown);
|
||||
|
||||
constructor(a: string, b: number);
|
||||
|
||||
public am(i: number, x: number, w: number, j: number, c: number, n: number): number;
|
||||
|
||||
public DB: number;
|
||||
|
||||
public DM: number;
|
||||
|
||||
public DV: number;
|
||||
|
||||
public FV: number;
|
||||
|
||||
public F1: number;
|
||||
|
||||
public F2: number;
|
||||
|
||||
protected copyTo(r: Mutable<BigInteger>): void;
|
||||
|
||||
protected fromInt(x: number): void;
|
||||
|
||||
protected fromString(s: string, b: number): void;
|
||||
|
||||
protected clamp(): void;
|
||||
|
||||
public toString(b: number): string;
|
||||
|
||||
public negate(): BigInteger;
|
||||
|
||||
public abs(): BigInteger;
|
||||
|
||||
public compareTo(a: BigInteger): number;
|
||||
|
||||
public bitLength(): number;
|
||||
|
||||
protected dlShiftTo(n: number, r: Mutable<BigInteger>): void;
|
||||
|
||||
protected drShiftTo(n: number, r: Mutable<BigInteger>): void;
|
||||
|
||||
protected lShiftTo(n: number, r: Mutable<BigInteger>): void;
|
||||
|
||||
protected rShiftTo(n: number, r: Mutable<BigInteger>): void;
|
||||
|
||||
protected subTo(a: BigInteger, r: Mutable<BigInteger>): void;
|
||||
|
||||
protected multiplyTo(a: BigInteger, r: Mutable<BigInteger>): void;
|
||||
|
||||
protected squareTo(r: Mutable<BigInteger>): void;
|
||||
|
||||
protected divRemTo(m: BigInteger, q: Mutable<BigInteger>, r: Mutable<BigInteger>): void;
|
||||
|
||||
public mod(a: BigInteger): BigInteger;
|
||||
|
||||
protected invDigit(): number;
|
||||
|
||||
protected isEven(): boolean;
|
||||
|
||||
protected exp(e: number, z: Classic | Montgomery): BigInteger;
|
||||
|
||||
public modPowInt(e: number, m: BigInteger): BigInteger;
|
||||
|
||||
public static ZERO: BigInteger;
|
||||
|
||||
public static ONE: BigInteger;
|
||||
}
|
||||
|
||||
class Classic {
|
||||
constructor(m: BigInteger);
|
||||
|
||||
public convert(x: BigInteger): BigInteger;
|
||||
|
||||
public revert(x: BigInteger): BigInteger;
|
||||
|
||||
public reduce(x: Mutable<BigInteger>): void;
|
||||
|
||||
public mulTo(x: BigInteger, r: Mutable<BigInteger>): void;
|
||||
|
||||
public sqrTo(x: BigInteger, y: BigInteger, r: Mutable<BigInteger>): void;
|
||||
}
|
||||
|
||||
class Montgomery {
|
||||
constructor(m: BigInteger);
|
||||
|
||||
public convert(x: BigInteger): BigInteger;
|
||||
|
||||
public revert(x: BigInteger): BigInteger;
|
||||
|
||||
public reduce(x: Mutable<BigInteger>): void;
|
||||
|
||||
public mulTo(x: BigInteger, r: Mutable<BigInteger>): void;
|
||||
|
||||
public sqrTo(x: BigInteger, y: BigInteger, r: Mutable<BigInteger>): void;
|
||||
}
|
||||
|
||||
//// KEYUTIL TYPES
|
||||
|
||||
type DecryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
|
||||
|
||||
type Decrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
|
||||
|
||||
type DecryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
|
||||
|
||||
type EncryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
|
||||
|
||||
type Encrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
|
||||
|
||||
type EncryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
|
||||
|
||||
type AlgList = {
|
||||
'AES-256-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 32; ivlen: 16; };
|
||||
'AES-192-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 24; ivlen: 16; };
|
||||
'AES-128-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 16; ivlen: 16; };
|
||||
'DES-EDE3-CBC': { 'proc': Decrypt3DES; 'eproc': Encrypt3DES; keylen: 24; ivlen: 8; };
|
||||
'DES-CBC': { 'proc': DecryptDES; 'eproc': EncryptDES; keylen: 8; ivlen: 8; };
|
||||
};
|
||||
|
||||
type AlgName = keyof AlgList;
|
||||
|
||||
type PEMHeadAlgName = 'RSA' | 'EC' | 'DSA';
|
||||
|
||||
type GetKeyRSAParam = RSAKey | {
|
||||
n: BigInteger;
|
||||
e: number;
|
||||
} | Record<'n' | 'e', HexString> | Record<'n' | 'e', HexString> & Record<'d' | 'p' | 'q' | 'dp' | 'dq' | 'co', HexString | null> | {
|
||||
n: BigInteger;
|
||||
e: number;
|
||||
d: BigInteger;
|
||||
} | {
|
||||
kty: 'RSA';
|
||||
} & Record<'n' | 'e', Base64URLString> | {
|
||||
kty: 'RSA';
|
||||
} & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | {
|
||||
kty: 'RSA';
|
||||
} & Record<'n' | 'e' | 'd', Base64URLString>;
|
||||
|
||||
type GetKeyECDSAParam = KJUR.crypto.ECDSA | {
|
||||
curve: KJUR.crypto.CurveName;
|
||||
xy: HexString;
|
||||
} | {
|
||||
curve: KJUR.crypto.CurveName;
|
||||
d: HexString;
|
||||
} | {
|
||||
kty: 'EC';
|
||||
crv: KJUR.crypto.CurveName;
|
||||
x: Base64URLString;
|
||||
y: Base64URLString;
|
||||
} | {
|
||||
kty: 'EC';
|
||||
crv: KJUR.crypto.CurveName;
|
||||
x: Base64URLString;
|
||||
y: Base64URLString;
|
||||
d: Base64URLString;
|
||||
};
|
||||
|
||||
type GetKeyDSAParam = KJUR.crypto.DSA | Record<'p' | 'q' | 'g', BigInteger> & Record<'y', BigInteger | null> | Record<'p' | 'q' | 'g' | 'x', BigInteger> & Record<'y', BigInteger | null>;
|
||||
|
||||
type GetKeyParam = GetKeyRSAParam | GetKeyECDSAParam | GetKeyDSAParam | string;
|
||||
|
||||
class KEYUTIL {
|
||||
public version: '1.0.0';
|
||||
|
||||
public parsePKCS5PEM(sPKCS5PEM: PEM): Partial<Record<'type' | 's', string>> & (Record<'cipher' | 'ivsalt', string> | Record<'cipher' | 'ivsalt', undefined>);
|
||||
|
||||
public getKeyAndUnusedIvByPasscodeAndIvsalt(algName: AlgName, passcode: string, ivsaltHex: HexString): Record<'keyhex' | 'ivhex', HexString>;
|
||||
|
||||
public decryptKeyB64(privateKeyB64: Base64String, sharedKeyAlgName: AlgName, sharedKeyHex: HexString, ivsaltHex: HexString): Base64String;
|
||||
|
||||
public getDecryptedKeyHex(sEncryptedPEM: PEM, passcode: string): HexString;
|
||||
|
||||
public getEncryptedPKCS5PEMFromPrvKeyHex(pemHeadAlg: PEMHeadAlgName, hPrvKey: string, passcode: string, sharedKeyAlgName?: AlgName | null, ivsaltHex?: HexString | null): PEM;
|
||||
|
||||
public parseHexOfEncryptedPKCS8(sHEX: HexString): {
|
||||
ciphertext: ASN1V;
|
||||
encryptionSchemeAlg: 'TripleDES';
|
||||
encryptionSchemeIV: ASN1V;
|
||||
pbkdf2Salt: ASN1V;
|
||||
pbkdf2Iter: number;
|
||||
};
|
||||
|
||||
public getPBKDF2KeyHexFromParam(info: ReturnType<this['parseHexOfEncryptedPKCS8']>, passcode: string): HexString;
|
||||
|
||||
private _getPlainPKCS8HexFromEncryptedPKCS8PEM(pkcs8PEM: PEM, passcode: string): HexString;
|
||||
|
||||
public getKeyFromEncryptedPKCS8PEM(prvKeyHex: HexString): ReturnType<this['getKeyFromPlainPrivatePKCS8Hex']>;
|
||||
|
||||
public parsePlainPrivatePKCS8Hex(pkcs8PrvHex: HexString): {
|
||||
algparam: ASN1V | null;
|
||||
algoid: ASN1V;
|
||||
keyidx: Idx<ASN1V>;
|
||||
};
|
||||
|
||||
public getKeyFromPlainPrivatePKCS8PEM(prvKeyHex: HexString): ReturnType<this['getKeyFromPlainPrivatePKCS8Hex']>;
|
||||
|
||||
public getKeyFromPlainPrivatePKCS8Hex(prvKeyHex: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA;
|
||||
|
||||
private _getKeyFromPublicPKCS8Hex(h: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA;
|
||||
|
||||
public parsePublicRawRSAKeyHex(pubRawRSAHex: HexString): Record<'n' | 'e', ASN1V>;
|
||||
|
||||
public parsePublicPKCS8Hex(pkcs8PubHex: HexString): {
|
||||
algparam: ASN1V | Record<'p' | 'q' | 'g', ASN1V> | null;
|
||||
algoid: ASN1V;
|
||||
key: ASN1V;
|
||||
};
|
||||
|
||||
public static getKey(param: GetKeyRSAParam): RSAKey;
|
||||
|
||||
public static getKey(param: GetKeyECDSAParam): KJUR.crypto.ECDSA;
|
||||
|
||||
public static getKey(param: GetKeyDSAParam): KJUR.crypto.DSA;
|
||||
|
||||
public static getKey(param: string, passcode?: string, hextype?: string): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
|
||||
|
||||
public static generateKeypair(alg: 'RSA', keylen: number): Record<'prvKeyObj' | 'pubKeyObj', RSAKey>;
|
||||
|
||||
public static generateKeypair(alg: 'EC', curve: KJUR.crypto.CurveName): Record<'prvKeyObj' | 'pubKeyObj', KJUR.crypto.ECDSA>;
|
||||
|
||||
public static getPEM(keyObjOrHex: RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA, formatType?: 'PKCS1PRV' | 'PKCS5PRV' | 'PKCS8PRV', passwd?: string, encAlg?: 'DES-CBC' | 'DES-EDE3-CBC' | 'AES-128-CBC' | 'AES-192-CBC' | 'AES-256-CBC', hexType?: string, ivsaltHex?: HexString): object; // To Do
|
||||
|
||||
public static getKeyFromCSRPEM(csrPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
|
||||
|
||||
public static getKeyFromCSRHex(csrHex: HexString): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
|
||||
|
||||
public static parseCSRHex(csrHex: HexString): Record<'p8pubkeyhex', ASN1TLV>;
|
||||
|
||||
public static getJWKFromKey(keyObj: RSAKey): {
|
||||
kty: 'RSA';
|
||||
} & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | {
|
||||
kty: 'RSA';
|
||||
} & Record<'n' | 'e', Base64URLString>;
|
||||
|
||||
public static getJWKFromKey(keyObj: KJUR.crypto.ECDSA): {
|
||||
kty: 'EC';
|
||||
crv: KJUR.crypto.CurveName;
|
||||
x: Base64URLString;
|
||||
y: Base64URLString;
|
||||
d: Base64URLString;
|
||||
} | {
|
||||
kty: 'EC';
|
||||
crv: KJUR.crypto.CurveName;
|
||||
x: Base64URLString;
|
||||
y: Base64URLString;
|
||||
};
|
||||
}
|
||||
|
||||
//// KJUR NAMESPACE (PARTIAL)
|
||||
|
||||
namespace KJUR {
|
||||
namespace crypto {
|
||||
type CurveName = 'secp128r1' | 'secp160k1' | 'secp160r1' | 'secp192k1' | 'secp192r1' | 'secp224r1' | 'secp256k1' | 'secp256r1' | 'secp384r1' | 'secp521r1';
|
||||
|
||||
class DSA {
|
||||
public p: BigInteger | null;
|
||||
|
||||
public q: BigInteger | null;
|
||||
|
||||
public g: BigInteger | null;
|
||||
|
||||
public y: BigInteger | null;
|
||||
|
||||
public x: BigInteger | null;
|
||||
|
||||
public type: 'DSA';
|
||||
|
||||
public isPrivate: boolean;
|
||||
|
||||
public isPublic: boolean;
|
||||
|
||||
public setPrivate(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger | null, x: BigInteger): void;
|
||||
|
||||
public setPrivateHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString | null, hX: HexString): void;
|
||||
|
||||
public setPublic(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger): void;
|
||||
|
||||
public setPublicHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString): void;
|
||||
|
||||
public signWithMessageHash(sHashHex: HexString): HexString;
|
||||
|
||||
public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean;
|
||||
|
||||
public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger];
|
||||
|
||||
public readPKCS5PrvKeyHex(h: HexString): void;
|
||||
|
||||
public readPKCS8PrvKeyHex(h: HexString): void;
|
||||
|
||||
public readPKCS8PubKeyHex(h: HexString): void;
|
||||
|
||||
public readCertPubKeyHex(h: HexString, nthPKI: number): void;
|
||||
}
|
||||
|
||||
class ECDSA {
|
||||
constructor(params?: {
|
||||
curve?: CurveName;
|
||||
prv?: HexString;
|
||||
pub?: HexString;
|
||||
});
|
||||
|
||||
public p: BigInteger | null;
|
||||
|
||||
public q: BigInteger | null;
|
||||
|
||||
public g: BigInteger | null;
|
||||
|
||||
public y: BigInteger | null;
|
||||
|
||||
public x: BigInteger | null;
|
||||
|
||||
public type: 'EC';
|
||||
|
||||
public isPrivate: boolean;
|
||||
|
||||
public isPublic: boolean;
|
||||
|
||||
public getBigRandom(limit: BigInteger): BigInteger;
|
||||
|
||||
public setNamedCurve(curveName: CurveName): void;
|
||||
|
||||
public setPrivateKeyHex(prvKeyHex: HexString): void;
|
||||
|
||||
public setPublicKeyHex(pubKeyHex: HexString): void;
|
||||
|
||||
public getPublicKeyXYHex(): Record<'x' | 'y', HexString>;
|
||||
|
||||
public getShortNISTPCurveName(): 'P-256' | 'P-384' | null;
|
||||
|
||||
public generateKeyPairHex(): Record<'ecprvhex' | 'ecpubhex', HexString>;
|
||||
|
||||
public signWithMessageHash(hashHex: HexString): HexString;
|
||||
|
||||
public signHex(hashHex: HexString, privHex: HexString): HexString;
|
||||
|
||||
public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean;
|
||||
|
||||
public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger];
|
||||
|
||||
public readPKCS5PrvKeyHex(h: HexString): void;
|
||||
|
||||
public readPKCS8PrvKeyHex(h: HexString): void;
|
||||
|
||||
public readPKCS8PubKeyHex(h: HexString): void;
|
||||
|
||||
public readCertPubKeyHex(h: HexString, nthPKI: number): void;
|
||||
|
||||
public static parseSigHex(sigHex: HexString): Record<'r' | 's', BigInteger>;
|
||||
|
||||
public static parseSigHexInHexRS(sigHex: HexString): Record<'r' | 's', ASN1V>;
|
||||
|
||||
public static asn1SigToConcatSig(asn1Sig: HexString): HexString;
|
||||
|
||||
public static concatSigToASN1Sig(concatSig: HexString): ASN1TLV;
|
||||
|
||||
public static hexRSSigToASN1Sig(hR: HexString, hS: HexString): ASN1TLV;
|
||||
|
||||
public static biRSSigToASN1Sig(biR: BigInteger, biS: BigInteger): ASN1TLV;
|
||||
|
||||
public static getName(s: CurveName | HexString): 'secp256r1' | 'secp256k1' | 'secp384r1' | null;
|
||||
}
|
||||
|
||||
class Signature {
|
||||
constructor(params?: ({
|
||||
alg: string;
|
||||
prov?: string;
|
||||
} | {}) & ({
|
||||
psssaltlen: number;
|
||||
} | {}) & ({
|
||||
prvkeypem: PEM;
|
||||
prvkeypas?: never;
|
||||
} | {}));
|
||||
|
||||
private _setAlgNames(): void;
|
||||
|
||||
private _zeroPaddingOfSignature(hex: HexString, bitLength: number): HexString;
|
||||
|
||||
public setAlgAndProvider(alg: string, prov: string): void;
|
||||
|
||||
public init(key: GetKeyParam, pass?: string): void;
|
||||
|
||||
public updateString(str: string): void;
|
||||
|
||||
public updateHex(hex: HexString): void;
|
||||
|
||||
public sign(): HexString;
|
||||
|
||||
public signString(str: string): HexString;
|
||||
|
||||
public signHex(hex: HexString): HexString;
|
||||
|
||||
public verify(hSigVal: string): boolean | 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// RSAKEY TYPES
|
||||
|
||||
class RSAKey {
|
||||
public n: BigInteger | null;
|
||||
|
||||
public e: number;
|
||||
|
||||
public d: BigInteger | null;
|
||||
|
||||
public p: BigInteger | null;
|
||||
|
||||
public q: BigInteger | null;
|
||||
|
||||
public dmp1: BigInteger | null;
|
||||
|
||||
public dmq1: BigInteger | null;
|
||||
|
||||
public coeff: BigInteger | null;
|
||||
|
||||
public type: 'RSA';
|
||||
|
||||
public isPrivate?: boolean;
|
||||
|
||||
public isPublic?: boolean;
|
||||
|
||||
//// RSA PUBLIC
|
||||
|
||||
protected doPublic(x: BigInteger): BigInteger;
|
||||
|
||||
public setPublic(N: BigInteger, E: number): void;
|
||||
|
||||
public setPublic(N: HexString, E: HexString): void;
|
||||
|
||||
public encrypt(text: string): HexString | null;
|
||||
|
||||
public encryptOAEP(text: string, hash?: string | ((s: string) => string), hashLen?: number): HexString | null;
|
||||
|
||||
//// RSA PRIVATE
|
||||
|
||||
protected doPrivate(x: BigInteger): BigInteger;
|
||||
|
||||
public setPrivate(N: BigInteger, E: number, D: BigInteger): void;
|
||||
|
||||
public setPrivate(N: HexString, E: HexString, D: HexString): void;
|
||||
|
||||
public setPrivateEx(N: HexString, E: HexString, D?: HexString | null, P?: HexString | null, Q?: HexString | null, DP?: HexString | null, DQ?: HexString | null, C?: HexString | null): void;
|
||||
|
||||
public generate(B: number, E: HexString): void;
|
||||
|
||||
public decrypt(ctext: HexString): string;
|
||||
|
||||
public decryptOAEP(ctext: HexString, hash?: string | ((s: string) => string), hashLen?: number): string | null;
|
||||
|
||||
//// RSA PEM
|
||||
|
||||
public getPosArrayOfChildrenFromHex(hPrivateKey: PEM): Idx<ASN1ObjectString>[];
|
||||
|
||||
public getHexValueArrayOfChildrenFromHex(hPrivateKey: PEM): Idx<ASN1ObjectString>[];
|
||||
|
||||
public readPrivateKeyFromPEMString(keyPEM: PEM): void;
|
||||
|
||||
public readPKCS5PrvKeyHex(h: HexString): void;
|
||||
|
||||
public readPKCS8PrvKeyHex(h: HexString): void;
|
||||
|
||||
public readPKCS5PubKeyHex(h: HexString): void;
|
||||
|
||||
public readPKCS8PubKeyHex(h: HexString): void;
|
||||
|
||||
public readCertPubKeyHex(h: HexString, nthPKI: Nth): void;
|
||||
|
||||
//// RSA SIGN
|
||||
|
||||
public sign(s: string, hashAlg: string): HexString;
|
||||
|
||||
public signWithMessageHash(sHashHex: HexString, hashAlg: string): HexString;
|
||||
|
||||
public signPSS(s: string, hashAlg: string, sLen: number): HexString;
|
||||
|
||||
public signWithMessageHashPSS(hHash: HexString, hashAlg: string, sLen: number): HexString;
|
||||
|
||||
public verify(sMsg: string, hSig: HexString): boolean | 0;
|
||||
|
||||
public verifyWithMessageHash(sHashHex: HexString, hSig: HexString): boolean | 0;
|
||||
|
||||
public verifyPSS(sMsg: string, hSig: HexString, hashAlg: string, sLen: number): boolean;
|
||||
|
||||
public verifyWithMessageHashPSS(hHash: HexString, hSig: HexString, hashAlg: string, sLen: number): boolean;
|
||||
|
||||
public static SALT_LEN_HLEN: -1;
|
||||
|
||||
public static SALT_LEN_MAX: -2;
|
||||
|
||||
public static SALT_LEN_RECOVER: -2;
|
||||
}
|
||||
|
||||
/// RNG TYPES
|
||||
class SecureRandom {
|
||||
public nextBytes(ba: Mutable<ByteNumber[]>): void;
|
||||
}
|
||||
|
||||
//// X509 TYPES
|
||||
|
||||
type ExtInfo = {
|
||||
critical: boolean;
|
||||
oid: OID;
|
||||
vidx: Idx<ASN1V>;
|
||||
};
|
||||
|
||||
type ExtAIAInfo = Record<'ocsp' | 'caissuer', string>;
|
||||
|
||||
type ExtCertificatePolicy = {
|
||||
id: OIDName;
|
||||
} & Partial<{
|
||||
cps: string;
|
||||
} | {
|
||||
unotice: string;
|
||||
}>;
|
||||
|
||||
class X509 {
|
||||
public hex: HexString | null;
|
||||
|
||||
public version: number;
|
||||
|
||||
public foffset: number;
|
||||
|
||||
public aExtInfo: null;
|
||||
|
||||
public getVersion(): number;
|
||||
|
||||
public getSerialNumberHex(): ASN1V;
|
||||
|
||||
public getSignatureAlgorithmField(): OIDName;
|
||||
|
||||
public getIssuerHex(): ASN1TLV;
|
||||
|
||||
public getIssuerString(): HexString;
|
||||
|
||||
public getSubjectHex(): ASN1TLV;
|
||||
|
||||
public getSubjectString(): HexString;
|
||||
|
||||
public getNotBefore(): TimeValue;
|
||||
|
||||
public getNotAfter(): TimeValue;
|
||||
|
||||
public getPublicKeyHex(): ASN1TLV;
|
||||
|
||||
public getPublicKeyIdx(): Idx<Mutable<Nth[]>>;
|
||||
|
||||
public getPublicKeyContentIdx(): Idx<Mutable<Nth[]>>;
|
||||
|
||||
public getPublicKey(): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
|
||||
|
||||
public getSignatureAlgorithmName(): OIDName;
|
||||
|
||||
public getSignatureValueHex(): ASN1V;
|
||||
|
||||
public verifySignature(pubKey: GetKeyParam): boolean | 0;
|
||||
|
||||
public parseExt(): void;
|
||||
|
||||
public getExtInfo(oidOrName: OID | string): ExtInfo | undefined;
|
||||
|
||||
public getExtBasicConstraints(): ExtInfo | {} | {
|
||||
cA: true;
|
||||
pathLen?: number;
|
||||
};
|
||||
|
||||
public getExtKeyUsageBin(): BinString;
|
||||
|
||||
public getExtKeyUsageString(): string;
|
||||
|
||||
public getExtSubjectKeyIdentifier(): ASN1V | undefined;
|
||||
|
||||
public getExtAuthorityKeyIdentifier(): {
|
||||
kid: ASN1V;
|
||||
} | undefined;
|
||||
|
||||
public getExtExtKeyUsageName(): OIDName[] | undefined;
|
||||
|
||||
public getExtSubjectAltName(): Deprecated<string[]>;
|
||||
|
||||
public getExtSubjectAltName2(): ['MAIL' | 'DNS' | 'DN' | 'URI' | 'IP', string][] | undefined;
|
||||
|
||||
public getExtCRLDistributionPointsURI(): string[] | undefined;
|
||||
|
||||
public getExtAIAInfo(): ExtAIAInfo | undefined;
|
||||
|
||||
public getExtCertificatePolicies(): ExtCertificatePolicy[] | undefined;
|
||||
|
||||
public readCertPEM(sCertPEM: PEM): void;
|
||||
|
||||
public readCertHex(sCertHex: HexString): void;
|
||||
|
||||
public getInfo(): string;
|
||||
|
||||
public static hex2dn(hex: HexString, idx?: Idx<HexString>): string;
|
||||
|
||||
public static hex2rdn(hex: HexString, idx?: Idx<HexString>): string;
|
||||
|
||||
public static hex2attrTypeValue(hex: HexString, idx?: Idx<HexString>): string;
|
||||
|
||||
public static getPublicKeyFromCertPEM(sCertPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
|
||||
|
||||
public static getPublicKeyInfoPropOfCertPEM(sCertPEM: PEM): {
|
||||
algparam: ASN1V | null;
|
||||
leyhex: ASN1V;
|
||||
algoid: ASN1V;
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import cluster from 'node:cluster';
|
||||
import chalk from 'chalk';
|
||||
import { default as Xev } from 'xev';
|
||||
import Xev from 'xev';
|
||||
|
||||
import Logger from '@/services/logger.js';
|
||||
import { envOption } from '../env.js';
|
||||
@@ -12,7 +12,7 @@ import { workerMain } from './worker.js';
|
||||
|
||||
const logger = new Logger('core', 'cyan');
|
||||
const clusterLogger = logger.createSubLogger('cluster', 'orange', false);
|
||||
const ev = new Xev.default();
|
||||
const ev = new Xev();
|
||||
|
||||
/**
|
||||
* Init process
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { default as Xev } from 'xev';
|
||||
import Xev from 'xev';
|
||||
import { deliverQueue, inboxQueue } from '../queue/queues.js';
|
||||
|
||||
const ev = new Xev.default();
|
||||
const ev = new Xev();
|
||||
|
||||
const interval = 10000;
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import si from 'systeminformation';
|
||||
import { default as Xev } from 'xev';
|
||||
import Xev from 'xev';
|
||||
import * as osUtils from 'os-utils';
|
||||
|
||||
const ev = new Xev.default();
|
||||
const ev = new Xev();
|
||||
|
||||
const interval = 2000;
|
||||
|
||||
|
@@ -73,6 +73,7 @@ import { PasswordResetRequest } from '@/models/entities/password-reset-request.j
|
||||
import { UserPending } from '@/models/entities/user-pending.js';
|
||||
|
||||
import { entities as charts } from '@/services/chart/entities.js';
|
||||
import { Webhook } from '@/models/entities/webhook.js';
|
||||
|
||||
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
|
||||
|
||||
@@ -171,6 +172,7 @@ export const entities = [
|
||||
Ad,
|
||||
PasswordResetRequest,
|
||||
UserPending,
|
||||
Webhook,
|
||||
...charts,
|
||||
];
|
||||
|
||||
@@ -207,7 +209,11 @@ export const db = new DataSource({
|
||||
});
|
||||
|
||||
export async function initDb() {
|
||||
await db.connect();
|
||||
if (db.isInitialized) {
|
||||
// nop
|
||||
} else {
|
||||
await db.connect();
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetDb() {
|
||||
|
@@ -1,33 +0,0 @@
|
||||
import { Context } from 'cafy';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export class ID<Maybe = string> extends Context<string | (Maybe extends {} ? string : Maybe)> {
|
||||
public readonly name = 'ID';
|
||||
|
||||
constructor(optional = false, nullable = false) {
|
||||
super(optional, nullable);
|
||||
|
||||
this.push((v: any) => {
|
||||
if (typeof v !== 'string') {
|
||||
return new Error('must-be-an-id');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public getType() {
|
||||
return super.getType('String');
|
||||
}
|
||||
|
||||
public makeOptional(): ID<undefined> {
|
||||
return new ID(true, false);
|
||||
}
|
||||
|
||||
public makeNullable(): ID<null> {
|
||||
return new ID(false, true);
|
||||
}
|
||||
|
||||
public makeOptionalNullable(): ID<undefined | null> {
|
||||
return new ID(true, true);
|
||||
}
|
||||
}
|
@@ -42,7 +42,8 @@ async function getCaptchaResponse(url: string, secret: string, response: string)
|
||||
headers: {
|
||||
'User-Agent': config.userAgent,
|
||||
},
|
||||
timeout: 10 * 1000,
|
||||
// TODO
|
||||
//timeout: 10 * 1000,
|
||||
agent: getAgentByUrl,
|
||||
}).catch(e => {
|
||||
throw `${e.message || e}`;
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as http from 'node:http';
|
||||
import * as https from 'node:https';
|
||||
import { URL } from 'node:url';
|
||||
import CacheableLookup from 'cacheable-lookup';
|
||||
import fetch from 'node-fetch';
|
||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
||||
import config from '@/config/index.js';
|
||||
import { URL } from 'node:url';
|
||||
|
||||
export async function getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record<string, string>) {
|
||||
const res = await getResponse({
|
||||
@@ -35,7 +35,7 @@ export async function getHtml(url: string, accept = 'text/html, */*', timeout =
|
||||
}
|
||||
|
||||
export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number }) {
|
||||
const timeout = args?.timeout || 10 * 1000;
|
||||
const timeout = args.timeout || 10 * 1000;
|
||||
|
||||
const controller = new AbortController();
|
||||
setTimeout(() => {
|
||||
@@ -47,7 +47,7 @@ export async function getResponse(args: { url: string, method: string, body?: st
|
||||
headers: args.headers,
|
||||
body: args.body,
|
||||
timeout,
|
||||
size: args?.size || 10 * 1024 * 1024,
|
||||
size: args.size || 10 * 1024 * 1024,
|
||||
agent: getAgentByUrl,
|
||||
signal: controller.signal,
|
||||
});
|
||||
@@ -120,9 +120,9 @@ export const httpsAgent = config.proxy
|
||||
*/
|
||||
export function getAgentByUrl(url: URL, bypassProxy = false) {
|
||||
if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) {
|
||||
return url.protocol == 'http:' ? _http : _https;
|
||||
return url.protocol === 'http:' ? _http : _https;
|
||||
} else {
|
||||
return url.protocol == 'http:' ? httpAgent : httpsAgent;
|
||||
return url.protocol === 'http:' ? httpAgent : httpsAgent;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -23,7 +23,7 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
|
||||
}
|
||||
|
||||
// ファイルが添付されているとき
|
||||
if ((note.files || []).length != 0) {
|
||||
if ((note.files || []).length !== 0) {
|
||||
summary += ` (📎${note.files!.length})`;
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,7 @@ export interface Schema extends OfSchema {
|
||||
readonly optional?: boolean;
|
||||
readonly items?: Schema;
|
||||
readonly properties?: Obj;
|
||||
readonly required?: ReadonlyArray<keyof NonNullable<this['properties']>>;
|
||||
readonly required?: ReadonlyArray<Extract<keyof NonNullable<this['properties']>, string>>;
|
||||
readonly description?: string;
|
||||
readonly example?: any;
|
||||
readonly format?: string;
|
||||
@@ -98,6 +98,9 @@ export interface Schema extends OfSchema {
|
||||
readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
|
||||
readonly maxLength?: number;
|
||||
readonly minLength?: number;
|
||||
readonly maximum?: number;
|
||||
readonly minimum?: number;
|
||||
readonly pattern?: string;
|
||||
}
|
||||
|
||||
type RequiredPropertyNames<s extends Obj> = {
|
||||
@@ -105,24 +108,26 @@ type RequiredPropertyNames<s extends Obj> = {
|
||||
// K is not optional
|
||||
s[K]['optional'] extends false ? K :
|
||||
// K has default value
|
||||
s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K : never
|
||||
s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K :
|
||||
never
|
||||
}[keyof s];
|
||||
|
||||
export interface Obj { [key: string]: Schema; }
|
||||
export type Obj = Record<string, Schema>;
|
||||
|
||||
// https://github.com/misskey-dev/misskey/issues/8535
|
||||
// To avoid excessive stack depth error,
|
||||
// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
|
||||
export type ObjType<s extends Obj, RequiredProps extends keyof s> =
|
||||
{ -readonly [P in keyof s]?: SchemaType<s[P]> } &
|
||||
{ -readonly [P in RequiredProps]: SchemaType<s[P]> } &
|
||||
{ -readonly [P in RequiredPropertyNames<s>]: SchemaType<s[P]> };
|
||||
UnionToIntersection<
|
||||
{ -readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]> } &
|
||||
{ -readonly [R in RequiredProps]-?: SchemaType<s[R]> } &
|
||||
{ -readonly [P in keyof s]?: SchemaType<s[P]> }
|
||||
>;
|
||||
|
||||
type NullOrUndefined<p extends Schema, T> =
|
||||
p['nullable'] extends true
|
||||
? p['optional'] extends true
|
||||
? (T | null | undefined)
|
||||
: (T | null)
|
||||
: p['optional'] extends true
|
||||
? (T | undefined)
|
||||
: T;
|
||||
| (p['nullable'] extends true ? null : never)
|
||||
| (p['optional'] extends true ? undefined : never)
|
||||
| T;
|
||||
|
||||
// https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
|
||||
// Get intersection from union
|
||||
@@ -139,9 +144,9 @@ export type SchemaTypeDef<p extends Schema> =
|
||||
p['type'] extends 'number' ? number :
|
||||
p['type'] extends 'string' ? (
|
||||
p['enum'] extends readonly string[] ?
|
||||
p['enum'][number] :
|
||||
p['format'] extends 'date-time' ? string : // Dateにする??
|
||||
string
|
||||
p['enum'][number] :
|
||||
p['format'] extends 'date-time' ? string : // Dateにする??
|
||||
string
|
||||
) :
|
||||
p['type'] extends 'boolean' ? boolean :
|
||||
p['type'] extends 'object' ? (
|
||||
|
49
packages/backend/src/misc/webhook-cache.ts
Normal file
49
packages/backend/src/misc/webhook-cache.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Webhooks } from '@/models/index.js';
|
||||
import { Webhook } from '@/models/entities/webhook.js';
|
||||
import { subsdcriber } from '../db/redis.js';
|
||||
|
||||
let webhooksFetched = false;
|
||||
let webhooks: Webhook[] = [];
|
||||
|
||||
export async function getActiveWebhooks() {
|
||||
if (!webhooksFetched) {
|
||||
webhooks = await Webhooks.findBy({
|
||||
active: true,
|
||||
});
|
||||
webhooksFetched = true;
|
||||
}
|
||||
|
||||
return webhooks;
|
||||
}
|
||||
|
||||
subsdcriber.on('message', async (_, data) => {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message;
|
||||
switch (type) {
|
||||
case 'webhookCreated':
|
||||
if (body.active) {
|
||||
webhooks.push(body);
|
||||
}
|
||||
break;
|
||||
case 'webhookUpdated':
|
||||
if (body.active) {
|
||||
const i = webhooks.findIndex(a => a.id === body.id);
|
||||
if (i > -1) {
|
||||
webhooks[i] = body;
|
||||
} else {
|
||||
webhooks.push(body);
|
||||
}
|
||||
} else {
|
||||
webhooks = webhooks.filter(a => a.id !== body.id);
|
||||
}
|
||||
break;
|
||||
case 'webhookDeleted':
|
||||
webhooks = webhooks.filter(a => a.id !== body.id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
@@ -1,6 +1,6 @@
|
||||
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||
import { DriveFile } from './drive-file.js';
|
||||
import { id } from '../id.js';
|
||||
import { DriveFile } from './drive-file.js';
|
||||
|
||||
@Entity()
|
||||
@Index(['usernameLower', 'host'], { unique: true })
|
||||
@@ -207,7 +207,7 @@ export class User {
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether to show users replying to other users in the timeline'
|
||||
comment: 'Whether to show users replying to other users in the timeline',
|
||||
})
|
||||
public showTimelineReplies: boolean;
|
||||
|
||||
|
73
packages/backend/src/models/entities/webhook.ts
Normal file
73
packages/backend/src/models/entities/webhook.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user.js';
|
||||
import { id } from '../id.js';
|
||||
|
||||
export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const;
|
||||
|
||||
@Entity()
|
||||
export class Webhook {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Antenna.',
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The owner ID.',
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
comment: 'The name of the Antenna.',
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}',
|
||||
})
|
||||
public on: (typeof webhookEventTypes)[number][];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
})
|
||||
public url: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
})
|
||||
public secret: string;
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public active: boolean;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト送信日時
|
||||
*/
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public latestSentAt: Date | null;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト送信時のHTTPステータスコード
|
||||
*/
|
||||
@Column('integer', {
|
||||
nullable: true,
|
||||
})
|
||||
public latestStatus: number | null;
|
||||
}
|
@@ -64,6 +64,7 @@ import { Ad } from './entities/ad.js';
|
||||
import { PasswordResetRequest } from './entities/password-reset-request.js';
|
||||
import { UserPending } from './entities/user-pending.js';
|
||||
import { InstanceRepository } from './repositories/instance.js';
|
||||
import { Webhook } from './entities/webhook.js';
|
||||
|
||||
export const Announcements = db.getRepository(Announcement);
|
||||
export const AnnouncementReads = db.getRepository(AnnouncementRead);
|
||||
@@ -125,5 +126,6 @@ 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);
|
||||
export const PasswordResetRequests = db.getRepository(PasswordResetRequest);
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { db } from '@/db/postgre.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { Users, DriveFolders } from '../index.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { toPuny } from '@/misc/convert-host.js';
|
||||
import { awaitAll, Promiseable } from '@/prelude/await-all.js';
|
||||
@@ -9,6 +8,7 @@ import config from '@/config/index.js';
|
||||
import { query, appendQuery } from '@/prelude/url.js';
|
||||
import { Meta } from '@/models/entities/meta.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { Users, DriveFolders } from '../index.js';
|
||||
|
||||
type PackOptions = {
|
||||
detail?: boolean,
|
||||
@@ -29,7 +29,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
|
||||
|
||||
getPublicProperties(file: DriveFile): DriveFile['properties'] {
|
||||
if (file.properties.orientation != null) {
|
||||
const properties = JSON.parse(JSON.stringify(file.properties));
|
||||
const properties = structuredClone(file.properties);
|
||||
if (file.properties.orientation >= 5) {
|
||||
[properties.width, properties.height] = [properties.height, properties.width];
|
||||
}
|
||||
@@ -111,7 +111,40 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
|
||||
|
||||
async pack(
|
||||
src: DriveFile['id'] | DriveFile,
|
||||
options?: PackOptions
|
||||
options?: PackOptions,
|
||||
): Promise<Packed<'DriveFile'>> {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
self: false,
|
||||
}, options);
|
||||
|
||||
const file = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
|
||||
|
||||
return await awaitAll<Packed<'DriveFile'>>({
|
||||
id: file.id,
|
||||
createdAt: file.createdAt.toISOString(),
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
md5: file.md5,
|
||||
size: file.size,
|
||||
isSensitive: file.isSensitive,
|
||||
blurhash: file.blurhash,
|
||||
properties: opts.self ? file.properties : this.getPublicProperties(file),
|
||||
url: opts.self ? file.url : this.getPublicUrl(file, false),
|
||||
thumbnailUrl: this.getPublicUrl(file, true),
|
||||
comment: file.comment,
|
||||
folderId: file.folderId,
|
||||
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
|
||||
detail: true,
|
||||
}) : null,
|
||||
userId: opts.withUser ? file.userId : null,
|
||||
user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null,
|
||||
});
|
||||
},
|
||||
|
||||
async packNullable(
|
||||
src: DriveFile['id'] | DriveFile,
|
||||
options?: PackOptions,
|
||||
): Promise<Packed<'DriveFile'> | null> {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
@@ -145,9 +178,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
|
||||
|
||||
async packMany(
|
||||
files: (DriveFile['id'] | DriveFile)[],
|
||||
options?: PackOptions
|
||||
) {
|
||||
const items = await Promise.all(files.map(f => this.pack(f, options)));
|
||||
return items.filter(x => x != null);
|
||||
options?: PackOptions,
|
||||
): Promise<Packed<'DriveFile'>[]> {
|
||||
const items = await Promise.all(files.map(f => this.packNullable(f, options)));
|
||||
return items.filter((x): x is Packed<'DriveFile'> => x != null);
|
||||
},
|
||||
});
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { db } from '@/db/postgre.js';
|
||||
import { Page } from '@/models/entities/page.js';
|
||||
import { Packed } from '@/misc/schema.js';
|
||||
import { Users, DriveFiles, PageLikes } from '../index.js';
|
||||
import { awaitAll } from '@/prelude/await-all.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Users, DriveFiles, PageLikes } from '../index.js';
|
||||
|
||||
export const PageRepository = db.getRepository(Page).extend({
|
||||
async pack(
|
||||
@@ -14,7 +14,7 @@ export const PageRepository = db.getRepository(Page).extend({
|
||||
const meId = me ? me.id : null;
|
||||
const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
|
||||
|
||||
const attachedFiles: Promise<DriveFile | undefined>[] = [];
|
||||
const attachedFiles: Promise<DriveFile | null>[] = [];
|
||||
const collectFile = (xs: any[]) => {
|
||||
for (const x of xs) {
|
||||
if (x.type === 'image') {
|
||||
@@ -73,7 +73,7 @@ export const PageRepository = db.getRepository(Page).extend({
|
||||
script: page.script,
|
||||
eyeCatchingImageId: page.eyeCatchingImageId,
|
||||
eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
|
||||
attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
|
||||
attachedFiles: DriveFiles.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)),
|
||||
likedCount: page.likedCount,
|
||||
isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
|
||||
});
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { EntityRepository, Repository, In, Not } from 'typeorm';
|
||||
import Ajv from 'ajv';
|
||||
import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
|
||||
import config from '@/config/index.js';
|
||||
import { Packed } from '@/misc/schema.js';
|
||||
import { awaitAll, Promiseable } from '@/prelude/await-all.js';
|
||||
@@ -9,8 +8,9 @@ import { populateEmojis } from '@/misc/populate-emojis.js';
|
||||
import { getAntennas } from '@/misc/antenna-cache.js';
|
||||
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { Instance } from '../entities/instance.js';
|
||||
import { db } from '@/db/postgre.js';
|
||||
import { Instance } from '../entities/instance.js';
|
||||
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
|
||||
|
||||
const userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
|
||||
|
||||
@@ -112,7 +112,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||
const joinings = await UserGroupJoinings.findBy({ userId: userId });
|
||||
|
||||
const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
|
||||
.where(`message.groupId = :groupId`, { groupId: j.userGroupId })
|
||||
.where('message.groupId = :groupId', { groupId: j.userGroupId })
|
||||
.andWhere('message.userId != :userId', { userId: userId })
|
||||
.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
|
||||
.andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
|
||||
@@ -204,8 +204,18 @@ export const UserRepository = db.getRepository(User).extend({
|
||||
);
|
||||
},
|
||||
|
||||
getAvatarUrl(user: User): string {
|
||||
// TODO: avatarIdがあるがavatarがない(JOINされてない)場合のハンドリング
|
||||
async getAvatarUrl(user: User): Promise<string> {
|
||||
if (user.avatar) {
|
||||
return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
|
||||
} else if (user.avatarId) {
|
||||
const avatar = await DriveFiles.findOneByOrFail({ id: user.avatarId });
|
||||
return DriveFiles.getPublicUrl(avatar, true) || this.getIdenticonUrl(user.id);
|
||||
} else {
|
||||
return this.getIdenticonUrl(user.id);
|
||||
}
|
||||
},
|
||||
|
||||
getAvatarUrlSync(user: User): string {
|
||||
if (user.avatar) {
|
||||
return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
|
||||
} else {
|
||||
@@ -223,7 +233,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||
options?: {
|
||||
detail?: D,
|
||||
includeSecrets?: boolean,
|
||||
}
|
||||
},
|
||||
): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
@@ -274,7 +284,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
host: user.host,
|
||||
avatarUrl: this.getAvatarUrl(user),
|
||||
avatarUrl: this.getAvatarUrlSync(user),
|
||||
avatarBlurhash: user.avatar?.blurhash || null,
|
||||
avatarColor: null, // 後方互換性のため
|
||||
isAdmin: user.isAdmin || falsy,
|
||||
@@ -283,7 +293,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||
isCat: user.isCat || falsy,
|
||||
instance: user.host ? userInstanceCache.fetch(user.host,
|
||||
() => Instances.findOneBy({ host: user.host! }),
|
||||
v => v != null
|
||||
v => v != null,
|
||||
).then(instance => instance ? {
|
||||
name: instance.name,
|
||||
softwareName: instance.softwareName,
|
||||
@@ -403,7 +413,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||
options?: {
|
||||
detail?: D,
|
||||
includeSecrets?: boolean,
|
||||
}
|
||||
},
|
||||
): Promise<IsUserDetailed<D>[]> {
|
||||
return Promise.all(users.map(u => this.pack(u, me, options)));
|
||||
},
|
||||
|
@@ -27,6 +27,7 @@ export const packedEmojiSchema = {
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
|
@@ -21,6 +21,7 @@ export const packedUserLiteSchema = {
|
||||
type: 'string',
|
||||
nullable: true, optional: false,
|
||||
example: 'misskey.example.com',
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
avatarUrl: {
|
||||
type: 'string',
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import httpSignature from 'http-signature';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import config from '@/config/index.js';
|
||||
import { envOption } from '../env.js';
|
||||
@@ -8,13 +9,15 @@ import processInbox from './processors/inbox.js';
|
||||
import processDb from './processors/db/index.js';
|
||||
import processObjectStorage from './processors/object-storage/index.js';
|
||||
import processSystemQueue from './processors/system/index.js';
|
||||
import processWebhookDeliver from './processors/webhook-deliver.js';
|
||||
import { endedPollNotification } from './processors/ended-poll-notification.js';
|
||||
import { queueLogger } from './logger.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { getJobInfo } from './get-job-info.js';
|
||||
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue } from './queues.js';
|
||||
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue, webhookDeliverQueue } from './queues.js';
|
||||
import { ThinUser } from './types.js';
|
||||
import { IActivity } from '@/remote/activitypub/type.js';
|
||||
import { Webhook, webhookEventTypes } from '@/models/entities/webhook.js';
|
||||
|
||||
function renderError(e: Error): any {
|
||||
return {
|
||||
@@ -26,6 +29,7 @@ function renderError(e: Error): any {
|
||||
|
||||
const systemLogger = queueLogger.createSubLogger('system');
|
||||
const deliverLogger = queueLogger.createSubLogger('deliver');
|
||||
const webhookLogger = queueLogger.createSubLogger('webhook');
|
||||
const inboxLogger = queueLogger.createSubLogger('inbox');
|
||||
const dbLogger = queueLogger.createSubLogger('db');
|
||||
const objectStorageLogger = queueLogger.createSubLogger('objectStorage');
|
||||
@@ -70,6 +74,14 @@ objectStorageQueue
|
||||
.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
|
||||
|
||||
webhookDeliverQueue
|
||||
.on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`))
|
||||
.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
|
||||
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||
|
||||
export function deliver(user: ThinUser, content: unknown, to: string | null) {
|
||||
if (content == null) return null;
|
||||
if (to == null) return null;
|
||||
@@ -251,12 +263,36 @@ export function createCleanRemoteFilesJob() {
|
||||
});
|
||||
}
|
||||
|
||||
export function webhookDeliver(webhook: Webhook, type: typeof webhookEventTypes[number], content: unknown) {
|
||||
const data = {
|
||||
type,
|
||||
content,
|
||||
webhookId: webhook.id,
|
||||
userId: webhook.userId,
|
||||
to: webhook.url,
|
||||
secret: webhook.secret,
|
||||
createdAt: Date.now(),
|
||||
eventId: uuid(),
|
||||
};
|
||||
|
||||
return webhookDeliverQueue.add(data, {
|
||||
attempts: 4,
|
||||
timeout: 1 * 60 * 1000, // 1min
|
||||
backoff: {
|
||||
type: 'apBackoff',
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
});
|
||||
}
|
||||
|
||||
export default function() {
|
||||
if (envOption.onlyServer) return;
|
||||
|
||||
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
|
||||
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
|
||||
endedPollNotificationQueue.process(endedPollNotification);
|
||||
webhookDeliverQueue.process(64, processWebhookDeliver);
|
||||
processDb(dbQueue);
|
||||
processObjectStorage(objectStorageQueue);
|
||||
|
||||
|
59
packages/backend/src/queue/processors/webhook-deliver.ts
Normal file
59
packages/backend/src/queue/processors/webhook-deliver.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { URL } from 'node:url';
|
||||
import Bull from 'bull';
|
||||
import Logger from '@/services/logger.js';
|
||||
import { WebhookDeliverJobData } from '../types.js';
|
||||
import { getResponse, StatusError } from '@/misc/fetch.js';
|
||||
import { Webhooks } from '@/models/index.js';
|
||||
import config from '@/config/index.js';
|
||||
|
||||
const logger = new Logger('webhook');
|
||||
|
||||
export default async (job: Bull.Job<WebhookDeliverJobData>) => {
|
||||
try {
|
||||
logger.debug(`delivering ${job.data.webhookId}`);
|
||||
|
||||
const res = await getResponse({
|
||||
url: job.data.to,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': 'Misskey-Hooks',
|
||||
'X-Misskey-Host': config.host,
|
||||
'X-Misskey-Hook-Id': job.data.webhookId,
|
||||
'X-Misskey-Hook-Secret': job.data.secret,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
hookId: job.data.webhookId,
|
||||
userId: job.data.userId,
|
||||
eventId: job.data.eventId,
|
||||
createdAt: job.data.createdAt,
|
||||
type: job.data.type,
|
||||
body: job.data.content,
|
||||
}),
|
||||
});
|
||||
|
||||
Webhooks.update({ id: job.data.webhookId }, {
|
||||
latestSentAt: new Date(),
|
||||
latestStatus: res.status,
|
||||
});
|
||||
|
||||
return 'Success';
|
||||
} catch (res) {
|
||||
Webhooks.update({ id: job.data.webhookId }, {
|
||||
latestSentAt: new Date(),
|
||||
latestStatus: res instanceof StatusError ? res.statusCode : 1,
|
||||
});
|
||||
|
||||
if (res instanceof StatusError) {
|
||||
// 4xx
|
||||
if (res.isClientError) {
|
||||
return `${res.statusCode} ${res.statusMessage}`;
|
||||
}
|
||||
|
||||
// 5xx etc.
|
||||
throw `${res.statusCode} ${res.statusMessage}`;
|
||||
} else {
|
||||
// DNS error, socket error, timeout ...
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
};
|
@@ -1,6 +1,6 @@
|
||||
import config from '@/config/index.js';
|
||||
import { initialize as initializeQueue } from './initialize.js';
|
||||
import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData } from './types.js';
|
||||
import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData } from './types.js';
|
||||
|
||||
export const systemQueue = initializeQueue<Record<string, unknown>>('system');
|
||||
export const endedPollNotificationQueue = initializeQueue<EndedPollNotificationJobData>('endedPollNotification');
|
||||
@@ -8,6 +8,7 @@ export const deliverQueue = initializeQueue<DeliverJobData>('deliver', config.de
|
||||
export const inboxQueue = initializeQueue<InboxJobData>('inbox', config.inboxJobPerSec || 16);
|
||||
export const dbQueue = initializeQueue<DbJobData>('db');
|
||||
export const objectStorageQueue = initializeQueue<ObjectStorageJobData>('objectStorage');
|
||||
export const webhookDeliverQueue = initializeQueue<WebhookDeliverJobData>('webhookDeliver', 64);
|
||||
|
||||
export const queues = [
|
||||
systemQueue,
|
||||
@@ -16,4 +17,5 @@ export const queues = [
|
||||
inboxQueue,
|
||||
dbQueue,
|
||||
objectStorageQueue,
|
||||
webhookDeliverQueue,
|
||||
];
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { Note } from '@/models/entities/note';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Webhook } from '@/models/entities/webhook';
|
||||
import { IActivity } from '@/remote/activitypub/type.js';
|
||||
import httpSignature from 'http-signature';
|
||||
|
||||
@@ -46,6 +47,17 @@ export type EndedPollNotificationJobData = {
|
||||
noteId: Note['id'];
|
||||
};
|
||||
|
||||
export type WebhookDeliverJobData = {
|
||||
type: string;
|
||||
content: unknown;
|
||||
webhookId: Webhook['id'];
|
||||
userId: User['id'];
|
||||
to: string;
|
||||
secret: string;
|
||||
createdAt: number;
|
||||
eventId: string;
|
||||
};
|
||||
|
||||
export type ThinUser = {
|
||||
id: User['id'];
|
||||
};
|
||||
|
@@ -95,7 +95,7 @@ function genSigningString(request: Request, includeHeaders: string[]) {
|
||||
|
||||
function lcObjectKey(src: Record<string, string>) {
|
||||
const dst: Record<string, string> = {};
|
||||
for (const key of Object.keys(src).filter(x => x != '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key];
|
||||
for (const key of Object.keys(src).filter(x => x !== '__proto__' && typeof src[x] === 'string')) dst[key.toLowerCase()] = src[key];
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
@@ -79,37 +79,46 @@ export default class DeliverManager {
|
||||
|
||||
const inboxes = new Set<string>();
|
||||
|
||||
// build inbox list
|
||||
for (const recipe of this.recipes) {
|
||||
if (isFollowers(recipe)) {
|
||||
// followers deliver
|
||||
// TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
|
||||
// ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
|
||||
const followers = await Followings.find({
|
||||
where: {
|
||||
followeeId: this.actor.id,
|
||||
followerHost: Not(IsNull()),
|
||||
},
|
||||
select: {
|
||||
followerSharedInbox: true,
|
||||
followerInbox: true,
|
||||
},
|
||||
}) as {
|
||||
followerSharedInbox: string | null;
|
||||
followerInbox: string;
|
||||
}[];
|
||||
/*
|
||||
build inbox list
|
||||
|
||||
for (const following of followers) {
|
||||
const inbox = following.followerSharedInbox || following.followerInbox;
|
||||
inboxes.add(inbox);
|
||||
}
|
||||
} else if (isDirect(recipe)) {
|
||||
// direct deliver
|
||||
const inbox = recipe.to.inbox;
|
||||
if (inbox) inboxes.add(inbox);
|
||||
Process follower recipes first to avoid duplication when processing
|
||||
direct recipes later.
|
||||
*/
|
||||
if (this.recipes.some(r => isFollowers(r))) {
|
||||
// followers deliver
|
||||
// TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう
|
||||
// ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう?
|
||||
const followers = await Followings.find({
|
||||
where: {
|
||||
followeeId: this.actor.id,
|
||||
followerHost: Not(IsNull()),
|
||||
},
|
||||
select: {
|
||||
followerSharedInbox: true,
|
||||
followerInbox: true,
|
||||
},
|
||||
}) as {
|
||||
followerSharedInbox: string | null;
|
||||
followerInbox: string;
|
||||
}[];
|
||||
|
||||
for (const following of followers) {
|
||||
const inbox = following.followerSharedInbox || following.followerInbox;
|
||||
inboxes.add(inbox);
|
||||
}
|
||||
}
|
||||
|
||||
this.recipes.filter((recipe): recipe is IDirectRecipe =>
|
||||
// followers recipes have already been processed
|
||||
isDirect(recipe)
|
||||
// check that shared inbox has not been added yet
|
||||
&& !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox))
|
||||
// check that they actually have an inbox
|
||||
&& recipe.to.inbox != null,
|
||||
)
|
||||
.forEach(recipe => inboxes.add(recipe.to.inbox!));
|
||||
|
||||
// deliver
|
||||
for (const inbox of inboxes) {
|
||||
deliver(this.actor, this.activity, inbox);
|
||||
|
@@ -18,7 +18,7 @@ export const performReadActivity = async (actor: CacheableRemoteUser, activity:
|
||||
return `skip: message not found`;
|
||||
}
|
||||
|
||||
if (actor.id != message.recipientId) {
|
||||
if (actor.id !== message.recipientId) {
|
||||
return `skip: actor is not a message recipient`;
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import unfollow from '@/services/following/delete.js';
|
||||
import cancelRequest from '@/services/following/requests/cancel.js';
|
||||
import {IAccept} from '../../type.js';
|
||||
import { IAccept } from '../../type.js';
|
||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||
import { Followings } from '@/models/index.js';
|
||||
import DbResolver from '../../db-resolver.js';
|
||||
|
@@ -113,7 +113,8 @@ export class LdSignature {
|
||||
headers: {
|
||||
Accept: 'application/ld+json, application/json',
|
||||
},
|
||||
timeout: this.loderTimeout,
|
||||
// TODO
|
||||
//timeout: this.loderTimeout,
|
||||
agent: u => u.protocol === 'http:' ? httpAgent : httpsAgent,
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { toArray, unique } from '@/prelude/array.js';
|
||||
import { IObject, isMention, IApMention } from '../type.js';
|
||||
import { resolvePerson } from './person.js';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import Resolver from '../resolver.js';
|
||||
import { toArray, unique } from '@/prelude/array.js';
|
||||
import { CacheableUser, User } from '@/models/entities/user.js';
|
||||
import { IObject, isMention, IApMention } from '../type.js';
|
||||
import Resolver from '../resolver.js';
|
||||
import { resolvePerson } from './person.js';
|
||||
|
||||
export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
|
||||
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
|
||||
@@ -12,7 +12,7 @@ export async function extractApMentions(tags: IObject | IObject[] | null | undef
|
||||
|
||||
const limit = promiseLimit<CacheableUser | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
|
||||
hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))),
|
||||
)).filter((x): x is CacheableUser => x != null);
|
||||
|
||||
return mentionedUsers;
|
||||
|
@@ -1,17 +1,8 @@
|
||||
import { URL } from 'node:url';
|
||||
import promiseLimit from 'promise-limit';
|
||||
|
||||
import $, { Context } from 'cafy';
|
||||
import config from '@/config/index.js';
|
||||
import Resolver from '../resolver.js';
|
||||
import { resolveImage } from './image.js';
|
||||
import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js';
|
||||
import { fromHtml } from '../../../mfm/from-html.js';
|
||||
import { htmlToMfm } from '../misc/html-to-mfm.js';
|
||||
import { resolveNote, extractEmojis } from './note.js';
|
||||
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
|
||||
import { extractApHashtags } from './tag.js';
|
||||
import { apLogger } from '../logger.js';
|
||||
import { Note } from '@/models/entities/note.js';
|
||||
import { updateUsertags } from '@/services/update-hashtag.js';
|
||||
import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js';
|
||||
@@ -32,6 +23,14 @@ import { StatusError } from '@/misc/fetch.js';
|
||||
import { uriPersonCache } from '@/services/user-cache.js';
|
||||
import { publishInternalEvent } from '@/services/stream.js';
|
||||
import { db } from '@/db/postgre.js';
|
||||
import { apLogger } from '../logger.js';
|
||||
import { htmlToMfm } from '../misc/html-to-mfm.js';
|
||||
import { fromHtml } from '../../../mfm/from-html.js';
|
||||
import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js';
|
||||
import Resolver from '../resolver.js';
|
||||
import { extractApHashtags } from './tag.js';
|
||||
import { resolveNote, extractEmojis } from './note.js';
|
||||
import { resolveImage } from './image.js';
|
||||
|
||||
const logger = apLogger;
|
||||
|
||||
@@ -54,20 +53,33 @@ function validateActor(x: IObject, uri: string): IActor {
|
||||
throw new Error(`invalid Actor type '${x.type}'`);
|
||||
}
|
||||
|
||||
const validate = (name: string, value: any, validater: Context) => {
|
||||
const e = validater.test(value);
|
||||
if (e) throw new Error(`invalid Actor: ${name} ${e.message}`);
|
||||
};
|
||||
if (!(typeof x.id === 'string' && x.id.length > 0)) {
|
||||
throw new Error('invalid Actor: wrong id');
|
||||
}
|
||||
|
||||
validate('id', x.id, $.default.str.min(1));
|
||||
validate('inbox', x.inbox, $.default.str.min(1));
|
||||
validate('preferredUsername', x.preferredUsername, $.default.str.min(1).max(128).match(/^\w([\w-.]*\w)?$/));
|
||||
if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) {
|
||||
throw new Error('invalid Actor: wrong inbox');
|
||||
}
|
||||
|
||||
if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) {
|
||||
throw new Error('invalid Actor: wrong username');
|
||||
}
|
||||
|
||||
// These fields are only informational, and some AP software allows these
|
||||
// fields to be very long. If they are too long, we cut them off. This way
|
||||
// we can at least see these users and their activities.
|
||||
validate('name', truncate(x.name, nameLength), $.default.optional.nullable.str);
|
||||
validate('summary', truncate(x.summary, summaryLength), $.default.optional.nullable.str);
|
||||
if (x.name) {
|
||||
if (!(typeof x.name === 'string' && x.name.length > 0)) {
|
||||
throw new Error('invalid Actor: wrong name');
|
||||
}
|
||||
x.name = truncate(x.name, nameLength);
|
||||
}
|
||||
if (x.summary) {
|
||||
if (!(typeof x.summary === 'string' && x.summary.length > 0)) {
|
||||
throw new Error('invalid Actor: wrong summary');
|
||||
}
|
||||
x.summary = truncate(x.summary, summaryLength);
|
||||
}
|
||||
|
||||
const idHost = toPuny(new URL(x.id!).hostname);
|
||||
if (idHost !== expectHost) {
|
||||
@@ -271,7 +283,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
||||
* @param resolver Resolver
|
||||
* @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します)
|
||||
*/
|
||||
export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: Record<string, unknown>): Promise<void> {
|
||||
export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
// URIがこのサーバーを指しているならスキップ
|
||||
@@ -289,7 +301,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
||||
|
||||
if (resolver == null) resolver = new Resolver();
|
||||
|
||||
const object = hint || await resolver.resolve(uri) as any;
|
||||
const object = hint || await resolver.resolve(uri);
|
||||
|
||||
const person = validateActor(object, uri);
|
||||
|
||||
@@ -400,10 +412,10 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<C
|
||||
const services: {
|
||||
[x: string]: (id: string, username: string) => any
|
||||
} = {
|
||||
'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }),
|
||||
'misskey:authentication:github': (id, login) => ({ id, login }),
|
||||
'misskey:authentication:discord': (id, name) => $discord(id, name),
|
||||
};
|
||||
'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }),
|
||||
'misskey:authentication:github': (id, login) => ({ id, login }),
|
||||
'misskey:authentication:discord': (id, name) => $discord(id, name),
|
||||
};
|
||||
|
||||
const $discord = (id: string, name: string) => {
|
||||
if (typeof name !== 'string') {
|
||||
@@ -461,7 +473,7 @@ export async function updateFeatured(userId: User['id']) {
|
||||
|
||||
// Resolve to (Ordered)Collection Object
|
||||
const collection = await resolver.resolveCollection(user.featured);
|
||||
if (!isCollectionOrOrderedCollection(collection)) throw new Error(`Object is not Collection or OrderedCollection`);
|
||||
if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection');
|
||||
|
||||
// Resolve to Object(may be Note) arrays
|
||||
const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
|
||||
|
@@ -69,7 +69,7 @@ export async function updateQuestion(value: any) {
|
||||
const oldCount = poll.votes[poll.choices.indexOf(choice)];
|
||||
const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems;
|
||||
|
||||
if (oldCount != newCount) {
|
||||
if (oldCount !== newCount) {
|
||||
changed = true;
|
||||
poll.votes[poll.choices.indexOf(choice)] = newCount;
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import { getInstanceActor } from '@/services/instance-actor.js';
|
||||
|
||||
// to anonymise reporters, the reporting actor must be a system user
|
||||
// object has to be a uri or array of uris
|
||||
export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => {
|
||||
export const renderFlag = (user: ILocalUser, object: [string], content: string) => {
|
||||
return {
|
||||
type: 'Flag',
|
||||
actor: `${config.url}/users/${user.id}`,
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import renderDocument from './document.js';
|
||||
import renderHashtag from './hashtag.js';
|
||||
import renderMention from './mention.js';
|
||||
import renderEmoji from './emoji.js';
|
||||
import { In, IsNull } from 'typeorm';
|
||||
import config from '@/config/index.js';
|
||||
import toHtml from '../misc/get-note-html.js';
|
||||
import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js';
|
||||
import { In, IsNull } from 'typeorm';
|
||||
import { Emoji } from '@/models/entities/emoji.js';
|
||||
import { Poll } from '@/models/entities/poll.js';
|
||||
import toHtml from '../misc/get-note-html.js';
|
||||
import renderEmoji from './emoji.js';
|
||||
import renderMention from './mention.js';
|
||||
import renderHashtag from './hashtag.js';
|
||||
import renderDocument from './document.js';
|
||||
|
||||
export default async function renderNote(note: Note, dive = true, isTalk = false): Promise<Record<string, unknown>> {
|
||||
const getPromisedFiles = async (ids: string[]) => {
|
||||
@@ -83,7 +83,7 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
|
||||
const files = await getPromisedFiles(note.fileIds);
|
||||
|
||||
const text = note.text;
|
||||
let poll: Poll | null;
|
||||
let poll: Poll | null = null;
|
||||
|
||||
if (note.hasPoll) {
|
||||
poll = await Polls.findOneBy({ noteId: note.id });
|
||||
@@ -159,7 +159,7 @@ export async function getEmojis(names: string[]): Promise<Emoji[]> {
|
||||
names.map(name => Emojis.findOneBy({
|
||||
name,
|
||||
host: IsNull(),
|
||||
}))
|
||||
})),
|
||||
);
|
||||
|
||||
return emojis.filter(emoji => emoji != null) as Emoji[];
|
||||
|
@@ -2,10 +2,10 @@ import config from '@/config/index.js';
|
||||
import { getJson } from '@/misc/fetch.js';
|
||||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { getInstanceActor } from '@/services/instance-actor.js';
|
||||
import { signedGet } from './request.js';
|
||||
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { extractDbHost } from '@/misc/convert-host.js';
|
||||
import { signedGet } from './request.js';
|
||||
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
|
||||
|
||||
export default class Resolver {
|
||||
private history: Set<string>;
|
||||
@@ -56,13 +56,13 @@ export default class Resolver {
|
||||
this.user = await getInstanceActor();
|
||||
}
|
||||
|
||||
const object = this.user
|
||||
const object = (this.user
|
||||
? await signedGet(value, this.user)
|
||||
: await getJson(value, 'application/activity+json, application/ld+json');
|
||||
: await getJson(value, 'application/activity+json, application/ld+json')) as IObject;
|
||||
|
||||
if (object == null || (
|
||||
Array.isArray(object['@context']) ?
|
||||
!object['@context'].includes('https://www.w3.org/ns/activitystreams') :
|
||||
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
|
||||
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
|
||||
)) {
|
||||
throw new Error('invalid response');
|
||||
|
@@ -2,7 +2,7 @@ export type obj = { [x: string]: any };
|
||||
export type ApObject = IObject | string | (IObject | string)[];
|
||||
|
||||
export interface IObject {
|
||||
'@context': string | obj | obj[];
|
||||
'@context': string | string[] | obj | obj[];
|
||||
type: string | string[];
|
||||
id?: string;
|
||||
summary?: string;
|
||||
@@ -48,7 +48,7 @@ export function getOneApId(value: ApObject): string {
|
||||
export function getApId(value: string | IObject): string {
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value.id === 'string') return value.id;
|
||||
throw new Error(`cannot detemine id`);
|
||||
throw new Error('cannot detemine id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,7 @@ export function getApId(value: string | IObject): string {
|
||||
export function getApType(value: IObject): string {
|
||||
if (typeof value.type === 'string') return value.type;
|
||||
if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0];
|
||||
throw new Error(`cannot detect type`);
|
||||
throw new Error('cannot detect type');
|
||||
}
|
||||
|
||||
export function getOneApHrefNullable(value: ApObject | undefined): string | undefined {
|
||||
|
@@ -15,7 +15,7 @@ type IWebFinger = {
|
||||
export default async function(query: string): Promise<IWebFinger> {
|
||||
const url = genUrl(query);
|
||||
|
||||
return await getJson(url, 'application/jrd+json, application/json');
|
||||
return await getJson(url, 'application/jrd+json, application/json') as IWebFinger;
|
||||
}
|
||||
|
||||
function genUrl(query: string) {
|
||||
|
@@ -1,32 +1,26 @@
|
||||
import Router from '@koa/router';
|
||||
import { FindOptionsWhere, IsNull, LessThan } from 'typeorm';
|
||||
import config from '@/config/index.js';
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id.js';
|
||||
import * as url from '@/prelude/url.js';
|
||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
|
||||
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
|
||||
import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||
import { IsNull, LessThan } from 'typeorm';
|
||||
import { Following } from '@/models/entities/following.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const userId = ctx.params.user;
|
||||
|
||||
// Get 'cursor' parameter
|
||||
const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor);
|
||||
|
||||
// Get 'page' parameter
|
||||
const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
|
||||
const page: boolean = ctx.request.query.page === 'true';
|
||||
|
||||
// Validate parameters
|
||||
if (cursorErr || pageErr) {
|
||||
const cursor = ctx.request.query.cursor;
|
||||
if (cursor != null && typeof cursor !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
const page = ctx.request.query.page === 'true';
|
||||
|
||||
const user = await Users.findOneBy({
|
||||
id: userId,
|
||||
host: IsNull(),
|
||||
@@ -57,7 +51,7 @@ export default async (ctx: Router.RouterContext) => {
|
||||
if (page) {
|
||||
const query = {
|
||||
followeeId: user.id,
|
||||
} as any;
|
||||
} as FindOptionsWhere<Following>;
|
||||
|
||||
// カーソルが指定されている場合
|
||||
if (cursor) {
|
||||
@@ -86,7 +80,7 @@ export default async (ctx: Router.RouterContext) => {
|
||||
inStock ? `${partOf}?${url.query({
|
||||
page: 'true',
|
||||
cursor: followings[followings.length - 1].id,
|
||||
})}` : undefined
|
||||
})}` : undefined,
|
||||
);
|
||||
|
||||
ctx.body = renderActivity(rendered);
|
||||
|
@@ -1,33 +1,26 @@
|
||||
import Router from '@koa/router';
|
||||
import { LessThan, IsNull, FindOptionsWhere } from 'typeorm';
|
||||
import config from '@/config/index.js';
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id.js';
|
||||
import * as url from '@/prelude/url.js';
|
||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
|
||||
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
|
||||
import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
import { Users, Followings, UserProfiles } from '@/models/index.js';
|
||||
import { LessThan, IsNull, FindOptionsWhere } from 'typeorm';
|
||||
import { Following } from '@/models/entities/following.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const userId = ctx.params.user;
|
||||
|
||||
// Get 'cursor' parameter
|
||||
const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor);
|
||||
|
||||
// Get 'page' parameter
|
||||
const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
|
||||
const page: boolean = ctx.request.query.page === 'true';
|
||||
|
||||
// Validate parameters
|
||||
if (cursorErr || pageErr) {
|
||||
const cursor = ctx.request.query.cursor;
|
||||
if (cursor != null && typeof cursor !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
const page = ctx.request.query.page === 'true';
|
||||
|
||||
const user = await Users.findOneBy({
|
||||
id: userId,
|
||||
host: IsNull(),
|
||||
@@ -87,7 +80,7 @@ export default async (ctx: Router.RouterContext) => {
|
||||
inStock ? `${partOf}?${url.query({
|
||||
page: 'true',
|
||||
cursor: followings[followings.length - 1].id,
|
||||
})}` : undefined
|
||||
})}` : undefined,
|
||||
);
|
||||
|
||||
ctx.body = renderActivity(rendered);
|
||||
|
@@ -1,36 +1,37 @@
|
||||
import Router from '@koa/router';
|
||||
import { Brackets, IsNull } from 'typeorm';
|
||||
import config from '@/config/index.js';
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id.js';
|
||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||
import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
|
||||
import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
import renderNote from '@/remote/activitypub/renderer/note.js';
|
||||
import renderCreate from '@/remote/activitypub/renderer/create.js';
|
||||
import renderAnnounce from '@/remote/activitypub/renderer/announce.js';
|
||||
import { countIf } from '@/prelude/array.js';
|
||||
import * as url from '@/prelude/url.js';
|
||||
import { Users, Notes } from '@/models/index.js';
|
||||
import { makePaginationQuery } from '../api/common/make-pagination-query.js';
|
||||
import { Brackets, IsNull } from 'typeorm';
|
||||
import { Note } from '@/models/entities/note.js';
|
||||
import { makePaginationQuery } from '../api/common/make-pagination-query.js';
|
||||
import { setResponseType } from '../activitypub.js';
|
||||
|
||||
export default async (ctx: Router.RouterContext) => {
|
||||
const userId = ctx.params.user;
|
||||
|
||||
// Get 'sinceId' parameter
|
||||
const [sinceId, sinceIdErr] = $.default.optional.type(ID).get(ctx.request.query.since_id);
|
||||
const sinceId = ctx.request.query.since_id;
|
||||
if (sinceId != null && typeof sinceId !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get 'untilId' parameter
|
||||
const [untilId, untilIdErr] = $.default.optional.type(ID).get(ctx.request.query.until_id);
|
||||
const untilId = ctx.request.query.until_id;
|
||||
if (untilId != null && typeof untilId !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get 'page' parameter
|
||||
const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
|
||||
const page: boolean = ctx.request.query.page === 'true';
|
||||
const page = ctx.request.query.page === 'true';
|
||||
|
||||
// Validate parameters
|
||||
if (sinceIdErr || untilIdErr || pageErr || countIf(x => x != null, [sinceId, untilId]) > 1) {
|
||||
if (countIf(x => x != null, [sinceId, untilId]) > 1) {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
@@ -52,8 +53,8 @@ export default async (ctx: Router.RouterContext) => {
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), sinceId, untilId)
|
||||
.andWhere('note.userId = :userId', { userId: user.id })
|
||||
.andWhere(new Brackets(qb => { qb
|
||||
.where(`note.visibility = 'public'`)
|
||||
.orWhere(`note.visibility = 'home'`);
|
||||
.where('note.visibility = \'public\'')
|
||||
.orWhere('note.visibility = \'home\'');
|
||||
}))
|
||||
.andWhere('note.localOnly = FALSE');
|
||||
|
||||
@@ -76,7 +77,7 @@ export default async (ctx: Router.RouterContext) => {
|
||||
notes.length ? `${partOf}?${url.query({
|
||||
page: 'true',
|
||||
until_id: notes[notes.length - 1].id,
|
||||
})}` : undefined
|
||||
})}` : undefined,
|
||||
);
|
||||
|
||||
ctx.body = renderActivity(rendered);
|
||||
@@ -85,7 +86,7 @@ export default async (ctx: Router.RouterContext) => {
|
||||
// index page
|
||||
const rendered = renderOrderedCollection(partOf, user.notesCount,
|
||||
`${partOf}?page=true`,
|
||||
`${partOf}?page=true&since_id=000000000000000000000000`
|
||||
`${partOf}?page=true&since_id=000000000000000000000000`,
|
||||
);
|
||||
ctx.body = renderActivity(rendered);
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import * as crypto from 'node:crypto';
|
||||
import config from '@/config/index.js';
|
||||
import * as jsrsasign from 'jsrsasign';
|
||||
import config from '@/config/index.js';
|
||||
|
||||
const ECC_PRELUDE = Buffer.from([0x04]);
|
||||
const NULL_BYTE = Buffer.from([0]);
|
||||
@@ -121,14 +121,14 @@ export function verifyLogin({
|
||||
signature: Buffer,
|
||||
challenge: string
|
||||
}) {
|
||||
if (clientData.type != 'webauthn.get') {
|
||||
if (clientData.type !== 'webauthn.get') {
|
||||
throw new Error('type is not webauthn.get');
|
||||
}
|
||||
|
||||
if (hash(clientData.challenge).toString('hex') != challenge) {
|
||||
if (hash(clientData.challenge).toString('hex') !== challenge) {
|
||||
throw new Error('challenge mismatch');
|
||||
}
|
||||
if (clientData.origin != config.scheme + '://' + config.host) {
|
||||
if (clientData.origin !== config.scheme + '://' + config.host) {
|
||||
throw new Error('origin mismatch');
|
||||
}
|
||||
|
||||
@@ -145,14 +145,14 @@ export function verifyLogin({
|
||||
|
||||
export const procedures = {
|
||||
none: {
|
||||
verify({ publicKey }: {publicKey: Map<number, Buffer>}) {
|
||||
verify({ publicKey }: { publicKey: Map<number, Buffer> }) {
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length != 32) {
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length != 32) {
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ export const procedures = {
|
||||
rpIdHash: Buffer,
|
||||
credentialId: Buffer,
|
||||
}) {
|
||||
if (attStmt.alg != -7) {
|
||||
if (attStmt.alg !== -7) {
|
||||
throw new Error('alg mismatch');
|
||||
}
|
||||
|
||||
@@ -196,11 +196,11 @@ export const procedures = {
|
||||
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length != 32) {
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length != 32) {
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ export const procedures = {
|
||||
.map((key: any) => PEMString(key))
|
||||
.concat([GSR2]);
|
||||
|
||||
if (getCertSubject(certificateChain[0]).CN != 'attest.android.com') {
|
||||
if (getCertSubject(certificateChain[0]).CN !== 'attest.android.com') {
|
||||
throw new Error('invalid common name');
|
||||
}
|
||||
|
||||
@@ -283,11 +283,11 @@ export const procedures = {
|
||||
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length != 32) {
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length != 32) {
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
@@ -332,11 +332,11 @@ export const procedures = {
|
||||
|
||||
const negTwo = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length != 32) {
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree = publicKey.get(-3);
|
||||
if (!negThree || negThree.length != 32) {
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
@@ -353,7 +353,7 @@ export const procedures = {
|
||||
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-ecdaa-algorithm-v2.0-id-20180227.html#ecdaa-verify-operation
|
||||
throw new Error('ECDAA-Verify is not supported');
|
||||
} else {
|
||||
if (attStmt.alg != -7) throw new Error('alg mismatch');
|
||||
if (attStmt.alg !== -7) throw new Error('alg mismatch');
|
||||
|
||||
throw new Error('self attestation is not supported');
|
||||
}
|
||||
@@ -377,7 +377,7 @@ export const procedures = {
|
||||
credentialId: Buffer
|
||||
}) {
|
||||
const x5c: Buffer[] = attStmt.x5c;
|
||||
if (x5c.length != 1) {
|
||||
if (x5c.length !== 1) {
|
||||
throw new Error('x5c length does not match expectation');
|
||||
}
|
||||
|
||||
@@ -387,11 +387,11 @@ export const procedures = {
|
||||
|
||||
const negTwo: Buffer = publicKey.get(-2);
|
||||
|
||||
if (!negTwo || negTwo.length != 32) {
|
||||
if (!negTwo || negTwo.length !== 32) {
|
||||
throw new Error('invalid or no -2 key given');
|
||||
}
|
||||
const negThree: Buffer = publicKey.get(-3);
|
||||
if (!negThree || negThree.length != 32) {
|
||||
if (!negThree || negThree.length !== 32) {
|
||||
throw new Error('invalid or no -3 key given');
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import { publishMainStream } from '@/services/stream.js';
|
||||
export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
|
||||
if (redirect) {
|
||||
//#region Cookie
|
||||
ctx.cookies.set('igi', user.token, {
|
||||
ctx.cookies.set('igi', user.token!, {
|
||||
path: '/',
|
||||
// SEE: https://github.com/koajs/koa/issues/974
|
||||
// When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Schema } from '@/misc/schema.js';
|
||||
|
||||
import * as ep___admin_meta from './endpoints/admin/meta.js';
|
||||
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
|
||||
import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
|
||||
import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
|
||||
@@ -201,6 +202,11 @@ import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||
import * as ep___i_update from './endpoints/i/update.js';
|
||||
import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js';
|
||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||
import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
|
||||
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
|
||||
import * as ep___messaging_history from './endpoints/messaging/history.js';
|
||||
import * as ep___messaging_messages from './endpoints/messaging/messages.js';
|
||||
import * as ep___messaging_messages_create from './endpoints/messaging/messages/create.js';
|
||||
@@ -304,6 +310,7 @@ import * as ep___users_show from './endpoints/users/show.js';
|
||||
import * as ep___users_stats from './endpoints/users/stats.js';
|
||||
|
||||
const eps = [
|
||||
['admin/meta', ep___admin_meta],
|
||||
['admin/abuse-user-reports', ep___admin_abuseUserReports],
|
||||
['admin/accounts/create', ep___admin_accounts_create],
|
||||
['admin/accounts/delete', ep___admin_accounts_delete],
|
||||
@@ -505,6 +512,11 @@ const eps = [
|
||||
['i/update-email', ep___i_updateEmail],
|
||||
['i/update', ep___i_update],
|
||||
['i/user-group-invites', ep___i_userGroupInvites],
|
||||
['i/webhooks/create', ep___i_webhooks_create],
|
||||
['i/webhooks/list', ep___i_webhooks_list],
|
||||
['i/webhooks/show', ep___i_webhooks_show],
|
||||
['i/webhooks/update', ep___i_webhooks_update],
|
||||
['i/webhooks/delete', ep___i_webhooks_delete],
|
||||
['messaging/history', ep___messaging_history],
|
||||
['messaging/messages', ep___messaging_messages],
|
||||
['messaging/messages/create', ep___messaging_messages_create],
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import define from '../../../define.js';
|
||||
import { Announcements, AnnouncementReads } from '@/models/index.js';
|
||||
import { Announcement } from '@/models/entities/announcement.js';
|
||||
import define from '../../../define.js';
|
||||
import { makePaginationQuery } from '../../../common/make-pagination-query.js';
|
||||
|
||||
export const meta = {
|
||||
@@ -68,11 +69,21 @@ export default define(meta, paramDef, async (ps) => {
|
||||
|
||||
const announcements = await query.take(ps.limit).getMany();
|
||||
|
||||
const reads = new Map<Announcement, number>();
|
||||
|
||||
for (const announcement of announcements) {
|
||||
(announcement as any).reads = await AnnouncementReads.countBy({
|
||||
reads.set(announcement, await AnnouncementReads.countBy({
|
||||
announcementId: announcement.id,
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
return announcements;
|
||||
return announcements.map(announcement => ({
|
||||
id: announcement.id,
|
||||
createdAt: announcement.createdAt.toISOString(),
|
||||
updatedAt: announcement.updatedAt?.toISOString() ?? null,
|
||||
title: announcement.title,
|
||||
text: announcement.text,
|
||||
imageUrl: announcement.imageUrl,
|
||||
reads: reads.get(announcement)!,
|
||||
}));
|
||||
});
|
||||
|
@@ -27,7 +27,12 @@ export const paramDef = {
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z0-9\/\-*]+$/.toString().slice(1, -1) },
|
||||
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
|
||||
hostname: { type: 'string', nullable: true, default: null },
|
||||
hostname: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
default: null,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
@@ -40,6 +40,7 @@ export const meta = {
|
||||
userHost: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
md5: {
|
||||
type: 'string',
|
||||
@@ -151,11 +152,20 @@ export const meta = {
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@@ -40,6 +40,7 @@ export const meta = {
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
@@ -54,7 +55,12 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', nullable: true, default: null },
|
||||
host: { type: 'string', nullable: true, default: null },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
default: null,
|
||||
description: 'Use `null` to represent the local host.',
|
||||
},
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
|
@@ -38,8 +38,9 @@ export const meta = {
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
type: 'null',
|
||||
optional: false,
|
||||
description: 'The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files.',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
|
@@ -17,7 +17,11 @@ export const paramDef = {
|
||||
ids: { type: 'array', items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
category: { type: 'string', nullable: true },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
},
|
||||
required: ['ids'],
|
||||
} as const;
|
||||
|
@@ -23,7 +23,11 @@ export const paramDef = {
|
||||
properties: {
|
||||
id: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string' },
|
||||
category: { type: 'string', nullable: true },
|
||||
category: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'Use `null` to reset the category.',
|
||||
},
|
||||
aliases: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import define from '../../../define.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { publishInternalEvent } from '@/services/stream.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import define from '../../define.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import define from '../../define.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
@@ -24,10 +24,15 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
offset: { type: 'integer', default: 0 },
|
||||
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
|
||||
state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" },
|
||||
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
|
||||
username: { type: 'string', default: null },
|
||||
hostname: { type: 'string', default: null },
|
||||
state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: 'all' },
|
||||
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
|
||||
username: { type: 'string', nullable: true, default: null },
|
||||
hostname: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
default: null,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
@@ -397,12 +397,14 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||
}
|
||||
|
||||
await db.transaction(async transactionalEntityManager => {
|
||||
const meta = await transactionalEntityManager.findOne(Meta, {
|
||||
const metas = await transactionalEntityManager.find(Meta, {
|
||||
order: {
|
||||
id: 'DESC',
|
||||
},
|
||||
});
|
||||
|
||||
const meta = metas[0];
|
||||
|
||||
if (meta) {
|
||||
await transactionalEntityManager.update(Meta, meta.id, set);
|
||||
} else {
|
||||
|
@@ -57,13 +57,9 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.noSuchAntenna);
|
||||
}
|
||||
|
||||
const antennaQuery = AntennaNotes.createQueryBuilder('joining')
|
||||
.select('joining.noteId')
|
||||
.where('joining.antennaId = :antennaId', { antennaId: antenna.id });
|
||||
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
|
||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere(`note.id IN (${ antennaQuery.getQuery() })`)
|
||||
.innerJoin(AntennaNotes.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id')
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('user.avatar', 'avatar')
|
||||
.leftJoinAndSelect('user.banner', 'banner')
|
||||
@@ -75,7 +71,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
||||
.setParameters(antennaQuery.getParameters());
|
||||
.andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id });
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
generateMutedUserQuery(query, user);
|
||||
|
@@ -20,7 +20,7 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
isPublic: { type: 'boolean' },
|
||||
isPublic: { type: 'boolean', default: false },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
},
|
||||
required: ['name'],
|
||||
|
@@ -57,12 +57,8 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.noSuchClip);
|
||||
}
|
||||
|
||||
const clipQuery = ClipNotes.createQueryBuilder('joining')
|
||||
.select('joining.noteId')
|
||||
.where('joining.clipId = :clipId', { clipId: clip.id });
|
||||
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.andWhere(`note.id IN (${ clipQuery.getQuery() })`)
|
||||
.innerJoin(ClipNotes.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id')
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('user.avatar', 'avatar')
|
||||
.leftJoinAndSelect('user.banner', 'banner')
|
||||
@@ -74,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
||||
.setParameters(clipQuery.getParameters());
|
||||
.andWhere('clipNote.clipId = :clipId', { clipId: clip.id });
|
||||
|
||||
if (user) {
|
||||
generateVisibilityQuery(query, user);
|
||||
|
@@ -48,7 +48,6 @@ export const paramDef = {
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
// @ts-ignore
|
||||
export default define(meta, paramDef, async (ps, user, _, file, cleanup) => {
|
||||
// Get 'name' parameter
|
||||
let name = ps.name || file.originalname;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import define from '../../../define.js';
|
||||
import { DriveFiles } from '@/models/index.js';
|
||||
import define from '../../../define.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import define from '../../../define.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { DriveFiles, Users } from '@/models/index.js';
|
||||
import define from '../../../define.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['drive'],
|
||||
@@ -28,27 +28,30 @@ export const meta = {
|
||||
code: 'ACCESS_DENIED',
|
||||
id: '25b73c73-68b1-41d0-bad1-381cfdf6579f',
|
||||
},
|
||||
|
||||
fileIdOrUrlRequired: {
|
||||
message: 'fileId or url required.',
|
||||
code: 'INVALID_PARAM',
|
||||
id: '89674805-722c-440c-8d88-5641830dc3e4',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['fileId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
let file: DriveFile | undefined;
|
||||
let file: DriveFile | null = null;
|
||||
|
||||
if (ps.fileId) {
|
||||
file = await DriveFiles.findOneBy({ id: ps.fileId });
|
||||
@@ -62,8 +65,6 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
thumbnailUrl: ps.url,
|
||||
}],
|
||||
});
|
||||
} else {
|
||||
throw new ApiError(meta.errors.fileIdOrUrlRequired);
|
||||
}
|
||||
|
||||
if (file == null) {
|
||||
|
@@ -22,7 +22,7 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
host: { type: 'string', nullable: true },
|
||||
host: { type: 'string', nullable: true, description: 'Omit or use `null` to not filter by host.' },
|
||||
blocked: { type: 'boolean', nullable: true },
|
||||
notResponding: { type: 'boolean', nullable: true },
|
||||
suspended: { type: 'boolean', nullable: true },
|
||||
|
@@ -50,10 +50,10 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
|
||||
const clientData = JSON.parse(ps.clientDataJSON);
|
||||
|
||||
if (clientData.type != 'webauthn.create') {
|
||||
if (clientData.type !== 'webauthn.create') {
|
||||
throw new Error('not a creation attestation');
|
||||
}
|
||||
if (clientData.origin != config.scheme + '://' + config.host) {
|
||||
if (clientData.origin !== config.scheme + '://' + config.host) {
|
||||
throw new Error('origin mismatch');
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
const credentialId = authData.slice(55, 55 + credentialIdLength);
|
||||
const publicKeyData = authData.slice(55 + credentialIdLength);
|
||||
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
|
||||
if (publicKey.get(3) != -7) {
|
||||
if (publicKey.get(3) !== -7) {
|
||||
throw new Error('alg mismatch');
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
take: ps.limit,
|
||||
skip: ps.offset,
|
||||
order: {
|
||||
id: ps.sort == 'asc' ? 1 : -1,
|
||||
id: ps.sort === 'asc' ? 1 : -1,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -0,0 +1,43 @@
|
||||
import define from '../../../define.js';
|
||||
import { genId } from '@/misc/gen-id.js';
|
||||
import { Webhooks } from '@/models/index.js';
|
||||
import { publishInternalEvent } from '@/services/stream.js';
|
||||
import { webhookEventTypes } from '@/models/entities/webhook.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['webhooks'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
url: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||
secret: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||
on: { type: 'array', items: {
|
||||
type: 'string', enum: webhookEventTypes,
|
||||
} },
|
||||
},
|
||||
required: ['name', 'url', 'secret', 'on'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const webhook = await Webhooks.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
name: ps.name,
|
||||
url: ps.url,
|
||||
secret: ps.secret,
|
||||
on: ps.on,
|
||||
}).then(x => Webhooks.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
publishInternalEvent('webhookCreated', webhook);
|
||||
|
||||
return webhook;
|
||||
});
|
@@ -0,0 +1,44 @@
|
||||
import define from '../../../define.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { Webhooks } from '@/models/index.js';
|
||||
import { publishInternalEvent } from '@/services/stream.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['webhooks'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
noSuchWebhook: {
|
||||
message: 'No such webhook.',
|
||||
code: 'NO_SUCH_WEBHOOK',
|
||||
id: 'bae73e5a-5522-4965-ae19-3a8688e71d82',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
webhookId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['webhookId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const webhook = await Webhooks.findOneBy({
|
||||
id: ps.webhookId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (webhook == null) {
|
||||
throw new ApiError(meta.errors.noSuchWebhook);
|
||||
}
|
||||
|
||||
await Webhooks.delete(webhook.id);
|
||||
|
||||
publishInternalEvent('webhookDeleted', webhook);
|
||||
});
|
25
packages/backend/src/server/api/endpoints/i/webhooks/list.ts
Normal file
25
packages/backend/src/server/api/endpoints/i/webhooks/list.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import define from '../../../define.js';
|
||||
import { Webhooks } from '@/models/index.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['webhooks', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:account',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const webhooks = await Webhooks.findBy({
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
return webhooks;
|
||||
});
|
41
packages/backend/src/server/api/endpoints/i/webhooks/show.ts
Normal file
41
packages/backend/src/server/api/endpoints/i/webhooks/show.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import define from '../../../define.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { Webhooks } from '@/models/index.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['webhooks'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:account',
|
||||
|
||||
errors: {
|
||||
noSuchWebhook: {
|
||||
message: 'No such webhook.',
|
||||
code: 'NO_SUCH_WEBHOOK',
|
||||
id: '50f614d9-3047-4f7e-90d8-ad6b2d5fb098',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
webhookId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['webhookId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const webhook = await Webhooks.findOneBy({
|
||||
id: ps.webhookId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (webhook == null) {
|
||||
throw new ApiError(meta.errors.noSuchWebhook);
|
||||
}
|
||||
|
||||
return webhook;
|
||||
});
|
@@ -0,0 +1,59 @@
|
||||
import define from '../../../define.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
import { Webhooks } from '@/models/index.js';
|
||||
import { publishInternalEvent } from '@/services/stream.js';
|
||||
import { webhookEventTypes } from '@/models/entities/webhook.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['webhooks'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
errors: {
|
||||
noSuchWebhook: {
|
||||
message: 'No such webhook.',
|
||||
code: 'NO_SUCH_WEBHOOK',
|
||||
id: 'fb0fea69-da18-45b1-828d-bd4fd1612518',
|
||||
},
|
||||
},
|
||||
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
webhookId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
url: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||
secret: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||
on: { type: 'array', items: {
|
||||
type: 'string', enum: webhookEventTypes,
|
||||
} },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const webhook = await Webhooks.findOneBy({
|
||||
id: ps.webhookId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (webhook == null) {
|
||||
throw new ApiError(meta.errors.noSuchWebhook);
|
||||
}
|
||||
|
||||
await Webhooks.update(webhook.id, {
|
||||
name: ps.name,
|
||||
url: ps.url,
|
||||
secret: ps.secret,
|
||||
on: ps.on,
|
||||
active: ps.active,
|
||||
});
|
||||
|
||||
publishInternalEvent('webhookUpdated', webhook);
|
||||
});
|
@@ -47,14 +47,25 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
groupId: { 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' },
|
||||
markAsRead: { type: 'boolean', default: true },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['groupId'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@@ -126,7 +137,5 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
|
||||
populateGroup: false,
|
||||
})));
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
});
|
||||
|
@@ -67,12 +67,23 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
text: { type: 'string', nullable: true, maxLength: 3000 },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['groupId'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@@ -169,6 +169,7 @@ export const meta = {
|
||||
host: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
|
@@ -38,7 +38,11 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
expiresAt: { type: 'integer', nullable: true },
|
||||
expiresAt: {
|
||||
type: 'integer',
|
||||
nullable: true,
|
||||
description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.',
|
||||
},
|
||||
},
|
||||
required: ['userId'],
|
||||
} as const;
|
||||
|
@@ -19,7 +19,7 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
local: { type: 'boolean' },
|
||||
local: { type: 'boolean', default: false },
|
||||
reply: { type: 'boolean' },
|
||||
renote: { type: 'boolean' },
|
||||
withFiles: { type: 'boolean' },
|
||||
@@ -52,19 +52,19 @@ export default define(meta, paramDef, async (ps) => {
|
||||
query.andWhere('note.userHost IS NULL');
|
||||
}
|
||||
|
||||
if (ps.reply != undefined) {
|
||||
if (ps.reply !== undefined) {
|
||||
query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL');
|
||||
}
|
||||
|
||||
if (ps.renote != undefined) {
|
||||
if (ps.renote !== undefined) {
|
||||
query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL');
|
||||
}
|
||||
|
||||
if (ps.withFiles != undefined) {
|
||||
if (ps.withFiles !== undefined) {
|
||||
query.andWhere(ps.withFiles ? `note.fileIds != '{}'` : `note.fileIds = '{}'`);
|
||||
}
|
||||
|
||||
if (ps.poll != undefined) {
|
||||
if (ps.poll !== undefined) {
|
||||
query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE');
|
||||
}
|
||||
|
||||
|
@@ -57,7 +57,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
conversation.push(p);
|
||||
}
|
||||
|
||||
if (conversation.length == ps.limit) {
|
||||
if (conversation.length === ps.limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import ms from 'ms';
|
||||
import { In } from 'typeorm';
|
||||
import create from '@/services/note/create.js';
|
||||
import define from '../../define.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { Note } from '@/models/entities/note.js';
|
||||
import { noteVisibilities } from '../../../../types.js';
|
||||
import { Channel } from '@/models/entities/channel.js';
|
||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||
import { noteVisibilities } from '../../../../types.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import define from '../../define.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
@@ -59,12 +60,6 @@ export const meta = {
|
||||
id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
|
||||
},
|
||||
|
||||
contentRequired: {
|
||||
message: 'Content required. You need to set text, fileIds, renoteId or poll.',
|
||||
code: 'CONTENT_REQUIRED',
|
||||
id: '6f57e42b-c348-439b-bc45-993995cc515a',
|
||||
},
|
||||
|
||||
cannotCreateAlreadyExpiredPoll: {
|
||||
message: 'Poll is already expired.',
|
||||
code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
|
||||
@@ -88,33 +83,45 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: "public" },
|
||||
visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' },
|
||||
visibleUserIds: { type: 'array', uniqueItems: true, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
text: { type: 'string', nullable: true, maxLength: MAX_NOTE_TEXT_LENGTH, default: null },
|
||||
text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true },
|
||||
cw: { type: 'string', nullable: true, maxLength: 100 },
|
||||
localOnly: { type: 'boolean', default: false },
|
||||
noExtractMentions: { type: 'boolean', default: false },
|
||||
noExtractHashtags: { type: 'boolean', default: false },
|
||||
noExtractEmojis: { type: 'boolean', default: false },
|
||||
fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
mediaIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 16, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
fileIds: {
|
||||
type: 'array',
|
||||
uniqueItems: true,
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
items: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
mediaIds: {
|
||||
deprecated: true,
|
||||
description: 'Use `fileIds` instead. If both are specified, this property is discarded.',
|
||||
type: 'array',
|
||||
uniqueItems: true,
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
items: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
replyId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
renoteId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
channelId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
poll: {
|
||||
type: 'object', nullable: true,
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
properties: {
|
||||
choices: {
|
||||
type: 'array', uniqueItems: true, minItems: 2, maxItems: 10,
|
||||
items: {
|
||||
type: 'string', minLength: 1, maxLength: 50,
|
||||
},
|
||||
type: 'array',
|
||||
uniqueItems: true,
|
||||
minItems: 2,
|
||||
maxItems: 10,
|
||||
items: { type: 'string', minLength: 1, maxLength: 50 },
|
||||
},
|
||||
multiple: { type: 'boolean', default: false },
|
||||
expiresAt: { type: 'integer', nullable: true },
|
||||
@@ -123,36 +130,62 @@ export const paramDef = {
|
||||
required: ['choices'],
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
// (re)note with text, files and poll are optional
|
||||
properties: {
|
||||
text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false },
|
||||
},
|
||||
required: ['text'],
|
||||
},
|
||||
{
|
||||
// (re)note with files, text and poll are optional
|
||||
required: ['fileIds'],
|
||||
},
|
||||
{
|
||||
// (re)note with files, text and poll are optional
|
||||
required: ['mediaIds'],
|
||||
},
|
||||
{
|
||||
// (re)note with poll, text and files are optional
|
||||
properties: {
|
||||
poll: { type: 'object', nullable: false },
|
||||
},
|
||||
required: ['poll'],
|
||||
},
|
||||
{
|
||||
// pure renote
|
||||
required: ['renoteId'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
let visibleUsers: User[] = [];
|
||||
if (ps.visibleUserIds) {
|
||||
visibleUsers = (await Promise.all(ps.visibleUserIds.map(id => Users.findOneBy({ id }))))
|
||||
.filter(x => x != null) as User[];
|
||||
visibleUsers = await Users.findBy({
|
||||
id: In(ps.visibleUserIds),
|
||||
});
|
||||
}
|
||||
|
||||
let files: DriveFile[] = [];
|
||||
const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null;
|
||||
if (fileIds != null) {
|
||||
files = (await Promise.all(fileIds.map(fileId =>
|
||||
DriveFiles.findOneBy({
|
||||
id: fileId,
|
||||
userId: user.id,
|
||||
})
|
||||
))).filter(file => file != null) as DriveFile[];
|
||||
files = await DriveFiles.findBy({
|
||||
userId: user.id,
|
||||
id: In(fileIds),
|
||||
});
|
||||
}
|
||||
|
||||
let renote: Note | null;
|
||||
let renote: Note | null = null;
|
||||
if (ps.renoteId != null) {
|
||||
// Fetch renote to note
|
||||
renote = await Notes.findOneBy({ id: ps.renoteId });
|
||||
|
||||
if (renote == null) {
|
||||
throw new ApiError(meta.errors.noSuchRenoteTarget);
|
||||
} else if (renote.renoteId && !renote.text && !renote.fileIds) {
|
||||
} else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
|
||||
throw new ApiError(meta.errors.cannotReRenote);
|
||||
}
|
||||
|
||||
@@ -168,17 +201,14 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
}
|
||||
}
|
||||
|
||||
let reply: Note | null;
|
||||
let reply: Note | null = null;
|
||||
if (ps.replyId != null) {
|
||||
// Fetch reply
|
||||
reply = await Notes.findOneBy({ id: ps.replyId });
|
||||
|
||||
if (reply == null) {
|
||||
throw new ApiError(meta.errors.noSuchReplyTarget);
|
||||
}
|
||||
|
||||
// 返信対象が引用でないRenoteだったらエラー
|
||||
if (reply.renoteId && !reply.text && !reply.fileIds) {
|
||||
} else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
|
||||
throw new ApiError(meta.errors.cannotReplyToPureRenote);
|
||||
}
|
||||
|
||||
@@ -204,12 +234,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
}
|
||||
}
|
||||
|
||||
// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
|
||||
if (!(ps.text || files.length || renote || ps.poll)) {
|
||||
throw new ApiError(meta.errors.contentRequired);
|
||||
}
|
||||
|
||||
let channel: Channel | undefined;
|
||||
let channel: Channel | null = null;
|
||||
if (ps.channelId != null) {
|
||||
channel = await Channels.findOneBy({ id: ps.channelId });
|
||||
|
||||
|
@@ -35,7 +35,11 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
withFiles: { type: 'boolean' },
|
||||
withFiles: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Only show notes that have attached files.',
|
||||
},
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
|
@@ -48,7 +48,11 @@ export const paramDef = {
|
||||
includeMyRenotes: { type: 'boolean', default: true },
|
||||
includeRenotedMyNotes: { type: 'boolean', default: true },
|
||||
includeLocalRenotes: { type: 'boolean', default: true },
|
||||
withFiles: { type: 'boolean' },
|
||||
withFiles: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Only show notes that have attached files.',
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
@@ -37,7 +37,11 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
withFiles: { type: 'boolean' },
|
||||
withFiles: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Only show notes that have attached files.',
|
||||
},
|
||||
fileType: { type: 'array', items: {
|
||||
type: 'string',
|
||||
} },
|
||||
|
@@ -110,7 +110,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
|
||||
if (exist.length) {
|
||||
if (poll.multiple) {
|
||||
if (exist.some(x => x.choice == ps.choice)) {
|
||||
if (exist.some(x => x.choice === ps.choice)) {
|
||||
throw new ApiError(meta.errors.alreadyVoted);
|
||||
}
|
||||
} else {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { DeepPartial, FindOptionsWhere } from 'typeorm';
|
||||
import { NoteReactions } from '@/models/index.js';
|
||||
import { NoteReaction } from '@/models/entities/note-reaction.js';
|
||||
import define from '../../define.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { NoteReactions } from '@/models/index.js';
|
||||
import { DeepPartial } from 'typeorm';
|
||||
import { NoteReaction } from '@/models/entities/note-reaction.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes', 'reactions'],
|
||||
@@ -45,7 +45,7 @@ export const paramDef = {
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
const query = {
|
||||
noteId: ps.noteId,
|
||||
} as DeepPartial<NoteReaction>;
|
||||
} as FindOptionsWhere<NoteReaction>;
|
||||
|
||||
if (ps.type) {
|
||||
// ローカルリアクションはホスト名が . とされているが
|
||||
|
@@ -25,21 +25,44 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tag: { type: 'string' },
|
||||
query: { type: 'array', items: {
|
||||
type: 'array', items: {
|
||||
type: 'string',
|
||||
},
|
||||
} },
|
||||
reply: { type: 'boolean', nullable: true, default: null },
|
||||
renote: { type: 'boolean', nullable: true, default: null },
|
||||
withFiles: { type: 'boolean' },
|
||||
withFiles: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Only show notes that have attached files.',
|
||||
},
|
||||
poll: { type: 'boolean', nullable: true, default: null },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
tag: { type: 'string', minLength: 1 },
|
||||
},
|
||||
required: ['tag'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
query: {
|
||||
type: 'array',
|
||||
description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.',
|
||||
items: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
},
|
||||
minItems: 1,
|
||||
},
|
||||
minItems: 1,
|
||||
},
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@@ -35,7 +35,11 @@ export const paramDef = {
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
offset: { type: 'integer', default: 0 },
|
||||
host: { type: 'string', nullable: true },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
},
|
||||
|
@@ -38,7 +38,11 @@ export const paramDef = {
|
||||
includeMyRenotes: { type: 'boolean', default: true },
|
||||
includeRenotedMyNotes: { type: 'boolean', default: true },
|
||||
includeLocalRenotes: { type: 'boolean', default: true },
|
||||
withFiles: { type: 'boolean' },
|
||||
withFiles: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Only show notes that have attached files.',
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import define from '../../define.js';
|
||||
import { getNote } from '../../common/getters.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { URLSearchParams } from 'node:url';
|
||||
import fetch from 'node-fetch';
|
||||
import config from '@/config/index.js';
|
||||
import { getAgentByUrl } from '@/misc/fetch.js';
|
||||
import { URLSearchParams } from 'node:url';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { Notes } from '@/models/index.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { getNote } from '../../common/getters.js';
|
||||
import define from '../../define.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
@@ -75,11 +75,17 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
Accept: 'application/json, */*',
|
||||
},
|
||||
body: params,
|
||||
timeout: 10000,
|
||||
// TODO
|
||||
//timeout: 10000,
|
||||
agent: getAgentByUrl,
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
const json = (await res.json()) as {
|
||||
translations: {
|
||||
detected_source_language: string;
|
||||
text: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
return {
|
||||
sourceLang: json.translations[0].detected_source_language,
|
||||
|
@@ -42,7 +42,11 @@ export const paramDef = {
|
||||
includeMyRenotes: { type: 'boolean', default: true },
|
||||
includeRenotedMyNotes: { type: 'boolean', default: true },
|
||||
includeLocalRenotes: { type: 'boolean', default: true },
|
||||
withFiles: { type: 'boolean' },
|
||||
withFiles: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Only show notes that have attached files.',
|
||||
},
|
||||
},
|
||||
required: ['listId'],
|
||||
} as const;
|
||||
@@ -59,12 +63,8 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
}
|
||||
|
||||
//#region Construct query
|
||||
const listQuery = UserListJoinings.createQueryBuilder('joining')
|
||||
.select('joining.userId')
|
||||
.where('joining.userListId = :userListId', { userListId: list.id });
|
||||
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
||||
.andWhere(`note.userId IN (${ listQuery.getQuery() })`)
|
||||
.innerJoin(UserListJoinings.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId')
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('user.avatar', 'avatar')
|
||||
.leftJoinAndSelect('user.banner', 'banner')
|
||||
@@ -76,7 +76,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
||||
.setParameters(listQuery.getParameters());
|
||||
.andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import define from '../../define.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { Pages, Users } from '@/models/index.js';
|
||||
import { Page } from '@/models/entities/page.js';
|
||||
import { IsNull } from 'typeorm';
|
||||
import define from '../../define.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['pages'],
|
||||
@@ -26,17 +26,26 @@ export const meta = {
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pageId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
pageId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['pageId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
},
|
||||
required: ['name', 'username'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
let page: Page | undefined;
|
||||
let page: Page | null = null;
|
||||
|
||||
if (ps.pageId) {
|
||||
page = await Pages.findOneBy({ id: ps.pageId });
|
||||
|
@@ -38,14 +38,29 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
username: { type: 'string' },
|
||||
host: { type: 'string', nullable: true },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
},
|
||||
required: ['username', 'host'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@@ -38,14 +38,29 @@ export const meta = {
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
username: { type: 'string' },
|
||||
host: { type: 'string', nullable: true },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
},
|
||||
required: ['username', 'host'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@@ -28,7 +28,10 @@ export const paramDef = {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
detail: { type: 'boolean', default: true },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{ required: ['username'] },
|
||||
{ required: ['host'] },
|
||||
],
|
||||
} as const;
|
||||
|
||||
// TODO: avatar,bannerをJOINしたいけどエラーになる
|
||||
|
@@ -23,9 +23,9 @@ export const meta = {
|
||||
items: {
|
||||
type: 'object',
|
||||
ref: 'UserDetailed',
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
|
||||
errors: {
|
||||
@@ -46,15 +46,33 @@ export const meta = {
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
userIds: { type: 'array', uniqueItems: true, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
username: { type: 'string' },
|
||||
host: { type: 'string', nullable: true },
|
||||
},
|
||||
required: [],
|
||||
anyOf: [
|
||||
{
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
userIds: { type: 'array', uniqueItems: true, items: {
|
||||
type: 'string', format: 'misskey:id',
|
||||
} },
|
||||
},
|
||||
required: ['userIds'],
|
||||
},
|
||||
{
|
||||
properties: {
|
||||
username: { type: 'string' },
|
||||
host: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'The local host is represented with `null`.',
|
||||
},
|
||||
},
|
||||
required: ['username'],
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
@@ -24,17 +24,17 @@ export default async (ctx: Koa.Context) => {
|
||||
ctx.body = { error };
|
||||
}
|
||||
|
||||
if (typeof username != 'string') {
|
||||
if (typeof username !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof password != 'string') {
|
||||
if (typeof password !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
if (token != null && typeof token != 'string') {
|
||||
if (token != null && typeof token !== 'string') {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import Koa from 'koa';
|
||||
import Router from '@koa/router';
|
||||
import { getJson } from '@/misc/fetch.js';
|
||||
import { OAuth2 } from 'oauth';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { getJson } from '@/misc/fetch.js';
|
||||
import config from '@/config/index.js';
|
||||
import { publishMainStream } from '@/services/stream.js';
|
||||
import { redisClient } from '../../../db/redis.js';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import signin from '../common/signin.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { Users, UserProfiles } from '@/models/index.js';
|
||||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { redisClient } from '../../../db/redis.js';
|
||||
import signin from '../common/signin.js';
|
||||
|
||||
function getUserToken(ctx: Koa.BaseContext): string | null {
|
||||
return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
|
||||
@@ -54,7 +54,7 @@ router.get('/disconnect/discord', async ctx => {
|
||||
integrations: profile.integrations,
|
||||
});
|
||||
|
||||
ctx.body = `Discordの連携を解除しました :v:`;
|
||||
ctx.body = 'Discordの連携を解除しました :v:';
|
||||
|
||||
// Publish i updated event
|
||||
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
|
||||
@@ -140,7 +140,7 @@ router.get('/dc/cb', async ctx => {
|
||||
|
||||
const code = ctx.query.code;
|
||||
|
||||
if (!code) {
|
||||
if (!code || typeof code !== 'string') {
|
||||
ctx.throw(400, 'invalid session');
|
||||
return;
|
||||
}
|
||||
@@ -174,17 +174,17 @@ router.get('/dc/cb', async ctx => {
|
||||
}
|
||||
}));
|
||||
|
||||
const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
|
||||
const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
});
|
||||
})) as Record<string, unknown>;
|
||||
|
||||
if (!id || !username || !discriminator) {
|
||||
if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
|
||||
ctx.throw(400, 'invalid session');
|
||||
return;
|
||||
}
|
||||
|
||||
const profile = await UserProfiles.createQueryBuilder()
|
||||
.where(`"integrations"->'discord'->>'id' = :id`, { id: id })
|
||||
.where('"integrations"->\'discord\'->>\'id\' = :id', { id: id })
|
||||
.andWhere('"userHost" IS NULL')
|
||||
.getOne();
|
||||
|
||||
@@ -211,7 +211,7 @@ router.get('/dc/cb', async ctx => {
|
||||
} else {
|
||||
const code = ctx.query.code;
|
||||
|
||||
if (!code) {
|
||||
if (!code || typeof code !== 'string') {
|
||||
ctx.throw(400, 'invalid session');
|
||||
return;
|
||||
}
|
||||
@@ -245,10 +245,10 @@ router.get('/dc/cb', async ctx => {
|
||||
}
|
||||
}));
|
||||
|
||||
const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
|
||||
const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
});
|
||||
if (!id || !username || !discriminator) {
|
||||
})) as Record<string, unknown>;
|
||||
if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
|
||||
ctx.throw(400, 'invalid session');
|
||||
return;
|
||||
}
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import Koa from 'koa';
|
||||
import Router from '@koa/router';
|
||||
import { getJson } from '@/misc/fetch.js';
|
||||
import { OAuth2 } from 'oauth';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { getJson } from '@/misc/fetch.js';
|
||||
import config from '@/config/index.js';
|
||||
import { publishMainStream } from '@/services/stream.js';
|
||||
import { redisClient } from '../../../db/redis.js';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import signin from '../common/signin.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { Users, UserProfiles } from '@/models/index.js';
|
||||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { redisClient } from '../../../db/redis.js';
|
||||
import signin from '../common/signin.js';
|
||||
|
||||
function getUserToken(ctx: Koa.BaseContext): string | null {
|
||||
return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
|
||||
@@ -54,7 +54,7 @@ router.get('/disconnect/github', async ctx => {
|
||||
integrations: profile.integrations,
|
||||
});
|
||||
|
||||
ctx.body = `GitHubの連携を解除しました :v:`;
|
||||
ctx.body = 'GitHubの連携を解除しました :v:';
|
||||
|
||||
// Publish i updated event
|
||||
publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
|
||||
@@ -138,7 +138,7 @@ router.get('/gh/cb', async ctx => {
|
||||
|
||||
const code = ctx.query.code;
|
||||
|
||||
if (!code) {
|
||||
if (!code || typeof code !== 'string') {
|
||||
ctx.throw(400, 'invalid session');
|
||||
return;
|
||||
}
|
||||
@@ -167,16 +167,16 @@ router.get('/gh/cb', async ctx => {
|
||||
}
|
||||
}));
|
||||
|
||||
const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
|
||||
const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
|
||||
'Authorization': `bearer ${accessToken}`,
|
||||
});
|
||||
if (!login || !id) {
|
||||
})) as Record<string, unknown>;
|
||||
if (typeof login !== 'string' || typeof id !== 'string') {
|
||||
ctx.throw(400, 'invalid session');
|
||||
return;
|
||||
}
|
||||
|
||||
const link = await UserProfiles.createQueryBuilder()
|
||||
.where(`"integrations"->'github'->>'id' = :id`, { id: id })
|
||||
.where('"integrations"->\'github\'->>\'id\' = :id', { id: id })
|
||||
.andWhere('"userHost" IS NULL')
|
||||
.getOne();
|
||||
|
||||
@@ -189,7 +189,7 @@ router.get('/gh/cb', async ctx => {
|
||||
} else {
|
||||
const code = ctx.query.code;
|
||||
|
||||
if (!code) {
|
||||
if (!code || typeof code !== 'string') {
|
||||
ctx.throw(400, 'invalid session');
|
||||
return;
|
||||
}
|
||||
@@ -219,11 +219,11 @@ router.get('/gh/cb', async ctx => {
|
||||
}
|
||||
}));
|
||||
|
||||
const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
|
||||
const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
|
||||
'Authorization': `bearer ${accessToken}`,
|
||||
});
|
||||
})) as Record<string, unknown>;
|
||||
|
||||
if (!login || !id) {
|
||||
if (typeof login !== 'string' || typeof id !== 'string') {
|
||||
ctx.throw(400, 'invalid session');
|
||||
return;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user