forked from naskya/firefish
Firefish v1.0.5-dev22
This commit is contained in:
parent
74f7ef6b98
commit
9fcd3e496f
71 changed files with 359 additions and 694 deletions
195
.config/ci.yml
195
.config/ci.yml
|
@ -1,195 +0,0 @@
|
|||
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
# Firefish configuration
|
||||
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
# ┌─────┐
|
||||
#───┘ URL └─────────────────────────────────────────────────────
|
||||
|
||||
# Final accessible URL seen by a user.
|
||||
url: https://example.tld/
|
||||
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
# URL SETTINGS AFTER THAT!
|
||||
|
||||
# ┌───────────────────────┐
|
||||
#───┘ Port and TLS settings └───────────────────────────────────
|
||||
|
||||
#
|
||||
# Misskey requires a reverse proxy to support HTTPS connections.
|
||||
#
|
||||
# +----- https://example.tld/ ------------+
|
||||
# +------+ |+-------------+ +----------------+|
|
||||
# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
|
||||
# +------+ |+-------------+ +----------------+|
|
||||
# +---------------------------------------+
|
||||
#
|
||||
# You need to set up a reverse proxy. (e.g. nginx)
|
||||
# An encrypted connection with HTTPS is highly recommended
|
||||
# because tokens may be transferred in GET requests.
|
||||
|
||||
# The port that your Misskey server should listen on.
|
||||
port: 3000
|
||||
|
||||
# ┌──────────────────────────┐
|
||||
#───┘ PostgreSQL configuration └────────────────────────────────
|
||||
|
||||
db:
|
||||
host: postgres
|
||||
port: 5432
|
||||
|
||||
# Database name
|
||||
db: postgres
|
||||
|
||||
# Auth
|
||||
user: postgres
|
||||
pass: test
|
||||
|
||||
# Whether disable Caching queries
|
||||
#disableCache: true
|
||||
|
||||
# Extra Connection options
|
||||
#extra:
|
||||
# ssl: true
|
||||
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Redis configuration └─────────────────────────────────────
|
||||
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
||||
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
|
||||
#pass: example-pass
|
||||
#prefix: example-prefix
|
||||
#db: 1
|
||||
|
||||
# ┌─────────────────────────────┐
|
||||
#───┘ Elasticsearch configuration └─────────────────────────────
|
||||
|
||||
#elasticsearch:
|
||||
# host: localhost
|
||||
# port: 9200
|
||||
# ssl: false
|
||||
# user:
|
||||
# pass:
|
||||
|
||||
# ┌───────────────┐
|
||||
#───┘ ID generation └───────────────────────────────────────────
|
||||
|
||||
# You can select the ID generation method.
|
||||
# You don't usually need to change this setting, but you can
|
||||
# change it according to your preferences.
|
||||
|
||||
# Available methods:
|
||||
# aid ... Short, Millisecond accuracy
|
||||
# meid ... Similar to ObjectID, Millisecond accuracy
|
||||
# ulid ... Millisecond accuracy
|
||||
# objectid ... This is left for backward compatibility
|
||||
|
||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||
# ID SETTINGS AFTER THAT!
|
||||
|
||||
id: 'aid'
|
||||
|
||||
# ┌─────────────────────┐
|
||||
#───┘ Other configuration └─────────────────────────────────────
|
||||
|
||||
# Max note length, should be < 8000.
|
||||
#maxNoteLength: 3000
|
||||
|
||||
# Whether disable HSTS
|
||||
#disableHsts: true
|
||||
|
||||
# Number of worker processes
|
||||
#clusterLimit: 1
|
||||
|
||||
# Job concurrency per worker
|
||||
# deliverJobConcurrency: 128
|
||||
# inboxJobConcurrency: 16
|
||||
|
||||
# Job rate limiter
|
||||
# deliverJobPerSec: 128
|
||||
# inboxJobPerSec: 16
|
||||
|
||||
# Job attempts
|
||||
# deliverJobMaxAttempts: 12
|
||||
# inboxJobMaxAttempts: 8
|
||||
|
||||
# IP address family used for outgoing request (ipv4, ipv6 or dual)
|
||||
#outgoingAddressFamily: ipv4
|
||||
|
||||
# Syslog option
|
||||
#syslog:
|
||||
# host: localhost
|
||||
# port: 514
|
||||
|
||||
# Proxy for HTTP/HTTPS
|
||||
#proxy: http://127.0.0.1:3128
|
||||
|
||||
#proxyBypassHosts: [
|
||||
# 'example.com',
|
||||
# '192.0.2.8'
|
||||
#]
|
||||
|
||||
# Proxy for SMTP/SMTPS
|
||||
#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT
|
||||
#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
|
||||
#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
|
||||
|
||||
# Media Proxy
|
||||
#mediaProxy: https://example.com/proxy
|
||||
|
||||
# Proxy remote files (default: false)
|
||||
#proxyRemoteFiles: true
|
||||
|
||||
#allowedPrivateNetworks: [
|
||||
# '127.0.0.1/32'
|
||||
#]
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
||||
# Managed hosting settings
|
||||
# !!!!!!!!!!
|
||||
# >>>>>> NORMAL SELF-HOSTERS, STAY AWAY! <<<<<<
|
||||
# >>>>>> YOU DON'T NEED THIS! <<<<<<
|
||||
# !!!!!!!!!!
|
||||
# Each category is optional, but if each item in each category is mandatory!
|
||||
# If you mess this up, that's on you, you've been warned...
|
||||
|
||||
#maxUserSignups: 100
|
||||
#isManagedHosting: true
|
||||
#deepl:
|
||||
# managed: true
|
||||
# authKey: ''
|
||||
# isPro: false
|
||||
#
|
||||
#email:
|
||||
# managed: true
|
||||
# address: 'example@email.com'
|
||||
# host: 'email.com'
|
||||
# port: 587
|
||||
# user: 'example@email.com'
|
||||
# pass: ''
|
||||
# useImplicitSslTls: false
|
||||
#
|
||||
#objectStorage:
|
||||
# managed: true
|
||||
# baseUrl: ''
|
||||
# bucket: ''
|
||||
# prefix: ''
|
||||
# endpoint: ''
|
||||
# region: ''
|
||||
# accessKey: ''
|
||||
# secretKey: ''
|
||||
# useSsl: true
|
||||
# connnectOverProxy: false
|
||||
# setPublicReadOnUpload: true
|
||||
# s3ForcePathStyle: true
|
||||
|
||||
# !!!!!!!!!!
|
||||
# >>>>>> AGAIN, NORMAL SELF-HOSTERS, STAY AWAY! <<<<<<
|
||||
# >>>>>> YOU DON'T NEED THIS, ABOVE SETTINGS ARE FOR MANAGED HOSTING ONLY! <<<<<<
|
||||
# !!!!!!!!!!
|
||||
|
||||
# Seriously. Do NOT fill out the above settings if you're self-hosting.
|
||||
# They're much better off being set from the control panel.
|
|
@ -1,38 +0,0 @@
|
|||
url: http://localhost:3000
|
||||
port: 3000
|
||||
|
||||
db:
|
||||
host: 127.0.0.1
|
||||
port: 5432
|
||||
|
||||
db: firefish
|
||||
|
||||
user: firefish
|
||||
pass: firefish
|
||||
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
family: 4
|
||||
#sonic:
|
||||
# host: localhost
|
||||
# port: 1491
|
||||
# auth: SecretPassword
|
||||
# collection: notes
|
||||
# bucket: default
|
||||
|
||||
#elasticsearch:
|
||||
# host: localhost
|
||||
# port: 9200
|
||||
# ssl: false
|
||||
# user:
|
||||
# pass:
|
||||
|
||||
id: 'aid'
|
||||
|
||||
reservedUsernames:
|
||||
- root
|
||||
- admin
|
||||
- administrator
|
||||
- me
|
||||
- system
|
|
@ -1,4 +0,0 @@
|
|||
# db settings
|
||||
POSTGRES_PASSWORD=test
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_DB=postgres
|
|
@ -1,82 +0,0 @@
|
|||
replicaCount: 1
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 0.5
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1
|
||||
memory: 1Gi
|
||||
|
||||
firefish:
|
||||
domain: example.tld
|
||||
smtp:
|
||||
from_address: noreply@example.tld
|
||||
port: 587
|
||||
server: smtp.gmail.com
|
||||
useImplicitSslTls: false
|
||||
login: me@example.tld
|
||||
password: CHANGEME
|
||||
objectStorage:
|
||||
baseUrl: https://example-bucket.nyc3.cdn.digitaloceanspaces.com
|
||||
access_key: CHANGEME
|
||||
access_secret: CHANGEME
|
||||
bucket: example-bucket
|
||||
endpoint: nyc3.digitaloceanspaces.com:443
|
||||
region: nyc3
|
||||
allowedPrivateNetworks: []
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
hosts:
|
||||
- host: example.tld
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls:
|
||||
- secretName: example-tld-certificate
|
||||
hosts:
|
||||
- example.tld
|
||||
|
||||
elasticsearch:
|
||||
enabled: false
|
||||
|
||||
postgresql:
|
||||
auth:
|
||||
password: CHANGEME
|
||||
postgresPassword: CHANGEME
|
||||
primary:
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: vultr-block-storage
|
||||
size: 25Gi
|
||||
resources:
|
||||
requests:
|
||||
cpu: 0.25
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 0.5
|
||||
memory: 512Mi
|
||||
metrics:
|
||||
enabled: true
|
||||
|
||||
redis:
|
||||
auth:
|
||||
password: CHANGEME
|
||||
master:
|
||||
resources:
|
||||
requests:
|
||||
cpu: 0.25
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 0.5
|
||||
memory: 256Mi
|
||||
persistence:
|
||||
storageclass: vultr-block-storage
|
||||
size: 10Gi
|
||||
replica:
|
||||
replicaCount: 0
|
||||
metrics:
|
||||
enabled: true
|
|
@ -52,7 +52,7 @@ gulp.task("build:backend:script", () => {
|
|||
"./packages/backend/src/server/web/bios.js",
|
||||
"./packages/backend/src/server/web/cli.js",
|
||||
])
|
||||
.pipe(replace("LANGS", JSON.stringify(Object.keys(locales))))
|
||||
.pipe(replace("SUPPORTED_LANGS", JSON.stringify(Object.keys(locales))))
|
||||
.pipe(
|
||||
terser({
|
||||
toplevel: true,
|
||||
|
|
|
@ -112,7 +112,7 @@ you: "Tu"
|
|||
clickToShow: "Fes clic per a mostrar"
|
||||
sensitive: "NSFW"
|
||||
add: "Afegeix"
|
||||
reaction: "Reaccions"
|
||||
reaction: "Reacció"
|
||||
reactionSetting: "Reaccions a mostrar al selector de reaccions"
|
||||
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem
|
||||
\"+\" per afegir."
|
||||
|
@ -792,11 +792,11 @@ customEmojis: Emojis personalitzats
|
|||
cacheRemoteFilesDescription: Quan aquesta opció està desactivada, els fitxers remots
|
||||
es carreguen directament del servidor remot. Desactivar-la farà que baixi l'ús d'emmagatzematge,
|
||||
però incrementa el tràfic, perquè les miniatures no es generaran.
|
||||
flagAsBot: Marca aquest compte com a bot
|
||||
flagAsBot: Marca aquest compte com automatitzat
|
||||
flagAsBotDescription: Activa aquesta opció si aquest compte és controlat per un programa.
|
||||
Si s'activa, això actuarà com una bandera per a altres desenvolupadors i ajuda a
|
||||
prevenir cadenes de interaccions infinites amb altres bots a més d'ajustar els sistemes
|
||||
interns de Firefish per tractar aquest compte com un bot.
|
||||
prevenir cadenes de interaccions infinites amb altres comptes automatitzats a més
|
||||
d'ajustar els sistemes interns de Firefish per tractar aquest compte com automatitzat.
|
||||
flagAsCat: Ets un gat? 🐱
|
||||
flagShowTimelineReplies: Mostra respostes a la línia de temps
|
||||
flagAsCatDescription: Guanyaràs unes orelles de gat i parlares com un gat!
|
||||
|
@ -2127,7 +2127,7 @@ clipsDesc: Els clips són com marcadors categoritzats que es poden compartir. Po
|
|||
selectChannel: Selecciona un canal
|
||||
isLocked: Aquest compte té les següents aprovacions
|
||||
isPatron: Mecenes de Firefish
|
||||
isBot: Aquest compte és un bot
|
||||
isBot: Aquest es un compte automatitzat
|
||||
isModerator: Moderador
|
||||
isAdmin: Administrador
|
||||
_filters:
|
||||
|
@ -2199,3 +2199,16 @@ openServerInfo: Mostra la informació del servidor fent clic al símbol del serv
|
|||
en un missatge
|
||||
vibrate: Activar vibracions
|
||||
clickToShowPatterns: Fes clic per veure patrons de mòduls
|
||||
iconSet: Conjunt d'Icones
|
||||
_iconSets:
|
||||
fill: Omplerts
|
||||
regular: Normals
|
||||
bold: Negreta
|
||||
duotone: Bitó
|
||||
light: Prims
|
||||
showAttachedNotes: Mostrar publicacions que contenen aquest fitxer
|
||||
reactions: Reaccions
|
||||
attachedToNotes: Publicacions que contenen aquest fitxer
|
||||
replies: Respostes
|
||||
quotes: Cites
|
||||
renotes: Impulsos
|
||||
|
|
|
@ -2218,3 +2218,4 @@ openServerInfo: Anzeigen von Serverinformationen durch Anklicken des Server-Tick
|
|||
in einem Beitrag
|
||||
vibrate: Vibrationen abspielen
|
||||
clickToShowPatterns: Klicken um Modul-Muster anzuzeigen
|
||||
replies: Antworten
|
||||
|
|
|
@ -312,7 +312,7 @@ agreeTo: "I agree to {0}"
|
|||
tos: "Terms of Service"
|
||||
start: "Begin"
|
||||
home: "Home"
|
||||
remoteUserCaution: "Information from remote users may be incomplete."
|
||||
remoteUserCaution: "Information from remote users are incomplete."
|
||||
activity: "Activity"
|
||||
images: "Images"
|
||||
birthday: "Birthday"
|
||||
|
@ -2188,5 +2188,5 @@ _iconSets:
|
|||
regular: "Regular"
|
||||
fill: "Filled"
|
||||
duotone: "Duotone"
|
||||
moreUrls: "pinned pages"
|
||||
moreUrls: "Pinned pages"
|
||||
moreUrlsDescription: "Enter the pages you want to pin to the help menu in the lower left corner using this notation:\n\"Display name\": https://example.com/"
|
||||
|
|
|
@ -278,8 +278,7 @@ agreeTo: "Saya setuju kepada {0}"
|
|||
tos: "Syarat dan ketentuan"
|
||||
start: "Mulai"
|
||||
home: "Beranda"
|
||||
remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal
|
||||
dari instansi luar."
|
||||
remoteUserCaution: "Informasi dari pengguna luar tidak lengkap."
|
||||
activity: "Aktivitas"
|
||||
images: "Gambar"
|
||||
birthday: "Tanggal lahir"
|
||||
|
@ -2192,3 +2191,5 @@ reactions: Reaksi
|
|||
replies: Balasan
|
||||
quotes: Kutipan
|
||||
renotes: Postingan ulang
|
||||
showAttachedNotes: Tampilkan postingan dengan berkas ini
|
||||
attachedToNotes: Posting dengan berkas ini
|
||||
|
|
|
@ -268,7 +268,7 @@ agreeTo: "Sono d'accordo con {0}"
|
|||
tos: "Termini d'uso"
|
||||
start: "Inizia"
|
||||
home: "Home"
|
||||
remoteUserCaution: "Le informazioni degli utenti remoti possono essere incomplete."
|
||||
remoteUserCaution: "Le informazioni degli utenti remoti sono incomplete."
|
||||
activity: "Attività"
|
||||
images: "Immagini"
|
||||
birthday: "Compleanno"
|
||||
|
@ -2180,3 +2180,5 @@ reactions: Reazioni
|
|||
replies: Risposte
|
||||
quotes: Citazioni
|
||||
renotes: Boost
|
||||
showAttachedNotes: Mostra i post con questo allegato
|
||||
attachedToNotes: Post con questo allegato
|
||||
|
|
|
@ -1 +1 @@
|
|||
465a585f47037ce26834587429f0bd341e35f76f
|
||||
a6aa7d2b74020dfc9303995918e63727fb3cb2c0
|
||||
|
|
|
@ -139,7 +139,7 @@ importers:
|
|||
specifier: 6.0.1
|
||||
version: 6.0.1
|
||||
argon2:
|
||||
specifier: 0.31.2
|
||||
specifier: ^0.31.2
|
||||
version: 0.31.2
|
||||
aws-sdk:
|
||||
specifier: 2.1498.0
|
||||
|
@ -624,7 +624,7 @@ importers:
|
|||
specifier: 0.16.0
|
||||
version: 0.16.0
|
||||
'@types/autosize':
|
||||
specifier: 4.0.3
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3
|
||||
'@types/glob':
|
||||
specifier: 8.1.0
|
||||
|
@ -717,7 +717,7 @@ importers:
|
|||
specifier: 2.30.0
|
||||
version: 2.30.0
|
||||
emojilib:
|
||||
specifier: 3.0.11
|
||||
specifier: ^3.0.11
|
||||
version: 3.0.11
|
||||
eslint-config-prettier:
|
||||
specifier: 9.0.0
|
||||
|
@ -810,7 +810,7 @@ importers:
|
|||
specifier: 1.6.0
|
||||
version: 1.6.0
|
||||
tinyld:
|
||||
specifier: 1.3.4
|
||||
specifier: ^1.3.4
|
||||
version: 1.3.4
|
||||
typescript:
|
||||
specifier: 5.2.2
|
||||
|
@ -831,10 +831,10 @@ importers:
|
|||
specifier: 3.3.8
|
||||
version: 3.3.8(typescript@5.2.2)
|
||||
vue-draggable-plus:
|
||||
specifier: 0.2.7
|
||||
specifier: ^0.2.7
|
||||
version: 0.2.7(@types/sortablejs@1.15.5)
|
||||
vue-plyr:
|
||||
specifier: 7.0.0
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
vue-prism-editor:
|
||||
specifier: 2.0.0-alpha.2
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "firefish",
|
||||
"version": "1.0.5-dev21",
|
||||
"version": "1.0.5-dev22",
|
||||
"codename": "aqua",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
"adm-zip": "0.5.10",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "6.0.1",
|
||||
"argon2": "0.31.2",
|
||||
"argon2": "^0.31.2",
|
||||
"aws-sdk": "2.1498.0",
|
||||
"axios": "1.6.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import * as Acct from "@/misc/acct.js";
|
||||
import { Cache } from "@/misc/cache.js";
|
||||
import { getWordHardMute } from "@/misc/check-word-mute.js";
|
||||
import { getFullApAccount } from "@/misc/convert-host.js";
|
||||
import type { Packed } from "@/misc/schema.js";
|
||||
import type { Antenna } from "@/models/entities/antenna.js";
|
||||
import type { Note } from "@/models/entities/note.js";
|
||||
import type { User } from "@/models/entities/user.js";
|
||||
import { Blockings, Followings, UserProfiles } from "@/models/index.js";
|
||||
import { getFullApAccount } from "./convert-host.js";
|
||||
|
||||
const blockingCache = new Cache<User["id"][]>("blocking", 60 * 5);
|
||||
const mutedWordsCache = new Cache<string[][] | undefined>("mutedWords", 60 * 5);
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// AID
|
||||
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
|
||||
|
||||
import * as crypto from "node:crypto";
|
||||
|
||||
const TIME2000 = 946684800000;
|
||||
let counter = crypto.randomBytes(2).readUInt16LE(0);
|
||||
|
||||
function getTime(time: number) {
|
||||
time = time - TIME2000;
|
||||
if (time < 0) time = 0;
|
||||
|
||||
return time.toString(36).padStart(8, "0");
|
||||
}
|
||||
|
||||
function getNoise() {
|
||||
return counter.toString(36).padStart(2, "0").slice(-2);
|
||||
}
|
||||
|
||||
export function genAid(date: Date): string {
|
||||
const t = date.getTime();
|
||||
if (Number.isNaN(t)) throw "Failed to create AID: Invalid Date";
|
||||
counter++;
|
||||
return getTime(t) + getNoise();
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
const CHARS = "0123456789abcdef";
|
||||
|
||||
function getTime(time: number) {
|
||||
if (time < 0) time = 0;
|
||||
if (time === 0) {
|
||||
return CHARS[0];
|
||||
}
|
||||
|
||||
time += 0x800000000000;
|
||||
|
||||
return time.toString(16).padStart(12, CHARS[0]);
|
||||
}
|
||||
|
||||
function getRandom() {
|
||||
let str = "";
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
str += CHARS[Math.floor(Math.random() * CHARS.length)];
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
export function genMeid(date: Date): string {
|
||||
return getTime(date.getTime()) + getRandom();
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
const CHARS = "0123456789abcdef";
|
||||
|
||||
// 4bit Fixed hex value 'g'
|
||||
// 44bit UNIX Time ms in Hex
|
||||
// 48bit Random value in Hex
|
||||
|
||||
function getTime(time: number) {
|
||||
if (time < 0) time = 0;
|
||||
if (time === 0) {
|
||||
return CHARS[0];
|
||||
}
|
||||
|
||||
return time.toString(16).padStart(11, CHARS[0]);
|
||||
}
|
||||
|
||||
function getRandom() {
|
||||
let str = "";
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
str += CHARS[Math.floor(Math.random() * CHARS.length)];
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
export function genMeidg(date: Date): string {
|
||||
return `g${getTime(date.getTime())}${getRandom()}`;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
const CHARS = "0123456789abcdef";
|
||||
|
||||
function getTime(time: number) {
|
||||
if (time < 0) time = 0;
|
||||
if (time === 0) {
|
||||
return CHARS[0];
|
||||
}
|
||||
|
||||
time = Math.floor(time / 1000);
|
||||
|
||||
return time.toString(16).padStart(8, CHARS[0]);
|
||||
}
|
||||
|
||||
function getRandom() {
|
||||
let str = "";
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
str += CHARS[Math.floor(Math.random() * CHARS.length)];
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
export function genObjectId(date: Date): string {
|
||||
return getTime(date.getTime()) + getRandom();
|
||||
}
|
|
@ -6,6 +6,7 @@ import { shouldBlockInstance } from "@/misc/should-block-instance.js";
|
|||
import type { UserPublickey } from "@/models/entities/user-publickey.js";
|
||||
import type { CacheableRemoteUser } from "@/models/entities/user.js";
|
||||
import { Instances } from "@/models/index.js";
|
||||
import { verifySignature } from "@/remote/activitypub/check-fetch.js";
|
||||
import DbResolver from "@/remote/activitypub/db-resolver.js";
|
||||
import { LdSignature } from "@/remote/activitypub/misc/ld-signature.js";
|
||||
import { resolvePerson } from "@/remote/activitypub/models/person.js";
|
||||
|
@ -109,6 +110,12 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
|||
);
|
||||
}
|
||||
|
||||
if (httpSignatureValidated) {
|
||||
if (!verifySignature(signature, authUser.key)) {
|
||||
return "skip: Invalid HTTP signature";
|
||||
}
|
||||
}
|
||||
|
||||
// また、signatureのsignerは、activity.actorと一致する必要がある
|
||||
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
||||
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type Bull from "bull";
|
||||
|
||||
import { activeUsersChart } from "@/services/chart/index.js";
|
||||
import { queueLogger } from "../../logger.js";
|
||||
import { queueLogger } from "@/queue/logger.js";
|
||||
|
||||
const logger = queueLogger.createSubLogger("clean-charts");
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ import { checkExpiredMutings } from "./check-expired-mutings.js";
|
|||
import { cleanCharts } from "./clean-charts.js";
|
||||
import { clean } from "./clean.js";
|
||||
import { setLocalEmojiSizes } from "./local-emoji-size.js";
|
||||
import { resyncCharts } from "./resync-charts.js";
|
||||
import { tickCharts } from "./tick-charts.js";
|
||||
import { verifyLinks } from "./verify-links.js";
|
||||
|
||||
const jobs = {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import type { IncomingMessage } from "http";
|
||||
import { verify } from "node:crypto";
|
||||
import { createHash } from "node:crypto";
|
||||
import { URL } from "url";
|
||||
import config from "@/config/index.js";
|
||||
import { toPuny } from "@/misc/convert-host.js";
|
||||
|
@ -6,9 +8,10 @@ import { fetchMeta } from "@/misc/fetch-meta.js";
|
|||
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
|
||||
import type { UserPublickey } from "@/models/entities/user-publickey.js";
|
||||
import type { CacheableRemoteUser } from "@/models/entities/user.js";
|
||||
import { toSingle } from "@/prelude/array.js";
|
||||
import DbResolver from "@/remote/activitypub/db-resolver.js";
|
||||
import { getApId } from "@/remote/activitypub/type.js";
|
||||
import httpSignature from "@peertube/http-signature";
|
||||
import httpSignature, { IParsedSignature } from "@peertube/http-signature";
|
||||
|
||||
export async function hasSignature(req: IncomingMessage): Promise<string> {
|
||||
const meta = await fetchMeta();
|
||||
|
@ -28,10 +31,14 @@ export async function hasSignature(req: IncomingMessage): Promise<string> {
|
|||
export async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||
const meta = await fetchMeta();
|
||||
if (meta.secureMode || meta.privateMode) {
|
||||
if (req.headers.host !== config.host) return 400;
|
||||
|
||||
let signature;
|
||||
|
||||
try {
|
||||
signature = httpSignature.parseRequest(req, { headers: [] });
|
||||
signature = httpSignature.parseRequest(req, {
|
||||
headers: ["(request-target)", "host", "date"],
|
||||
});
|
||||
} catch (e) {
|
||||
return 401;
|
||||
}
|
||||
|
@ -108,6 +115,8 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
|
|||
if (!httpSignatureValidated) {
|
||||
return 403;
|
||||
}
|
||||
|
||||
return verifySignature(signature, authUser.key) ? 200 : 401;
|
||||
}
|
||||
return 200;
|
||||
}
|
||||
|
@ -130,3 +139,39 @@ export async function getSignatureUser(req: IncomingMessage): Promise<{
|
|||
keyId.hash = "";
|
||||
return await dbResolver.getAuthUserFromApId(getApId(keyId.toString()));
|
||||
}
|
||||
|
||||
export function verifySignature(
|
||||
sig: IParsedSignature,
|
||||
key: UserPublickey,
|
||||
): boolean {
|
||||
if (!["hs2019", "rsa-sha256"].includes(sig.algorithm.toLowerCase()))
|
||||
return false;
|
||||
try {
|
||||
return verify(
|
||||
"rsa-sha256",
|
||||
Buffer.from(sig.signingString, "utf8"),
|
||||
key.keyPem,
|
||||
Buffer.from(sig.params.signature, "base64"),
|
||||
);
|
||||
} catch {
|
||||
// Algo not supported
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function verifyDigest(
|
||||
body: string,
|
||||
digest: string | string[] | undefined,
|
||||
): boolean {
|
||||
digest = toSingle(digest);
|
||||
if (
|
||||
body == null ||
|
||||
digest == null ||
|
||||
!digest.toLowerCase().startsWith("sha-256=")
|
||||
)
|
||||
return false;
|
||||
|
||||
return (
|
||||
createHash("sha256").update(body).digest("base64") === digest.substring(8)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import config from "@/config/index.js";
|
||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||
import type { NoteReaction } from "@/models/entities/note-reaction.js";
|
||||
import type { Note } from "@/models/entities/note.js";
|
||||
import { Emojis } from "@/models/index.js";
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import Router from "@koa/router";
|
||||
import httpSignature from "@peertube/http-signature";
|
||||
import json from "koa-json-body";
|
||||
import bodyParser from "koa-bodyparser";
|
||||
|
||||
import config from "@/config/index.js";
|
||||
import { isSelfHost } from "@/misc/convert-host.js";
|
||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||
import { getUserKeypair } from "@/misc/keypair-store.js";
|
||||
|
@ -17,6 +18,7 @@ import { inbox as processInbox } from "@/queue/index.js";
|
|||
import {
|
||||
checkFetch,
|
||||
getSignatureUser,
|
||||
verifyDigest,
|
||||
} from "@/remote/activitypub/check-fetch.js";
|
||||
import renderEmoji from "@/remote/activitypub/renderer/emoji.js";
|
||||
import renderFollow from "@/remote/activitypub/renderer/follow.js";
|
||||
|
@ -26,6 +28,7 @@ import { renderLike } from "@/remote/activitypub/renderer/like.js";
|
|||
import renderNote from "@/remote/activitypub/renderer/note.js";
|
||||
import { renderPerson } from "@/remote/activitypub/renderer/person.js";
|
||||
import { getInstanceActor } from "@/services/instance-actor.js";
|
||||
import Koa from "koa";
|
||||
import { In, IsNull, Not } from "typeorm";
|
||||
import Featured from "./activitypub/featured.js";
|
||||
import Followers from "./activitypub/followers.js";
|
||||
|
@ -39,15 +42,27 @@ const router = new Router();
|
|||
//#region Routing
|
||||
|
||||
function inbox(ctx: Router.RouterContext) {
|
||||
if (ctx.req.headers.host !== config.host) {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
let signature;
|
||||
|
||||
try {
|
||||
signature = httpSignature.parseRequest(ctx.req, { headers: [] });
|
||||
signature = httpSignature.parseRequest(ctx.req, {
|
||||
headers: ["(request-target)", "digest", "host", "date"],
|
||||
});
|
||||
} catch (e) {
|
||||
ctx.status = 401;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!verifyDigest(ctx.request.rawBody, ctx.headers.digest)) {
|
||||
ctx.status = 401;
|
||||
return;
|
||||
}
|
||||
|
||||
processInbox(ctx.request.body, signature);
|
||||
|
||||
ctx.status = 202;
|
||||
|
@ -72,9 +87,23 @@ export function setResponseType(ctx: Router.RouterContext) {
|
|||
}
|
||||
}
|
||||
|
||||
async function parseJsonBodyOrFail(ctx: Router.RouterContext, next: Koa.Next) {
|
||||
const koaBodyParser = bodyParser({
|
||||
enableTypes: ["json"],
|
||||
detectJSON: () => true,
|
||||
});
|
||||
|
||||
try {
|
||||
await koaBodyParser(ctx, next);
|
||||
} catch {
|
||||
ctx.status = 400;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// inbox
|
||||
router.post("/inbox", json(), inbox);
|
||||
router.post("/users/:user/inbox", json(), inbox);
|
||||
router.post("/inbox", parseJsonBodyOrFail, inbox);
|
||||
router.post("/users/:user/inbox", parseJsonBodyOrFail, inbox);
|
||||
|
||||
// note
|
||||
router.get("/notes/:note", async (ctx, next) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createImportCustomEmojisJob } from "@/queue/index.js";
|
||||
import define from "@/server/api/define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { ApiError } from "@/server/api/error.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["admin", "emoji"],
|
||||
|
|
|
@ -3,7 +3,7 @@ import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
|||
import { Emojis } from "@/models/index.js";
|
||||
import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js";
|
||||
import define from "@/server/api/define.js";
|
||||
import { ApiError } from "../../../error.js";
|
||||
import { ApiError } from "@/server/api/error.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["admin", "emoji"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts", "drive"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts", "hashtags"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts", "notes"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts", "drive", "users"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts", "users", "notes"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts", "users", "reactions"],
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import define from "../../define.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["charts", "users"],
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
|
||||
import { Note } from "@/models/entities/note.js";
|
||||
// import { FindManyOptions, In } from "typeorm";
|
||||
import { Notes } from "@/models/index.js";
|
||||
import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js";
|
||||
import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js";
|
||||
import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js";
|
||||
import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js";
|
||||
// import config from "@/config/index.js";
|
||||
// import es from "@/db/elasticsearch.js";
|
||||
// import sonic from "@/db/sonic.js";
|
||||
// import meilisearch, { MeilisearchNote } from "@/db/meilisearch.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
export const meta = {
|
||||
|
@ -255,7 +250,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
while (found.length < ps.limit && start < noteIDs.length) {
|
||||
const chunk = noteIDs.slice(start, start + chunkSize);
|
||||
|
||||
let query: FindManyOptions = {
|
||||
const query: FindManyOptions = {
|
||||
where: {
|
||||
id: In(chunk),
|
||||
},
|
||||
|
|
|
@ -25,8 +25,7 @@ const assets = `${_dirname}/../../server/file/assets/`;
|
|||
const MAX_BYTE_RANGES = 10;
|
||||
|
||||
const commonReadableHandlerGenerator =
|
||||
(ctx: Koa.Context) =>
|
||||
(e: Error): void => {
|
||||
(ctx: Koa.Context) => (e: Error): void => {
|
||||
serverLogger.error(e);
|
||||
ctx.status = 500;
|
||||
ctx.set("Cache-Control", "max-age=300");
|
||||
|
|
|
@ -25,7 +25,12 @@
|
|||
//#region Detect language & fetch translations
|
||||
const v = localStorage.getItem("v") || VERSION;
|
||||
|
||||
const supportedLangs = LANGS;
|
||||
let supportedLangs = [];
|
||||
try {
|
||||
supportedLangs = SUPPORTED_LANGS;
|
||||
} catch {
|
||||
console.warn("LANGS not found");
|
||||
}
|
||||
let lang = localStorage.getItem("lang");
|
||||
if (lang == null || !supportedLangs.includes(lang)) {
|
||||
if (supportedLangs.includes(navigator.language)) {
|
||||
|
|
|
@ -31,7 +31,6 @@ import type { ILocalUser, IRemoteUser, User } from "@/models/entities/user.js";
|
|||
import {
|
||||
ChannelFollowings,
|
||||
Channels,
|
||||
Followings,
|
||||
Instances,
|
||||
MutedNotes,
|
||||
Mutings,
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"@rollup/plugin-json": "6.0.1",
|
||||
"@rollup/pluginutils": "5.0.5",
|
||||
"@syuilo/aiscript": "0.16.0",
|
||||
"@types/autosize": "4.0.3",
|
||||
"@types/autosize": "^4.0.3",
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/gulp": "4.0.17",
|
||||
"@types/gulp-rename": "2.0.5",
|
||||
|
@ -49,7 +49,7 @@
|
|||
"compare-versions": "6.1.0",
|
||||
"cropperjs": "2.0.0-beta.4",
|
||||
"date-fns": "2.30.0",
|
||||
"emojilib": "3.0.11",
|
||||
"emojilib": "^3.0.11",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-plugin-file-progress": "1.3.0",
|
||||
"eventemitter3": "5.0.1",
|
||||
|
@ -80,15 +80,15 @@
|
|||
"three": "0.158.0",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tinyld": "1.3.4",
|
||||
"tinyld": "^1.3.4",
|
||||
"typescript": "5.2.2",
|
||||
"unicode-emoji-json": "0.4.0",
|
||||
"uuid": "9.0.1",
|
||||
"vite": "5.0.0",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vue": "3.3.8",
|
||||
"vue-draggable-plus": "0.2.7",
|
||||
"vue-plyr": "7.0.0",
|
||||
"vue-draggable-plus": "^0.2.7",
|
||||
"vue-plyr": "^7.0.0",
|
||||
"vue-prism-editor": "2.0.0-alpha.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
message.groupId
|
||||
? message.user
|
||||
: isMe(message)
|
||||
? message.recipient
|
||||
: message.user
|
||||
? message.recipient
|
||||
: message.user
|
||||
"
|
||||
:show-indicator="true"
|
||||
disable-link
|
||||
|
|
|
@ -327,8 +327,8 @@ async function ok() {
|
|||
const result = props.input
|
||||
? inputValue.value
|
||||
: props.select
|
||||
? selectedValue.value
|
||||
: true;
|
||||
? selectedValue.value
|
||||
: true;
|
||||
done(false, result);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
? i18n.ts.selectFiles
|
||||
: i18n.ts.selectFolders
|
||||
: type === "file"
|
||||
? i18n.ts.selectFile
|
||||
: i18n.ts.selectFolder
|
||||
? i18n.ts.selectFile
|
||||
: i18n.ts.selectFolder
|
||||
}}
|
||||
<span
|
||||
v-if="selected.length > 0"
|
||||
|
|
|
@ -88,8 +88,8 @@ const preferedModalType =
|
|||
deviceKind === "desktop" && props.src != null
|
||||
? "popup"
|
||||
: deviceKind === "smartphone"
|
||||
? "drawer"
|
||||
: "dialog";
|
||||
? "drawer"
|
||||
: "dialog";
|
||||
|
||||
const modal = ref<InstanceType<typeof MkModal>>();
|
||||
|
||||
|
|
|
@ -113,9 +113,9 @@ const url =
|
|||
props.raw || defaultStore.state.loadRawImages
|
||||
? props.media.url
|
||||
: defaultStore.state.disableShowingAnimatedImages &&
|
||||
props.media.type.startsWith("image")
|
||||
? getStaticImageUrl(props.media.thumbnailUrl)
|
||||
: props.media.thumbnailUrl;
|
||||
props.media.type.startsWith("image")
|
||||
? getStaticImageUrl(props.media.thumbnailUrl)
|
||||
: props.media.thumbnailUrl;
|
||||
|
||||
const mediaType = computed(() => {
|
||||
return props.media.type === "video/quicktime"
|
||||
|
|
|
@ -169,22 +169,22 @@ const transitionName = computed(() =>
|
|||
? useSendAnime.value
|
||||
? "send"
|
||||
: type.value === "drawer"
|
||||
? "modal-drawer"
|
||||
: type.value === "popup"
|
||||
? "modal-popup"
|
||||
: "modal"
|
||||
? "modal-drawer"
|
||||
: type.value === "popup"
|
||||
? "modal-popup"
|
||||
: "modal"
|
||||
: "",
|
||||
);
|
||||
const transitionDuration = computed(() =>
|
||||
transitionName.value === "send"
|
||||
? 400
|
||||
: transitionName.value === "modal-popup"
|
||||
? 100
|
||||
: transitionName.value === "modal"
|
||||
? 200
|
||||
: transitionName.value === "modal-drawer"
|
||||
? 200
|
||||
: 0,
|
||||
? 100
|
||||
: transitionName.value === "modal"
|
||||
? 200
|
||||
: transitionName.value === "modal-drawer"
|
||||
? 200
|
||||
: 0,
|
||||
);
|
||||
|
||||
let contentClicking = false;
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
? `${props.height}px`
|
||||
: null
|
||||
: height
|
||||
? `min(${props.height}px, 100%)`
|
||||
: '100%',
|
||||
? `min(${props.height}px, 100%)`
|
||||
: '100%',
|
||||
}"
|
||||
tabindex="-1"
|
||||
>
|
||||
|
|
|
@ -259,12 +259,12 @@ const fetchMore = async (): Promise<void> => {
|
|||
offset: offset.value,
|
||||
}
|
||||
: props.pagination.reversed
|
||||
? {
|
||||
sinceId: items.value[0].id,
|
||||
}
|
||||
: {
|
||||
untilId: items.value[items.value.length - 1].id,
|
||||
}),
|
||||
? {
|
||||
sinceId: items.value[0].id,
|
||||
}
|
||||
: {
|
||||
untilId: items.value[items.value.length - 1].id,
|
||||
}),
|
||||
})
|
||||
.then(
|
||||
(res) => {
|
||||
|
@ -320,12 +320,12 @@ const fetchMoreAhead = async (): Promise<void> => {
|
|||
offset: offset.value,
|
||||
}
|
||||
: props.pagination.reversed
|
||||
? {
|
||||
untilId: items.value[0].id,
|
||||
}
|
||||
: {
|
||||
sinceId: items.value[items.value.length - 1].id,
|
||||
}),
|
||||
? {
|
||||
untilId: items.value[0].id,
|
||||
}
|
||||
: {
|
||||
sinceId: items.value[items.value.length - 1].id,
|
||||
}),
|
||||
})
|
||||
.then(
|
||||
(res) => {
|
||||
|
|
|
@ -81,10 +81,10 @@ const timer = computed(() =>
|
|||
remaining.value >= 86400
|
||||
? "_poll.remainingDays"
|
||||
: remaining.value >= 3600
|
||||
? "_poll.remainingHours"
|
||||
: remaining.value >= 60
|
||||
? "_poll.remainingMinutes"
|
||||
: "_poll.remainingSeconds",
|
||||
? "_poll.remainingHours"
|
||||
: remaining.value >= 60
|
||||
? "_poll.remainingMinutes"
|
||||
: "_poll.remainingSeconds",
|
||||
{
|
||||
s: Math.floor(remaining.value % 60),
|
||||
m: Math.floor(remaining.value / 60) % 60,
|
||||
|
|
|
@ -171,8 +171,8 @@ function get() {
|
|||
...(expiration.value === "at"
|
||||
? { expiresAt: calcAt() }
|
||||
: expiration.value === "after"
|
||||
? { expiredAfter: calcAfter() }
|
||||
: {}),
|
||||
? { expiredAfter: calcAfter() }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -76,8 +76,8 @@
|
|||
reply
|
||||
? 'ph-arrow-u-up-left'
|
||||
: renote
|
||||
? 'ph-quotes'
|
||||
: 'ph-paper-plane-tilt',
|
||||
? 'ph-quotes'
|
||||
: 'ph-paper-plane-tilt',
|
||||
)
|
||||
"
|
||||
></i>
|
||||
|
@ -360,7 +360,6 @@ const visibleUsers = ref([]);
|
|||
if (props.initialVisibleUsers) {
|
||||
props.initialVisibleUsers.forEach(pushVisibleUser);
|
||||
}
|
||||
const autocomplete = ref(null);
|
||||
const draghover = ref(false);
|
||||
const quoteId = ref(null);
|
||||
const hasNotSpecifiedMentions = ref(false);
|
||||
|
@ -417,10 +416,10 @@ const submitText = computed((): string => {
|
|||
return props.editId
|
||||
? i18n.ts.edit
|
||||
: props.renote
|
||||
? i18n.ts.quote
|
||||
: props.reply
|
||||
? i18n.ts.reply
|
||||
: i18n.ts.note;
|
||||
? i18n.ts.quote
|
||||
: props.reply
|
||||
? i18n.ts.reply
|
||||
: i18n.ts.note;
|
||||
});
|
||||
|
||||
const textLength = computed((): number => {
|
||||
|
@ -489,14 +488,14 @@ if (props.reply && props.reply.text != null) {
|
|||
const mention = x.host
|
||||
? `@${x.username}@${toASCII(x.host)}`
|
||||
: otherHost == null || otherHost === host
|
||||
? `@${x.username}`
|
||||
: `@${x.username}@${toASCII(otherHost)}`;
|
||||
? `@${x.username}`
|
||||
: `@${x.username}@${toASCII(otherHost)}`;
|
||||
|
||||
// 自分は除外
|
||||
// exclude me
|
||||
if ($i.username === x.username && (x.host == null || x.host === host))
|
||||
continue;
|
||||
|
||||
// 重複は除外
|
||||
// remove duplicates
|
||||
if (text.value.includes(`${mention} `)) continue;
|
||||
|
||||
text.value += `${mention} `;
|
||||
|
@ -505,10 +504,10 @@ if (props.reply && props.reply.text != null) {
|
|||
|
||||
if (props.channel) {
|
||||
visibility.value = "public";
|
||||
localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す
|
||||
localOnly.value = true; // TODO: Delete this once channels get federated
|
||||
}
|
||||
|
||||
// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
|
||||
// Inherit the original visibility
|
||||
if (
|
||||
props.reply &&
|
||||
["home", "followers", "specified"].includes(props.reply.visibility)
|
||||
|
@ -629,10 +628,6 @@ function togglePoll() {
|
|||
}
|
||||
}
|
||||
|
||||
function addTag(tag: string) {
|
||||
insertTextAtCursor(textareaEl.value, ` #${tag} `);
|
||||
}
|
||||
|
||||
function focus() {
|
||||
if (textareaEl.value) {
|
||||
textareaEl.value.focus();
|
||||
|
@ -736,6 +731,8 @@ function clear() {
|
|||
quoteId.value = null;
|
||||
}
|
||||
|
||||
// FIXME: ev.which is deprecated
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/which
|
||||
function onKeydown(ev: KeyboardEvent) {
|
||||
if (
|
||||
(ev.which === 10 || ev.which === 13) &&
|
||||
|
@ -772,7 +769,7 @@ async function onPaste(ev: ClipboardEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
const paste = ev.clipboardData.getData("text");
|
||||
const paste = ev.clipboardData?.getData("text") ?? "";
|
||||
|
||||
if (!props.renote && !quoteId.value && paste.startsWith(url + "/notes/")) {
|
||||
ev.preventDefault();
|
||||
|
@ -787,7 +784,7 @@ async function onPaste(ev: ClipboardEvent) {
|
|||
}
|
||||
|
||||
quoteId.value = paste
|
||||
.substr(url.length)
|
||||
.substring(url.length)
|
||||
.match(/^\/notes\/(.+?)\/?$/)[1];
|
||||
});
|
||||
}
|
||||
|
@ -886,8 +883,8 @@ async function post() {
|
|||
renoteId: props.renote
|
||||
? props.renote.id
|
||||
: quoteId.value
|
||||
? quoteId.value
|
||||
: undefined,
|
||||
? quoteId.value
|
||||
: undefined,
|
||||
channelId: props.channel ? props.channel.id : undefined,
|
||||
poll: poll.value,
|
||||
cw: useCw.value ? cw.value || "" : undefined,
|
||||
|
|
|
@ -371,10 +371,10 @@ function onChangeUsername(): void {
|
|||
const err = !username.value.match(/^[a-zA-Z0-9_]+$/)
|
||||
? "invalid-format"
|
||||
: username.value.length < 1
|
||||
? "min-range"
|
||||
: username.value.length > 20
|
||||
? "max-range"
|
||||
: null;
|
||||
? "min-range"
|
||||
: username.value.length > 20
|
||||
? "max-range"
|
||||
: null;
|
||||
|
||||
if (err) {
|
||||
usernameState.value = err;
|
||||
|
@ -410,16 +410,16 @@ function onChangeEmail(): void {
|
|||
emailState.value = result.available
|
||||
? "ok"
|
||||
: result.reason === "used"
|
||||
? "unavailable:used"
|
||||
: result.reason === "format"
|
||||
? "unavailable:format"
|
||||
: result.reason === "disposable"
|
||||
? "unavailable:disposable"
|
||||
: result.reason === "mx"
|
||||
? "unavailable:mx"
|
||||
: result.reason === "smtp"
|
||||
? "unavailable:smtp"
|
||||
: "unavailable";
|
||||
? "unavailable:used"
|
||||
: result.reason === "format"
|
||||
? "unavailable:format"
|
||||
: result.reason === "disposable"
|
||||
? "unavailable:disposable"
|
||||
: result.reason === "mx"
|
||||
? "unavailable:mx"
|
||||
: result.reason === "smtp"
|
||||
? "unavailable:smtp"
|
||||
: "unavailable";
|
||||
})
|
||||
.catch(() => {
|
||||
emailState.value = "error";
|
||||
|
|
|
@ -29,11 +29,11 @@ const _time =
|
|||
props.time == null
|
||||
? NaN
|
||||
: typeof props.time === "number"
|
||||
? props.time
|
||||
: (props.time instanceof Date
|
||||
? props.time
|
||||
: new Date(props.time)
|
||||
).getTime();
|
||||
? props.time
|
||||
: (props.time instanceof Date
|
||||
? props.time
|
||||
: new Date(props.time)
|
||||
).getTime();
|
||||
const invalid = Number.isNaN(_time);
|
||||
const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
|
||||
|
||||
|
@ -46,30 +46,30 @@ const relative = computed<string>(() => {
|
|||
return ago >= 31536000
|
||||
? i18n.t("_ago.yearsAgo", { n: Math.floor(ago / 31536000).toString() })
|
||||
: ago >= 2592000
|
||||
? i18n.t("_ago.monthsAgo", {
|
||||
n: Math.floor(ago / 2592000).toString(),
|
||||
})
|
||||
: ago >= 604800
|
||||
? i18n.t("_ago.weeksAgo", {
|
||||
n: Math.floor(ago / 604800).toString(),
|
||||
})
|
||||
: ago >= 86400
|
||||
? i18n.t("_ago.daysAgo", {
|
||||
n: Math.floor(ago / 86400).toString(),
|
||||
})
|
||||
: ago >= 3600
|
||||
? i18n.t("_ago.hoursAgo", {
|
||||
n: Math.floor(ago / 3600).toString(),
|
||||
})
|
||||
: ago >= 60
|
||||
? i18n.t("_ago.minutesAgo", { n: (~~(ago / 60)).toString() })
|
||||
: ago >= 10
|
||||
? i18n.t("_ago.secondsAgo", {
|
||||
n: (~~(ago % 60)).toString(),
|
||||
})
|
||||
: ago >= -1
|
||||
? i18n.ts._ago.justNow
|
||||
: i18n.ts._ago.future;
|
||||
? i18n.t("_ago.monthsAgo", {
|
||||
n: Math.floor(ago / 2592000).toString(),
|
||||
})
|
||||
: ago >= 604800
|
||||
? i18n.t("_ago.weeksAgo", {
|
||||
n: Math.floor(ago / 604800).toString(),
|
||||
})
|
||||
: ago >= 86400
|
||||
? i18n.t("_ago.daysAgo", {
|
||||
n: Math.floor(ago / 86400).toString(),
|
||||
})
|
||||
: ago >= 3600
|
||||
? i18n.t("_ago.hoursAgo", {
|
||||
n: Math.floor(ago / 3600).toString(),
|
||||
})
|
||||
: ago >= 60
|
||||
? i18n.t("_ago.minutesAgo", { n: (~~(ago / 60)).toString() })
|
||||
: ago >= 10
|
||||
? i18n.t("_ago.secondsAgo", {
|
||||
n: (~~(ago % 60)).toString(),
|
||||
})
|
||||
: ago >= -1
|
||||
? i18n.ts._ago.justNow
|
||||
: i18n.ts._ago.future;
|
||||
});
|
||||
|
||||
let tickId: number;
|
||||
|
|
|
@ -127,18 +127,18 @@ const pagination = {
|
|||
...(state.value === "federating"
|
||||
? { federating: true }
|
||||
: state.value === "subscribing"
|
||||
? { subscribing: true }
|
||||
: state.value === "publishing"
|
||||
? { publishing: true }
|
||||
: state.value === "suspended"
|
||||
? { suspended: true }
|
||||
: state.value === "blocked"
|
||||
? { blocked: true }
|
||||
: state.value === "silenced"
|
||||
? { silenced: true }
|
||||
: state.value === "notResponding"
|
||||
? { notResponding: true }
|
||||
: {}),
|
||||
? { subscribing: true }
|
||||
: state.value === "publishing"
|
||||
? { publishing: true }
|
||||
: state.value === "suspended"
|
||||
? { suspended: true }
|
||||
: state.value === "blocked"
|
||||
? { blocked: true }
|
||||
: state.value === "silenced"
|
||||
? { silenced: true }
|
||||
: state.value === "notResponding"
|
||||
? { notResponding: true }
|
||||
: {}),
|
||||
})),
|
||||
};
|
||||
|
||||
|
|
|
@ -105,8 +105,8 @@ async function init() {
|
|||
provider.value = meta.enableHcaptcha
|
||||
? "hcaptcha"
|
||||
: meta.enableRecaptcha
|
||||
? "recaptcha"
|
||||
: null;
|
||||
? "recaptcha"
|
||||
: null;
|
||||
}
|
||||
|
||||
function save() {
|
||||
|
|
|
@ -51,23 +51,23 @@ const label =
|
|||
props.type === "process"
|
||||
? "Process"
|
||||
: props.type === "active"
|
||||
? "Active"
|
||||
: props.type === "delayed"
|
||||
? "Delayed"
|
||||
: props.type === "waiting"
|
||||
? "Waiting"
|
||||
: ("?" as never);
|
||||
? "Active"
|
||||
: props.type === "delayed"
|
||||
? "Delayed"
|
||||
: props.type === "waiting"
|
||||
? "Waiting"
|
||||
: ("?" as never);
|
||||
|
||||
const color =
|
||||
props.type === "process"
|
||||
? "#c4a7e7"
|
||||
: props.type === "active"
|
||||
? "#31748f"
|
||||
: props.type === "delayed"
|
||||
? "#eb6f92"
|
||||
: props.type === "waiting"
|
||||
? "#f6c177"
|
||||
: ("?" as never);
|
||||
? "#31748f"
|
||||
: props.type === "delayed"
|
||||
? "#eb6f92"
|
||||
: props.type === "waiting"
|
||||
? "#f6c177"
|
||||
: ("?" as never);
|
||||
|
||||
onMounted(() => {
|
||||
const vLineColor = defaultStore.state.darkMode
|
||||
|
|
|
@ -96,23 +96,23 @@ const label =
|
|||
props.type === "process"
|
||||
? "Process"
|
||||
: props.type === "active"
|
||||
? "Active"
|
||||
: props.type === "delayed"
|
||||
? "Delayed"
|
||||
: props.type === "waiting"
|
||||
? "Waiting"
|
||||
: ("?" as never);
|
||||
? "Active"
|
||||
: props.type === "delayed"
|
||||
? "Delayed"
|
||||
: props.type === "waiting"
|
||||
? "Waiting"
|
||||
: ("?" as never);
|
||||
|
||||
const color =
|
||||
props.type === "process"
|
||||
? "#9ccfd8"
|
||||
: props.type === "active"
|
||||
? "#31748f"
|
||||
: props.type === "delayed"
|
||||
? "#eb6f92"
|
||||
: props.type === "waiting"
|
||||
? "#f6c177"
|
||||
: ("?" as never);
|
||||
? "#31748f"
|
||||
: props.type === "delayed"
|
||||
? "#eb6f92"
|
||||
: props.type === "waiting"
|
||||
? "#f6c177"
|
||||
: ("?" as never);
|
||||
|
||||
onMounted(() => {
|
||||
chartInstance = new Chart(chartEl.value, {
|
||||
|
|
|
@ -117,8 +117,8 @@ onMounted(() => {
|
|||
props.domain === "inbox"
|
||||
? "admin/queue/inbox-delayed"
|
||||
: props.domain === "deliver"
|
||||
? "admin/queue/deliver-delayed"
|
||||
: null,
|
||||
? "admin/queue/deliver-delayed"
|
||||
: null,
|
||||
{},
|
||||
).then((result) => {
|
||||
jobs.value = result;
|
||||
|
|
|
@ -290,14 +290,14 @@ async function init() {
|
|||
meta.sensitiveMediaDetectionSensitivity === "veryLow"
|
||||
? 0
|
||||
: meta.sensitiveMediaDetectionSensitivity === "low"
|
||||
? 1
|
||||
: meta.sensitiveMediaDetectionSensitivity === "medium"
|
||||
? 2
|
||||
: meta.sensitiveMediaDetectionSensitivity === "high"
|
||||
? 3
|
||||
: meta.sensitiveMediaDetectionSensitivity === "veryHigh"
|
||||
? 4
|
||||
: 0;
|
||||
? 1
|
||||
: meta.sensitiveMediaDetectionSensitivity === "medium"
|
||||
? 2
|
||||
: meta.sensitiveMediaDetectionSensitivity === "high"
|
||||
? 3
|
||||
: meta.sensitiveMediaDetectionSensitivity === "veryHigh"
|
||||
? 4
|
||||
: 0;
|
||||
setSensitiveFlagAutomatically.value = meta.setSensitiveFlagAutomatically;
|
||||
enableSensitiveMediaDetectionForVideos.value =
|
||||
meta.enableSensitiveMediaDetectionForVideos;
|
||||
|
@ -317,14 +317,14 @@ function save() {
|
|||
sensitiveMediaDetectionSensitivity.value === 0
|
||||
? "veryLow"
|
||||
: sensitiveMediaDetectionSensitivity.value === 1
|
||||
? "low"
|
||||
: sensitiveMediaDetectionSensitivity.value === 2
|
||||
? "medium"
|
||||
: sensitiveMediaDetectionSensitivity.value === 3
|
||||
? "high"
|
||||
: sensitiveMediaDetectionSensitivity.value === 4
|
||||
? "veryHigh"
|
||||
: 0,
|
||||
? "low"
|
||||
: sensitiveMediaDetectionSensitivity.value === 2
|
||||
? "medium"
|
||||
: sensitiveMediaDetectionSensitivity.value === 3
|
||||
? "high"
|
||||
: sensitiveMediaDetectionSensitivity.value === 4
|
||||
? "veryHigh"
|
||||
: 0,
|
||||
setSensitiveFlagAutomatically: setSensitiveFlagAutomatically.value,
|
||||
enableSensitiveMediaDetectionForVideos:
|
||||
enableSensitiveMediaDetectionForVideos.value,
|
||||
|
|
|
@ -98,14 +98,14 @@ function onEndpointChange() {
|
|||
p.type === "String"
|
||||
? ""
|
||||
: p.type === "Number"
|
||||
? 0
|
||||
: p.type === "Boolean"
|
||||
? false
|
||||
: p.type === "Array"
|
||||
? []
|
||||
: p.type === "Object"
|
||||
? {}
|
||||
: null;
|
||||
? 0
|
||||
: p.type === "Boolean"
|
||||
? false
|
||||
: p.type === "Array"
|
||||
? []
|
||||
: p.type === "Object"
|
||||
? {}
|
||||
: null;
|
||||
}
|
||||
body.value = JSON5.stringify(endpointBody, null, 2);
|
||||
});
|
||||
|
|
|
@ -229,14 +229,7 @@ function edit() {
|
|||
|
||||
watch(() => props.postId, fetchPost, { immediate: true });
|
||||
|
||||
const headerActions = computed(() => [
|
||||
{
|
||||
icon: `${icon("ph-pencil")}`,
|
||||
text: i18n.ts.edit,
|
||||
handler: edit,
|
||||
},
|
||||
]);
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePageMetadata(
|
||||
|
|
|
@ -108,11 +108,11 @@
|
|||
>{{ i18n.ts._theme.manage
|
||||
}}<template #suffix>{{ themesCount }}</template></FormLink
|
||||
>
|
||||
<FormLink to="https://assets.firefish.io/theme/list" external
|
||||
<!-- <FormLink to="https://assets.misskey.io/theme/list" external
|
||||
><template #icon
|
||||
><i :class="icon('ph-planet')"></i></template
|
||||
>{{ i18n.ts._theme.explore }}</FormLink
|
||||
>
|
||||
> -->
|
||||
<FormLink to="/settings/theme/install"
|
||||
><template #icon
|
||||
><i :class="icon('ph-download-simple')"></i></template
|
||||
|
|
|
@ -90,8 +90,8 @@
|
|||
color: color.forPreview
|
||||
? color.forPreview
|
||||
: theme.base === 'light'
|
||||
? '#5f5f5f'
|
||||
: '#dadada',
|
||||
? '#5f5f5f'
|
||||
: '#dadada',
|
||||
}"
|
||||
>
|
||||
A
|
||||
|
|
|
@ -270,12 +270,12 @@ definePageMetadata(
|
|||
src.value === "local"
|
||||
? "ph-users ph-lg"
|
||||
: src.value === "social"
|
||||
? "ph-handshake ph-lg"
|
||||
: src.value === "recommended"
|
||||
? "ph-thumbs-up ph-lg"
|
||||
: src.value === "global"
|
||||
? "ph-planet ph-lg"
|
||||
: "ph-house ph-lg",
|
||||
? "ph-handshake ph-lg"
|
||||
: src.value === "recommended"
|
||||
? "ph-thumbs-up ph-lg"
|
||||
: src.value === "global"
|
||||
? "ph-planet ph-lg"
|
||||
: "ph-house ph-lg",
|
||||
})),
|
||||
);
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import XCheatSheet from "@/components/MkCheatSheetDialog.vue";
|
||||
import XTutorial from "@/components/MkTutorialDialog.vue";
|
||||
import { defaultStore } from "@/store";
|
||||
import { host } from "@/config";
|
||||
import { i18n } from "@/i18n";
|
||||
import { instance } from "@/instance";
|
||||
import * as os from "@/os";
|
||||
import icon from "@/scripts/icon";
|
||||
import { defaultStore } from "@/store";
|
||||
import type { MenuItem } from "@/types/menu";
|
||||
|
||||
const instanceSpecificItems: MenuItem[] = [];
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
widgetProps.src === "list"
|
||||
? widgetProps.list.name
|
||||
: widgetProps.src === "antenna"
|
||||
? widgetProps.antenna.name
|
||||
: i18n.t("_timelines." + widgetProps.src)
|
||||
? widgetProps.antenna.name
|
||||
: i18n.t("_timelines." + widgetProps.src)
|
||||
}}</span>
|
||||
<i
|
||||
:class="
|
||||
|
@ -57,8 +57,8 @@
|
|||
widgetProps.src === 'list'
|
||||
? `list:${widgetProps.list.id}`
|
||||
: widgetProps.src === 'antenna'
|
||||
? `antenna:${widgetProps.antenna.id}`
|
||||
: widgetProps.src
|
||||
? `antenna:${widgetProps.antenna.id}`
|
||||
: widgetProps.src
|
||||
"
|
||||
:src="widgetProps.src"
|
||||
:list="widgetProps.list ? widgetProps.list.id : null"
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import Response from "./response";
|
||||
import OAuth from "./oauth";
|
||||
import { isCancel, RequestCanceledError } from "./cancel";
|
||||
import { ProxyConfig } from "./proxy_config";
|
||||
import { RequestCanceledError, isCancel } from "./cancel";
|
||||
import Converter from "./converter";
|
||||
import Entity from "./entity";
|
||||
import FilterContext from "./filter_context";
|
||||
import generator, {
|
||||
detector,
|
||||
MegalodonInterface,
|
||||
WebSocketInterface,
|
||||
detector,
|
||||
} from "./megalodon";
|
||||
import Misskey from "./misskey";
|
||||
import Entity from "./entity";
|
||||
import NotificationType from "./notification";
|
||||
import FilterContext from "./filter_context";
|
||||
import Converter from "./converter";
|
||||
import OAuth from "./oauth";
|
||||
import { ProxyConfig } from "./proxy_config";
|
||||
import Response from "./response";
|
||||
|
||||
export {
|
||||
Response,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import Response from "./response";
|
||||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import { DEFAULT_UA } from "./default";
|
||||
import Entity from "./entity";
|
||||
import Misskey from "./misskey";
|
||||
import OAuth from "./oauth";
|
||||
import proxyAgent, { ProxyConfig } from "./proxy_config";
|
||||
import Entity from "./entity";
|
||||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import Misskey from "./misskey";
|
||||
import { DEFAULT_UA } from "./default";
|
||||
import Response from "./response";
|
||||
|
||||
export interface WebSocketInterface {
|
||||
start(): void;
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import FormData from "form-data";
|
||||
import AsyncLock from "async-lock";
|
||||
import FormData from "form-data";
|
||||
|
||||
import MisskeyAPI from "./misskey/api_client";
|
||||
import fs from "node:fs";
|
||||
import MegalodonEntity from "@/entity";
|
||||
import { DEFAULT_UA } from "./default";
|
||||
import { ProxyConfig } from "./proxy_config";
|
||||
import OAuth from "./oauth";
|
||||
import Response from "./response";
|
||||
import Entity from "./entity";
|
||||
import {
|
||||
MegalodonInterface,
|
||||
WebSocketInterface,
|
||||
NoImplementedError,
|
||||
ArgumentError,
|
||||
MegalodonInterface,
|
||||
NoImplementedError,
|
||||
UnexpectedError,
|
||||
WebSocketInterface,
|
||||
} from "./megalodon";
|
||||
import MegalodonEntity from "@/entity";
|
||||
import fs from "node:fs";
|
||||
import MisskeyAPI from "./misskey/api_client";
|
||||
import MisskeyNotificationType from "./misskey/notification";
|
||||
import OAuth from "./oauth";
|
||||
import { ProxyConfig } from "./proxy_config";
|
||||
import Response from "./response";
|
||||
|
||||
type AccountCache = {
|
||||
locks: AsyncLock;
|
||||
|
|
Loading…
Reference in a new issue