Compare commits

...

19 Commits

Author SHA1 Message Date
de57dd7c97 11.36.0 2019-11-24 17:12:05 +09:00
9985c010bc Update master.ts 2019-11-24 17:11:53 +09:00
f7a328d66e Update dependencies 🚀 2019-11-24 17:09:32 +09:00
50598bcefb New Crowdin translations (#5573)
* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Danish)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Polish)
2019-11-24 17:01:34 +09:00
9c38e9722a Fix bug 2019-11-24 16:43:19 +09:00
b241967fb9 Fix: ローカルにフォロワー限定投稿が流れてくる (#5598) 2019-11-24 08:31:57 +09:00
1f86a6d329 Fix bug 2019-11-23 09:43:47 +09:00
7a6b5b0bfc Remove unused import 2019-11-18 06:30:58 +09:00
e406791b7b Fix bug 2019-11-18 06:27:22 +09:00
4ce2f596ee Refactor 2019-11-18 06:25:47 +09:00
567f71fe61 Refactor 2019-11-18 06:23:44 +09:00
70bb5879f9 boot: remove setAttribute() calls and translate reload msg (#5532)
* boot: remove setAttribute() calls and translate reload msg

* Update src/client/app/boot.js

Co-Authored-By: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2019-11-17 00:44:21 +09:00
cfd2d84b14 Link joinmisskey 2019-11-17 00:15:48 +09:00
44ab428803 無駄なAP deliverをしないように (#5589)
* DeliverManager, note/create

* recipe

* followers delivers

* comment

* rename

* fix

* cleanup
2019-11-09 18:51:54 +09:00
b34b728fbb Resolve #5587 (#5588)
* Resolve #5587

* stat
2019-11-09 18:24:41 +09:00
8ada1725bf 管理画面のジョブキュー一覧の修正 (#5586)
* Fix: inboxのジョブキューが表示されない

* ジョブキューで試行回数等を表示するように

* DBとオブジェクトストレージのジョブキューが表示されるように
2019-11-07 05:41:44 +09:00
873444c3c6 APの統計とログの修正と強化 (#5585)
* Fix #5580

* Improve AP logging
2019-11-07 00:02:18 +09:00
8bdd4fd061 Resolve #5582 (#5583) 2019-11-06 23:56:56 +09:00
f3b518fb62 Update yarn.lock (#5584) 2019-11-06 23:56:24 +09:00
57 changed files with 1229 additions and 826 deletions

View File

@ -1,6 +1,26 @@
ChangeLog ChangeLog
========= =========
11.36.0 (2019/11/24)
--------------------
### ✨Improvements
* ジョブキューで試行回数等を表示するように
* deliverエラー等の同じようなログが複数出てこないように、上でまとめて出すように
* deliverエラーのログではキューの詳細情報を格納しないように
* inbox/deliverのログに試行回数とキューが作られてからの時間を表示するように
* 無駄なAP deliverをしないように
* boot: remove setAttribute() calls and translate reload msg
### 🐛Fixes
* メンションの通知が届かない可能性がある問題を修正
* ブロックや閉鎖済みインスタンスのステータスが更新されてしまう問題を修正
* 「フォロワーを解除」アクティビティを正しく受け取らない問題を修正
* inboxのジョブキューが表示されない問題を修正
* ローカルにフォロワー限定投稿が流れてくる問題を修正
* リモートユーザーのアイコンがサムネイルで表示されない問題を修正
* DBとオブジェクトストレージのジョブキューが表示されない問題を修正
* エラーが発生したときにサーバーがクラッシュすることがある問題を修正
11.35.1 (2019/11/05) 11.35.1 (2019/11/05)
-------------------- --------------------
### 🐛Fixes ### 🐛Fixes

View File

@ -1,6 +1,6 @@
<a href="https://xn--931a.moe/"><img src="https://github.com/syuilo/misskey/blob/develop/assets/ai-orig.png?raw=true" align="right" height="320px"/></a> <a href="https://xn--931a.moe/"><img src="https://github.com/syuilo/misskey/blob/develop/assets/ai-orig.png?raw=true" align="right" height="320px"/></a>
[![Misskey](/assets/title.png)](https://misskey.io/) [![Misskey](/assets/title.png)](https://join.misskey.page/)
================================================================ ================================================================
[![CircleCI](https://img.shields.io/circleci/project/github/syuilo/misskey.svg?style=for-the-badge&logo=circleci)](https://circleci.com/gh/syuilo/misskey) [![CircleCI](https://img.shields.io/circleci/project/github/syuilo/misskey.svg?style=for-the-badge&logo=circleci)](https://circleci.com/gh/syuilo/misskey)
@ -10,7 +10,7 @@
**A forever evolving, sophisticated microblogging platform.** **A forever evolving, sophisticated microblogging platform.**
<p align="justify"> <p align="justify">
<a href="https://misskey.io">Misskey</a> is a decentralized microblogging platform born on Earth. <a href="https://join.misskey.page/">Misskey</a> is a decentralized microblogging platform born on Earth.
Since it exists within the Fediverse (a universe where various social media platforms are organized), Since it exists within the Fediverse (a universe where various social media platforms are organized),
it is mutually linked with other social media platforms. it is mutually linked with other social media platforms.
Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://join.misskey.page/">Find an instance!</a> Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? <a href="https://join.misskey.page/">Find an instance!</a>

View File

@ -11,7 +11,7 @@ const cssnano = require('gulp-cssnano');
const stylus = require('gulp-stylus'); const stylus = require('gulp-stylus');
import * as uglifyComposer from 'gulp-uglify/composer'; import * as uglifyComposer from 'gulp-uglify/composer';
import * as rimraf from 'rimraf'; import * as rimraf from 'rimraf';
import chalk from 'chalk'; import * as chalk from 'chalk';
import * as rename from 'gulp-rename'; import * as rename from 'gulp-rename';
import * as mocha from 'gulp-mocha'; import * as mocha from 'gulp-mocha';
import * as replace from 'gulp-replace'; import * as replace from 'gulp-replace';

View File

@ -289,6 +289,7 @@ common:
sync: "Synchronizace" sync: "Synchronizace"
save: "Uložit" save: "Uložit"
saved: "Uloženo" saved: "Uloženo"
preview: "Náhled"
room: "Místnost" room: "Místnost"
_room: _room:
graphicsQuality: "Kvalita grafiky" graphicsQuality: "Kvalita grafiky"
@ -399,6 +400,7 @@ common/views/components/games/reversi/reversi.index.vue:
invite: "Pozvat" invite: "Pozvat"
rule: "Jak hrát" rule: "Jak hrát"
mode-invite: "Pozvat" mode-invite: "Pozvat"
invitations: "Jste pozvaní ke hře!"
my-games: "Moje hra" my-games: "Moje hra"
all-games: "Všechny hry" all-games: "Všechny hry"
enter-username: "Zadejte své uživatelské jméno" enter-username: "Zadejte své uživatelské jméno"

View File

@ -258,6 +258,7 @@ common:
load-remote-media: "Vis medie-materiale fra en ekstern server" load-remote-media: "Vis medie-materiale fra en ekstern server"
save: "Gem" save: "Gem"
saved: "Gemt" saved: "Gemt"
preview: "Før-visning"
search: "Søg" search: "Søg"
delete: "Slet" delete: "Slet"
loading: "Henter" loading: "Henter"

View File

@ -251,6 +251,7 @@ common:
load-remote-media: "Zeige Inhalte von fremden Servern" load-remote-media: "Zeige Inhalte von fremden Servern"
save: "Speichern" save: "Speichern"
saved: "Gespeichert" saved: "Gespeichert"
preview: "Vorschau"
search: "Suche" search: "Suche"
delete: "Löschen" delete: "Löschen"
loading: "Laden" loading: "Laden"

View File

@ -300,6 +300,7 @@ common:
sync: "Sync" sync: "Sync"
save: "Save" save: "Save"
saved: "Saved" saved: "Saved"
preview: "Preview"
home-profile: "Home profile" home-profile: "Home profile"
deck-profile: "Deck profile" deck-profile: "Deck profile"
room: "Room" room: "Room"
@ -1352,9 +1353,9 @@ admin/views/charts.vue:
charts: charts:
federation-instances: "The number of instances: increase/decrease" federation-instances: "The number of instances: increase/decrease"
federation-instances-total: "Total number of instances" federation-instances-total: "Total number of instances"
notes: "The number of posts: increase/decrease (Combined)" notes: "Increase, or decrease in the number of posts (Combined)"
local-notes: "The number of posts: increase/decrease (Local)" local-notes: "Increase, or decrease in the number of posts (Local)"
remote-notes: "The number of posts: increase/decrease (Remote)" remote-notes: "Increase, or decrease in the number of posts (Remote)"
notes-total: "Total posts" notes-total: "Total posts"
users: "The number of users: increase/decrease" users: "The number of users: increase/decrease"
users-total: "Total users" users-total: "Total users"
@ -1533,7 +1534,7 @@ admin/views/federation.vue:
chart-srcs: chart-srcs:
requests: "Requests" requests: "Requests"
users: "Increase, or decrease in the number of users" users: "Increase, or decrease in the number of users"
users-total: "Total number of users" users-total: "Users in total"
notes: "Increase, or decrease in the number of notes" notes: "Increase, or decrease in the number of notes"
notes-total: "Total number of notes" notes-total: "Total number of notes"
ff: "Increase of followers" ff: "Increase of followers"

View File

@ -201,6 +201,7 @@ common:
navbar-position-left: "Izquierda" navbar-position-left: "Izquierda"
save: "Guardar" save: "Guardar"
saved: "Guardado" saved: "Guardado"
preview: "Vista previa"
search: "Buscar" search: "Buscar"
delete: "eliminar" delete: "eliminar"
loading: "cargando" loading: "cargando"

View File

@ -100,6 +100,11 @@ common:
"write:reactions": "Gérer vos réactions" "write:reactions": "Gérer vos réactions"
"write:votes": "Vote" "write:votes": "Vote"
"read:pages": "Afficher la page" "read:pages": "Afficher la page"
"write:pages": "Mettre à jour les Pages"
"read:page-likes": "Lire les favoris sur les Pages"
"write:page-likes": "Mettre à jour les favoris sur les Pages"
"read:user-groups": "Voir les groupes d'utilisateur·rice·s"
"write:user-groups": "Éditer les groupes des utilisateur·rice·s"
empty-timeline-info: empty-timeline-info:
follow-users-to-make-your-timeline: "Les utilisateur·rice·s suivant·e·s afficheront leurs publications sur votre fil." follow-users-to-make-your-timeline: "Les utilisateur·rice·s suivant·e·s afficheront leurs publications sur votre fil."
explore: "Trouver des utilisateur·rice·s" explore: "Trouver des utilisateur·rice·s"
@ -126,6 +131,7 @@ common:
geolocation-alert: "Votre appareil ne prend pas en charge les services de localisation" geolocation-alert: "Votre appareil ne prend pas en charge les services de localisation"
error: "Erreur" error: "Erreur"
enter-username: "Saisir un nom d'utilisateur" enter-username: "Saisir un nom d'utilisateur"
specified-recipient: "Correspondant·e"
add-visible-user: "Ajouter un utilisateur" add-visible-user: "Ajouter un utilisateur"
cw-placeholder: "Commenter le contenu (optionnel)" cw-placeholder: "Commenter le contenu (optionnel)"
username-prompt: "Saisir un nom d'utilisateur" username-prompt: "Saisir un nom d'utilisateur"
@ -284,6 +290,7 @@ common:
sync: "Synchroniser" sync: "Synchroniser"
save: "Enregistrer" save: "Enregistrer"
saved: "enregistré" saved: "enregistré"
preview: "Prévisualisation"
home-profile: "Profil principal" home-profile: "Profil principal"
deck-profile: "Profil deck" deck-profile: "Profil deck"
room: "Pièce" room: "Pièce"
@ -379,6 +386,7 @@ common/views/pages/explore.vue:
popular-tags: "Mots-clés populaires" popular-tags: "Mots-clés populaires"
federated: "Du Fédiverse" federated: "Du Fédiverse"
explore: "Explorer {host}" explore: "Explorer {host}"
explore-fediverse: "Explorer le Fédiverse"
users-info: "Actuellement, {users} utilisateur·rice·s se sont inscrit ici" users-info: "Actuellement, {users} utilisateur·rice·s se sont inscrit ici"
common/views/components/reactions-viewer.details.vue: common/views/components/reactions-viewer.details.vue:
few-users: "{users} ont réagit avec {reaction}" few-users: "{users} ont réagit avec {reaction}"
@ -606,7 +614,9 @@ common/views/components/reaction-picker.vue:
choose-reaction: "Envoyer une réaction" choose-reaction: "Envoyer une réaction"
input-reaction-placeholder: "ou insérez un émoji" input-reaction-placeholder: "ou insérez un émoji"
common/views/components/emoji-picker.vue: common/views/components/emoji-picker.vue:
recent-emoji: "Utilisés récemment"
custom-emoji: "Émoji personnalisé" custom-emoji: "Émoji personnalisé"
no-category: "Sans catégorie"
people: "Personnes" people: "Personnes"
animals-and-nature: "Animaux et nature" animals-and-nature: "Animaux et nature"
food-and-drink: "Nourriture et boisson" food-and-drink: "Nourriture et boisson"
@ -1350,6 +1360,7 @@ admin/views/drive.vue:
marked-as-sensitive: "Marqué comme sensible" marked-as-sensitive: "Marqué comme sensible"
unmarked-as-sensitive: "Marqué comme non sensible" unmarked-as-sensitive: "Marqué comme non sensible"
clean-remote-files: "Nettoyer le cache des fichiers distants" clean-remote-files: "Nettoyer le cache des fichiers distants"
clean-remote-files-are-you-sure: "Êtes-vous sûr de vouloir effacer tout les fichiers distants mis en cache ?"
clean-up: "Nettoyage" clean-up: "Nettoyage"
admin/views/users.vue: admin/views/users.vue:
operation: "Actions" operation: "Actions"
@ -1416,6 +1427,7 @@ admin/views/emoji.vue:
title: "Ajouter un émoji" title: "Ajouter un émoji"
name: "Nom de lémoji" name: "Nom de lémoji"
name-desc: "Vous pouvez utiliser les caractères a~z 0~9 _" name-desc: "Vous pouvez utiliser les caractères a~z 0~9 _"
category: "Catégories"
aliases: "Aliases" aliases: "Aliases"
aliases-desc: "Vous pouvez définir plus dun, séparés par des espaces." aliases-desc: "Vous pouvez définir plus dun, séparés par des espaces."
url: "URL de limage" url: "URL de limage"

View File

@ -130,6 +130,7 @@ common:
timeline: "タイムライン" timeline: "タイムライン"
save: "保存" save: "保存"
saved: "保存したで!" saved: "保存したで!"
preview: "試してみる"
search: "検索" search: "検索"
delete: "削除" delete: "削除"
loading: "読み込み中" loading: "読み込み中"

View File

@ -300,6 +300,7 @@ common:
sync: "동기화" sync: "동기화"
save: "저장" save: "저장"
saved: "저장하였습니다" saved: "저장하였습니다"
preview: "미리보기"
home-profile: "홈 프로필" home-profile: "홈 프로필"
deck-profile: "덱 프로필" deck-profile: "덱 프로필"
room: "룸" room: "룸"

View File

@ -162,6 +162,7 @@ common:
note-visibility: "Widoczność wpisów" note-visibility: "Widoczność wpisów"
remember-note-visibility: "Zapamiętaj widoczność wpisów" remember-note-visibility: "Zapamiętaj widoczność wpisów"
web-search-engine: "Wyszukiwarka internetowa" web-search-engine: "Wyszukiwarka internetowa"
web-search-engine-desc: "Wzór: https://www.google.com/?#q={{query}}"
paste: "Wklej" paste: "Wklej"
line-width: "Szerokości linii" line-width: "Szerokości linii"
line-width-thin: "Cienka" line-width-thin: "Cienka"
@ -193,6 +194,7 @@ common:
navbar-position-left: "Z lewej" navbar-position-left: "Z lewej"
save: "Zapisz" save: "Zapisz"
saved: "Zapisano" saved: "Zapisano"
preview: "Pokaż podgląd"
search: "Szukaj" search: "Szukaj"
delete: "Usuń" delete: "Usuń"
loading: "Ładowanie" loading: "Ładowanie"

View File

@ -12,7 +12,7 @@ common:
rich-contents: "Посты" rich-contents: "Посты"
rich-contents-desc: "Просто выложи свою идею, актуальные темы и всё, что тебе хочется показать миру. Ты можешь декорировать свои слова, прикреплять свои любимые картинки, отправлять файлы с фильмами и создать голосование - это те вещи, которые ты можешь сделать с помощью Misskey!" rich-contents-desc: "Просто выложи свою идею, актуальные темы и всё, что тебе хочется показать миру. Ты можешь декорировать свои слова, прикреплять свои любимые картинки, отправлять файлы с фильмами и создать голосование - это те вещи, которые ты можешь сделать с помощью Misskey!"
reaction: "Реакции" reaction: "Реакции"
reaction-desc: "Самый лёгкий способ выразить свои эмоции. Misskey позволяет добавлять различные виды реакций к постам других людей. Эмоциональный опыт из Misskey никогда не появится в других социальных сетях, позволяющих только жать “лайки”." reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
ui: "Интерфейс" ui: "Интерфейс"
ui-desc: "Нет такого интерфейса, понравившегося всем. Поэтому у Misskey имеется пользовательский интерфейс, широко настраиваемый под ваши вкусы. Создай себе уникальную домашнюю страницу редактируя, подстраивая оформление ленты и размещая виджеты, которые тоже можно кастомизировать." ui-desc: "Нет такого интерфейса, понравившегося всем. Поэтому у Misskey имеется пользовательский интерфейс, широко настраиваемый под ваши вкусы. Создай себе уникальную домашнюю страницу редактируя, подстраивая оформление ленты и размещая виджеты, которые тоже можно кастомизировать."
drive: "Хранилище файлов" drive: "Хранилище файлов"

View File

@ -300,6 +300,7 @@ common:
sync: "同步" sync: "同步"
save: "保存" save: "保存"
saved: "已保存" saved: "已保存"
preview: "预览"
home-profile: "定制首页数据" home-profile: "定制首页数据"
deck-profile: "定制Deck数据" deck-profile: "定制Deck数据"
room: "房间" room: "房间"

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "11.35.1", "version": "11.36.0",
"codename": "daybreak", "codename": "daybreak",
"repository": { "repository": {
"type": "git", "type": "git",
@ -36,12 +36,12 @@
"@fortawesome/free-brands-svg-icons": "5.11.2", "@fortawesome/free-brands-svg-icons": "5.11.2",
"@fortawesome/free-regular-svg-icons": "5.11.2", "@fortawesome/free-regular-svg-icons": "5.11.2",
"@fortawesome/free-solid-svg-icons": "5.11.2", "@fortawesome/free-solid-svg-icons": "5.11.2",
"@fortawesome/vue-fontawesome": "0.1.7", "@fortawesome/vue-fontawesome": "0.1.8",
"@koa/cors": "3.0.0", "@koa/cors": "3.0.0",
"@koa/multer": "2.0.0", "@koa/multer": "2.0.1",
"@koa/router": "8.0.2", "@koa/router": "8.0.2",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.10.3", "@types/bull": "3.10.6",
"@types/cbor": "2.0.0", "@types/cbor": "2.0.0",
"@types/dateformat": "3.0.1", "@types/dateformat": "3.0.1",
"@types/deep-equal": "1.0.1", "@types/deep-equal": "1.0.1",
@ -56,22 +56,22 @@
"@types/js-yaml": "3.12.1", "@types/js-yaml": "3.12.1",
"@types/jsdom": "12.2.4", "@types/jsdom": "12.2.4",
"@types/katex": "0.10.2", "@types/katex": "0.10.2",
"@types/koa": "2.0.50", "@types/koa": "2.0.52",
"@types/koa-bodyparser": "5.0.2", "@types/koa-bodyparser": "4.3.0",
"@types/koa-compress": "2.0.9", "@types/koa-compress": "2.0.9",
"@types/koa-cors": "0.0.0", "@types/koa-cors": "0.0.0",
"@types/koa-favicon": "2.0.19", "@types/koa-favicon": "2.0.19",
"@types/koa-logger": "3.1.1", "@types/koa-logger": "3.1.1",
"@types/koa-mount": "4.0.0", "@types/koa-mount": "4.0.0",
"@types/koa-send": "4.1.2", "@types/koa-send": "4.1.2",
"@types/koa-views": "2.0.3", "@types/koa-views": "2.0.4",
"@types/koa__cors": "2.2.3", "@types/koa__cors": "2.2.3",
"@types/koa__multer": "2.0.0", "@types/koa__multer": "2.0.1",
"@types/koa__router": "8.0.0", "@types/koa__router": "8.0.2",
"@types/lolex": "3.1.1", "@types/lolex": "5.1.0",
"@types/mocha": "5.2.7", "@types/mocha": "5.2.7",
"@types/node": "12.7.12", "@types/node": "12.12.12",
"@types/nodemailer": "6.2.1", "@types/nodemailer": "6.2.2",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
"@types/parse5": "5.0.2", "@types/parse5": "5.0.2",
@ -86,51 +86,51 @@
"@types/request": "2.48.3", "@types/request": "2.48.3",
"@types/request-promise-native": "1.0.17", "@types/request-promise-native": "1.0.17",
"@types/request-stats": "3.0.0", "@types/request-stats": "3.0.0",
"@types/rimraf": "2.0.2", "@types/rimraf": "2.0.3",
"@types/seedrandom": "2.4.28", "@types/seedrandom": "2.4.28",
"@types/sharp": "0.22.3", "@types/sharp": "0.23.0",
"@types/showdown": "1.9.3", "@types/showdown": "1.9.3",
"@types/speakeasy": "2.0.5", "@types/speakeasy": "2.0.5",
"@types/systeminformation": "3.23.1", "@types/systeminformation": "3.54.1",
"@types/tinycolor2": "1.4.2", "@types/tinycolor2": "1.4.2",
"@types/tmp": "0.1.0", "@types/tmp": "0.1.0",
"@types/uuid": "3.4.5", "@types/uuid": "3.4.6",
"@types/web-push": "3.3.0", "@types/web-push": "3.3.0",
"@types/webpack": "4.39.3", "@types/webpack": "4.41.0",
"@types/webpack-stream": "3.2.10", "@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.40", "@types/websocket": "1.0.0",
"@types/ws": "6.0.3", "@types/ws": "6.0.3",
"@typescript-eslint/parser": "2.3.3", "@typescript-eslint/parser": "2.8.0",
"agentkeepalive": "4.1.0", "agentkeepalive": "4.1.0",
"animejs": "3.1.0", "animejs": "3.1.0",
"apexcharts": "3.10.1", "apexcharts": "3.10.1",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.2", "autosize": "4.0.2",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.548.0", "aws-sdk": "2.578.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bootstrap": "4.3.1", "bootstrap": "4.3.1",
"bootstrap-vue": "2.0.4", "bootstrap-vue": "2.1.0",
"bull": "3.11.0", "bull": "3.12.1",
"cafy": "15.1.1", "cafy": "15.2.0",
"cbor": "5.0.1", "cbor": "5.0.1",
"chai": "4.2.0", "chai": "4.2.0",
"chalk": "2.4.2", "chalk": "3.0.0",
"cli-highlight": "2.1.1", "cli-highlight": "2.1.4",
"commander": "3.0.2", "commander": "4.0.1",
"content-disposition": "0.5.3", "content-disposition": "0.5.3",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "3.2.0", "css-loader": "3.2.0",
"cssnano": "4.1.10", "cssnano": "4.1.10",
"dateformat": "3.0.3", "dateformat": "3.0.3",
"deep-equal": "1.1.0", "deep-equal": "1.1.1",
"diskusage": "1.1.3", "diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0", "double-ended-queue": "2.1.0-0",
"eslint": "6.5.1", "eslint": "6.7.0",
"eslint-plugin-vue": "5.2.3", "eslint-plugin-vue": "6.0.1",
"eventemitter3": "4.0.0", "eventemitter3": "4.0.0",
"feed": "4.0.0", "feed": "4.0.0",
"file-type": "12.3.0", "file-type": "12.4.0",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"gulp": "4.0.2", "gulp": "4.0.2",
"gulp-cssnano": "2.1.3", "gulp-cssnano": "2.1.3",
@ -145,18 +145,18 @@
"gulp-util": "3.0.8", "gulp-util": "3.0.8",
"hard-source-webpack-plugin": "0.13.1", "hard-source-webpack-plugin": "0.13.1",
"html-minifier": "4.0.0", "html-minifier": "4.0.0",
"http-signature": "1.2.0", "http-signature": "1.3.1",
"https-proxy-agent": "3.0.0", "https-proxy-agent": "3.0.1",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0", "is-root": "2.1.0",
"is-svg": "4.2.0", "is-svg": "4.2.0",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"jsdom": "15.1.1", "jsdom": "15.2.1",
"json5": "2.1.1", "json5": "2.1.1",
"json5-loader": "3.0.0", "json5-loader": "3.0.0",
"jsrsasign": "8.0.12", "jsrsasign": "8.0.12",
"katex": "0.11.1", "katex": "0.11.1",
"koa": "2.10.0", "koa": "2.11.0",
"koa-bodyparser": "4.2.1", "koa-bodyparser": "4.2.1",
"koa-compress": "3.0.0", "koa-compress": "3.0.0",
"koa-favicon": "2.0.1", "koa-favicon": "2.0.1",
@ -168,21 +168,21 @@
"koa-views": "6.2.1", "koa-views": "6.2.1",
"langmap": "0.0.16", "langmap": "0.0.16",
"loader-utils": "1.2.3", "loader-utils": "1.2.3",
"lolex": "4.2.0", "lolex": "5.1.1",
"lookup-dns-cache": "2.1.0", "lookup-dns-cache": "2.1.0",
"mocha": "6.2.1", "mocha": "6.2.2",
"moji": "0.5.1", "moji": "0.5.1",
"ms": "2.1.2", "ms": "2.1.2",
"multer": "1.4.2", "multer": "1.4.2",
"nested-property": "1.0.1", "nested-property": "1.0.2",
"node-fetch": "2.6.0", "node-fetch": "2.6.0",
"nodemailer": "6.3.1", "nodemailer": "6.3.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"object-assign-deep": "0.4.0", "object-assign-deep": "0.4.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "5.1.0", "parse5": "5.1.1",
"parsimmon": "1.13.0", "parsimmon": "1.13.0",
"pg": "7.12.1", "pg": "7.14.0",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss-loader": "3.0.0", "postcss-loader": "3.0.0",
"prismjs": "1.17.1", "prismjs": "1.17.1",
@ -192,10 +192,10 @@
"pug": "2.0.4", "pug": "2.0.4",
"punycode": "2.1.1", "punycode": "2.1.1",
"pureimage": "0.1.6", "pureimage": "0.1.6",
"qrcode": "1.4.2", "qrcode": "1.4.4",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"randomcolor": "0.5.4", "randomcolor": "0.5.4",
"ratelimiter": "3.3.1", "ratelimiter": "3.4.0",
"recaptcha-promise": "0.1.3", "recaptcha-promise": "0.1.3",
"reconnecting-websocket": "4.2.0", "reconnecting-websocket": "4.2.0",
"redis": "2.8.0", "redis": "2.8.0",
@ -203,14 +203,14 @@
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rename": "1.0.4", "rename": "1.0.4",
"request": "2.88.0", "request": "2.88.0",
"request-promise-native": "1.0.7", "request-promise-native": "1.0.8",
"request-stats": "3.0.0", "request-stats": "3.0.0",
"require-all": "3.0.0", "require-all": "3.0.0",
"rimraf": "3.0.0", "rimraf": "3.0.0",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"sharp": "0.23.1", "sharp": "0.23.3",
"showdown": "1.9.0", "showdown": "1.9.0",
"showdown-highlightjs-extension": "0.1.2", "showdown-highlightjs-extension": "0.1.2",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
@ -220,52 +220,52 @@
"stylus-loader": "3.0.2", "stylus-loader": "3.0.2",
"summaly": "2.3.1", "summaly": "2.3.1",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "4.14.11", "systeminformation": "4.15.3",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "2.1.3", "terser-webpack-plugin": "2.2.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.109.0", "three": "0.110.0",
"tinycolor2": "1.4.1", "tinycolor2": "1.4.1",
"tmp": "0.1.0", "tmp": "0.1.0",
"ts-loader": "6.2.0", "ts-loader": "6.2.1",
"ts-node": "8.4.1", "ts-node": "8.5.2",
"tslint": "5.20.0", "tslint": "5.20.1",
"tslint-sonarts": "1.9.0", "tslint-sonarts": "1.9.0",
"typeorm": "0.2.19", "typeorm": "0.2.20",
"typescript": "3.6.4", "typescript": "3.7.2",
"uglify-es": "3.3.9", "uglify-es": "3.3.9",
"ulid": "2.3.0", "ulid": "2.3.0",
"url-loader": "2.2.0", "url-loader": "2.3.0",
"uuid": "3.3.3", "uuid": "3.3.3",
"v-animate-css": "0.0.3", "v-animate-css": "0.0.3",
"v-debounce": "0.1.2", "v-debounce": "0.1.2",
"vue": "2.6.10", "vue": "2.6.10",
"vue-color": "2.7.0", "vue-color": "2.7.0",
"vue-content-loading": "1.6.0", "vue-content-loading": "1.6.0",
"vue-cropperjs": "4.0.0", "vue-cropperjs": "4.0.1",
"vue-i18n": "8.14.1", "vue-i18n": "8.15.0",
"vue-js-modal": "1.3.31", "vue-js-modal": "1.3.31",
"vue-json-pretty": "1.6.2", "vue-json-pretty": "1.6.2",
"vue-loader": "15.7.1", "vue-loader": "15.7.2",
"vue-marquee-text-component": "1.1.1", "vue-marquee-text-component": "1.1.1",
"vue-prism-component": "1.1.1", "vue-prism-component": "1.1.1",
"vue-router": "3.1.3", "vue-router": "3.1.3",
"vue-sequential-entrance": "1.1.3", "vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2", "vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.3.3", "vue-svg-inline-loader": "1.4.3",
"vue-template-compiler": "2.6.10", "vue-template-compiler": "2.6.10",
"vuedraggable": "2.23.2", "vuedraggable": "2.23.2",
"vuewordcloud": "18.7.11", "vuewordcloud": "18.7.11",
"vuex": "3.1.1", "vuex": "3.1.2",
"vuex-persistedstate": "2.5.4", "vuex-persistedstate": "2.7.0",
"web-push": "3.4.0", "web-push": "3.4.1",
"webpack": "4.41.1", "webpack": "4.41.2",
"webpack-cli": "3.3.9", "webpack-cli": "3.3.10",
"websocket": "1.0.30", "websocket": "1.0.30",
"ws": "7.1.2", "ws": "7.2.0",
"xev": "2.0.1" "xev": "2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/fluent-ffmpeg": "2.1.10" "@types/fluent-ffmpeg": "2.1.11"
} }
} }

View File

@ -1,5 +1,5 @@
import * as cluster from 'cluster'; import * as cluster from 'cluster';
import chalk from 'chalk'; import * as chalk from 'chalk';
import Xev from 'xev'; import Xev from 'xev';
import Logger from '../services/logger'; import Logger from '../services/logger';

View File

@ -1,6 +1,6 @@
import * as os from 'os'; import * as os from 'os';
import * as cluster from 'cluster'; import * as cluster from 'cluster';
import chalk from 'chalk'; import * as chalk from 'chalk';
import * as portscanner from 'portscanner'; import * as portscanner from 'portscanner';
import * as isRoot from 'is-root'; import * as isRoot from 'is-root';
@ -11,14 +11,15 @@ import { lessThan } from '../prelude/array';
import { program } from '../argv'; import { program } from '../argv';
import { showMachineInfo } from '../misc/show-machine-info'; import { showMachineInfo } from '../misc/show-machine-info';
import { initDb } from '../db/postgre'; import { initDb } from '../db/postgre';
import * as meta from '../meta.json';
const logger = new Logger('core', 'cyan'); const logger = new Logger('core', 'cyan');
const bootLogger = logger.createSubLogger('boot', 'magenta', false); const bootLogger = logger.createSubLogger('boot', 'magenta', false);
function greet(config: Config) { function greet() {
if (!program.quiet) { if (!program.quiet) {
//#region Misskey logo //#region Misskey logo
const v = `v${config.version}`; const v = `v${meta.version}`;
console.log(' _____ _ _ '); console.log(' _____ _ _ ');
console.log(' | |_|___ ___| |_ ___ _ _ '); console.log(' | |_|___ ___| |_ ___ _ _ ');
console.log(' | | | | |_ -|_ -| \'_| -_| | |'); console.log(' | | | | |_ -|_ -| \'_| -_| | |');
@ -34,7 +35,7 @@ function greet(config: Config) {
} }
bootLogger.info('Welcome to Misskey!'); bootLogger.info('Welcome to Misskey!');
bootLogger.info(`Misskey v${config.version}`, null, true); bootLogger.info(`Misskey v${meta.version}`, null, true);
} }
/** /**
@ -44,11 +45,11 @@ export async function masterMain() {
let config!: Config; let config!: Config;
try { try {
greet();
// initialize app // initialize app
config = await init(); config = await init();
greet(config);
if (config.port == null || Number.isNaN(config.port)) { if (config.port == null || Number.isNaN(config.port)) {
bootLogger.error('The port is not configured. Please configure port.', null, true); bootLogger.error('The port is not configured. Please configure port.', null, true);
process.exit(1); process.exit(1);

View File

@ -48,14 +48,15 @@
</ui-select> </ui-select>
</ui-horizon-group> </ui-horizon-group>
<sequential-entrance animation="entranceFromTop" delay="25"> <sequential-entrance animation="entranceFromTop" delay="25">
<div class="xvvuvgsv" v-for="job in jobs"> <div class="xvvuvgsv" v-for="job in jobs" :key="job.id">
<b>{{ job.id }}</b> <b>{{ job.id }}</b>
<template v-if="domain === 'deliver'"> <template v-if="domain === 'deliver'">
<span>{{ job.data.to }}</span> <span>{{ job.data.to }}</span>
</template> </template>
<template v-if="domain === 'inbox'"> <template v-if="domain === 'inbox'">
<span>{{ job.activity.id }}</span> <span>{{ job.data.activity.id }}</span>
</template> </template>
<span>{{ `(${job.attempts}/${job.maxAttempts}, ${Math.floor((jobsFetched - job.timestamp) / 1000 / 60)}min)` }}</span>
</div> </div>
</sequential-entrance> </sequential-entrance>
<ui-info v-if="jobs.length == jobsLimit">{{ $t('result-is-truncated', { n: jobsLimit }) }}</ui-info> <ui-info v-if="jobs.length == jobsLimit">{{ $t('result-is-truncated', { n: jobsLimit }) }}</ui-info>
@ -84,6 +85,7 @@ export default Vue.extend({
chartLimit: 200, chartLimit: 200,
jobs: [], jobs: [],
jobsLimit: 50, jobsLimit: 50,
jobsFetched: Date.now(),
domain: 'deliver', domain: 'deliver',
state: 'delayed', state: 'delayed',
faTasks, faPaperPlane, faInbox, faChartBar, faDatabase, faCloud faTasks, faPaperPlane, faInbox, faChartBar, faDatabase, faCloud
@ -140,6 +142,7 @@ export default Vue.extend({
state: this.state, state: this.state,
limit: this.jobsLimit limit: this.jobsLimit
}).then(jobs => { }).then(jobs => {
this.jobsFetched = Date.now(),
this.jobs = jobs; this.jobs = jobs;
}); });
}, },
@ -149,7 +152,8 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
.xvvuvgsv .xvvuvgsv
> b margin-left -6px
margin-right 16px > b, span
margin 0 6px
</style> </style>

View File

@ -72,13 +72,17 @@
//#region Fetch locale data //#region Fetch locale data
const cachedLocale = localStorage.getItem('locale'); const cachedLocale = localStorage.getItem('locale');
const localeKey = localStorage.getItem('localeKey'); const localeKey = localStorage.getItem('localeKey');
let localeData = null;
if (cachedLocale == null || localeKey != `${ver}.${lang}`) { if (cachedLocale == null || localeKey != `${ver}.${lang}`) {
const locale = await fetch(`/assets/locales/${lang}.json?ver=${ver}`) const locale = await fetch(`/assets/locales/${lang}.json?ver=${ver}`)
.then(response => response.json()); .then(response => response.json());
localeData = locale;
localStorage.setItem('locale', JSON.stringify(locale)); localStorage.setItem('locale', JSON.stringify(locale));
localStorage.setItem('localeKey', `${ver}.${lang}`); localStorage.setItem('localeKey', `${ver}.${lang}`);
} else {
localeData = JSON.parse(cachedLocale);
} }
//#endregion //#endregion
@ -99,8 +103,7 @@
// If mobile, insert the viewport meta tag // If mobile, insert the viewport meta tag
if (isMobile) { if (isMobile) {
const viewport = document.getElementsByName("viewport").item(0); const viewport = document.getElementsByName("viewport").item(0);
viewport.setAttribute('content', viewport.content = `${viewport.content},minimum-scale=1,maximum-scale=1,user-scalable=no`;
`${viewport.getAttribute('content')},minimum-scale=1,maximum-scale=1,user-scalable=no`);
head.appendChild(viewport); head.appendChild(viewport);
} }
@ -113,9 +116,9 @@
// Note: 'async' make it possible to load the script asyncly. // Note: 'async' make it possible to load the script asyncly.
// 'defer' make it possible to run the script when the dom loaded. // 'defer' make it possible to run the script when the dom loaded.
const script = document.createElement('script'); const script = document.createElement('script');
script.setAttribute('src', `/assets/${app}.${ver}.js`); script.src = `/assets/${app}.${ver}.js`;
script.setAttribute('async', 'true'); script.async = true;
script.setAttribute('defer', 'true'); script.defer = true;
head.appendChild(script); head.appendChild(script);
// 3秒経ってもスクリプトがロードされない場合はバージョンが古くて // 3秒経ってもスクリプトがロードされない場合はバージョンが古くて
@ -138,10 +141,10 @@
localStorage.setItem('v', meta.version); localStorage.setItem('v', meta.version);
alert( alert(
'Misskeyの新しいバージョンがあります。ページを再度読み込みします。' + localeData.common._settings["update-available"] +
'\n\n' + '\n' +
'New version of Misskey available. The page will be reloaded.'); localeData.common._settings["update-available-desc"]
);
refresh(); refresh();
} }
}, 3000); }, 3000);

View File

@ -76,13 +76,15 @@ export default Vue.extend({
this.$root.api('ap/show', { this.$root.api('ap/show', {
uri: acct uri: acct
}).then((res: { type: string, object: any }) => { }).then((res: { type: string, object: any }) => {
if (res.type !== 'User') { if (res.type === 'User') {
this.user = res.object;
} else if (res.type === 'Note') {
this.$router.replace(`/notes/${res.object.id}`);
} else {
this.$root.dialog({ this.$root.dialog({
type: 'error', type: 'error',
text: 'acct is not an user' text: 'Not supported'
}); });
} else {
this.user = res.object;
} }
}).catch((e: any) => { }).catch((e: any) => {
this.$root.dialog({ this.$root.dialog({

View File

@ -1,7 +1,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as request from 'request'; import * as request from 'request';
import config from '../config'; import config from '../config';
import chalk from 'chalk'; import * as chalk from 'chalk';
import Logger from '../services/logger'; import Logger from '../services/logger';
export async function downloadUrl(url: string, path: string) { export async function downloadUrl(url: string, path: string) {

15
src/queue/get-job-info.ts Normal file
View File

@ -0,0 +1,15 @@
import * as Bull from 'bull';
export function getJobInfo(job: Bull.Job, increment = false) {
const age = Date.now() - job.timestamp;
const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m`
: age > 10000 ? `${Math.floor(age / 1000)}s`
: `${age}ms`;
// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
const maxAttempts = job.opts ? job.opts.attempts : 0;
return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
}

View File

@ -11,6 +11,7 @@ import processDb from './processors/db';
import procesObjectStorage from './processors/object-storage'; import procesObjectStorage from './processors/object-storage';
import { queueLogger } from './logger'; import { queueLogger } from './logger';
import { DriveFile } from '../models/entities/drive-file'; import { DriveFile } from '../models/entities/drive-file';
import { getJobInfo } from './get-job-info';
function initializeQueue(name: string) { function initializeQueue(name: string) {
return new Queue(name, { return new Queue(name, {
@ -44,19 +45,19 @@ const objectStorageLogger = queueLogger.createSubLogger('objectStorage');
deliverQueue deliverQueue
.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`)) .on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => deliverLogger.debug(`active id=${job.id} to=${job.data.to}`)) .on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) id=${job.id} to=${job.data.to}`)) .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) id=${job.id} to=${job.data.to}`, { job, e: renderError(err) })) .on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) })) .on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => deliverLogger.warn(`stalled id=${job.id} to=${job.data.to}`)); .on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
inboxQueue inboxQueue
.on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`)) .on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => inboxLogger.debug(`active id=${job.id}`)) .on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) })) .on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) }))
.on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) })) .on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => inboxLogger.warn(`stalled id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`)); .on('stalled', (job) => inboxLogger.warn(`stalled ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`));
dbQueue dbQueue
.on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`)) .on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`))

View File

@ -5,6 +5,8 @@ import Logger from '../../services/logger';
import { Instances } from '../../models'; import { Instances } from '../../models';
import { instanceChart } from '../../services/chart'; import { instanceChart } from '../../services/chart';
import { fetchNodeinfo } from '../../services/fetch-nodeinfo'; import { fetchNodeinfo } from '../../services/fetch-nodeinfo';
import { fetchMeta } from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host';
const logger = new Logger('deliver'); const logger = new Logger('deliver');
@ -13,6 +15,23 @@ let latest: string | null = null;
export default async (job: Bull.Job) => { export default async (job: Bull.Job) => {
const { host } = new URL(job.data.to); const { host } = new URL(job.data.to);
// ブロックしてたら中断
const meta = await fetchMeta();
if (meta.blockedHosts.includes(toPuny(host))) {
return 'skip (blocked)';
}
// closedなら中断
const closedHosts = await Instances.find({
where: {
isMarkedAsClosed: true
},
cache: 60 * 1000
});
if (closedHosts.map(x => x.host).includes(toPuny(host))) {
return 'skip (closed)';
}
try { try {
if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) { if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) {
logger.debug(`delivering ${latest}`); logger.debug(`delivering ${latest}`);
@ -48,8 +67,6 @@ export default async (job: Bull.Job) => {
}); });
if (res != null && res.hasOwnProperty('statusCode')) { if (res != null && res.hasOwnProperty('statusCode')) {
logger.warn(`deliver failed: ${res.statusCode} ${res.statusMessage} to=${job.data.to}`);
// 4xx // 4xx
if (res.statusCode >= 400 && res.statusCode < 500) { if (res.statusCode >= 400 && res.statusCode < 500) {
// HTTPステータスコード4xxはクライアントエラーであり、それはつまり // HTTPステータスコード4xxはクライアントエラーであり、それはつまり
@ -61,7 +78,6 @@ export default async (job: Bull.Job) => {
throw `${res.statusCode} ${res.statusMessage}`; throw `${res.statusCode} ${res.statusMessage}`;
} else { } else {
// DNS error, socket error, timeout ... // DNS error, socket error, timeout ...
logger.warn(`deliver failed: ${res} to=${job.data.to}`);
throw res; throw res;
} }
} }

View File

@ -0,0 +1,131 @@
import { Users, Followings } from '../../models';
import { ILocalUser, IRemoteUser } from '../../models/entities/user';
import { deliver } from '../../queue';
//#region types
interface IRecipe {
type: string;
}
interface IFollowersRecipe extends IRecipe {
type: 'Followers';
}
interface IDirectRecipe extends IRecipe {
type: 'Direct';
to: IRemoteUser;
}
const isFollowers = (recipe: any): recipe is IFollowersRecipe =>
recipe.type === 'Followers';
const isDirect = (recipe: any): recipe is IDirectRecipe =>
recipe.type === 'Direct';
//#endregion
export default class DeliverManager {
private actor: ILocalUser;
private activity: any;
private recipes: IRecipe[] = [];
/**
* Constructor
* @param actor Actor
* @param activity Activity to deliver
*/
constructor(actor: ILocalUser, activity: any) {
this.actor = actor;
this.activity = activity;
}
/**
* Add recipe for followers deliver
*/
public addFollowersRecipe() {
const deliver = {
type: 'Followers'
} as IFollowersRecipe;
this.addRecipe(deliver);
}
/**
* Add recipe for direct deliver
* @param to To
*/
public addDirectRecipe(to: IRemoteUser) {
const recipe = {
type: 'Direct',
to
} as IDirectRecipe;
this.addRecipe(recipe);
}
/**
* Add recipe
* @param recipe Recipe
*/
public addRecipe(recipe: IRecipe) {
this.recipes.push(recipe);
}
/**
* Execute delivers
*/
public async execute() {
if (!Users.isLocalUser(this.actor)) return;
const inboxes: string[] = [];
// build inbox list
for (const recipe of this.recipes) {
if (isFollowers(recipe)) {
// followers deliver
const followers = await Followings.find({
followeeId: this.actor.id
});
for (const following of followers) {
if (Followings.isRemoteFollower(following)) {
const inbox = following.followerSharedInbox || following.followerInbox;
if (!inboxes.includes(inbox)) inboxes.push(inbox);
}
}
} else if (isDirect(recipe)) {
// direct deliver
const inbox = recipe.to.inbox;
if (inbox && !inboxes.includes(inbox)) inboxes.push(inbox);
}
}
// deliver
for (const inbox of inboxes) {
deliver(this.actor, this.activity, inbox);
}
}
}
//#region Utilities
/**
* Deliver activity to followers
* @param activity Activity
* @param from Followee
*/
export async function deliverToFollowers(actor: ILocalUser, activity: any) {
const manager = new DeliverManager(actor, activity);
manager.addFollowersRecipe();
await manager.execute();
}
/**
* Deliver activity to user
* @param activity Activity
* @param to Target user
*/
export async function deliverToUser(actor: ILocalUser, activity: any, to: IRemoteUser) {
const manager = new DeliverManager(actor, activity);
manager.addDirectRecipe(to);
await manager.execute();
}
//#endregion

View File

@ -13,14 +13,10 @@ export default async (actor: IRemoteUser, activity: IAccept): Promise<void> => {
const resolver = new Resolver(); const resolver = new Resolver();
let object; const object = await resolver.resolve(activity.object).catch(e => {
try {
object = await resolver.resolve(activity.object);
} catch (e) {
logger.error(`Resolution failed: ${e}`); logger.error(`Resolution failed: ${e}`);
throw e; throw e;
} });
switch (object.type) { switch (object.type) {
case 'Follow': case 'Follow':

View File

@ -13,14 +13,10 @@ export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> =>
const resolver = new Resolver(); const resolver = new Resolver();
let object; const object = await resolver.resolve(activity.object).catch(e => {
try {
object = await resolver.resolve(activity.object);
} catch (e) {
logger.error(`Resolution failed: ${e}`); logger.error(`Resolution failed: ${e}`);
throw e; throw e;
} });
if (validPost.includes(object.type)) { if (validPost.includes(object.type)) {
announceNote(resolver, actor, activity, object); announceNote(resolver, actor, activity, object);

View File

@ -13,14 +13,10 @@ export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
const resolver = new Resolver(); const resolver = new Resolver();
let object; const object = await resolver.resolve(activity.object).catch(e => {
try {
object = await resolver.resolve(activity.object);
} catch (e) {
logger.error(`Resolution failed: ${e}`); logger.error(`Resolution failed: ${e}`);
throw e; throw e;
} });
if (validPost.includes(object.type)) { if (validPost.includes(object.type)) {
createNote(resolver, actor, object); createNote(resolver, actor, object);

View File

@ -13,14 +13,10 @@ export default async (actor: IRemoteUser, activity: IReject): Promise<void> => {
const resolver = new Resolver(); const resolver = new Resolver();
let object; const object = await resolver.resolve(activity.object).catch(e => {
try {
object = await resolver.resolve(activity.object);
} catch (e) {
logger.error(`Resolution failed: ${e}`); logger.error(`Resolution failed: ${e}`);
throw e; throw e;
} });
switch (object.type) { switch (object.type) {
case 'Follow': case 'Follow':

View File

@ -20,14 +20,10 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
const resolver = new Resolver(); const resolver = new Resolver();
let object; const object = await resolver.resolve(activity.object).catch(e => {
try {
object = await resolver.resolve(activity.object);
} catch (e) {
logger.error(`Resolution failed: ${e}`); logger.error(`Resolution failed: ${e}`);
throw e; throw e;
} });
switch (object.type) { switch (object.type) {
case 'Follow': case 'Follow':

View File

@ -4,7 +4,6 @@ import config from '../../../config';
import Resolver from '../resolver'; import Resolver from '../resolver';
import { resolveImage } from './image'; import { resolveImage } from './image';
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId } from '../type'; import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId } from '../type';
import { DriveFile } from '../../../models/entities/drive-file';
import { fromHtml } from '../../../mfm/fromHtml'; import { fromHtml } from '../../../mfm/fromHtml';
import { resolveNote, extractEmojis } from './note'; import { resolveNote, extractEmojis } from './note';
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc'; import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
@ -201,18 +200,18 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
updateUsertags(user!, tags); updateUsertags(user!, tags);
//#region アバターとヘッダー画像をフェッチ //#region アバターとヘッダー画像をフェッチ
const [avatar, banner] = (await Promise.all<DriveFile | null>([ const [avatar, banner] = await Promise.all([
person.icon, person.icon,
person.image person.image
].map(img => ].map(img =>
img == null img == null
? Promise.resolve(null) ? Promise.resolve(null)
: resolveImage(user!, img).catch(() => null) : resolveImage(user!, img).catch(() => null)
))); ));
const avatarId = avatar ? avatar.id : null; const avatarId = avatar ? avatar.id : null;
const bannerId = banner ? banner.id : null; const bannerId = banner ? banner.id : null;
const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar) : null; const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar, true) : null;
const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null; const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null;
const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null; const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null;
const bannerColor = banner && banner.properties.avgColor ? banner.properties.avgColor : null; const bannerColor = banner && banner.properties.avgColor ? banner.properties.avgColor : null;
@ -290,14 +289,14 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
logger.info(`Updating the Person: ${person.id}`); logger.info(`Updating the Person: ${person.id}`);
// アバターとヘッダー画像をフェッチ // アバターとヘッダー画像をフェッチ
const [avatar, banner] = (await Promise.all<DriveFile | null>([ const [avatar, banner] = await Promise.all([
person.icon, person.icon,
person.image person.image
].map(img => ].map(img =>
img == null img == null
? Promise.resolve(null) ? Promise.resolve(null)
: resolveImage(exist, img).catch(() => null) : resolveImage(exist, img).catch(() => null)
))); ));
// カスタム絵文字取得 // カスタム絵文字取得
const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => { const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => {
@ -326,7 +325,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
if (avatar) { if (avatar) {
updates.avatarId = avatar.id; updates.avatarId = avatar.id;
updates.avatarUrl = DriveFiles.getPublicUrl(avatar); updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true);
updates.avatarColor = avatar.properties.avgColor ? avatar.properties.avgColor : null; updates.avatarColor = avatar.properties.avgColor ? avatar.properties.avgColor : null;
} }

View File

@ -6,15 +6,10 @@ import * as cache from 'lookup-dns-cache';
import config from '../../config'; import config from '../../config';
import { ILocalUser } from '../../models/entities/user'; import { ILocalUser } from '../../models/entities/user';
import { publishApLogStream } from '../../services/stream'; import { publishApLogStream } from '../../services/stream';
import { apLogger } from './logger'; import { UserKeypairs } from '../../models';
import { UserKeypairs, Instances } from '../../models';
import { fetchMeta } from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import * as httpsProxyAgent from 'https-proxy-agent'; import * as httpsProxyAgent from 'https-proxy-agent';
export const logger = apLogger.createSubLogger('deliver');
const agent = config.proxy const agent = config.proxy
? new httpsProxyAgent(config.proxy) ? new httpsProxyAgent(config.proxy)
: new https.Agent({ : new https.Agent({
@ -24,28 +19,7 @@ const agent = config.proxy
export default async (user: ILocalUser, url: string, object: any) => { export default async (user: ILocalUser, url: string, object: any) => {
const timeout = 10 * 1000; const timeout = 10 * 1000;
const { protocol, host, hostname, port, pathname, search } = new URL(url); const { protocol, hostname, port, pathname, search } = new URL(url);
// ブロックしてたら中断
const meta = await fetchMeta();
if (meta.blockedHosts.includes(toPuny(host))) {
logger.info(`skip (blocked) ${url}`);
return;
}
// closedなら中断
const closedHosts = await Instances.find({
where: {
isMarkedAsClosed: true
},
cache: 60 * 1000
});
if (closedHosts.map(x => x.host).includes(toPuny(host))) {
logger.info(`skip (closed) ${url}`);
return;
}
logger.info(`--> ${url}`);
const data = JSON.stringify(object); const data = JSON.stringify(object);
@ -73,10 +47,8 @@ export default async (user: ILocalUser, url: string, object: any) => {
} }
}, res => { }, res => {
if (res.statusCode! >= 400) { if (res.statusCode! >= 400) {
logger.warn(`${url} --> ${res.statusCode}`);
reject(res); reject(res);
} else { } else {
logger.succ(`${url} --> ${res.statusCode}`);
resolve(); resolve();
} }
}); });

View File

@ -2,7 +2,7 @@ import webFinger from './webfinger';
import config from '../config'; import config from '../config';
import { createPerson, updatePerson } from './activitypub/models/person'; import { createPerson, updatePerson } from './activitypub/models/person';
import { remoteLogger } from './logger'; import { remoteLogger } from './logger';
import chalk from 'chalk'; import * as chalk from 'chalk';
import { User, IRemoteUser } from '../models/entities/user'; import { User, IRemoteUser } from '../models/entities/user';
import { Users } from '../models'; import { Users } from '../models';
import { toPuny } from '../misc/convert-host'; import { toPuny } from '../misc/convert-host';

View File

@ -5,7 +5,7 @@ import authenticate from './authenticate';
import call from './call'; import call from './call';
import { ApiError } from './error'; import { ApiError } from './error';
export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res) => { export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => {
const body = ctx.request.body; const body = ctx.request.body;
const reply = (x?: any, y?: ApiError) => { const reply = (x?: any, y?: ApiError) => {

View File

@ -6,7 +6,7 @@ import { Signins } from '../../../models';
import { genId } from '../../../misc/gen-id'; import { genId } from '../../../misc/gen-id';
import { publishMainStream } from '../../../services/stream'; import { publishMainStream } from '../../../services/stream';
export default function(ctx: Koa.BaseContext, user: ILocalUser, redirect = false) { export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
if (redirect) { if (redirect) {
//#region Cookie //#region Cookie
const expires = 1000 * 60 * 60 * 24 * 365; // One Year const expires = 1000 * 60 * 60 * 24 * 365; // One Year

View File

@ -1,6 +1,6 @@
import $ from 'cafy'; import $ from 'cafy';
import define from '../../../define'; import define from '../../../define';
import { deliverQueue, inboxQueue } from '../../../../../queue'; import { deliverQueue, inboxQueue, dbQueue, objectStorageQueue } from '../../../../../queue';
export const meta = { export const meta = {
tags: ['admin'], tags: ['admin'],
@ -10,11 +10,11 @@ export const meta = {
params: { params: {
domain: { domain: {
validator: $.str, validator: $.str.or(['deliver', 'inbox', 'db', 'objectStorage']),
}, },
state: { state: {
validator: $.str, validator: $.str.or(['active', 'waiting', 'delayed']),
}, },
limit: { limit: {
@ -28,13 +28,22 @@ export default define(meta, async (ps) => {
const queue = const queue =
ps.domain === 'deliver' ? deliverQueue : ps.domain === 'deliver' ? deliverQueue :
ps.domain === 'inbox' ? inboxQueue : ps.domain === 'inbox' ? inboxQueue :
ps.domain === 'db' ? dbQueue :
ps.domain === 'objectStorage' ? objectStorageQueue :
null as never; null as never;
const jobs = await queue.getJobs([ps.state], 0, ps.limit!); const jobs = await queue.getJobs([ps.state], 0, ps.limit!);
return jobs.map(job => ({ return jobs.map(job => {
const data = job.data;
delete data.content;
delete data.user;
return {
id: job.id, id: job.id,
data: job.data, data,
attempts: job.attemptsMade, attempts: job.attemptsMade,
})); maxAttempts: job.opts ? job.opts.attempts : 0,
timestamp: job.timestamp,
};
});
}); });

View File

@ -10,7 +10,7 @@ import { ensure } from '../../../prelude/ensure';
import { verifyLogin, hash } from '../2fa'; import { verifyLogin, hash } from '../2fa';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
export default async (ctx: Koa.BaseContext) => { export default async (ctx: Koa.Context) => {
ctx.set('Access-Control-Allow-Origin', config.url); ctx.set('Access-Control-Allow-Origin', config.url);
ctx.set('Access-Control-Allow-Credentials', 'true'); ctx.set('Access-Control-Allow-Credentials', 'true');

View File

@ -15,8 +15,8 @@ import { UserProfile } from '../../../models/entities/user-profile';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { UsedUsername } from '../../../models/entities/used-username'; import { UsedUsername } from '../../../models/entities/used-username';
export default async (ctx: Koa.BaseContext) => { export default async (ctx: Koa.Context) => {
const body = ctx.request.body as any; const body = ctx.request.body;
const instance = await fetchMeta(true); const instance = await fetchMeta(true);

View File

@ -12,11 +12,11 @@ import { Users, UserProfiles } from '../../../models';
import { ILocalUser } from '../../../models/entities/user'; import { ILocalUser } from '../../../models/entities/user';
import { ensure } from '../../../prelude/ensure'; import { ensure } from '../../../prelude/ensure';
function getUserToken(ctx: Koa.BaseContext) { function getUserToken(ctx: Koa.Context) {
return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1]; return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1];
} }
function compareOrigin(ctx: Koa.BaseContext) { function compareOrigin(ctx: Koa.Context) {
function normalizeUrl(url: string) { function normalizeUrl(url: string) {
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
} }

View File

@ -12,11 +12,11 @@ import { Users, UserProfiles } from '../../../models';
import { ILocalUser } from '../../../models/entities/user'; import { ILocalUser } from '../../../models/entities/user';
import { ensure } from '../../../prelude/ensure'; import { ensure } from '../../../prelude/ensure';
function getUserToken(ctx: Koa.BaseContext) { function getUserToken(ctx: Koa.Context) {
return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1]; return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1];
} }
function compareOrigin(ctx: Koa.BaseContext) { function compareOrigin(ctx: Koa.Context) {
function normalizeUrl(url: string) { function normalizeUrl(url: string) {
return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : ''; return url ? url.endsWith('/') ? url.substr(0, url.length - 1) : url : '';
} }

View File

@ -11,11 +11,11 @@ import { Users, UserProfiles } from '../../../models';
import { ILocalUser } from '../../../models/entities/user'; import { ILocalUser } from '../../../models/entities/user';
import { ensure } from '../../../prelude/ensure'; import { ensure } from '../../../prelude/ensure';
function getUserToken(ctx: Koa.BaseContext) { function getUserToken(ctx: Koa.Context) {
return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1]; return ((ctx.headers['cookie'] || '').match(/i=(\w+)/) || [null, null])[1];
} }
function compareOrigin(ctx: Koa.BaseContext) { function compareOrigin(ctx: Koa.Context) {
function normalizeUrl(url: string) { function normalizeUrl(url: string) {
return url.endsWith('/') ? url.substr(0, url.length - 1) : url; return url.endsWith('/') ? url.substr(0, url.length - 1) : url;
} }

View File

@ -25,17 +25,8 @@ export default class extends Channel {
@autobind @autobind
private async onNote(note: PackedNote) { private async onNote(note: PackedNote) {
if ((note.user as PackedUser).host !== null) return; if ((note.user as PackedUser).host !== null) return;
if (note.visibility === 'home') return; if (note.visibility !== 'public') return;
if (['followers', 'specified'].includes(note.visibility)) {
note = await Notes.pack(note.id, this.user, {
detail: true
});
if (note.isHidden) {
return;
}
} else {
// リプライなら再pack // リプライなら再pack
if (note.replyId != null) { if (note.replyId != null) {
note.reply = await Notes.pack(note.replyId, this.user, { note.reply = await Notes.pack(note.replyId, this.user, {
@ -48,7 +39,6 @@ export default class extends Channel {
detail: true detail: true
}); });
} }
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (shouldMuteThisNote(note, this.muting)) return; if (shouldMuteThisNote(note, this.muting)) return;

View File

@ -8,12 +8,12 @@ import { InternalStorage } from '../../services/drive/internal-storage';
const assets = `${__dirname}/../../server/file/assets/`; const assets = `${__dirname}/../../server/file/assets/`;
const commonReadableHandlerGenerator = (ctx: Koa.BaseContext) => (e: Error): void => { const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => {
serverLogger.error(e); serverLogger.error(e);
ctx.status = 500; ctx.status = 500;
}; };
export default async function(ctx: Koa.BaseContext) { export default async function(ctx: Koa.Context) {
const key = ctx.params.key; const key = ctx.params.key;
// Fetch drive file // Fetch drive file

View File

@ -6,7 +6,7 @@ import { createTemp } from '../../misc/create-temp';
import { downloadUrl } from '../../misc/donwload-url'; import { downloadUrl } from '../../misc/donwload-url';
import { detectMine } from '../../misc/detect-mine'; import { detectMine } from '../../misc/detect-mine';
export async function proxyMedia(ctx: Koa.BaseContext) { export async function proxyMedia(ctx: Koa.Context) {
const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url;
// Create temp file // Create temp file

View File

@ -2,7 +2,7 @@ import * as Koa from 'koa';
import * as manifest from '../../client/assets/manifest.json'; import * as manifest from '../../client/assets/manifest.json';
import { fetchMeta } from '../../misc/fetch-meta'; import { fetchMeta } from '../../misc/fetch-meta';
module.exports = async (ctx: Koa.BaseContext) => { module.exports = async (ctx: Koa.Context) => {
const json = JSON.parse(JSON.stringify(manifest)); const json = JSON.parse(JSON.stringify(manifest));
const instance = await fetchMeta(true); const instance = await fetchMeta(true);

View File

@ -8,7 +8,7 @@ import { query } from '../../prelude/url';
const logger = new Logger('url-preview'); const logger = new Logger('url-preview');
module.exports = async (ctx: Koa.BaseContext) => { module.exports = async (ctx: Koa.Context) => {
const meta = await fetchMeta(); const meta = await fetchMeta();
logger.info(meta.summalyProxy logger.info(meta.summalyProxy

View File

@ -24,6 +24,22 @@ export default async function(follower: User, followee: User, silent = false) {
await Followings.delete(following.id); await Followings.delete(following.id);
decrementFollowing(follower, followee);
// Publish unfollow event
if (!silent && Users.isLocalUser(follower)) {
Users.pack(followee, follower, {
detail: true
}).then(packed => publishMainStream(follower.id, 'unfollow', packed));
}
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
deliver(follower, content, followee.inbox);
}
}
export async function decrementFollowing(follower: User, followee: User) {
//#region Decrement following count //#region Decrement following count
Users.decrement({ id: follower.id }, 'followingCount', 1); Users.decrement({ id: follower.id }, 'followingCount', 1);
//#endregion //#endregion
@ -47,16 +63,4 @@ export default async function(follower: User, followee: User, silent = false) {
//#endregion //#endregion
perUserFollowingChart.update(follower, followee, false); perUserFollowingChart.update(follower, followee, false);
// Publish unfollow event
if (!silent && Users.isLocalUser(follower)) {
Users.pack(followee, follower, {
detail: true
}).then(packed => publishMainStream(follower.id, 'unfollow', packed));
}
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
deliver(follower, content, followee.inbox);
}
} }

View File

@ -4,7 +4,8 @@ import renderReject from '../../../remote/activitypub/renderer/reject';
import { deliver } from '../../../queue'; import { deliver } from '../../../queue';
import { publishMainStream } from '../../stream'; import { publishMainStream } from '../../stream';
import { User, ILocalUser } from '../../../models/entities/user'; import { User, ILocalUser } from '../../../models/entities/user';
import { Users, FollowRequests } from '../../../models'; import { Users, FollowRequests, Followings } from '../../../models';
import { decrementFollowing } from '../delete';
export default async function(followee: User, follower: User) { export default async function(followee: User, follower: User) {
if (Users.isRemoteUser(follower)) { if (Users.isRemoteUser(follower)) {
@ -17,11 +18,25 @@ export default async function(followee: User, follower: User) {
deliver(followee as ILocalUser, content, follower.inbox); deliver(followee as ILocalUser, content, follower.inbox);
} }
await FollowRequests.delete({ const request = await FollowRequests.findOne({
followeeId: followee.id, followeeId: followee.id,
followerId: follower.id followerId: follower.id
}); });
if (request) {
await FollowRequests.delete(request.id);
} else {
const following = await Followings.findOne({
followeeId: followee.id,
followerId: follower.id
});
if (following) {
await Followings.delete(following.id);
decrementFollowing(follower, followee);
}
}
Users.pack(followee, follower, { Users.pack(followee, follower, {
detail: true detail: true
}).then(packed => publishMainStream(follower.id, 'unfollow', packed)); }).then(packed => publishMainStream(follower.id, 'unfollow', packed));

View File

@ -2,13 +2,13 @@ import config from '../../config';
import renderAdd from '../../remote/activitypub/renderer/add'; import renderAdd from '../../remote/activitypub/renderer/add';
import renderRemove from '../../remote/activitypub/renderer/remove'; import renderRemove from '../../remote/activitypub/renderer/remove';
import { renderActivity } from '../../remote/activitypub/renderer'; import { renderActivity } from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
import { IdentifiableError } from '../../misc/identifiable-error'; import { IdentifiableError } from '../../misc/identifiable-error';
import { User, ILocalUser } from '../../models/entities/user'; import { User } from '../../models/entities/user';
import { Note } from '../../models/entities/note'; import { Note } from '../../models/entities/note';
import { Notes, UserNotePinings, Users, Followings } from '../../models'; import { Notes, UserNotePinings, Users } from '../../models';
import { UserNotePining } from '../../models/entities/user-note-pinings'; import { UserNotePining } from '../../models/entities/user-note-pinings';
import { genId } from '../../misc/gen-id'; import { genId } from '../../misc/gen-id';
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
/** /**
* 指定した投稿をピン留めします * 指定した投稿をピン留めします
@ -82,36 +82,9 @@ export async function deliverPinnedChange(userId: User['id'], noteId: Note['id']
if (!Users.isLocalUser(user)) return; if (!Users.isLocalUser(user)) return;
const queue = await CreateRemoteInboxes(user);
if (queue.length < 1) return;
const target = `${config.url}/users/${user.id}/collections/featured`; const target = `${config.url}/users/${user.id}/collections/featured`;
const item = `${config.url}/notes/${noteId}`; const item = `${config.url}/notes/${noteId}`;
const content = renderActivity(isAddition ? renderAdd(user, target, item) : renderRemove(user, target, item)); const content = renderActivity(isAddition ? renderAdd(user, target, item) : renderRemove(user, target, item));
for (const inbox of queue) {
deliver(user, content, inbox);
}
}
/** deliverToFollowers(user, content);
* ローカルユーザーのリモートフォロワーのinboxリストを作成する
* @param user ローカルユーザー
*/
async function CreateRemoteInboxes(user: ILocalUser): Promise<string[]> {
const followers = await Followings.find({
followeeId: user.id
});
const queue: string[] = [];
for (const following of followers) {
if (Followings.isRemoteFollower(following)) {
const inbox = following.followerSharedInbox || following.followerInbox;
if (!queue.includes(inbox)) queue.push(inbox);
}
}
return queue;
} }

View File

@ -1,34 +1,17 @@
import renderUpdate from '../../remote/activitypub/renderer/update'; import renderUpdate from '../../remote/activitypub/renderer/update';
import { renderActivity } from '../../remote/activitypub/renderer'; import { renderActivity } from '../../remote/activitypub/renderer';
import { deliver } from '../../queue'; import { Users } from '../../models';
import { Followings, Users } from '../../models';
import { User } from '../../models/entities/user'; import { User } from '../../models/entities/user';
import { renderPerson } from '../../remote/activitypub/renderer/person'; import { renderPerson } from '../../remote/activitypub/renderer/person';
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
export async function publishToFollowers(userId: User['id']) { export async function publishToFollowers(userId: User['id']) {
const user = await Users.findOne(userId); const user = await Users.findOne(userId);
if (user == null) throw new Error('user not found'); if (user == null) throw new Error('user not found');
const followers = await Followings.find({
followeeId: user.id
});
const queue: string[] = [];
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信
if (Users.isLocalUser(user)) { if (Users.isLocalUser(user)) {
for (const following of followers) {
if (Followings.isRemoteFollower(following)) {
const inbox = following.followerSharedInbox || following.followerInbox;
if (!queue.includes(inbox)) queue.push(inbox);
}
}
if (queue.length > 0) {
const content = renderActivity(renderUpdate(await renderPerson(user), user)); const content = renderActivity(renderUpdate(await renderPerson(user), user));
for (const inbox of queue) { deliverToFollowers(user, content);
deliver(user, content, inbox);
}
}
} }
} }

View File

@ -1,6 +1,6 @@
import * as cluster from 'cluster'; import * as cluster from 'cluster';
import * as os from 'os'; import * as os from 'os';
import chalk from 'chalk'; import * as chalk from 'chalk';
import * as dateformat from 'dateformat'; import * as dateformat from 'dateformat';
import { program } from '../argv'; import { program } from '../argv';
import { getRepository } from 'typeorm'; import { getRepository } from 'typeorm';
@ -103,7 +103,7 @@ export default class Logger {
worker: worker.toString(), worker: worker.toString(),
domain: [this.domain].concat(subDomains).map(d => d.name), domain: [this.domain].concat(subDomains).map(d => d.name),
level: level, level: level,
message: message, message: message.substr(0, 1000), // 1024を超えるとログが挿入できずエラーになり無限ループする
data: data, data: data,
} as Log); } as Log);
} }

View File

@ -1,6 +1,6 @@
import es from '../../db/elasticsearch'; import es from '../../db/elasticsearch';
import { publishMainStream, publishNotesStream } from '../stream'; import { publishMainStream, publishNotesStream } from '../stream';
import { deliver } from '../../queue'; import DeliverManager from '../../remote/activitypub/deliver-manager';
import renderNote from '../../remote/activitypub/renderer/note'; import renderNote from '../../remote/activitypub/renderer/note';
import renderCreate from '../../remote/activitypub/renderer/create'; import renderCreate from '../../remote/activitypub/renderer/create';
import renderAnnounce from '../../remote/activitypub/renderer/announce'; import renderAnnounce from '../../remote/activitypub/renderer/announce';
@ -17,7 +17,7 @@ import extractMentions from '../../misc/extract-mentions';
import extractEmojis from '../../misc/extract-emojis'; import extractEmojis from '../../misc/extract-emojis';
import extractHashtags from '../../misc/extract-hashtags'; import extractHashtags from '../../misc/extract-hashtags';
import { Note, IMentionedRemoteUsers } from '../../models/entities/note'; import { Note, IMentionedRemoteUsers } from '../../models/entities/note';
import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, UserProfiles } from '../../models'; import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles } from '../../models';
import { DriveFile } from '../../models/entities/drive-file'; import { DriveFile } from '../../models/entities/drive-file';
import { App } from '../../models/entities/app'; import { App } from '../../models/entities/app';
import { Not, getConnection, In } from 'typeorm'; import { Not, getConnection, In } from 'typeorm';
@ -244,13 +244,7 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
const nm = new NotificationManager(user, note); const nm = new NotificationManager(user, note);
const nmRelatedPromises = []; const nmRelatedPromises = [];
createMentionedEvents(mentionedUsers, note, nm); await createMentionedEvents(mentionedUsers, note, nm);
const noteActivity = await renderNoteOrRenoteActivity(data, note);
if (Users.isLocalUser(user)) {
deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
}
const profile = await UserProfiles.findOne(user.id).then(ensure); const profile = await UserProfiles.findOne(user.id).then(ensure);
@ -294,11 +288,42 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
} }
} }
publish(user, note, data.reply, data.renote, noteActivity);
Promise.all(nmRelatedPromises).then(() => { Promise.all(nmRelatedPromises).then(() => {
nm.deliver(); nm.deliver();
}); });
//#region AP deliver
if (Users.isLocalUser(user)) {
(async () => {
const noteActivity = await renderNoteOrRenoteActivity(data, note);
const dm = new DeliverManager(user, noteActivity);
// メンションされたリモートユーザーに配送
for (const u of mentionedUsers.filter(u => Users.isRemoteUser(u))) {
dm.addDirectRecipe(u as IRemoteUser);
}
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
if (data.reply && data.reply.userHost !== null) {
const u = await Users.findOne(data.reply.userId);
if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u);
}
// 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送
if (data.renote && data.renote.userHost !== null) {
const u = await Users.findOne(data.renote.userId);
if (u && Users.isRemoteUser(u)) dm.addDirectRecipe(u);
}
// フォロワーに配送
if (['public', 'home', 'followers'].includes(note.visibility)) {
dm.addFollowersRecipe();
}
dm.execute();
})();
}
//#endregion
} }
// Register to search database // Register to search database
@ -320,29 +345,6 @@ function incRenoteCount(renote: Note) {
Notes.increment({ id: renote.id }, 'score', 1); Notes.increment({ id: renote.id }, 'score', 1);
} }
async function publish(user: User, note: Note, reply: Note | null | undefined, renote: Note | null | undefined, noteActivity: any) {
if (Users.isLocalUser(user)) {
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
if (reply && reply.userHost !== null) {
Users.findOne(reply.userId).then(ensure).then(u => {
deliver(user, noteActivity, u.inbox);
});
}
// 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送
if (renote && renote.userHost !== null) {
Users.findOne(renote.userId).then(ensure).then(u => {
deliver(user, noteActivity, u.inbox);
});
}
}
if (['public', 'home', 'followers'].includes(note.visibility)) {
// フォロワーに配信
publishToFollowers(note, user, noteActivity);
}
}
async function insertNote(user: User, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) { async function insertNote(user: User, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) {
const insert = new Note({ const insert = new Note({
id: genId(data.createdAt!), id: genId(data.createdAt!),
@ -472,34 +474,6 @@ async function notifyToWatchersOfReplyee(reply: Note, user: User, nm: Notificati
} }
} }
async function publishToFollowers(note: Note, user: User, noteActivity: any) {
const followers = await Followings.find({
followeeId: note.userId
});
const queue: string[] = [];
for (const following of followers) {
if (Followings.isRemoteFollower(following)) {
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信
if (Users.isLocalUser(user)) {
const inbox = following.followerSharedInbox || following.followerInbox;
if (!queue.includes(inbox)) queue.push(inbox);
}
}
}
for (const inbox of queue) {
deliver(user as any, noteActivity, inbox);
}
}
function deliverNoteToMentionedRemoteUsers(mentionedUsers: User[], user: ILocalUser, noteActivity: any) {
for (const u of mentionedUsers.filter(u => Users.isRemoteUser(u))) {
deliver(user, noteActivity, (u as IRemoteUser).inbox);
}
}
async function createMentionedEvents(mentionedUsers: User[], note: Note, nm: NotificationManager) { async function createMentionedEvents(mentionedUsers: User[], note: Note, nm: NotificationManager) {
for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) {
const detailPackedNote = await Notes.pack(note, u, { const detailPackedNote = await Notes.pack(note, u, {

View File

@ -3,14 +3,14 @@ import renderDelete from '../../remote/activitypub/renderer/delete';
import renderAnnounce from '../../remote/activitypub/renderer/announce'; import renderAnnounce from '../../remote/activitypub/renderer/announce';
import renderUndo from '../../remote/activitypub/renderer/undo'; import renderUndo from '../../remote/activitypub/renderer/undo';
import { renderActivity } from '../../remote/activitypub/renderer'; import { renderActivity } from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
import renderTombstone from '../../remote/activitypub/renderer/tombstone'; import renderTombstone from '../../remote/activitypub/renderer/tombstone';
import config from '../../config'; import config from '../../config';
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
import { User } from '../../models/entities/user'; import { User } from '../../models/entities/user';
import { Note } from '../../models/entities/note'; import { Note } from '../../models/entities/note';
import { Notes, Users, Followings, Instances } from '../../models'; import { Notes, Users, Instances } from '../../models';
import { notesChart, perUserNotesChart, instanceChart } from '../chart'; import { notesChart, perUserNotesChart, instanceChart } from '../chart';
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
/** /**
* 投稿を削除します。 * 投稿を削除します。
@ -49,22 +49,7 @@ export default async function(user: User, note: Note, quiet = false) {
? renderUndo(renderAnnounce(renote.uri || `${config.url}/notes/${renote.id}`, note), user) ? renderUndo(renderAnnounce(renote.uri || `${config.url}/notes/${renote.id}`, note), user)
: renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user)); : renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
const queue: string[] = []; deliverToFollowers(user, content);
const followers = await Followings.find({
followeeId: note.userId
});
for (const following of followers) {
if (Followings.isRemoteFollower(following)) {
const inbox = following.followerSharedInbox || following.followerInbox;
if (!queue.includes(inbox)) queue.push(inbox);
}
}
for (const inbox of queue) {
deliver(user as any, content, inbox);
}
} }
//#endregion //#endregion

View File

@ -1,9 +1,9 @@
import renderUpdate from '../../../remote/activitypub/renderer/update'; import renderUpdate from '../../../remote/activitypub/renderer/update';
import { renderActivity } from '../../../remote/activitypub/renderer'; import { renderActivity } from '../../../remote/activitypub/renderer';
import { deliver } from '../../../queue';
import renderNote from '../../../remote/activitypub/renderer/note'; import renderNote from '../../../remote/activitypub/renderer/note';
import { Users, Notes, Followings } from '../../../models'; import { Users, Notes } from '../../../models';
import { Note } from '../../../models/entities/note'; import { Note } from '../../../models/entities/note';
import { deliverToFollowers } from '../../../remote/activitypub/deliver-manager';
export async function deliverQuestionUpdate(noteId: Note['id']) { export async function deliverQuestionUpdate(noteId: Note['id']) {
const note = await Notes.findOne(noteId); const note = await Notes.findOne(noteId);
@ -12,26 +12,9 @@ export async function deliverQuestionUpdate(noteId: Note['id']) {
const user = await Users.findOne(note.userId); const user = await Users.findOne(note.userId);
if (user == null) throw new Error('note not found'); if (user == null) throw new Error('note not found');
const followers = await Followings.find({
followeeId: user.id
});
const queue: string[] = [];
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信
if (Users.isLocalUser(user)) { if (Users.isLocalUser(user)) {
for (const following of followers) {
if (Followings.isRemoteFollower(following)) {
const inbox = following.followerSharedInbox || following.followerInbox;
if (!queue.includes(inbox)) queue.push(inbox);
}
}
if (queue.length > 0) {
const content = renderActivity(renderUpdate(await renderNote(note, false), user)); const content = renderActivity(renderUpdate(await renderNote(note, false), user));
for (const inbox of queue) { deliverToFollowers(user, content);
deliver(user, content, inbox);
}
}
} }
} }

View File

@ -20,6 +20,7 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true,
"typeRoots": [ "typeRoots": [
"node_modules/@types", "node_modules/@types",
"src/@types" "src/@types"

View File

@ -4,7 +4,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as webpack from 'webpack'; import * as webpack from 'webpack';
import chalk from 'chalk'; import * as chalk from 'chalk';
const { VueLoaderPlugin } = require('vue-loader'); const { VueLoaderPlugin } = require('vue-loader');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const ProgressBarPlugin = require('progress-bar-webpack-plugin');

1125
yarn.lock

File diff suppressed because it is too large Load Diff