Merge branch 'main' into fix-chat-continuation

This commit is contained in:
Norm 2022-07-19 07:08:51 +00:00
commit 7d4cbd6ecf
275 changed files with 3571 additions and 5420 deletions

View file

@ -72,24 +72,6 @@ redis:
# 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 └─────────────────────────────────────

3
.github/FUNDING.yml vendored
View file

@ -1,3 +0,0 @@
# These are supported funding model platforms
patreon: syuilo

View file

@ -1,42 +0,0 @@
---
name: 🐛 Bug Report
about: Create a report to help us improve
title: ''
labels: ⚠bug?
assignees: ''
---
<!--
Thanks for reporting!
First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
-->
## 💡 Summary
<!-- Tell us what the bug is -->
## 🥰 Expected Behavior
<!--- Tell us what should happen -->
## 🤬 Actual Behavior
<!--
Tell us what happens instead of the expected behavior.
Please include errors from the developer console and/or server log files if you have access to them.
-->
## 📝 Steps to Reproduce
1.
2.
3.
## 📌 Environment
<!-- Tell us where on the platform it happens -->
Misskey version:
Your OS:
Your browser:

View file

@ -1,12 +0,0 @@
---
name: ✨ Feature Request
about: Suggest an idea for this project
title: ''
labels: ✨Feature
assignees: ''
---
## Summary
<!-- Tell us what the suggestion is -->

View file

@ -1,7 +0,0 @@
contact_links:
- name: 👪 Misskey Forum
url: https://forum.misskey.io/
about: Ask questions and share knowledge
- name: 💬 Misskey official Discord
url: https://discord.gg/Wp8gVStHW3
about: Chat freely about Misskey

View file

@ -1,17 +0,0 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
# What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
# Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->

View file

@ -11,15 +11,25 @@ You should also include the user name that made the change.
## 12.x.x (unreleased)
### Changes
- ハイライトがみつけるに統合されました
- カスタム絵文字ページはインスタンス情報ページに統合されました
- 連合ページはインスタンス情報ページに統合されました
### Improvements
- Client: Fix URL-encoded routing
- Server: Allow GET method for some endpoints @syuilo
- Server: Add rate limit to i/notifications @tamaina
- Client: Improve control panel @syuilo
- Client: Show warning in control panel when there is an unresolved abuse report @syuilo
- Client: For notes with specified visibility, show recipients when hovering over visibility symbol. @Johann150
- Client: Add rss-ticker widget @syuilo
- Client: Removing entries from a clip @futchitwo
- Client: Poll highlights in explore page @syuilo
- Make possible to delete an account by admin @syuilo
- Improve player detection in URL preview @mei23
- Add Badge Image to Push Notification #8012 @tamaina
- Client: Removing entries from a clip @futchitwo
- Server: Improve performance
- Server: Supports IPv6 on Redis transport. @mei23
IPv4/IPv6 is used by default. You can tune this behavior via `redis.family`.

View file

@ -734,7 +734,6 @@ gallery: "المعرض"
recentPosts: "المشاركات الحديثة"
popularPosts: "المشاركات المتداولة"
shareWithNote: "شاركه في ملاحظة"
ads: "الإعلانات"
expiration: "ينتهي استطلاع الرأي في"
memo: "تذكير"
priority: "الأولوية"
@ -786,7 +785,6 @@ voteConfirm: "متيقِّن من تصويتك لـ {choice}؟"
hide: "إخفاء"
leaveGroup: "مغادرة الفريق"
leaveGroupConfirm: "متيقن من مغادرة \"{name}\"؟"
welcomeBackWithName: "مرحبًا بك مجددًا {name}"
clickToFinishEmailVerification: "انقر [{ok}] لاستيثاق بريدك الإلكتروني."
overridedDeviceKind: "نوع الجهاز"
smartphone: "هاتف ذكي"

View file

@ -765,7 +765,6 @@ gallery: "গ্যালারী"
recentPosts: "নতুন পোস্ট"
popularPosts: "জনপ্রিয় পোস্ট"
shareWithNote: "নোটের মাধ্যমে শেয়ার করুন"
ads: "বিজ্ঞাপন"
expiration: "নির্দিষ্ট সময়সীমা"
memo: "মেমো"
priority: "অগ্রাধিকার"
@ -821,7 +820,6 @@ hide: "লুকান"
leaveGroup: "গ্রুপ ছেড়ে চলে যান"
leaveGroupConfirm: "\"{name}\" গ্রুপ ছেড়ে চলে যেতে চান?"
useDrawerReactionPickerForMobile: "মোবাইলে রিঅ্যাকশন পিকারকে ড্রয়ারে প্রদর্শন করুন"
welcomeBackWithName: "আবার স্বাগতম, {name}"
clickToFinishEmailVerification: " [{ok}] ক্লিক করার মাধ্যমে আপনার ইমেল ঠিকানা নিশ্চিত করুন।"
overridedDeviceKind: "ডিভাইসের ধরন"
smartphone: "স্মার্টফোন"

View file

@ -766,7 +766,6 @@ gallery: "Galerie"
recentPosts: "Neue Beiträge"
popularPosts: "Beliebte Beiträge"
shareWithNote: "Mit Notiz teilen"
ads: "Werbung"
expiration: "Frist"
memo: "Merkzettel"
priority: "Priorität"
@ -822,7 +821,6 @@ hide: "Inhalt verbergen"
leaveGroup: "Gruppe verlassen"
leaveGroupConfirm: "Möchtest du „{name}“ wirklich verlassen?"
useDrawerReactionPickerForMobile: "Auf mobilen Geräten ausfahrbare Reaktionsauswahl anzeigen"
welcomeBackWithName: "Willkommen zurück, {name}"
clickToFinishEmailVerification: "Drücke bitte auf [{ok}], um die Email-Bestätigung abzuschließen."
overridedDeviceKind: "Gerätetyp"
smartphone: "Smartphone"
@ -1659,3 +1657,17 @@ _deck:
list: "Listen"
mentions: "Erwähnungen"
direct: "Direktnachrichten"
recentNHours: "Letzten {n} Stunden"
recentNDays: "Letzten {n} Tage"
isSystemAccount: "Ein Benutzerkonto, dass durch das System erstellt und automatisch kontrolliert wird."
typeToConfirm: "Bitte gib zur Bestätigung {x} ein"
deleteAccount: "Benutzerkonto löschen"
numberOfPageCache: "Seitencachegröße"
numberOfPageCacheDescription: "Das Erhöhen dieses Caches führt zu einer angenehmerern Benutzererfahrung, erhöht aber Serverlast und Arbeitsspeicherauslastung."
file: "Datei"
unclip: "Aus Clip entfernen"
confirmToUnclipAlreadyClippedNote: "Diese Notiz ist bereits im \"{name}\" Clip enthalten. Möchtest du sie aus diesem Clip entfernen?"
noEmailServerWarning: "Es ist kein Email-Server konfiguriert."
thereIsUnresolvedAbuseReportWarning: "Es liegen ungelöste Meldungen vor."
recommended: "Empfehlung"
check: "Check"

View file

@ -766,7 +766,6 @@ gallery: "Gallery"
recentPosts: "Recent posts"
popularPosts: "Popular posts"
shareWithNote: "Share with note"
ads: "Advertisements"
expiration: "Deadline"
memo: "Memo"
priority: "Priority"
@ -822,7 +821,6 @@ hide: "Hide"
leaveGroup: "Leave group"
leaveGroupConfirm: "Are you sure you want to leave \"{name}\"?"
useDrawerReactionPickerForMobile: "Display reaction picker as drawer on mobile"
welcomeBackWithName: "Welcome back, {name}"
clickToFinishEmailVerification: "Please click [{ok}] to complete email verification."
overridedDeviceKind: "Device type"
smartphone: "Smartphone"
@ -852,6 +850,13 @@ typeToConfirm: "Please enter {x} to confirm"
deleteAccount: "Delete account"
numberOfPageCache: "Number of cached pages"
numberOfPageCacheDescription: "Increasing this number will improve convenience for users but cause more server load as well as more memory to be used."
file: "File"
unclip: "Unclip"
confirmToUnclipAlreadyClippedNote: "This note is already part of the \"{name}\" clip. Do you want to remove it from this clip instead?"
noEmailServerWarning: "Email server not configured."
thereIsUnresolvedAbuseReportWarning: "There are unsolved reports."
recommended: "Recommended"
check: "Check"
_emailUnavailable:
used: "This email address is already being used"
format: "The format of this email address is invalid"
@ -1206,6 +1211,7 @@ _widgets:
trends: "Trending"
clock: "Clock"
rss: "RSS reader"
rssMarquee: "RSS ticker"
activity: "Activity"
photos: "Photos"
digitalClock: "Digital clock"

View file

@ -761,7 +761,6 @@ gallery: "Galería"
recentPosts: "Posts recientes"
popularPosts: "Más vistos"
shareWithNote: "Compartir con una nota"
ads: "Anuncios"
expiration: "Termina el"
memo: "Notas"
priority: "Prioridad"

View file

@ -760,7 +760,6 @@ gallery: "Galerie"
recentPosts: "Les plus récentes"
popularPosts: "Les plus consultées"
shareWithNote: "Partager dans une note"
ads: "Publicité"
expiration: "Échéance"
memo: "Pense-bête"
priority: "Priorité"
@ -815,7 +814,6 @@ voteConfirm: "Confirmez-vous votre vote pour « {choice} » ?"
hide: "Masquer"
leaveGroup: "Quitter le groupe"
leaveGroupConfirm: "Êtes vous sûr de vouloir quitter \"{name}\" ?"
welcomeBackWithName: "Heureux de vous revoir, {name}"
clickToFinishEmailVerification: "Veuillez cliquer sur [{ok}] afin de compléter la vérification par courriel."
overridedDeviceKind: "Type dappareil"
smartphone: "Smartphone"

View file

@ -765,7 +765,6 @@ gallery: "Galeri"
recentPosts: "Postingan terbaru"
popularPosts: "Postingan populer"
shareWithNote: "Bagikan dengan catatan"
ads: "Iklan"
expiration: "Batas akhir"
memo: "Memo"
priority: "Prioritas"
@ -821,7 +820,6 @@ hide: "Sembunyikan"
leaveGroup: "Keluar grup"
leaveGroupConfirm: "Apakah kamu yakin untuk keluar dari \"{name}\"?"
useDrawerReactionPickerForMobile: "Tampilkan bilah reaksi sebagai laci di ponsel"
welcomeBackWithName: "Selamat datang kembali, {name}."
clickToFinishEmailVerification: "Mohon klik [{ok}] untuk menyelesaikan verifikasi email."
overridedDeviceKind: "Tipe perangkat"
smartphone: "Ponsel"

View file

@ -753,7 +753,6 @@ gallery: "Galleria"
recentPosts: "Le più recenti"
popularPosts: "Le più visualizzate"
shareWithNote: "Condividere in nota"
ads: "Pubblicità"
expiration: "Scadenza"
memo: "Promemoria"
priority: "Priorità"
@ -801,7 +800,6 @@ hide: "Nascondere"
leaveGroup: "Esci dal gruppo"
leaveGroupConfirm: "Uscire da「{name}」?"
useDrawerReactionPickerForMobile: "Mostra sul drawer da dispositivo mobile"
welcomeBackWithName: "Bentornato/a, {name}"
clickToFinishEmailVerification: "Fai click su [{ok}] per completare la verifica dell'indirizzo email."
indefinitely: "Non scade"
tenMinutes: "10 minuti"

View file

@ -768,7 +768,6 @@ gallery: "ギャラリー"
recentPosts: "最近の投稿"
popularPosts: "人気の投稿"
shareWithNote: "ノートで共有"
ads: "広告"
expiration: "期限"
memo: "メモ"
priority: "優先度"
@ -824,7 +823,6 @@ hide: "隠す"
leaveGroup: "グループから抜ける"
leaveGroupConfirm: "「{name}」から抜けますか?"
useDrawerReactionPickerForMobile: "モバイルデバイスのときドロワーで表示"
welcomeBackWithName: "おかえりなさい、{name}さん"
clickToFinishEmailVerification: "[{ok}]を押して、メールアドレスの確認を完了してください。"
overridedDeviceKind: "デバイスタイプ"
smartphone: "スマートフォン"
@ -1244,6 +1242,7 @@ _widgets:
trends: "トレンド"
clock: "時計"
rss: "RSSリーダー"
rssTicker: "RSSティッカー"
activity: "アクティビティ"
photos: "フォト"
digitalClock: "デジタル時計"

View file

@ -645,7 +645,6 @@ goBack: "戻る"
info: "情報"
user: "ユーザー"
administration: "管理"
ads: "広告"
expiration: "期限"
memo: "メモ"
high: "高い"

View file

@ -765,7 +765,6 @@ gallery: "갤러리"
recentPosts: "최근 포스트"
popularPosts: "인기 포스트"
shareWithNote: "노트로 공유"
ads: "광고"
expiration: "기한"
memo: "메모"
priority: "우선순위"
@ -821,7 +820,6 @@ hide: "숨기기"
leaveGroup: "그룹 나가기"
leaveGroupConfirm: "\"{name}\"에서 나갈까요?"
useDrawerReactionPickerForMobile: "모바일에서 드로어 메뉴로 표시"
welcomeBackWithName: "환영합니다, {name}님"
clickToFinishEmailVerification: "[{ok}]를 눌러 이메일 인증을 완료하세요."
overridedDeviceKind: "장치 유형"
smartphone: "스마트폰"

View file

@ -739,7 +739,6 @@ gallery: "Galeria"
recentPosts: "Ostatnie wpisy"
popularPosts: "Popularne wpisy"
shareWithNote: "Udostępnij z wpisem"
ads: "Reklamy"
expiration: "Ankieta kończy się"
memo: "Notatki"
priority: "Priorytet"

View file

@ -763,7 +763,6 @@ gallery: "Галерея"
recentPosts: "Недавние публикации"
popularPosts: "Популярные публикации"
shareWithNote: "Поделиться заметкой"
ads: "Реклама"
expiration: "Опрос длится"
memo: "Памятка"
priority: "Приоритет"
@ -819,7 +818,6 @@ hide: "Спрятать"
leaveGroup: "Покинуть группу"
leaveGroupConfirm: "Покинуть группу «{name}»?"
useDrawerReactionPickerForMobile: "Выдвижная палитра на мобильном устройстве"
welcomeBackWithName: "С возвращением, {name}!"
clickToFinishEmailVerification: "Пожалуйста, нажмите [{ok}], чтобы завершить подтверждение адреса электронной почты."
overridedDeviceKind: "Тип устройства"
smartphone: "Смартфон"

View file

@ -764,7 +764,6 @@ gallery: "Galéria"
recentPosts: "Najnovšie príspevky"
popularPosts: "Populárne príspevky"
shareWithNote: "Zdieľať s poznámkou"
ads: "Reklamy"
expiration: "Ukončiť hlasovanie"
memo: "Memo"
priority: "Priorita"
@ -820,7 +819,6 @@ hide: "Skryť"
leaveGroup: "Opustiť skupiny"
leaveGroupConfirm: "Naozaj chcete opustiť \"{name}\"?"
useDrawerReactionPickerForMobile: "Zobraziť výber reakcií ako šuflík na mobile"
welcomeBackWithName: "Vitajte späť, {name}"
clickToFinishEmailVerification: "Kliknutím na [{ok}] dokončíte overeniu emailu."
overridedDeviceKind: "Typ zariadenia"
smartphone: "Smartfón"

View file

@ -765,7 +765,6 @@ gallery: "Thư viện ảnh"
recentPosts: "Tút gần đây"
popularPosts: "Tút được xem nhiều nhất"
shareWithNote: "Chia sẻ kèm với tút"
ads: "Quảng cáo"
expiration: "Thời hạn"
memo: "Lưu ý"
priority: "Ưu tiên"
@ -821,7 +820,6 @@ hide: "Ẩn"
leaveGroup: "Rời khỏi nhóm"
leaveGroupConfirm: "Bạn có chắc muốn rời khỏi nhóm \"{name}\"?"
useDrawerReactionPickerForMobile: "Hiện bộ chọn biểu cảm dạng xổ ra trên điện thoại"
welcomeBackWithName: "Chào mừng trở lại, {name}"
clickToFinishEmailVerification: "Vui lòng nhấn [{ok}] để hoàn tất việc đăng ký."
overridedDeviceKind: "Loại thiết bị"
smartphone: "Điện thoại"

View file

@ -765,7 +765,6 @@ gallery: "图库"
recentPosts: "最新发布"
popularPosts: "热门投稿"
shareWithNote: "在帖子中分享"
ads: "广告"
expiration: "截止时间"
memo: "便笺"
priority: "优先级"
@ -821,7 +820,6 @@ hide: "隐藏"
leaveGroup: "离开群组"
leaveGroupConfirm: "确定离开「{name}」?"
useDrawerReactionPickerForMobile: "在移动设备上使用抽屉显示"
welcomeBackWithName: "欢迎回来,{name}"
clickToFinishEmailVerification: "点击 [{ok}] 完成电子邮件地址认证。"
overridedDeviceKind: "设备类型"
smartphone: "智能手机"

View file

@ -765,7 +765,6 @@ gallery: "相簿"
recentPosts: "最新貼文"
popularPosts: "熱門的貼文"
shareWithNote: "在貼文中分享"
ads: "廣告"
expiration: "期限"
memo: "備忘錄"
priority: "優先級"
@ -821,7 +820,6 @@ hide: "隱藏"
leaveGroup: "離開群組"
leaveGroupConfirm: "確定離開「{name}」?"
useDrawerReactionPickerForMobile: "在移動設備上使用抽屜顯示"
welcomeBackWithName: "歡迎回來,{name}"
clickToFinishEmailVerification: "點擊 [{ok}] 完成電子郵件地址認證。"
overridedDeviceKind: "裝置類型"
smartphone: "智慧型手機"

View file

@ -1,10 +1,9 @@
{
"name": "misskey",
"name": "foundkey",
"version": "12.111.1-test.1",
"codename": "indigo",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
"url": "https://akkoma.dev/FoundKeyGang/FoundKey.git"
},
"private": true,
"scripts": {
@ -41,10 +40,10 @@
"devDependencies": {
"@types/gulp": "4.0.9",
"@types/gulp-rename": "2.0.1",
"@typescript-eslint/parser": "5.27.1",
"@typescript-eslint/parser": "5.30.0",
"cross-env": "7.0.3",
"cypress": "10.0.3",
"cypress": "10.3.0",
"start-server-and-test": "1.14.0",
"typescript": "4.7.3"
"typescript": "4.7.4"
}
}

View file

@ -0,0 +1,12 @@
export class removeAds1657570176749 {
name = 'removeAds1657570176749'
async up(queryRunner) {
await queryRunner.query(`DROP TABLE "ad"`);
}
async down(queryRunner) {
await queryRunner.query(`CREATE TABLE public.ad ("id" character varying(32) NOT NULL, "createdAt" timestamp with time zone NOT NULL, "expiresAt" timestamp with time zone NOT NULL, "place" character varying(32) NOT NULL, "priority" character varying(32) NOT NULL, "url" character varying(1024) NOT NULL, "imageUrl" character varying(1024) NOT NULL, "memo" character varying(8192) NOT NULL, "ratio" integer DEFAULT 1 NOT NULL)`);
}
}

View file

@ -0,0 +1,13 @@
export class removeRepoUrl1658146000392 {
name = 'removeRepoUrl1658146000392';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "repositoryUrl"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "feedbackUrl"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "repositoryUrl" character varying(512) not null default 'https://github.com/misskey-dev/misskey'`);
await queryRunner.query(`ALTER TABLE "meta" ADD "feedbackUrl" character varying(512) default 'https://github.com/misskey-dev/misskey/issues/new'`);
}
}

View file

@ -5,7 +5,7 @@
"scripts": {
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
"lint": "eslint --quiet \"src/**/*.ts\"",
"lint": "eslint --quiet src --ext .ts",
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
"test": "npm run mocha"
},
@ -14,7 +14,7 @@
"lodash": "^4.17.21"
},
"dependencies": {
"@bull-board/koa": "3.11.1",
"@bull-board/koa": "4.0.0",
"@discordapp/twemoji": "14.0.2",
"@elastic/elasticsearch": "7.11.0",
"@koa/cors": "3.1.0",
@ -28,10 +28,10 @@
"archiver": "5.3.1",
"autobind-decorator": "2.4.0",
"autwh": "0.1.0",
"aws-sdk": "2.1152.0",
"aws-sdk": "2.1165.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.5",
"bull": "4.8.3",
"bull": "4.8.4",
"cacheable-lookup": "6.0.4",
"cbor": "8.1.0",
"chalk": "5.0.1",
@ -51,7 +51,7 @@
"ip-cidr": "3.0.10",
"is-svg": "4.3.2",
"js-yaml": "4.1.0",
"jsdom": "19.0.0",
"jsdom": "20.0.0",
"json5": "2.2.1",
"json5-loader": "4.0.1",
"jsonld": "6.0.0",
@ -69,29 +69,29 @@
"mime-types": "2.1.35",
"misskey-js": "0.0.14",
"mocha": "10.0.0",
"ms": "3.0.0-canary.1",
"multer": "1.4.4",
"nested-property": "4.0.0",
"node-fetch": "3.2.6",
"nodemailer": "6.7.5",
"nodemailer": "6.7.6",
"os-utils": "0.0.14",
"parse5": "6.0.1",
"parse5": "7.0.0",
"pg": "8.7.3",
"private-ip": "2.3.3",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
"pug": "3.0.2",
"punycode": "2.1.1",
"pureimage": "0.3.8",
"pureimage": "0.3.14",
"qrcode": "1.5.0",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.17.4",
"re2": "1.17.7",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",
"require-all": "3.0.0",
"rndstr": "1.0.0",
"rss-parser": "3.12.0",
"s-age": "1.1.2",
"sanitize-html": "2.7.0",
"semver": "7.3.7",
@ -102,16 +102,15 @@
"style-loader": "3.3.1",
"summaly": "2.6.0",
"syslog-pro": "1.0.0",
"systeminformation": "5.11.16",
"systeminformation": "5.11.22",
"tinycolor2": "1.4.2",
"tmp": "0.2.1",
"ts-loader": "9.3.0",
"ts-loader": "9.3.1",
"ts-node": "10.8.1",
"tsc-alias": "1.6.9",
"tsc-alias": "1.6.11",
"tsconfig-paths": "4.0.0",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.6",
"ulid": "2.3.0",
"typeorm": "0.3.7",
"unzipper": "0.10.11",
"uuid": "8.3.2",
"web-push": "3.5.0",
@ -121,7 +120,6 @@
},
"devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.97",
"@types/semver": "7.3.9",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.8",
"@types/cbor": "6.0.0",
@ -144,11 +142,10 @@
"@types/koa__multer": "2.0.4",
"@types/koa__router": "8.0.11",
"@types/mocha": "9.1.1",
"@types/node": "17.0.41",
"@types/node": "18.0.0",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.4",
"@types/oauth": "0.9.1",
"@types/parse5": "6.0.3",
"@types/pug": "2.0.6",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.4.2",
@ -157,7 +154,8 @@
"@types/redis": "4.0.11",
"@types/rename": "1.0.4",
"@types/sanitize-html": "2.6.2",
"@types/sharp": "0.30.2",
"@types/semver": "7.3.10",
"@types/sharp": "0.30.4",
"@types/sinonjs__fake-timers": "8.1.2",
"@types/speakeasy": "2.0.7",
"@types/tinycolor2": "1.4.3",
@ -166,12 +164,12 @@
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "5.27.1",
"@typescript-eslint/parser": "5.27.1",
"typescript": "4.7.3",
"eslint": "8.17.0",
"eslint-plugin-import": "2.26.0",
"@typescript-eslint/eslint-plugin": "^5.30.0",
"@typescript-eslint/parser": "^5.30.0",
"cross-env": "7.0.3",
"execa": "6.1.0"
"eslint": "^8.20.0",
"eslint-plugin-import": "^2.26.0",
"execa": "6.1.0",
"typescript": "^4.7.4"
}
}

View file

@ -28,6 +28,8 @@ export default function load() {
const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/_client_dist_/manifest.json`, 'utf-8'));
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
if (config.id && config.id !== 'aid') throw new Error('Unsupported ID algorithm. Only "aid" is supported.');
const mixin = {} as Mixin;
const url = tryCreateUrl(config.url);

View file

@ -1,5 +1,10 @@
export const MAX_NOTE_TEXT_LENGTH = 3000;
export const SECOND = 1000;
export const MINUTE = 60 * SECOND;
export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days

View file

@ -65,7 +65,6 @@ import { Channel } from '@/models/entities/channel.js';
import { ChannelFollowing } from '@/models/entities/channel-following.js';
import { ChannelNotePining } from '@/models/entities/channel-note-pining.js';
import { RegistryItem } from '@/models/entities/registry-item.js';
import { Ad } from '@/models/entities/ad.js';
import { PasswordResetRequest } from '@/models/entities/password-reset-request.js';
import { UserPending } from '@/models/entities/user-pending.js';
@ -168,7 +167,6 @@ export const entities = [
ChannelFollowing,
ChannelNotePining,
RegistryItem,
Ad,
PasswordResetRequest,
UserPending,
Webhook,

View file

@ -1,6 +1,8 @@
import * as parse5 from 'parse5';
import treeAdapter from 'parse5/lib/tree-adapters/default.js';
import { URL } from 'node:url';
import * as parse5 from 'parse5';
import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js';
const treeAdapter = TreeAdapter.defaultTreeAdapter;
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
@ -19,7 +21,7 @@ export function fromHtml(html: string, hashtagNames?: string[]): string {
return text.trim();
function getText(node: parse5.Node): string {
function getText(node: TreeAdapter.Node): string {
if (treeAdapter.isTextNode(node)) return node.value;
if (!treeAdapter.isElementNode(node)) return '';
if (node.nodeName === 'br') return '\n';
@ -31,7 +33,7 @@ export function fromHtml(html: string, hashtagNames?: string[]): string {
return '';
}
function appendChildren(childNodes: parse5.ChildNode[]): void {
function appendChildren(childNodes: TreeAdapter.ChildNode[]): void {
if (childNodes) {
for (const n of childNodes) {
analyze(n);
@ -39,7 +41,7 @@ export function fromHtml(html: string, hashtagNames?: string[]): string {
}
}
function analyze(node: parse5.Node) {
function analyze(node: TreeAdapter.Node) {
if (treeAdapter.isTextNode(node)) {
text += node.value;
return;
@ -170,7 +172,7 @@ export function fromHtml(html: string, hashtagNames?: string[]): string {
const t = getText(node);
if (t) {
text += '\n> ';
text += t.split('\n').join(`\n> `);
text += t.split('\n').join('\n> ');
}
break;
}

View file

@ -1,21 +1,23 @@
import { ulid } from 'ulid';
import { genAid } from './id/aid.js';
import { genMeid } from './id/meid.js';
import { genMeidg } from './id/meidg.js';
import { genObjectId } from './id/object-id.js';
import config from '@/config/index.js';
import * as crypto from 'node:crypto';
const metohd = config.id.toLowerCase();
// AID generation
// 8 chars: milliseconds elapsed since 2000-01-01 00:00:00.000Z encoded as base36
// + 2 random chars
const TIME2000 = 946684800000;
let counter = crypto.randomBytes(2).readUInt16LE(0);
export function genId(date?: Date): string {
if (!date || (date > new Date())) date = new Date();
switch (metohd) {
case 'aid': return genAid(date);
case 'meid': return genMeid(date);
case 'meidg': return genMeidg(date);
case 'ulid': return ulid(date.getTime());
case 'objectid': return genObjectId(date);
default: throw new Error('unrecognized id generation method');
}
let t = date.getTime();
t -= TIME2000;
if (t < 0) t = 0;
if (isNaN(t)) throw 'Failed to create AID: Invalid Date';
const time = t.toString(36).padStart(8, '0');
counter++;
const noise = counter.toString(36).padStart(2, '0').slice(-2);
return time + noise;
}

View file

@ -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 (isNaN(t)) throw 'Failed to create AID: Invalid Date';
counter++;
return getTime(t) + getNoise();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -1,59 +0,0 @@
import { Entity, Index, Column, PrimaryColumn } from 'typeorm';
import { id } from '../id.js';
@Entity()
export class Ad {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the Ad.',
})
public createdAt: Date;
@Index()
@Column('timestamp with time zone', {
comment: 'The expired date of the Ad.',
})
public expiresAt: Date;
@Column('varchar', {
length: 32, nullable: false,
})
public place: string;
// 今は使われていないが将来的に活用される可能性はある
@Column('varchar', {
length: 32, nullable: false,
})
public priority: string;
@Column('integer', {
default: 1, nullable: false,
})
public ratio: number;
@Column('varchar', {
length: 1024, nullable: false,
})
public url: string;
@Column('varchar', {
length: 1024, nullable: false,
})
public imageUrl: string;
@Column('varchar', {
length: 8192, nullable: false,
})
public memo: string;
constructor(data: Partial<Ad>) {
if (data == null) return;
for (const [k, v] of Object.entries(data)) {
(this as any)[k] = v;
}
}
}

View file

@ -316,20 +316,6 @@ export class Meta {
})
public ToSUrl: string | null;
@Column('varchar', {
length: 512,
default: 'https://github.com/misskey-dev/misskey',
nullable: false,
})
public repositoryUrl: string;
@Column('varchar', {
length: 512,
default: 'https://github.com/misskey-dev/misskey/issues/new',
nullable: true,
})
public feedbackUrl: string | null;
@Column('varchar', {
length: 8192,
nullable: true,

View file

@ -59,7 +59,6 @@ import { MutedNote } from './entities/muted-note.js';
import { ChannelFollowing } from './entities/channel-following.js';
import { ChannelNotePining } from './entities/channel-note-pining.js';
import { RegistryItem } from './entities/registry-item.js';
import { Ad } from './entities/ad.js';
import { PasswordResetRequest } from './entities/password-reset-request.js';
import { UserPending } from './entities/user-pending.js';
import { InstanceRepository } from './repositories/instance.js';
@ -126,5 +125,4 @@ export const ChannelFollowings = db.getRepository(ChannelFollowing);
export const ChannelNotePinings = db.getRepository(ChannelNotePining);
export const RegistryItems = db.getRepository(RegistryItem);
export const Webhooks = db.getRepository(Webhook);
export const Ads = db.getRepository(Ad);
export const PasswordResetRequests = db.getRepository(PasswordResetRequest);

View file

@ -12,13 +12,15 @@ export async function readNotification(
if (notificationIds.length === 0) return;
// Update documents
await Notifications.update({
const result = await Notifications.update({
id: In(notificationIds),
isRead: false,
}, {
isRead: true,
});
if (result.affected === 0) return;
if (!await Users.getHasUnreadNotification(userId)) return postReadAllNotifications(userId);
else return postReadNotifications(userId, notificationIds);
}
@ -27,7 +29,7 @@ export async function readNotificationByQuery(
userId: User['id'],
query: Record<string, any>
) {
const notificationIds = await Notifications.find({
const notificationIds = await Notifications.findBy({
...query,
notifieeId: userId,
isRead: false,

View file

@ -4,10 +4,6 @@ import * as ep___admin_meta from './endpoints/admin/meta.js';
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
@ -311,16 +307,13 @@ import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by
import * as ep___users_search from './endpoints/users/search.js';
import * as ep___users_show from './endpoints/users/show.js';
import * as ep___users_stats from './endpoints/users/stats.js';
import * as ep___fetchRss from './endpoints/fetch-rss.js';
const eps = [
['admin/meta', ep___admin_meta],
['admin/abuse-user-reports', ep___admin_abuseUserReports],
['admin/accounts/create', ep___admin_accounts_create],
['admin/accounts/delete', ep___admin_accounts_delete],
['admin/ad/create', ep___admin_ad_create],
['admin/ad/delete', ep___admin_ad_delete],
['admin/ad/list', ep___admin_ad_list],
['admin/ad/update', ep___admin_ad_update],
['admin/announcements/create', ep___admin_announcements_create],
['admin/announcements/delete', ep___admin_announcements_delete],
['admin/announcements/list', ep___admin_announcements_list],
@ -624,6 +617,7 @@ const eps = [
['users/search', ep___users_search],
['users/show', ep___users_show],
['users/stats', ep___users_stats],
['fetch-rss', ep___fetchRss],
];
export interface IEndpointMeta {

View file

@ -1,39 +0,0 @@
import define from '../../../define.js';
import { Ads } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
} as const;
export const paramDef = {
type: 'object',
properties: {
url: { type: 'string', minLength: 1 },
memo: { type: 'string' },
place: { type: 'string' },
priority: { type: 'string' },
ratio: { type: 'integer' },
expiresAt: { type: 'integer' },
imageUrl: { type: 'string', minLength: 1 },
},
required: ['url', 'memo', 'place', 'priority', 'ratio', 'expiresAt', 'imageUrl'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => {
await Ads.insert({
id: genId(),
createdAt: new Date(),
expiresAt: new Date(ps.expiresAt),
url: ps.url,
imageUrl: ps.imageUrl,
priority: ps.priority,
ratio: ps.ratio,
place: ps.place,
memo: ps.memo,
});
});

View file

@ -1,35 +0,0 @@
import define from '../../../define.js';
import { Ads } from '@/models/index.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
errors: {
noSuchAd: {
message: 'No such ad.',
code: 'NO_SUCH_AD',
id: 'ccac9863-3a03-416e-b899-8a64041118b1',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
id: { type: 'string', format: 'misskey:id' },
},
required: ['id'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
const ad = await Ads.findOneBy({ id: ps.id });
if (ad == null) throw new ApiError(meta.errors.noSuchAd);
await Ads.delete(ad.id);
});

View file

@ -1,30 +0,0 @@
import define from '../../../define.js';
import { Ads } from '@/models/index.js';
import { makePaginationQuery } from '../../../common/make-pagination-query.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
} as const;
export const paramDef = {
type: 'object',
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
},
required: [],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => {
const query = makePaginationQuery(Ads.createQueryBuilder('ad'), ps.sinceId, ps.untilId)
.andWhere('ad.expiresAt > :now', { now: new Date() });
const ads = await query.take(ps.limit).getMany();
return ads;
});

View file

@ -1,50 +0,0 @@
import define from '../../../define.js';
import { Ads } from '@/models/index.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
errors: {
noSuchAd: {
message: 'No such ad.',
code: 'NO_SUCH_AD',
id: 'b7aa1727-1354-47bc-a182-3a9c3973d300',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
id: { type: 'string', format: 'misskey:id' },
memo: { type: 'string' },
url: { type: 'string', minLength: 1 },
imageUrl: { type: 'string', minLength: 1 },
place: { type: 'string' },
priority: { type: 'string' },
ratio: { type: 'integer' },
expiresAt: { type: 'integer' },
},
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
const ad = await Ads.findOneBy({ id: ps.id });
if (ad == null) throw new ApiError(meta.errors.noSuchAd);
await Ads.update(ad.id, {
url: ps.url,
place: ps.place,
priority: ps.priority,
ratio: ps.ratio,
memo: ps.memo,
imageUrl: ps.imageUrl,
expiresAt: new Date(ps.expiresAt),
});
});

View file

@ -97,30 +97,6 @@ export const meta = {
},
},
},
ads: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
place: {
type: 'string',
optional: false, nullable: false,
},
url: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
imageUrl: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
},
},
},
enableEmail: {
type: 'boolean',
optional: false, nullable: false,
@ -318,8 +294,6 @@ export default define(meta, paramDef, async (ps, me) => {
description: instance.description,
langs: instance.langs,
tosUrl: instance.ToSUrl,
repositoryUrl: instance.repositoryUrl,
feedbackUrl: instance.feedbackUrl,
disableRegistration: instance.disableRegistration,
disableLocalTimeline: instance.disableLocalTimeline,
disableGlobalTimeline: instance.disableGlobalTimeline,

View file

@ -78,8 +78,6 @@ export const paramDef = {
swPublicKey: { type: 'string', nullable: true },
swPrivateKey: { type: 'string', nullable: true },
tosUrl: { type: 'string', nullable: true },
repositoryUrl: { type: 'string' },
feedbackUrl: { type: 'string' },
useObjectStorage: { type: 'boolean' },
objectStorageBaseUrl: { type: 'string', nullable: true },
objectStorageBucket: { type: 'string', nullable: true },
@ -313,14 +311,6 @@ export default define(meta, paramDef, async (ps, me) => {
set.ToSUrl = ps.tosUrl;
}
if (ps.repositoryUrl !== undefined) {
set.repositoryUrl = ps.repositoryUrl;
}
if (ps.feedbackUrl !== undefined) {
set.feedbackUrl = ps.feedbackUrl;
}
if (ps.useObjectStorage !== undefined) {
set.useObjectStorage = ps.useObjectStorage;
}

View file

@ -1,6 +1,6 @@
import define from '../../define.js';
import Resolver from '@/remote/activitypub/resolver.js';
import ms from 'ms';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['federation'],
@ -8,7 +8,7 @@ export const meta = {
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 30,
},

View file

@ -11,8 +11,8 @@ import { Note } from '@/models/entities/note.js';
import { CacheableLocalUser, User } from '@/models/entities/user.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { isActor, isPost, getApId } from '@/remote/activitypub/type.js';
import ms from 'ms';
import { SchemaType } from '@/misc/schema.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['federation'],
@ -20,7 +20,7 @@ export const meta = {
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 30,
},

View file

@ -1,15 +1,15 @@
import ms from 'ms';
import create from '@/services/blocking/create.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getUser } from '../../common/getters.js';
import { Blockings, NoteWatchings, Users } from '@/models/index.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['account'],
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 100,
},

View file

@ -1,15 +1,15 @@
import ms from 'ms';
import deleteBlocking from '@/services/blocking/delete.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getUser } from '../../common/getters.js';
import { Blockings, Users } from '@/models/index.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['account'],
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 100,
},

View file

@ -1,10 +1,10 @@
import ms from 'ms';
import { addFile } from '@/services/drive/add-file.js';
import define from '../../../define.js';
import { apiLogger } from '../../../logger.js';
import { ApiError } from '../../../error.js';
import { DriveFiles } from '@/models/index.js';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['drive'],
@ -12,7 +12,7 @@ export const meta = {
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 120,
},

View file

@ -1,14 +1,14 @@
import ms from 'ms';
import { uploadFromUrl } from '@/services/drive/upload-from-url.js';
import define from '../../../define.js';
import { DriveFiles } from '@/models/index.js';
import { publishMainStream } from '@/services/stream.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['drive'],
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 60,
},

View file

@ -1,12 +1,12 @@
import ms from 'ms';
import { createExportCustomEmojisJob } from '@/queue/index.js';
import define from '../define.js';
import { HOUR } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 1,
},
} as const;

View file

@ -0,0 +1,39 @@
import Parser from 'rss-parser';
import { getResponse } from '@/misc/fetch.js';
import config from '@/config/index.js';
import define from '../define.js';
const rssParser = new Parser();
export const meta = {
tags: ['meta'],
requireCredential: false,
allowGet: true,
cacheSec: 60 * 3,
} as const;
export const paramDef = {
type: 'object',
properties: {
url: { type: 'string' },
},
required: ['url'],
} as const;
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps) => {
const res = await getResponse({
url: ps.url,
method: 'GET',
headers: Object.assign({
'User-Agent': config.userAgent,
Accept: 'application/rss+xml, */*',
}),
timeout: 5000,
});
const text = await res.text();
return rssParser.parseString(text);
});

View file

@ -1,16 +1,16 @@
import ms from 'ms';
import create from '@/services/following/create.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getUser } from '../../common/getters.js';
import { Followings, Users } from '@/models/index.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['following', 'users'],
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 100,
},

View file

@ -1,15 +1,15 @@
import ms from 'ms';
import deleteFollowing from '@/services/following/delete.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getUser } from '../../common/getters.js';
import { Followings, Users } from '@/models/index.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['following', 'users'],
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 100,
},

View file

@ -1,15 +1,15 @@
import ms from 'ms';
import deleteFollowing from '@/services/following/delete.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { getUser } from '../../common/getters.js';
import { Followings, Users } from '@/models/index.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['following', 'users'],
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 100,
},

View file

@ -1,9 +1,9 @@
import ms from 'ms';
import define from '../../../define.js';
import { DriveFiles, GalleryPosts } from '@/models/index.js';
import { genId } from '../../../../../misc/gen-id.js';
import { GalleryPost } from '@/models/entities/gallery-post.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['gallery'],
@ -13,7 +13,7 @@ export const meta = {
kind: 'write:gallery',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 300,
},

View file

@ -1,7 +1,7 @@
import ms from 'ms';
import define from '../../../define.js';
import { DriveFiles, GalleryPosts } from '@/models/index.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['gallery'],
@ -11,7 +11,7 @@ export const meta = {
kind: 'write:gallery',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 300,
},

View file

@ -1,12 +1,12 @@
import define from '../../define.js';
import { createExportBlockingJob } from '@/queue/index.js';
import ms from 'ms';
import { HOUR } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 1,
},
} as const;

View file

@ -1,12 +1,12 @@
import define from '../../define.js';
import { createExportFollowingJob } from '@/queue/index.js';
import ms from 'ms';
import { HOUR } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 1,
},
} as const;

View file

@ -1,12 +1,12 @@
import define from '../../define.js';
import { createExportMuteJob } from '@/queue/index.js';
import ms from 'ms';
import { HOUR } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 1,
},
} as const;

View file

@ -1,12 +1,12 @@
import define from '../../define.js';
import { createExportNotesJob } from '@/queue/index.js';
import ms from 'ms';
import { DAY } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1day'),
duration: DAY,
max: 1,
},
} as const;

View file

@ -1,12 +1,12 @@
import define from '../../define.js';
import { createExportUserListsJob } from '@/queue/index.js';
import ms from 'ms';
import { MINUTE } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1min'),
duration: MINUTE,
max: 1,
},
} as const;

View file

@ -1,15 +1,15 @@
import define from '../../define.js';
import { createImportBlockingJob } from '@/queue/index.js';
import ms from 'ms';
import { ApiError } from '../../error.js';
import { DriveFiles } from '@/models/index.js';
import { HOUR } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 1,
},

View file

@ -1,14 +1,14 @@
import define from '../../define.js';
import { createImportFollowingJob } from '@/queue/index.js';
import ms from 'ms';
import { ApiError } from '../../error.js';
import { DriveFiles } from '@/models/index.js';
import { HOUR } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1hour'),
duratition: HOUR,
max: 1,
},

View file

@ -1,15 +1,15 @@
import define from '../../define.js';
import { createImportMutingJob } from '@/queue/index.js';
import ms from 'ms';
import { ApiError } from '../../error.js';
import { DriveFiles } from '@/models/index.js';
import { HOUR } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 1,
},

View file

@ -1,14 +1,14 @@
import define from '../../define.js';
import { createImportUserListsJob } from '@/queue/index.js';
import ms from 'ms';
import { ApiError } from '../../error.js';
import { DriveFiles } from '@/models/index.js';
import { HOUR } from '@/const.js';
export const meta = {
secure: true,
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 1,
},

View file

@ -2,12 +2,12 @@ import { publishMainStream } from '@/services/stream.js';
import define from '../../define.js';
import rndstr from 'rndstr';
import config from '@/config/index.js';
import ms from 'ms';
import bcrypt from 'bcryptjs';
import { Users, UserProfiles } from '@/models/index.js';
import { sendEmail } from '@/services/send-email.js';
import { ApiError } from '../../error.js';
import { validateEmailForAccount } from '@/services/validate-email-for-account.js';
import { HOUR } from '@/const.js';
export const meta = {
requireCredential: true,
@ -15,7 +15,7 @@ export const meta = {
secure: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 3,
},

View file

@ -1,8 +1,8 @@
import define from '../../../define.js';
import ms from 'ms';
import { ApiError } from '../../../error.js';
import { MessagingMessages } from '@/models/index.js';
import { deleteMessage } from '@/services/messages/delete.js';
import { SECOND, HOUR } from '@/const.js';
export const meta = {
tags: ['messaging'],
@ -12,9 +12,9 @@ export const meta = {
kind: 'write:messaging',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 300,
minInterval: ms('1sec'),
minInterval: SECOND,
},
errors: {

View file

@ -1,7 +1,7 @@
import { IsNull, MoreThan } from 'typeorm';
import config from '@/config/index.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { Ads, Emojis, Users } from '@/models/index.js';
import { Emojis, Users } from '@/models/index.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import define from '../define.js';
@ -53,16 +53,6 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
repositoryUrl: {
type: 'string',
optional: false, nullable: false,
default: 'https://github.com/misskey-dev/misskey',
},
feedbackUrl: {
type: 'string',
optional: false, nullable: false,
default: 'https://github.com/misskey-dev/misskey/issues/new',
},
defaultDarkTheme: {
type: 'string',
optional: false, nullable: true,
@ -168,30 +158,6 @@ export const meta = {
},
},
},
ads: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
place: {
type: 'string',
optional: false, nullable: false,
},
url: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
imageUrl: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
},
},
},
requireSetup: {
type: 'boolean',
optional: false, nullable: false,
@ -310,12 +276,6 @@ export default define(meta, paramDef, async (ps, me) => {
},
});
const ads = await Ads.find({
where: {
expiresAt: MoreThan(new Date()),
},
});
const response: any = {
maintainerName: instance.maintainerName,
maintainerEmail: instance.maintainerEmail,
@ -327,8 +287,6 @@ export default define(meta, paramDef, async (ps, me) => {
description: instance.description,
langs: instance.langs,
tosUrl: instance.ToSUrl,
repositoryUrl: instance.repositoryUrl,
feedbackUrl: instance.feedbackUrl,
disableRegistration: instance.disableRegistration,
disableLocalTimeline: instance.disableLocalTimeline,
disableGlobalTimeline: instance.disableGlobalTimeline,
@ -349,13 +307,6 @@ export default define(meta, paramDef, async (ps, me) => {
emojis: await Emojis.packMany(emojis),
defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme,
ads: ads.map(ad => ({
id: ad.id,
url: ad.url,
place: ad.place,
ratio: ad.ratio,
imageUrl: ad.imageUrl,
})),
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,

View file

@ -1,4 +1,3 @@
import ms from 'ms';
import { In } from 'typeorm';
import create from '@/services/note/create.js';
import { User } from '@/models/entities/user.js';
@ -10,6 +9,7 @@ import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { noteVisibilities } from '../../../../types.js';
import { ApiError } from '../../error.js';
import define from '../../define.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['notes'],
@ -17,7 +17,7 @@ export const meta = {
requireCredential: true,
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 300,
},

View file

@ -1,9 +1,9 @@
import ms from 'ms';
import deleteNote from '@/services/note/delete.js';
import { Users } from '@/models/index.js';
import define from '../../define.js';
import { getNote } from '../../common/getters.js';
import { ApiError } from '../../error.js';
import { SECOND, HOUR } from '@/const.js';
export const meta = {
tags: ['notes'],
@ -13,9 +13,9 @@ export const meta = {
kind: 'write:notes',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 300,
minInterval: ms('1sec'),
minInterval: SECOND,
},
errors: {

View file

@ -60,12 +60,21 @@ export default define(meta, paramDef, async (ps, user) => {
query.setParameters(mutingQuery.getParameters());
//#endregion
const polls = await query.take(ps.limit).skip(ps.offset).getMany();
const polls = await query
.orderBy('poll.noteId', 'DESC')
.take(ps.limit)
.skip(ps.offset)
.getMany();
if (polls.length === 0) return [];
const notes = await Notes.findBy({
const notes = await Notes.find({
where: {
id: In(polls.map(poll => poll.noteId)),
},
order: {
createdAt: 'DESC',
},
});
return await Notes.packMany(notes, user, {

View file

@ -8,6 +8,9 @@ export const meta = {
requireCredential: false,
allowGet: true,
cacheSec: 60,
res: {
type: 'array',
optional: false, nullable: false,

View file

@ -1,8 +1,8 @@
import ms from 'ms';
import deleteReaction from '@/services/note/reaction/delete.js';
import define from '../../../define.js';
import { getNote } from '../../../common/getters.js';
import { ApiError } from '../../../error.js';
import { SECOND, HOUR } from '@/const.js';
export const meta = {
tags: ['reactions', 'notes'],
@ -12,9 +12,9 @@ export const meta = {
kind: 'write:reactions',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 60,
minInterval: ms('3sec'),
minInterval: 3 * SECOND,
},
errors: {

View file

@ -1,9 +1,9 @@
import ms from 'ms';
import deleteNote from '@/services/note/delete.js';
import { Notes, Users } from '@/models/index.js';
import define from '../../define.js';
import { getNote } from '../../common/getters.js';
import { ApiError } from '../../error.js';
import { SECOND, HOUR } from '@/const.js';
export const meta = {
tags: ['notes'],
@ -13,9 +13,9 @@ export const meta = {
kind: 'write:notes',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 300,
minInterval: ms('1sec'),
minInterval: SECOND,
},
errors: {

View file

@ -1,9 +1,9 @@
import ms from 'ms';
import { Pages, DriveFiles } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import { Page } from '@/models/entities/page.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['pages'],
@ -13,7 +13,7 @@ export const meta = {
kind: 'write:pages',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 300,
},

View file

@ -1,8 +1,8 @@
import ms from 'ms';
import { Not } from 'typeorm';
import { Pages, DriveFiles } from '@/models/index.js';
import define from '../../define.js';
import { ApiError } from '../../error.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['pages'],
@ -12,7 +12,7 @@ export const meta = {
kind: 'write:pages',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 300,
},

View file

@ -1,11 +1,11 @@
import rndstr from 'rndstr';
import ms from 'ms';
import { IsNull } from 'typeorm';
import config from '@/config/index.js';
import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js';
import { sendEmail } from '@/services/send-email.js';
import { genId } from '@/misc/gen-id.js';
import define from '../define.js';
import { HOUR } from '@/const.js';
export const meta = {
tags: ['reset password'],
@ -15,7 +15,7 @@ export const meta = {
description: 'Request a users password to be reset.',
limit: {
duration: ms('1hour'),
duration: HOUR,
max: 3,
},

View file

@ -1,8 +1,8 @@
import ms from 'ms';
import { Users, Followings } from '@/models/index.js';
import define from '../../define.js';
import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query.js';
import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../common/generate-block-query.js';
import { DAY } from '@/const.js';
export const meta = {
tags: ['users'],
@ -39,7 +39,7 @@ export default define(meta, paramDef, async (ps, me) => {
.where('user.isLocked = FALSE')
.andWhere('user.isExplorable = TRUE')
.andWhere('user.host IS NULL')
.andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) })
.andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - (7 * DAY)) })
.andWhere('user.id != :meId', { meId: me.id })
.orderBy('user.followersCount', 'DESC');

View file

@ -51,7 +51,7 @@ export default class extends Channel {
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (iUserRelated(note, this.muting)) return;
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;

View file

@ -11,10 +11,10 @@ const router = new Router();
const nodeinfo2_1path = '/nodeinfo/2.1';
const nodeinfo2_0path = '/nodeinfo/2.0';
export const links = [/* (awaiting release) {
export const links = [{
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
href: config.url + nodeinfo2_1path
}, */{
}, {
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
href: config.url + nodeinfo2_0path,
}];
@ -39,9 +39,9 @@ const nodeinfo2 = async () => {
return {
software: {
name: 'misskey',
name: 'foundkey',
version: config.version,
repository: meta.repositoryUrl,
repository: 'https://akkoma.dev/FoundKeyGang/FoundKey',
},
protocols: ['activitypub'],
services: {
@ -64,7 +64,7 @@ const nodeinfo2 = async () => {
langs: meta.langs,
tosUrl: meta.ToSUrl,
repositoryUrl: meta.repositoryUrl,
feedbackUrl: meta.feedbackUrl,
feedbackUrl: 'ircs://irc.akkoma.dev/foundkey',
disableRegistration: meta.disableRegistration,
disableLocalTimeline: meta.disableLocalTimeline,
disableGlobalTimeline: meta.disableGlobalTimeline,

View file

@ -106,15 +106,39 @@
function renderError(code, details) {
let errorsElement = document.getElementById('errors');
if (!errorsElement) {
document.getElementsByTagName("head")[0].insertAdjacentHTML(
"beforeend",
`<link rel="stylesheet" href="../error.css" />`);
document.documentElement.innerHTML = `
<h1> An error has occurred. </h1>
<p>If the problem persists, please contact the administrator. You may also try the following options:</p>
<ul>
<li>Start <a href="/cli">the simple client</a></li>
<li>Attempt to repair in <a href="/bios">BIOS</a></li>
<li><a href="/flush">Flush preferences and cache</a></li>
</ul>
<hr>
<svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-triangle" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 9v2m0 4v.01"></path>
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
</svg>
<h1>An error has occurred!</h1>
<button class="button-big" onclick="location.reload(true);">
<span class="button-label-big">Refresh</span>
</button>
<p class="dont-worry">Don't worry, it's (probably) not your fault.</p>
<p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p>
<a href="/flush">
<button class="button-small">
<span class="button-label-small">Flush preferences and cache</span>
</button>
</a>
<br>
<a href="/cli">
<button class="button-small">
<span class="button-label-small">Start the simple client</span>
</button>
</a>
<br>
<a href="/bios">
<button class="button-small">
<span class="button-label-small">Attempt to repair in Repair Tool</span>
</button>
</a>
<br>
<div id="errors"></div>
`;
@ -122,8 +146,7 @@
}
const detailsElement = document.createElement('details');
detailsElement.innerHTML = `<summary><code>ERROR CODE: ${code}</code></summary>${JSON.stringify(details)}`;
detailsElement.innerHTML = `<br><summary><code>ERROR CODE: ${code}</code></summary>${JSON.stringify(details)}`;
errorsElement.appendChild(detailsElement);
}

View file

@ -0,0 +1,98 @@
* {
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
}
body,
html {
background-color: #222;
color: #dfddcc;
justify-content: center;
margin: auto;
width: 80%;
padding: 10px;
text-align: center;
}
button {
border-radius: 999px;
padding: 0px 12px 0px 12px;
border: none;
cursor: pointer;
margin-bottom: 12px;
}
.button-big {
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
line-height: 50px;
}
.button-big:hover {
background: rgb(153, 204, 0);
}
.button-small {
background: #444;
line-height: 40px;
}
.button-small:hover {
background: #555;
}
.button-label-big {
color: #222;
font-weight: bold;
font-size: 20px;
padding: 12px;
}
.button-label-small {
color: rgb(153, 204, 0);
font-size: 16px;
padding: 12px;
}
a {
color: rgb(134, 179, 0);
text-decoration: none;
}
p,
li {
font-size: 16px;
}
.dont-worry,
#msg {
font-size: 18px;
}
.icon-warning {
color: #dec340;
height: 4rem;
}
h1 {
font-size: 32px;
}
code {
font-family: Fira, FiraCode, monospace;
}
details {
background: #333;
margin-bottom: 2rem;
padding: 0.5rem 1rem;
border-radius: 5px;
justify-content: center;
margin: auto;
}
summary {
cursor: pointer;
}
summary > * {
display: inline;
}

View file

@ -5,7 +5,6 @@
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { readFileSync } from 'node:fs';
import ms from 'ms';
import Koa from 'koa';
import Router from '@koa/router';
import send from 'koa-send';
@ -27,6 +26,7 @@ import { genOpenapiSpec } from '../api/openapi/gen-spec.js';
import { urlPreviewHandler } from './url-preview.js';
import { manifestHandler } from './manifest.js';
import packFeed from './feed.js';
import { MINUTE, DAY } from '@/const.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@ -100,21 +100,21 @@ const router = new Router();
router.get('/static-assets/(.*)', async ctx => {
await send(ctx as any, ctx.path.replace('/static-assets/', ''), {
root: staticAssets,
maxage: ms('7 days'),
maxage: 7 * DAY,
});
});
router.get('/client-assets/(.*)', async ctx => {
await send(ctx as any, ctx.path.replace('/client-assets/', ''), {
root: clientAssets,
maxage: ms('7 days'),
maxage: 7 * DAY,
});
});
router.get('/assets/(.*)', async ctx => {
await send(ctx as any, ctx.path.replace('/assets/', ''), {
root: assets,
maxage: ms('7 days'),
maxage: 7 * DAY,
});
});
@ -137,7 +137,7 @@ router.get('/twemoji/(.*)', async ctx => {
await send(ctx as any, path, {
root: `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`,
maxage: ms('30 days'),
maxage: 30 * DAY,
});
});
@ -188,7 +188,7 @@ router.get('/twemoji-badge/(.*)', async ctx => {
router.get(`/sw.js`, async ctx => {
await send(ctx as any, `/sw.js`, {
root: swAssets,
maxage: ms('10 minutes'),
maxage: 10 * MINUTE,
});
});

View file

@ -5,7 +5,7 @@ html
head
meta(charset='utf-8')
meta(name='application-name' content='Misskey')
title Misskey BIOS
title Misskey Repair Tool
style
include ../bios.css
script
@ -13,7 +13,7 @@ html
body
header
h1 Misskey BIOS #{version}
h1 Misskey Repair Tool #{version}
main
div.tabs
button#ls edit local storage

View file

@ -1,6 +1,12 @@
doctype html
html
head
meta(charset='utf-8')
meta(name='application-name' content='Misskey')
title Flushing Misskey
style
include ../error.css
#msg
script.
const msg = document.getElementById('msg');

File diff suppressed because it is too large Load diff

View file

@ -186,7 +186,7 @@ export function connectStream(user: any, channel: string, listener: (message: Re
});
}
export const waitFire = async (user: any, channel: string, trgr: () => any, cond: (msg: Record<string, any>) => boolean) => {
export const waitFire = async (user: any, channel: string, trgr: () => any, cond: (msg: Record<string, any>) => boolean, params?: any) => {
return new Promise<boolean>(async (res, rej) => {
let timer: NodeJS.Timeout;
@ -198,7 +198,7 @@ export const waitFire = async (user: any, channel: string, trgr: () => any, cond
if (timer) clearTimeout(timer);
res(true);
}
});
}, params);
} catch (e) {
rej(e);
}
@ -208,7 +208,7 @@ export const waitFire = async (user: any, channel: string, trgr: () => any, cond
timer = setTimeout(() => {
ws.close();
res(false);
}, 5000);
}, 3000);
try {
await trgr();

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
"scripts": {
"watch": "vite build --watch --mode development",
"build": "vite build",
"lint": "eslint --quiet \"src/**/*.{ts,vue}\""
"lint": "eslint --quiet src --ext .ts,.vue"
},
"resolutions": {
"chokidar": "^3.3.1",
@ -15,7 +15,7 @@
"@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0",
"@syuilo/aiscript": "0.11.1",
"@vitejs/plugin-vue": "2.3.3",
"@vitejs/plugin-vue": "^3.0.0",
"@vue/compiler-sfc": "3.2.37",
"abort-controller": "3.0.0",
"autobind-decorator": "2.4.0",
@ -35,7 +35,7 @@
"escape-regexp": "0.0.1",
"eventemitter3": "4.0.7",
"feed": "4.2.2",
"idb-keyval": "6.1.0",
"idb-keyval": "6.2.0",
"insert-text-at-cursor": "0.3.0",
"json5": "2.2.1",
"katex": "0.15.6",
@ -45,36 +45,34 @@
"mocha": "10.0.0",
"ms": "2.1.3",
"nested-property": "4.0.0",
"photoswipe": "5.2.7",
"photoswipe": "5.2.8",
"prismjs": "1.28.0",
"private-ip": "2.3.3",
"promise-limit": "2.7.0",
"pug": "3.0.2",
"punycode": "2.1.1",
"qrcode": "1.5.0",
"querystring": "0.2.1",
"random-seed": "0.3.0",
"reflect-metadata": "0.1.13",
"rndstr": "1.0.0",
"rollup": "2.75.6",
"rollup": "2.75.7",
"s-age": "1.1.2",
"sass": "1.52.3",
"sass": "1.53.0",
"seedrandom": "3.0.5",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.141.0",
"three": "0.142.0",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.4.2",
"tsc-alias": "1.6.9",
"tsc-alias": "1.6.11",
"tsconfig-paths": "4.0.0",
"twemoji-parser": "14.0.0",
"typescript": "4.7.3",
"typescript": "4.7.4",
"uuid": "8.3.2",
"v-debounce": "0.1.2",
"vanilla-tilt": "1.7.2",
"vite": "2.9.10",
"vite": "3.0.0",
"vue": "3.2.37",
"vue-prism-editor": "2.0.0-alpha.2",
"vuedraggable": "4.0.1",
@ -93,20 +91,19 @@
"@types/oauth": "0.9.1",
"@types/punycode": "2.1.0",
"@types/qrcode": "1.4.2",
"@types/random-seed": "0.3.3",
"@types/seedrandom": "3.0.2",
"@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.3",
"@types/uuid": "8.3.4",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "5.27.1",
"@typescript-eslint/parser": "5.27.1",
"@typescript-eslint/eslint-plugin": "^5.30.0",
"@typescript-eslint/parser": "^5.30.0",
"cross-env": "7.0.3",
"cypress": "10.0.3",
"eslint": "8.17.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-vue": "9.1.0",
"cypress": "10.3.0",
"eslint": "^8.20.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-vue": "^9.1.1",
"start-server-and-test": "1.14.0"
}
}

View file

@ -1,5 +1,5 @@
<template>
<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')">
<XWindow ref="uiWindow" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')">
<template #header>
<i class="fas fa-exclamation-circle" style="margin-right: 0.5em;"></i>
<I18n :src="i18n.ts.reportAbuseOf" tag="span">
@ -40,19 +40,19 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const window = ref<InstanceType<typeof XWindow>>();
const uiWindow = ref<InstanceType<typeof XWindow>>();
const comment = ref(props.initialComment || '');
function send() {
os.apiWithDialog('users/report-abuse', {
userId: props.user.id,
comment: comment.value,
}, undefined).then(res => {
}).then(res => {
os.alert({
type: 'success',
text: i18n.ts.abuseReported
});
window.value?.close();
uiWindow.value?.close();
emit('closed');
});
}

View file

@ -20,6 +20,7 @@
<span v-if="emoji.isCustomEmoji" class="emoji"><img :src="defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span>
<span v-else-if="!defaultStore.state.useOsNativeEmojis" class="emoji"><img :src="emoji.url" :alt="emoji.emoji"/></span>
<span v-else class="emoji">{{ emoji.emoji }}</span>
<!-- eslint-disable-next-line vue/no-v-html -->
<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span>
<span v-if="emoji.aliasOf" class="alias">({{ emoji.aliasOf }})</span>
</li>

View file

@ -1,3 +1,4 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<code v-if="inline" :class="`language-${prismLang}`" v-html="html"></code>
<pre v-else :class="`language-${prismLang}`"><code :class="`language-${prismLang}`" v-html="html"></code></pre>
@ -5,7 +6,7 @@
<script lang="ts" setup>
import { computed } from 'vue';
import 'prismjs';
import Prism from 'prismjs';
import 'prismjs/themes/prism-okaidia.css';
const props = defineProps<{

View file

@ -1,13 +1,12 @@
<script lang="ts">
import { defineComponent, h, PropType, TransitionGroup } from 'vue';
import MkAd from '@/components/global/ad.vue';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
export default defineComponent({
props: {
items: {
type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>,
type: Array as PropType<{ id: string; createdAt: string; }[]>,
required: true,
},
direction: {
@ -25,11 +24,6 @@ export default defineComponent({
required: false,
default: false
},
ad: {
type: Boolean,
required: false,
default: false
},
},
setup(props, { slots, expose }) {
@ -77,17 +71,9 @@ export default defineComponent({
]));
return [el, separator];
} else {
if (props.ad && item._shouldInsertAd_) {
return [h(MkAd, {
class: 'a', // advertise()
key: item.id + ':ad',
prefer: ['horizontal', 'horizontal-big'],
}), el];
} else {
return el;
}
}
});
return () => h(

View file

@ -9,12 +9,12 @@
<form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit">
<div class="main _formRoot">
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required>
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required>
<template #label>{{ i18n.ts.username }}</template>
<template #prefix>@</template>
</MkInput>
<MkInput v-model="email" class="_formBlock" type="email" spellcheck="false" required>
<MkInput v-model="email" class="_formBlock" type="email" :spellcheck="false" required>
<template #label>{{ i18n.ts.emailAddress }}</template>
<template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template>
</MkInput>

View file

@ -1,5 +1,6 @@
<template>
<XModalWindow ref="dialog"
<XModalWindow
ref="dialog"
:width="450"
:can-close="false"
:with-ok-button="true"
@ -37,10 +38,10 @@
<option v-for="item in form[item].enum" :key="item.value" :value="item.value">{{ item.label }}</option>
</FormSelect>
<FormRadios v-else-if="form[item].type === 'radio'" v-model="values[item]" class="_formBlock">
<template #caption><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option>
</FormRadios>
<FormRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].mim" :max="form[item].max" :step="form[item].step" :text-converter="form[item].textConverter" class="_formBlock">
<FormRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :text-converter="form[item].textConverter" class="_formBlock">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
</FormRange>
@ -55,7 +56,6 @@
<script lang="ts">
import { defineComponent } from 'vue';
import XModalWindow from '@/components/ui/modal-window.vue';
import FormInput from './form/input.vue';
import FormTextarea from './form/textarea.vue';
import FormSwitch from './form/switch.vue';
@ -63,6 +63,7 @@ import FormSelect from './form/select.vue';
import FormRange from './form/range.vue';
import MkButton from './ui/button.vue';
import FormRadios from './form/radios.vue';
import XModalWindow from '@/components/ui/modal-window.vue';
export default defineComponent({
components: {
@ -91,31 +92,31 @@ export default defineComponent({
data() {
return {
values: {}
values: {},
};
},
created() {
for (const item in this.form) {
this.values[item] = this.form[item].hasOwnProperty('default') ? this.form[item].default : null;
this.values[item] = this.form[item].default ?? null;
}
},
methods: {
ok() {
this.$emit('done', {
result: this.values
result: this.values,
});
this.$refs.dialog.close();
},
cancel() {
this.$emit('done', {
canceled: true
canceled: true,
});
this.$refs.dialog.close();
}
}
},
},
});
</script>

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