Compare commits

...

27 commits

Author SHA1 Message Date
Johann150 6c7f1774e3
server: fix thread mutes not applying to renotes
Changelog: Fixed
2022-12-15 21:20:24 +01:00
Johann150 af43df15ca
reduce duplication in secureRndstr 2022-12-15 20:46:17 +01:00
Johann150 5f83383ab8
fix import error in tests 2022-12-15 20:45:55 +01:00
Johann150 8c759dde6c
server: fix error about duplicate resolve 2022-12-15 19:44:55 +01:00
Johann150 84d83d908a
client: add button to unrenote
Changelog: Added
2022-12-15 17:52:19 +01:00
Johann150 16d091497a
server: use extractDbHost instead of toPuny, translate comments
Also swapped logical or for nullish coalescing operator in some places.
2022-12-15 00:32:15 +01:00
Johann150 ef53ec276a
activitypub: simplify some URI/id related checks
followup on previous commit
2022-12-15 00:31:23 +01:00
Johann150 3582fd8260
activitypub: centrally check id matches URL in resolver
This makes some duplicated checks in models/note and models/person
unnecessary.
2022-12-15 00:29:39 +01:00
Johann150 6256ddbd30
client: remove unused variables 2022-12-14 22:09:29 +01:00
Johann150 00fcc238f7
client: remove broken instance ticker from landing page 2022-12-14 22:08:27 +01:00
Johann150 9f1670d5fd
server: fix default not found error image 2022-12-14 19:05:41 +01:00
Norm ff31b8b06d server: remove bios and cli
The BIOS and CLI functionality were mainly for debugging purposes.
If a user has to use those to resolve an issue with the server, that
really should be fixed at the source instead.

Closes: FoundKeyGang/FoundKey#283
Changelog: Removed
2022-12-14 17:59:25 +00:00
Johann150 e317a771b3
remove vscode config files 2022-12-14 18:52:36 +01:00
Johann150 398ee6435b
client: replace repo link with foundkey link 2022-12-14 18:21:24 +01:00
Johann150 ffff2ae5ef
server: fix missing import
closes FoundKeyGang/FoundKey#286
2022-12-14 18:08:44 +01:00
Johann150 ccc8bf0289
chore: fix more miscellaneous lints 2022-12-13 23:09:32 +01:00
Johann150 a231b36d59
chore: fix lint about unused variables in entities 2022-12-13 23:09:32 +01:00
Johann150 8e9c65fab0
chore: fix some import related lints 2022-12-13 23:09:31 +01:00
Norm 78a3051313
remove unneeded TODO 2022-12-13 16:46:29 -05:00
Norm 78717e85d3
server: change JSON.parse/stringify to structuredClone
structuredClone is more typesafe than using JSON.parse and
JSON.stringify.

Now that Node 18.x is the new baseline, this should be safe to use now.

See https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
for details.
2022-12-13 16:45:38 -05:00
Norm a9d3cae511
server: add return type to extractApMentions 2022-12-13 16:31:15 -05:00
Norm bd27b7ca3a
server: add typing for renderFollowRelay 2022-12-13 16:06:18 -05:00
Norm e28a9eb8e8
use tsc --noEmit for backend and client
See https://github.com/misskey-dev/misskey/pull/9316
2022-12-13 16:02:06 -05:00
Norm e5a4c5d2d0
chore: update @typescript-eslint packages 2022-12-13 15:57:26 -05:00
Norm 6bba55c196
sw: add TypeScript type checking
This implements the upstream changes from
https://github.com/misskey-dev/misskey/pull/9314 but updated to our
version of ESLint.

Also updates TypeScript to 4.9.4 for all packages.
2022-12-13 15:42:08 -05:00
Norm 1d469f3c34
fix import typo 2022-12-13 15:12:29 -05:00
Norm 3f0228e14c
server: use color-convert KEYWORD instead of extracting parameter type 2022-12-13 15:11:29 -05:00
109 changed files with 377 additions and 596 deletions

View file

@ -1,5 +1,4 @@
.autogen
.vscode
.config
.woodpecker
Dockerfile

View file

@ -2,9 +2,10 @@ root = true
[*]
indent_style = tab
indent_size = 2
indent_size = 4
charset = utf-8
insert_final_newline = true
[*.yml]
indent_style = space
indent_size = 2

2
.gitignore vendored
View file

@ -1,6 +1,6 @@
# Visual Studio Code
/.vscode
!/.vscode/extensions.json
/.vsls.json
# Intelij-IDEA
/.idea

View file

@ -1,8 +0,0 @@
{
"recommendations": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin"
]
}

View file

@ -1,4 +0,0 @@
{
"$schema": "http://json.schemastore.org/vsls",
"gitignore": "exclude"
}

22
.woodpecker/lint-sw.yml Normal file
View file

@ -0,0 +1,22 @@
clone:
git:
image: woodpeckerci/plugin-git
settings:
depth: 1 # CI does not need commit history
recursive: true
pipeline:
install:
when:
event:
- pull_request
image: node:18.6.0
commands:
- yarn install
lint:
when:
event:
- pull_request
image: node:18.6.0
commands:
- yarn workspace sw run lint

View file

@ -36,7 +36,7 @@ gulp.task('copy:client:locales', cb => {
});
gulp.task('build:backend:script', () => {
return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
return gulp.src(['./packages/backend/src/server/web/boot.js'])
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
.pipe(terser({
toplevel: true
@ -45,7 +45,7 @@ gulp.task('build:backend:script', () => {
});
gulp.task('build:backend:style', () => {
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css'])
return gulp.src(['./packages/backend/src/server/web/style.css'])
.pipe(cssnano({
zindex: false
}))

View file

@ -96,6 +96,8 @@ unfollow: "Unfollow"
followRequestPending: "Follow request pending"
renote: "Renote"
unrenote: "Take back renote"
unrenoteAll: "Take back all renotes"
unrenoteAllConfirm: "Are you sure that you want to take back all renotes of this note?"
quote: "Quote"
pinnedNote: "Pinned note"
you: "You"

View file

@ -46,11 +46,11 @@
"devDependencies": {
"@types/gulp": "4.0.9",
"@types/gulp-rename": "2.0.1",
"@typescript-eslint/parser": "^5.44.0",
"@typescript-eslint/parser": "^5.46.1",
"cross-env": "7.0.3",
"cypress": "10.3.0",
"start-server-and-test": "1.14.0",
"typescript": "^4.9.3"
"typescript": "^4.9.4"
},
"packageManager": "yarn@3.3.0"
}

View file

@ -7,7 +7,7 @@
"scripts": {
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
"lint": "eslint src --ext .ts",
"lint": "tsc --noEmit && eslint src --ext .ts",
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"migrate": "npx typeorm migration:run -d ormconfig.js",
"start": "node --experimental-json-modules ./built/index.js",
@ -166,14 +166,14 @@
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"cross-env": "7.0.3",
"eslint": "^8.29.0",
"eslint-plugin-import": "^2.26.0",
"execa": "6.1.0",
"form-data": "^4.0.0",
"sinon": "^14.0.2",
"typescript": "^4.9.3"
"typescript": "^4.9.4"
}
}

View file

@ -40,7 +40,7 @@ export default function load(): Config {
config.images = Object.assign({
info: '/twemoji/1f440.svg',
notFound: '/twemoji/2040.svg',
notFound: '/twemoji/2049.svg',
error: '/twemoji/1f480.svg',
}, config.images ?? {});

View file

@ -1,6 +1,5 @@
// https://github.com/typeorm/typeorm/issues/2400
import pg from 'pg';
import { SECOND } from '@/const.js';
pg.types.setTypeParser(20, Number);
@ -8,6 +7,7 @@ import { Logger, DataSource } from 'typeorm';
import * as highlight from 'cli-highlight';
import config from '@/config/index.js';
import { SECOND } from '@/const.js';
import { User } from '@/models/entities/user.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { DriveFolder } from '@/models/entities/drive-folder.js';
@ -78,33 +78,33 @@ import { redisClient } from './redis.js';
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
class MyCustomLogger implements Logger {
private highlight(sql: string) {
private highlight(sql: string): string {
return highlight.highlight(sql, {
language: 'sql', ignoreIllegals: true,
});
}
public logQuery(query: string, parameters?: any[]) {
public logQuery(query: string): void {
sqlLogger.info(this.highlight(query).substring(0, 100));
}
public logQueryError(error: string, query: string, parameters?: any[]) {
public logQueryError(error: string, query: string): void {
sqlLogger.error(this.highlight(query));
}
public logQuerySlow(time: number, query: string, parameters?: any[]) {
public logQuerySlow(time: number, query: string): void {
sqlLogger.warn(this.highlight(query));
}
public logSchemaBuild(message: string) {
public logSchemaBuild(message: string): void {
sqlLogger.info(message);
}
public log(message: string) {
public log(message: string): void {
sqlLogger.info(message);
}
public logMigration(message: string) {
public logMigration(message: string): void {
sqlLogger.info(message);
}
}

View file

@ -1,7 +1,7 @@
import * as crypto from 'node:crypto';
const L_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
const LU_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const LU_CHARS = L_CHARS + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
export function secureRndstrCustom(length = 32, chars: string): string {
const chars_len = chars.length;

View file

@ -17,7 +17,7 @@ export class AbuseUserReport {
@Column(id())
public targetUserId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class AbuseUserReport {
@Column(id())
public reporterId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -39,7 +39,7 @@ export class AbuseUserReport {
})
public assigneeId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -41,7 +41,7 @@ export class AccessToken {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -53,7 +53,7 @@ export class AccessToken {
})
public appId: App['id'] | null;
@ManyToOne(type => App, {
@ManyToOne(() => App, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class AnnouncementRead {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -28,7 +28,7 @@ export class AnnouncementRead {
@Column(id())
public announcementId: Announcement['id'];
@ManyToOne(type => Announcement, {
@ManyToOne(() => Announcement, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class AntennaNote {
})
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class AntennaNote {
})
public antennaId: Antenna['id'];
@ManyToOne(type => Antenna, {
@ManyToOne(() => Antenna, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class Antenna {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -42,7 +42,7 @@ export class Antenna {
})
public userListId: UserList['id'] | null;
@ManyToOne(type => UserList, {
@ManyToOne(() => UserList, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -54,7 +54,7 @@ export class Antenna {
})
public userGroupJoiningId: UserGroupJoining['id'] | null;
@ManyToOne(type => UserGroupJoining, {
@ManyToOne(() => UserGroupJoining, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class App {
})
public userId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'SET NULL',
nullable: true,
})

View file

@ -11,7 +11,7 @@ export class AttestationChallenge {
@PrimaryColumn(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -1,4 +1,4 @@
import { Entity, PrimaryColumn, Index, Column, ManyToOne, OneToOne, JoinColumn } from 'typeorm';
import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm';
import { id } from '../id.js';
import { AccessToken } from './access-token.js';
import { App } from './app.js';

View file

@ -21,7 +21,7 @@ export class Blocking {
})
public blockeeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class Blocking {
})
public blockerId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class ChannelFollowing {
})
public followeeId: Channel['id'];
@ManyToOne(type => Channel, {
@ManyToOne(() => Channel, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -35,7 +35,7 @@ export class ChannelFollowing {
})
public followerId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class ChannelNotePining {
@Column(id())
public channelId: Channel['id'];
@ManyToOne(type => Channel, {
@ManyToOne(() => Channel, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class ChannelNotePining {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -28,7 +28,7 @@ export class Channel {
})
public userId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'SET NULL',
})
@JoinColumn()
@ -53,7 +53,7 @@ export class Channel {
})
public bannerId: DriveFile['id'] | null;
@ManyToOne(type => DriveFile, {
@ManyToOne(() => DriveFile, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class ClipNote {
})
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class ClipNote {
})
public clipId: Clip['id'];
@ManyToOne(type => Clip, {
@ManyToOne(() => Clip, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class Clip {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -23,7 +23,7 @@ export class DriveFile {
})
public userId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'RESTRICT',
})
@JoinColumn()
@ -144,7 +144,7 @@ export class DriveFile {
})
public folderId: DriveFolder['id'] | null;
@ManyToOne(type => DriveFolder, {
@ManyToOne(() => DriveFolder, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -27,7 +27,7 @@ export class DriveFolder {
})
public userId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -41,7 +41,7 @@ export class DriveFolder {
})
public parentId: DriveFolder['id'] | null;
@ManyToOne(type => DriveFolder, {
@ManyToOne(() => DriveFolder, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -20,7 +20,7 @@ export class FollowRequest {
})
public followeeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -33,7 +33,7 @@ export class FollowRequest {
})
public followerId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class Following {
})
public followeeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class Following {
})
public followerId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class GalleryLike {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -25,7 +25,7 @@ export class GalleryLike {
@Column(id())
public postId: GalleryPost['id'];
@ManyToOne(type => GalleryPost, {
@ManyToOne(() => GalleryPost, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -37,7 +37,7 @@ export class GalleryPost {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class MessagingMessage {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -35,7 +35,7 @@ export class MessagingMessage {
})
public recipientId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -48,7 +48,7 @@ export class MessagingMessage {
})
public groupId: UserGroup['id'] | null;
@ManyToOne(type => UserGroup, {
@ManyToOne(() => UserGroup, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -81,7 +81,7 @@ export class MessagingMessage {
})
public fileId: DriveFile['id'] | null;
@ManyToOne(type => DriveFile, {
@ManyToOne(() => DriveFile, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -134,7 +134,7 @@ export class Meta {
})
public proxyAccountId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class ModerationLog {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -17,7 +17,7 @@ export class MutedNote {
})
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -30,7 +30,7 @@ export class MutedNote {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -27,7 +27,7 @@ export class Muting {
})
public muteeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -40,7 +40,7 @@ export class Muting {
})
public muterId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class NoteFavorite {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class NoteFavorite {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class NoteReaction {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class NoteReaction {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -20,7 +20,7 @@ export class NoteThreadMuting {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -14,7 +14,7 @@ export class NoteUnread {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -24,7 +24,7 @@ export class NoteUnread {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class NoteWatching {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -35,7 +35,7 @@ export class NoteWatching {
})
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -27,7 +27,7 @@ export class Note {
})
public replyId: Note['id'] | null;
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -41,7 +41,7 @@ export class Note {
})
public renoteId: Note['id'] | null;
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -75,7 +75,7 @@ export class Note {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -179,7 +179,7 @@ export class Note {
})
public channelId: Channel['id'] | null;
@ManyToOne(type => Channel, {
@ManyToOne(() => Channel, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -28,7 +28,7 @@ export class Notification {
})
public notifieeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -45,7 +45,7 @@ export class Notification {
})
public notifierId: User['id'] | null;
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -89,7 +89,7 @@ export class Notification {
})
public noteId: Note['id'] | null;
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -101,7 +101,7 @@ export class Notification {
})
public followRequestId: FollowRequest['id'] | null;
@ManyToOne(type => FollowRequest, {
@ManyToOne(() => FollowRequest, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -113,7 +113,7 @@ export class Notification {
})
public userGroupInvitationId: UserGroupInvitation['id'] | null;
@ManyToOne(type => UserGroupInvitation, {
@ManyToOne(() => UserGroupInvitation, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -165,7 +165,7 @@ export class Notification {
})
public appAccessTokenId: AccessToken['id'] | null;
@ManyToOne(type => AccessToken, {
@ManyToOne(() => AccessToken, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class PageLike {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -25,7 +25,7 @@ export class PageLike {
@Column(id())
public pageId: Page['id'];
@ManyToOne(type => Page, {
@ManyToOne(() => Page, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -57,7 +57,7 @@ export class Page {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -69,7 +69,7 @@ export class Page {
})
public eyeCatchingImageId: DriveFile['id'] | null;
@ManyToOne(type => DriveFile, {
@ManyToOne(() => DriveFile, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -22,7 +22,7 @@ export class PasswordResetRequest {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class PollVote {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -29,7 +29,7 @@ export class PollVote {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -9,7 +9,7 @@ export class Poll {
@PrimaryColumn(id())
public noteId: Note['id'];
@OneToOne(type => Note, {
@OneToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -25,7 +25,7 @@ export class RegistryItem {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class RenoteMuting {
})
public muteeId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class RenoteMuting {
})
public muterId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -16,7 +16,7 @@ export class Signin {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -14,7 +14,7 @@ export class SwSubscription {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class UserGroupInvitation {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class UserGroupInvitation {
})
public userGroupId: UserGroup['id'];
@ManyToOne(type => UserGroup, {
@ManyToOne(() => UserGroup, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class UserGroupJoining {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class UserGroupJoining {
})
public userGroupId: UserGroup['id'];
@ManyToOne(type => UserGroup, {
@ManyToOne(() => UserGroup, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -25,7 +25,7 @@ export class UserGroup {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -7,7 +7,7 @@ export class UserKeypair {
@PrimaryColumn(id())
public userId: User['id'];
@OneToOne(type => User, {
@OneToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class UserListJoining {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -34,7 +34,7 @@ export class UserListJoining {
})
public userListId: UserList['id'];
@ManyToOne(type => UserList, {
@ManyToOne(() => UserList, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -19,7 +19,7 @@ export class UserList {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -18,7 +18,7 @@ export class UserNotePining {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -27,7 +27,7 @@ export class UserNotePining {
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
@ManyToOne(() => Note, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -11,7 +11,7 @@ export class UserProfile {
@PrimaryColumn(id())
public userId: User['id'];
@OneToOne(type => User, {
@OneToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
@ -161,7 +161,7 @@ export class UserProfile {
})
public pinnedPageId: Page['id'] | null;
@OneToOne(type => Page, {
@OneToOne(() => Page, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -7,7 +7,7 @@ export class UserPublickey {
@PrimaryColumn(id())
public userId: User['id'];
@OneToOne(type => User, {
@OneToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -13,7 +13,7 @@ export class UserSecurityKey {
@Column(id())
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -81,7 +81,7 @@ export class User {
})
public avatarId: DriveFile['id'] | null;
@OneToOne(type => DriveFile, {
@OneToOne(() => DriveFile, {
onDelete: 'SET NULL',
})
@JoinColumn()
@ -94,7 +94,7 @@ export class User {
})
public bannerId: DriveFile['id'] | null;
@OneToOne(type => DriveFile, {
@OneToOne(() => DriveFile, {
onDelete: 'SET NULL',
})
@JoinColumn()

View file

@ -21,7 +21,7 @@ export class Webhook {
})
public userId: User['id'];
@ManyToOne(type => User, {
@ManyToOne(() => User, {
onDelete: 'CASCADE',
})
@JoinColumn()

View file

@ -27,9 +27,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) {
// TODO
//const properties = structuredClone(file.properties);
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];
}

View file

@ -20,7 +20,7 @@ export function initialize<T>(name: string, limitPerSec = -1): Bull.Queue<T> {
}
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
function apBackoff(attemptsMade: number, err: Error) {
function apBackoff(attemptsMade: number /*, err: Error */): number {
const baseDelay = MINUTE;
const maxBackoff = 8 * HOUR;
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;

View file

@ -1,7 +1,7 @@
import escapeRegexp from 'escape-regexp';
import config from '@/config/index.js';
import { Note } from '@/models/entities/note.js';
import { CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js';
import { CacheableUser } from '@/models/entities/user.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { Notes, MessagingMessages } from '@/models/index.js';
import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';

View file

@ -1,4 +1,3 @@
import { IsNull, Not } from 'typeorm';
import { ILocalUser, IRemoteUser, User } from '@/models/entities/user.js';
import { Users, Followings } from '@/models/index.js';
import { deliver } from '@/queue/index.js';

View file

@ -9,7 +9,7 @@ import { getApId, IObject, ICreate } from '@/remote/activitypub/type.js';
/**
* 稿
*/
export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false): Promise<string> {
const uri = getApId(note);
if (typeof note === 'object') {

View file

@ -1,5 +1,5 @@
import { CacheableRemoteUser } from '@/models/entities/user.js';
import { getApType, IUpdate, isActor } from '@/remote/activitypub/type.js';
import { getApId, getApType, IUpdate, isActor } from '@/remote/activitypub/type.js';
import { apLogger } from '@/remote/activitypub/logger.js';
import { updateQuestion } from '@/remote/activitypub/models/question.js';
import { Resolver } from '@/remote/activitypub/resolver.js';
@ -21,7 +21,11 @@ export default async (actor: CacheableRemoteUser, activity: IUpdate, resolver: R
});
if (isActor(object)) {
await updatePerson(actor.uri!, resolver, object);
if (actor.uri !== getApId(object)) {
return 'skip: actor id !== updated actor id';
}
await updatePerson(object, resolver);
return 'ok: Person updated';
} else if (getApType(object) === 'Question') {
await updateQuestion(object, resolver).catch(e => console.log(e));

View file

@ -5,7 +5,7 @@ import { Resolver } from '@/remote/activitypub/resolver.js';
import { IObject, isMention, IApMention } from '../type.js';
import { resolvePerson } from './person.js';
export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) {
export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver): Promise<CacheableUser[]> {
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
const limit = promiseLimit<CacheableUser | null>(2);

View file

@ -28,9 +28,7 @@ import { extractApHashtags } from './tag.js';
import { extractPollFromQuestion } from './question.js';
import { extractApMentions } from './mention.js';
export function validateNote(object: any, uri: string): Error | null {
const expectHost = extractDbHost(uri);
export function validateNote(object: IObject): Error | null {
if (object == null) {
return new Error('invalid Note: object is null');
}
@ -39,12 +37,20 @@ export function validateNote(object: any, uri: string): Error | null {
return new Error(`invalid Note: invalid object type ${getApType(object)}`);
}
if (object.id && extractDbHost(object.id) !== expectHost) {
return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractDbHost(object.id)}`);
const id = getApId(object);
if (id == null) {
// Only transient objects or anonymous objects may not have an id or an id that is explicitly null.
// We consider all Notes as not transient and not anonymous so require ids for them.
return new Error(`invalid Note: id required but not present`);
}
if (object.attributedTo && extractDbHost(getOneApId(object.attributedTo)) !== expectHost) {
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractDbHost(object.attributedTo)}`);
// Check that the server is authorized to act on behalf of this author.
const expectHost = extractDbHost(id);
const attributedToHost = object.attributedTo
? extractDbHost(getOneApId(object.attributedTo))
: null;
if (attributedToHost !== expectHost) {
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${attributedToHost}`);
}
return null;
@ -64,10 +70,9 @@ export async function fetchNote(object: string | IObject): Promise<Note | null>
* Noteを作成します
*/
export async function createNote(value: string | IObject, resolver: Resolver, silent = false): Promise<Note | null> {
const object: any = await resolver.resolve(value);
const object: IObject = await resolver.resolve(value);
const entryUri = getApId(value);
const err = validateNote(object, entryUri);
const err = validateNote(object);
if (err) {
apLogger.error(`${err.message}`, {
resolver: {

View file

@ -13,7 +13,7 @@ import { genId } from '@/misc/gen-id.js';
import { instanceChart, usersChart } from '@/services/chart/index.js';
import { UserPublickey } from '@/models/entities/user-publickey.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { toPuny } from '@/misc/convert-host.js';
import { extractDbHost } from '@/misc/convert-host.js';
import { UserProfile } from '@/models/entities/user-profile.js';
import { toArray } from '@/prelude/array.js';
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
@ -39,8 +39,7 @@ const summaryLength = 2048;
* @param x Fetched object
* @param uri Fetch target URI
*/
function validateActor(x: IObject, uri: string): IActor {
const expectHost = toPuny(new URL(uri).hostname);
function validateActor(x: IObject): IActor {
if (x == null) {
throw new Error('invalid Actor: object is null');
@ -50,7 +49,10 @@ function validateActor(x: IObject, uri: string): IActor {
throw new Error(`invalid Actor type '${x.type}'`);
}
if (!(typeof x.id === 'string' && x.id.length > 0)) {
const uri = getApId(x);
if (uri == null) {
// Only transient objects or anonymous objects may not have an id or an id that is explicitly null.
// We consider all actors as not transient and not anonymous so require ids for them.
throw new Error('invalid Actor: wrong id');
}
@ -78,17 +80,13 @@ function validateActor(x: IObject, uri: string): IActor {
x.summary = truncate(x.summary, summaryLength);
}
const idHost = toPuny(new URL(x.id!).hostname);
if (idHost !== expectHost) {
throw new Error('invalid Actor: id has different host');
}
if (x.publicKey) {
if (typeof x.publicKey.id !== 'string') {
throw new Error('invalid Actor: publicKey.id is not a string');
}
const publicKeyIdHost = toPuny(new URL(x.publicKey.id).hostname);
const expectHost = extractDbHost(uri);
const publicKeyIdHost = extractDbHost(x.publicKey.id);
if (publicKeyIdHost !== expectHost) {
throw new Error('invalid Actor: publicKey.id has different host');
}
@ -131,20 +129,18 @@ export async function fetchPerson(uri: string, resolver: Resolver): Promise<Cach
/**
* Personを作成します
*/
export async function createPerson(uri: string, resolver: Resolver): Promise<User> {
if (typeof uri !== 'string') throw new Error('uri is not string');
if (uri.startsWith(config.url)) {
export async function createPerson(value: string | IObject, resolver: Resolver): Promise<User> {
if (getApId(value).startsWith(config.url)) {
throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user');
}
const object = await resolver.resolve(uri) as any;
const object = await resolver.resolve(value) as any;
const person = validateActor(object, uri);
const person = validateActor(object);
apLogger.info(`Creating the Person: ${person.id}`);
const host = toPuny(new URL(object.id).hostname);
const host = extractDbHost(object.id);
const { fields } = analyzeAttachments(person.attachment || []);
@ -274,33 +270,32 @@ export async function createPerson(uri: string, resolver: Resolver): Promise<Use
/**
* Update Person information.
* If the target Person is not registered in FoundKey, it is ignored.
* @param uri URI of Person
* @param value URI of Person or Person itself
* @param resolver Resolver
* @param hint Hint of Person object (If this value is a valid Person, it is used for updating without Remote resolve.)
*/
export async function updatePerson(uri: string, resolver: Resolver, hint?: IObject): Promise<void> {
if (typeof uri !== 'string') throw new Error('uri is not string');
export async function updatePerson(value: IObject | string, resolver: Resolver): Promise<void> {
const uri = getApId(value);
// URIがこのサーバーを指しているならスキップ
if (uri.startsWith(config.url + '/')) {
// skip local URIs
if (uri.startsWith(config.url)) {
return;
}
//#region このサーバーに既に登録されているか
// do we already know this user?
const exist = await Users.findOneBy({ uri }) as IRemoteUser;
if (exist == null) {
return;
}
//#endregion
const object = hint || await resolver.resolve(uri);
const object = await resolver.resolve(value);
const person = validateActor(object, uri);
const person = validateActor(object);
apLogger.info(`Updating the Person: ${person.id}`);
// アバターとヘッダー画像をフェッチ
// Fetch avatar and banner image
const [avatar, banner] = await Promise.all([
person.icon,
person.image,
@ -310,7 +305,7 @@ export async function updatePerson(uri: string, resolver: Resolver, hint?: IObje
: resolveImage(exist, img, resolver).catch(() => null),
));
// カスタム絵文字取得
// Get custom emoji
const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => {
apLogger.info(`extractEmojis: ${e}`);
return [] as Emoji[];
@ -318,7 +313,7 @@ export async function updatePerson(uri: string, resolver: Resolver, hint?: IObje
const emojiNames = emojis.map(emoji => emoji.name);
const { fields } = analyzeAttachments(person.attachment || []);
const { fields } = analyzeAttachments(person.attachment ?? []);
const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32);
@ -327,7 +322,7 @@ export async function updatePerson(uri: string, resolver: Resolver, hint?: IObje
const updates = {
lastFetchedAt: new Date(),
inbox: person.inbox,
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
sharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
followersUri: person.followers ? getApId(person.followers) : undefined,
featured: person.featured,
emojis: emojiNames,
@ -367,14 +362,13 @@ export async function updatePerson(uri: string, resolver: Resolver, hint?: IObje
publishInternalEvent('remoteUserUpdated', { id: exist.id });
// ハッシュタグ更新
updateUsertags(exist, tags);
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする
// If the user in question is already a follower, followers will also be updated.
await Followings.update({
followerId: exist.id,
}, {
followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
followerSharedInbox: person.sharedInbox ?? (person.endpoints ? person.endpoints.sharedInbox : undefined),
});
await updateFeatured(exist.id, resolver).catch(err => apLogger.error(err));

View file

@ -2,13 +2,20 @@ import config from '@/config/index.js';
import { Relay } from '@/models/entities/relay.js';
import { ILocalUser } from '@/models/entities/user.js';
export function renderFollowRelay(relay: Relay, relayActor: ILocalUser) {
export type FollowRelay = {
id: string;
type: 'Follow';
actor: string;
object: 'https://www.w3.org/ns/activitystreams#Public';
};
export function renderFollowRelay(relay: Relay, relayActor: ILocalUser): FollowRelay {
const follow = {
id: `${config.url}/activities/follow-relay/${relay.id}`,
type: 'Follow',
actor: `${config.url}/users/${relayActor.id}`,
object: 'https://www.w3.org/ns/activitystreams#Public',
};
} as const;
return follow;
}

View file

@ -1,5 +1,3 @@
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 { extractDbHost, isSelfHost } from '@/misc/convert-host.js';
@ -13,7 +11,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderFollow from '@/remote/activitypub/renderer/follow.js';
import { shouldBlockInstance } from '@/misc/should-block-instance.js';
import { signedGet } from './request.js';
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
import { getApId, IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
import { parseUri } from './db-resolver.js';
/**
@ -86,11 +84,18 @@ export class Resolver {
const object = await signedGet(value, this.user);
if (object == null || (
Array.isArray(object['@context']) ?
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
)) {
if (
object == null
|| // check that this is an activitypub object by looking at the @context
(
Array.isArray(object['@context']) ?
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
)
// Did we actually get the object that corresponds to the canonical URL?
// Does the host we requested stuff from actually correspond to the host that owns the activity?
|| !(getApId(object) == null || getApId(object) === value)
) {
throw new Error('invalid response');
}

View file

@ -2,14 +2,13 @@ import { URL } from 'node:url';
import chalk from 'chalk';
import { IsNull } from 'typeorm';
import { DAY } from '@/const.js';
import config from '@/config/index.js';
import { isSelfHost, toPuny } from '@/misc/convert-host.js';
import { User, IRemoteUser } from '@/models/entities/user.js';
import { Users } from '@/models/index.js';
import { Resolver } from '@/remote/activitypub/resolver.js';
import webFinger from './webfinger.js';
import { createPerson, updatePerson } from './activitypub/models/person.js';
import { remoteLogger } from './logger.js';
import { Resolver } from '@/remote/activitypub/resolver.js';
const logger = remoteLogger.createSubLogger('resolve-user');

View file

@ -1,8 +1,7 @@
import * as crypto from 'node:crypto';
import Koa from 'koa';
import { IsNull, Not } from 'typeorm';
import { Apps, AuthSessions, AccessTokens } from '@/models/index.js';
import config from '@/config/index.js';
import { Apps, AuthSessions } from '@/models/index.js';
import { compareUrl } from './compare-url.js';
export async function oauth(ctx: Koa.Context): void {

View file

@ -1,6 +1,6 @@
import * as fs from 'node:fs';
import Ajv from 'ajv';
import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js';
import { CacheableLocalUser } from '@/models/entities/user.js';
import { Schema, SchemaType } from '@/misc/schema.js';
import { AccessToken } from '@/models/entities/access-token.js';
import { IEndpointMeta } from './endpoints.js';

View file

@ -114,8 +114,8 @@ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined):
return await mergePack(
me,
isActor(object) ? await createPerson(getApId(object), resolver) : null,
isPost(object) ? await createNote(getApId(object), resolver, true) : null,
isActor(object) ? await createPerson(object, resolver) : null,
isPost(object) ? await createNote(object, resolver, true) : null,
);
}

View file

@ -1,6 +1,5 @@
import { Apps } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import { unique } from '@/prelude/array.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { kinds } from '@/misc/api-permissions.js';
import define from '../../define.js';

View file

@ -32,7 +32,7 @@ export default define(meta, paramDef, async (ps, user) => {
token: ps.token,
});
if (result.affected == 0) {
if (result.affected === 0) {
throw new ApiError(meta.errors.noSuchSession);
}
});

View file

@ -1,4 +1,4 @@
import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index.js';
import { Apps, AuthSessions, Users } from '@/models/index.js';
import define from '../../../define.js';
import { ApiError } from '../../../error.js';

View file

@ -1,10 +1,11 @@
import { Notes } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import define from '../../define.js';
import { makePaginationQuery } from '../../common/make-pagination-query.js';
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
import { ApiError } from '@/server/api/error.js';
import { getNote } from '../../common/getters.js';
export const meta = {
tags: ['notes'],
@ -37,7 +38,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
const note = await getNote(ps.noteId, user).catch(err => {
await getNote(ps.noteId, user).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
throw err;
});

View file

@ -1,7 +1,7 @@
import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
import { NoteFavorites, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
import { ApiError } from '@/server/api/error.js';
import { getNote } from '../../common/getters.js';
import define from '../../define.js';
import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['notes'],

View file

@ -1,40 +0,0 @@
* {
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
}
html {
background: #ffb4e1;
}
main {
background: #dedede;
}
main > .tabs {
padding: 16px;
border-bottom: solid 4px #c3c3c3;
}
#lsEditor > .adder {
margin: 16px;
padding: 16px;
border: solid 2px #c3c3c3;
}
#lsEditor > .adder > textarea {
display: block;
width: 100%;
min-height: 5em;
box-sizing: border-box;
}
#lsEditor > .record {
padding: 16px;
border-bottom: solid 1px #c3c3c3;
}
#lsEditor > .record > header {
font-weight: bold;
}
#lsEditor > .record > textarea {
display: block;
width: 100%;
min-height: 5em;
box-sizing: border-box;
}

View file

@ -1,87 +0,0 @@
'use strict';
window.onload = async () => {
const account = JSON.parse(localStorage.getItem('account'));
const i = account.token;
const api = (endpoint, data = {}) => {
const promise = new Promise((resolve, reject) => {
// Append a credential
if (i) data.i = i;
// Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve();
} else {
reject(body.error);
}
}).catch(reject);
});
return promise;
};
const content = document.getElementById('content');
document.getElementById('ls').addEventListener('click', () => {
content.innerHTML = '';
const lsEditor = document.createElement('div');
lsEditor.id = 'lsEditor';
const adder = document.createElement('div');
adder.classList.add('adder');
const addKeyInput = document.createElement('input');
const addValueTextarea = document.createElement('textarea');
const addButton = document.createElement('button');
addButton.textContent = 'add';
addButton.addEventListener('click', () => {
localStorage.setItem(addKeyInput.value, addValueTextarea.value);
location.reload();
});
adder.appendChild(addKeyInput);
adder.appendChild(addValueTextarea);
adder.appendChild(addButton);
lsEditor.appendChild(adder);
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i);
const record = document.createElement('div');
record.classList.add('record');
const header = document.createElement('header');
header.textContent = k;
const textarea = document.createElement('textarea');
textarea.textContent = localStorage.getItem(k);
const saveButton = document.createElement('button');
saveButton.textContent = 'save';
saveButton.addEventListener('click', () => {
localStorage.setItem(k, textarea.value);
location.reload();
});
const removeButton = document.createElement('button');
removeButton.textContent = 'remove';
removeButton.addEventListener('click', () => {
localStorage.removeItem(k);
location.reload();
});
record.appendChild(header);
record.appendChild(textarea);
record.appendChild(saveButton);
record.appendChild(removeButton);
lsEditor.appendChild(record);
}
content.appendChild(lsEditor);
});
};

View file

@ -1,19 +0,0 @@
* {
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
}
html {
background: #ffb4e1;
}
main {
background: #dedede;
}
#tl > div {
padding: 16px;
border-bottom: solid 1px #c3c3c3;
}
#tl > div > header {
font-weight: bold;
}

View file

@ -1,55 +0,0 @@
'use strict';
window.onload = async () => {
const account = JSON.parse(localStorage.getItem('account'));
const i = account.token;
const api = (endpoint, data = {}) => {
const promise = new Promise((resolve, reject) => {
// Append a credential
if (i) data.i = i;
// Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve();
} else {
reject(body.error);
}
}).catch(reject);
});
return promise;
};
document.getElementById('submit').addEventListener('click', () => {
api('notes/create', {
text: document.getElementById('text').value
}).then(() => {
location.reload();
});
});
api('notes/timeline').then(notes => {
const tl = document.getElementById('tl');
for (const note of notes) {
const el = document.createElement('div');
const name = document.createElement('header');
name.textContent = `${note.user.name} @${note.user.username}`;
const text = document.createElement('div');
text.textContent = `${note.text}`;
el.appendChild(name);
el.appendChild(text);
tl.appendChild(el);
}
});
};

View file

@ -485,18 +485,6 @@ router.get('/_info_card_', async ctx => {
});
});
router.get('/bios', async ctx => {
await ctx.render('bios', {
version: config.version,
});
});
router.get('/cli', async ctx => {
await ctx.render('cli', {
version: config.version,
});
});
const override = (source: string, target: string, depth = 0) =>
[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/');

View file

@ -3,9 +3,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
import manifest from './manifest.json' assert { type: 'json' };
export const manifestHandler = async (ctx: Koa.Context): Promise<void> => {
// TODO
//const res = structuredClone(manifest);
const res = JSON.parse(JSON.stringify(manifest));
const res = structuredClone(manifest);
const instance = await fetchMeta(true);

View file

@ -1,20 +0,0 @@
doctype html
html
head
meta(charset='utf-8')
meta(name='application-name' content='FoundKey')
title FoundKey Repair Tool
style
include ../bios.css
script
include ../bios.js
body
header
h1 FoundKey Repair Tool #{version}
main
div.tabs
button#ls edit local storage
div#content

View file

@ -1,21 +0,0 @@
doctype html
html
head
meta(charset='utf-8')
meta(name='application-name' content='FoundKey')
title FoundKey Cli
style
include ../cli.css
script
include ../cli.js
body
header
h1 FoundKey Cli #{version}
main
div#form
textarea#text
button#submit submit
div#tl

View file

@ -5,12 +5,11 @@ import { format as dateFormat } from 'date-fns';
import * as SyslogPro from 'syslog-pro';
import config from '@/config/index.js';
import { envOption } from '@/env.js';
type Color = Parameters<typeof convertColor.keyword.rgb>[0];
import type { KEYWORD } from 'color-convert/conversions.js';
type Domain = {
name: string;
color?: Color;
color?: KEYWORD;
};
type Level = 'error' | 'success' | 'warning' | 'debug' | 'info';
@ -30,7 +29,7 @@ export default class Logger {
* @param color Log message color
* @param store Whether to store messages
*/
constructor(domain: string, color?: Color, store = true) {
constructor(domain: string, color?: KEYWORD, store = true) {
this.domain = {
name: domain,
color,
@ -59,7 +58,7 @@ export default class Logger {
* @param store Whether to store messages
* @returns A Logger instance whose parent logger is this instance.
*/
public createSubLogger(domain: string, color?: Color, store = true): Logger {
public createSubLogger(domain: string, color?: KEYWORD, store = true): Logger {
const logger = new Logger(domain, color, store);
logger.parentLogger = this;
return logger;

View file

@ -98,7 +98,12 @@ class NotificationManager {
const threadMuted = await NoteThreadMutings.findOneBy({
userId: x.target,
threadId: this.note.threadId || this.note.id,
threadId: In([
// replies
this.note.threadId ?? this.note.id,
// renotes
this.note.renoteId ?? undefined
]),
mutingNotificationTypes: ArrayOverlap([x.reason]),
});

View file

@ -44,7 +44,7 @@ export async function addRelay(inbox: string): Promise<Relay> {
}).then(x => Relays.findOneByOrFail(x.identifiers[0]));
const relayActor = await getRelayActor();
const follow = await renderFollowRelay(relay, relayActor);
const follow = renderFollowRelay(relay, relayActor);
const activity = renderActivity(follow);
deliver(relayActor, activity, relay.inbox);
@ -96,9 +96,7 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act
const relays = await relaysCache.fetch('');
if (relays == null || relays.length === 0) return;
// TODO
//const copy = structuredClone(activity);
const copy = JSON.parse(JSON.stringify(activity));
const copy = structuredClone(activity);
if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public'];
const signed = await attachLdSignature(copy, user);

View file

@ -1,10 +1,27 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import rndstr from 'rndstr';
import { initDb } from '../src/db/postgre.js';
import { initTestDb } from './utils.js';
function rndstr(length): string {
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const chars_len = 62;
let str = '';
for (let i = 0; i < length; i++) {
let rand = Math.floor(Math.random() * chars_len);
if (rand === chars_len) {
rand = chars_len - 1;
}
str += chars.charAt(rand);
}
return str;
}
describe('ActivityPub', () => {
before(async () => {
//await initTestDb();
@ -13,7 +30,7 @@ describe('ActivityPub', () => {
describe('Parse minimum object', () => {
const host = 'https://host1.test';
const preferredUsername = `${rndstr('A-Z', 4)}${rndstr('a-z', 4)}`;
const preferredUsername = `${rndstr(8)}`;
const actorId = `${host}/users/${preferredUsername.toLowerCase()}`;
const actor = {
@ -27,7 +44,7 @@ describe('ActivityPub', () => {
const post = {
'@context': 'https://www.w3.org/ns/activitystreams',
id: `${host}/users/${rndstr('0-9a-z', 8)}`,
id: `${host}/users/${rndstr(8)}`,
type: 'Note',
attributedTo: actor.id,
to: 'https://www.w3.org/ns/activitystreams#Public',
@ -66,10 +83,10 @@ describe('ActivityPub', () => {
describe('Truncate long name', () => {
const host = 'https://host1.test';
const preferredUsername = `${rndstr('A-Z', 4)}${rndstr('a-z', 4)}`;
const preferredUsername = `${rndstr(8)}`;
const actorId = `${host}/users/${preferredUsername.toLowerCase()}`;
const name = rndstr('0-9a-z', 129);
const name = rndstr(129);
const actor = {
'@context': 'https://www.w3.org/ns/activitystreams',

View file

@ -5,7 +5,7 @@
"scripts": {
"watch": "vite build --watch --mode development",
"build": "vite build",
"lint": "eslint src --ext .ts,.vue"
"lint": "tsc --noEmit && eslint src --ext .ts,.vue"
},
"dependencies": {
"@discordapp/twemoji": "14.0.2",
@ -64,7 +64,7 @@
"tsc-alias": "1.7.0",
"tsconfig-paths": "4.1.0",
"twemoji-parser": "14.0.0",
"typescript": "^4.9.3",
"typescript": "^4.9.4",
"uuid": "8.3.2",
"v-debounce": "0.1.2",
"vanilla-tilt": "1.7.2",
@ -92,8 +92,8 @@
"@types/uuid": "8.3.4",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"cross-env": "7.0.3",
"cypress": "10.3.0",
"eslint": "^8.29.0",

View file

@ -61,6 +61,22 @@ function renote(viaKeyboard = false): void {
visibility: props.note.visibility,
});
},
}, {
text: i18n.ts.unrenoteAll,
icon: 'fas fa-trash-alt',
danger: true,
action: () => {
os.confirm({
type: 'warning',
text: i18n.ts.unrenoteAllConfirm,
}).then(({ canceled }) => {
if (canceled) return;
os.api('notes/unrenote', {
noteId: props.note.id,
});
});
},
}, {
text: i18n.ts.quote,
icon: 'fas fa-quote-right',

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