1
0
mirror of https://github.com/MisskeyIO/misskey synced 2024-12-12 21:58:54 +09:00

Merge pull request MisskeyIO#547 from MisskeyIO/update-host

2024.3.1-host.2a
This commit is contained in:
まっちゃとーにゅ 2024-03-20 17:50:14 +09:00 committed by GitHub
commit daa8810af6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 157 additions and 52 deletions

View File

@ -4,8 +4,6 @@ on:
push:
branches:
- beta
tags:
- "**"
workflow_dispatch:
jobs:

View File

@ -5,7 +5,7 @@ on:
branches:
- host
tags:
- "**"
- '[0-9.]+-host.*'
workflow_dispatch:
jobs:

51
.github/workflows/docker-io.yml vendored Normal file
View File

@ -0,0 +1,51 @@
name: Publish Docker image (io)
on:
push:
branches:
- io
tags:
- '[0-9.]+-io.*'
workflow_dispatch:
jobs:
push_to_registry:
name: Push Docker image to GitHub Container Registry
runs-on: ubuntu-22.04
if: github.repository == 'MisskeyIO/misskey'
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/misskeyio/misskey
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare image tags
run: |
echo "FORMATTED_BRANCH_NAME=$(echo ${{ github.ref_name }} | sed -e 's/\//-/g' )" >> $GITHUB_ENV
- name: Build and Push to GitHub Container Registry
uses: docker/build-push-action@v5
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
push: true
platforms: ${{ steps.buildx.outputs.platforms }}
provenance: false
labels: ${{ env.FORMATTED_BRANCH_NAME }}
cache-from: type=registry,ref=ghcr.io/misskeyio/misskey:io-buildcache
cache-to: type=registry,ref=ghcr.io/misskeyio/misskey:io-buildcache,mode=max
tags: |
ghcr.io/misskeyio/misskey:latest
ghcr.io/misskeyio/misskey:${{ env.FORMATTED_BRANCH_NAME }}

View File

@ -406,6 +406,7 @@ name: "Name"
antennaSource: "Antenna source"
antennaKeywords: "Keywords to listen to"
antennaExcludeKeywords: "Keywords to exclude"
antennaExcludeBots: "Exclude bots"
antennaKeywordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
notifyAntenna: "Notify about new notes"
withFileAntenna: "Only notes with files"

4
locales/index.d.ts vendored
View File

@ -1640,6 +1640,10 @@ export interface Locale extends ILocale {
*
*/
"antennaExcludeKeywords": string;
/**
* Botアカウントを除外
*/
"antennaExcludeBots": string;
/**
* AND指定になりOR指定になります
*/

View File

@ -406,6 +406,7 @@ name: "名前"
antennaSource: "受信ソース"
antennaKeywords: "受信キーワード"
antennaExcludeKeywords: "除外キーワード"
antennaExcludeBots: "Botアカウントを除外"
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
notifyAntenna: "新しいノートを通知する"
withFileAntenna: "ファイルが添付されたノートのみ"

View File

@ -405,6 +405,7 @@ name: "이름"
antennaSource: "받을 소스"
antennaKeywords: "받을 검색어"
antennaExcludeKeywords: "제외할 검색어"
antennaExcludeBots: "봇 제외"
antennaKeywordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다"
notifyAntenna: "새로운 노트를 알림"
withFileAntenna: "파일이 첨부된 노트만"

View File

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2024.3.1-host.2",
"version": "2024.3.1-host.2a",
"codename": "nasubi",
"repository": {
"type": "git",

View File

@ -0,0 +1,11 @@
export class AntennaExcludeBots1710919614510 {
name = 'AntennaExcludeBots1710919614510'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" ADD "excludeBots" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "excludeBots"`);
}
}

View File

@ -97,7 +97,7 @@ export class AntennaService implements OnApplicationShutdown {
}
@bindThis
public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise<void> {
public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise<void> {
const antennas = await this.getAntennas();
const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const)));
const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna);
@ -121,10 +121,12 @@ export class AntennaService implements OnApplicationShutdown {
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
@bindThis
public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise<boolean> {
public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise<boolean> {
if (note.visibility === 'specified') return false;
if (note.visibility === 'followers') return false;
if (antenna.excludeBots && noteUser.isBot) return false;
if (antenna.localOnly && noteUser.host != null) return false;
if (!antenna.withReplies && note.replyId != null) return false;

View File

@ -39,6 +39,7 @@ export class AntennaEntityService {
caseSensitive: antenna.caseSensitive,
localOnly: antenna.localOnly,
notify: antenna.notify,
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies,
withFile: antenna.withFile,
isActive: antenna.isActive,

View File

@ -0,0 +1,5 @@
const specialCharactersRegexp = /(\(.*?\)|(\+.*(?=@)))/gu;
export function normalizeEmailAddress(email: string | null): string | null {
return email?.replaceAll(specialCharactersRegexp, '') ?? null;
}

View File

@ -79,6 +79,11 @@ export class MiAntenna {
})
public caseSensitive: boolean;
@Column('boolean', {
default: false,
})
public excludeBots: boolean;
@Column('boolean', {
default: false,
})

View File

@ -76,6 +76,11 @@ export const packedAntennaSchema = {
type: 'boolean',
optional: false, nullable: false,
},
excludeBots: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
withReplies: {
type: 'boolean',
optional: false, nullable: false,

View File

@ -81,6 +81,7 @@ export class ExportAntennasProcessorService {
}) : null,
caseSensitive: antenna.caseSensitive,
localOnly: antenna.localOnly,
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies,
withFile: antenna.withFile,
notify: antenna.notify,

View File

@ -44,6 +44,7 @@ const validate = new Ajv().compile({
} },
caseSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' },
excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' },
withFile: { type: 'boolean' },
notify: { type: 'boolean' },
@ -88,6 +89,7 @@ export class ImportAntennasProcessorService {
users: (antenna.src === 'list' && antenna.userListAccts !== null ? antenna.userListAccts : antenna.users).filter(Boolean),
caseSensitive: antenna.caseSensitive,
localOnly: antenna.localOnly,
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies,
withFile: antenna.withFile,
notify: antenna.notify,

View File

@ -389,12 +389,10 @@ export class ApiCallService implements OnApplicationShutdown {
id: err.id,
},
{
e: {
message: err.message,
code: err.name,
id: err.id,
},
},
);
} else {
const errId = randomUUID();
@ -416,12 +414,10 @@ export class ApiCallService implements OnApplicationShutdown {
kind: 'server',
},
{
e: {
message: err.message,
code: err.name,
id: errId,
},
},
);
}
});

View File

@ -63,7 +63,7 @@ export abstract class Endpoint<T extends IEndpointMeta, Ps extends Schema> {
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
}, {
param: errors[0].schemaPath,
reason: errors[0].message,
reason: errors[0].message ?? 'Invalid',
});
return Promise.reject(err);
}

View File

@ -85,11 +85,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: '5c77c4d7-0f68-48f9-8694-8453a2294840',
},
{
e: {
message: err.message,
code: err.name,
}
}
},
);
}

View File

@ -65,6 +65,7 @@ export const paramDef = {
} },
caseSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' },
excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' },
withFile: { type: 'boolean' },
notify: { type: 'boolean' },
@ -125,6 +126,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
users: ps.users,
caseSensitive: ps.caseSensitive,
localOnly: ps.localOnly,
excludeBots: ps.excludeBots,
withReplies: ps.withReplies,
withFile: ps.withFile,
notify: ps.notify,

View File

@ -64,6 +64,7 @@ export const paramDef = {
} },
caseSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' },
excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' },
withFile: { type: 'boolean' },
notify: { type: 'boolean' },
@ -121,6 +122,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
users: ps.users,
caseSensitive: ps.caseSensitive,
localOnly: ps.localOnly,
excludeBots: ps.excludeBots,
withReplies: ps.withReplies,
withFile: ps.withFile,
notify: ps.notify,

View File

@ -191,11 +191,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: '6708863c-6791-4487-aa01-2d682c6e7db0',
},
{
e: {
message: err.message,
code: err.name,
},
},
);
} finally {
if (cleanup) cleanup();

View File

@ -11,15 +11,15 @@ export class ApiError extends Error {
public id: string;
public kind: string;
public httpStatusCode?: number;
public info?: any;
public info?: Record<string, string>;
constructor(err: E, info?: any | null | undefined) {
constructor(err: E, info?: Record<string, string> | null | undefined) {
super(err.message);
this.message = err.message;
this.code = err.code;
this.id = err.id;
this.kind = err.kind ?? 'client';
this.httpStatusCode = err.httpStatusCode;
this.info = info;
this.info = info ?? undefined;
}
}

View File

@ -22,6 +22,7 @@ import type { MiLocalUser } from '@/models/User.js';
import { CacheService } from '@/core/CacheService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { RoleService } from '@/core/RoleService.js';
import { normalizeEmailAddress } from '@/misc/normalize-email-address.js';
import type { FastifyInstance } from 'fastify';
@Injectable()
@ -175,7 +176,7 @@ export class JWTIdentifyProviderService {
preferred_username: user.username,
profile: `${this.config.url}/@${user.username}`,
picture: user.avatarUrl ?? undefined,
email: profile.emailVerified ? profile.email : undefined,
email: profile.emailVerified ? normalizeEmailAddress(profile.email) : undefined,
email_verified: profile.emailVerified,
mfa_enabled: profile.twoFactorEnabled,
updated_at: Math.floor((user.updatedAt?.getTime() ?? user.createdAt.getTime()) / 1000),

View File

@ -26,6 +26,7 @@ import { RoleService } from '@/core/RoleService.js';
import type { MiLocalUser } from '@/models/User.js';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import { normalizeEmailAddress } from '@/misc/normalize-email-address.js';
import type { FastifyInstance } from 'fastify';
@Injectable()
@ -440,7 +441,7 @@ export class SAMLIdentifyProviderService {
},
'saml:Subject': {
'saml:NameID': profile.emailVerified
? { '@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', '#text': profile.email }
? { '@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', '#text': normalizeEmailAddress(profile.email) }
: { '@Format': 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', '#text': user.id },
'saml:SubjectConfirmation': {
'@Method': 'urn:oasis:names:tc:SAML:2.0:cm:bearer',
@ -531,24 +532,14 @@ export class SAMLIdentifyProviderService {
'#text': user.avatarUrl,
},
}] : []),
...(profile.emailVerified ? [
{
'@Name': 'mail',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xsi:type': 'xs:string',
'#text': profile.email,
},
},
{
...(profile.emailVerified ? [{
'@Name': 'email',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
'saml:AttributeValue': {
'@xsi:type': 'xs:string',
'#text': profile.email,
'#text': normalizeEmailAddress(profile.email),
},
},
] : []),
}] : []),
{
'@Name': 'email_verified',
'@NameFormat': 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',

View File

@ -45,6 +45,7 @@ describe('アンテナ', () => {
users: [''],
withFile: false,
withReplies: false,
excludeBots: false,
};
let root: User;
@ -159,6 +160,7 @@ describe('アンテナ', () => {
users: [''],
withFile: false,
withReplies: false,
excludeBots: false,
localOnly: false,
};
assert.deepStrictEqual(response, expected);

View File

@ -48,6 +48,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
</template>
</MkSelect>
<details v-if="details" class="_acrylic" style="margin: 1em 0;">
<summary>{{ i18n.ts.details }}</summary>
<div class="_gaps_s" style="text-align: initial;">
<MkKeyValue v-for="(value, key) in details" :key="key" :value="value">
<template #key>{{ key }}</template>
<template #value>{{ value }}</template>
</MkKeyValue>
</div>
</details>
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
@ -66,6 +75,7 @@ import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import { i18n } from '@/i18n.js';
type Input = {
@ -89,11 +99,12 @@ type Result = string | number | true | null;
const props = withDefaults(defineProps<{
type?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'waiting';
icon?: string;
title?: string | null;
text?: string | null;
input?: Input;
select?: Select;
icon?: string;
details?: Record<string, string>;
actions?: {
text: string;
primary?: boolean,
@ -107,11 +118,12 @@ const props = withDefaults(defineProps<{
cancelText?: string;
}>(), {
type: 'info',
icon: undefined,
title: undefined,
text: undefined,
input: undefined,
select: undefined,
icon: undefined,
details: undefined,
actions: undefined,
showOkButton: true,
showCancelButton: false,

View File

@ -47,6 +47,7 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey
type: 'error',
title,
text,
details: err.info,
actions: [{
value: 'ok',
text: i18n.ts.gotIt,
@ -81,6 +82,7 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey
type: 'error',
title,
text,
details: err.info,
});
});
@ -113,7 +115,9 @@ export function promiseDialog<T>(
} else {
alert({
type: 'error',
text: err,
title: err.message,
text: err.id,
details: err.info,
});
}
});
@ -217,6 +221,7 @@ export function alert(props: {
type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
title?: string | null;
text?: string | null;
details?: Record<string, string>;
}): Promise<void> {
return new Promise(resolve => {
popup(MkDialog, props, {
@ -231,6 +236,7 @@ export function confirm(props: {
type: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
title?: string | null;
text?: string | null;
details?: Record<string, string>;
okText?: string;
cancelText?: string;
}): Promise<{ canceled: boolean }> {
@ -257,6 +263,7 @@ export function actions<T extends {
type: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
title?: string | null;
text?: string | null;
details?: Record<string, string>;
actions: T;
}): Promise<{
canceled: true; result: undefined;

View File

@ -26,6 +26,7 @@ const draft = ref({
users: [],
keywords: [],
excludeKeywords: [],
excludeBots: false,
withReplies: false,
caseSensitive: false,
localOnly: false,

View File

@ -26,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.users }}</template>
<template #caption>{{ i18n.ts.antennaUsersDescription }} <button class="_textButton" @click="addUser">{{ i18n.ts.addUser }}</button></template>
</MkTextarea>
<MkSwitch v-model="excludeBots">{{ i18n.ts.antennaExcludeBots }}</MkSwitch>
<MkSwitch v-model="withReplies">{{ i18n.ts.withReplies }}</MkSwitch>
<MkTextarea v-model="keywords">
<template #label>{{ i18n.ts.antennaKeywords }}</template>
@ -78,6 +79,7 @@ const keywords = ref<string>(props.antenna.keywords.map(x => x.join(' ')).join('
const excludeKeywords = ref<string>(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'));
const caseSensitive = ref<boolean>(props.antenna.caseSensitive);
const localOnly = ref<boolean>(props.antenna.localOnly);
const excludeBots = ref<boolean>(props.antenna.excludeBots);
const withReplies = ref<boolean>(props.antenna.withReplies);
const withFile = ref<boolean>(props.antenna.withFile);
const notify = ref<boolean>(props.antenna.notify);
@ -94,6 +96,7 @@ async function saveAntenna() {
name: name.value,
src: src.value,
userListId: userListId.value,
excludeBots: excludeBots.value,
withReplies: withReplies.value,
withFile: withFile.value,
notify: notify.value,

View File

@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2024.3.1-host.2",
"version": "2024.3.1-host.2a",
"description": "Misskey SDK for JavaScript",
"types": "./built/dts/index.d.ts",
"exports": {

View File

@ -4586,6 +4586,8 @@ export type components = {
localOnly: boolean;
notify: boolean;
/** @default false */
excludeBots: boolean;
/** @default false */
withReplies: boolean;
withFile: boolean;
isActive: boolean;
@ -10588,6 +10590,7 @@ export type operations = {
users: string[];
caseSensitive: boolean;
localOnly?: boolean;
excludeBots?: boolean;
withReplies: boolean;
withFile: boolean;
notify: boolean;
@ -10869,6 +10872,7 @@ export type operations = {
users?: string[];
caseSensitive?: boolean;
localOnly?: boolean;
excludeBots?: boolean;
withReplies?: boolean;
withFile?: boolean;
notify?: boolean;