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
=========
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)
--------------------
### 🐛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>
[![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)
@ -10,7 +10,7 @@
**A forever evolving, sophisticated microblogging platform.**
<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),
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>

View File

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

View File

@ -289,6 +289,7 @@ common:
sync: "Synchronizace"
save: "Uložit"
saved: "Uloženo"
preview: "Náhled"
room: "Místnost"
_room:
graphicsQuality: "Kvalita grafiky"
@ -399,6 +400,7 @@ common/views/components/games/reversi/reversi.index.vue:
invite: "Pozvat"
rule: "Jak hrát"
mode-invite: "Pozvat"
invitations: "Jste pozvaní ke hře!"
my-games: "Moje hra"
all-games: "Všechny hry"
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"
save: "Gem"
saved: "Gemt"
preview: "Før-visning"
search: "Søg"
delete: "Slet"
loading: "Henter"

View File

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

View File

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

View File

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

View File

@ -100,6 +100,11 @@ common:
"write:reactions": "Gérer vos réactions"
"write:votes": "Vote"
"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:
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"
@ -126,6 +131,7 @@ common:
geolocation-alert: "Votre appareil ne prend pas en charge les services de localisation"
error: "Erreur"
enter-username: "Saisir un nom d'utilisateur"
specified-recipient: "Correspondant·e"
add-visible-user: "Ajouter un utilisateur"
cw-placeholder: "Commenter le contenu (optionnel)"
username-prompt: "Saisir un nom d'utilisateur"
@ -284,6 +290,7 @@ common:
sync: "Synchroniser"
save: "Enregistrer"
saved: "enregistré"
preview: "Prévisualisation"
home-profile: "Profil principal"
deck-profile: "Profil deck"
room: "Pièce"
@ -379,6 +386,7 @@ common/views/pages/explore.vue:
popular-tags: "Mots-clés populaires"
federated: "Du Fédiverse"
explore: "Explorer {host}"
explore-fediverse: "Explorer le Fédiverse"
users-info: "Actuellement, {users} utilisateur·rice·s se sont inscrit ici"
common/views/components/reactions-viewer.details.vue:
few-users: "{users} ont réagit avec {reaction}"
@ -606,7 +614,9 @@ common/views/components/reaction-picker.vue:
choose-reaction: "Envoyer une réaction"
input-reaction-placeholder: "ou insérez un émoji"
common/views/components/emoji-picker.vue:
recent-emoji: "Utilisés récemment"
custom-emoji: "Émoji personnalisé"
no-category: "Sans catégorie"
people: "Personnes"
animals-and-nature: "Animaux et nature"
food-and-drink: "Nourriture et boisson"
@ -1350,6 +1360,7 @@ admin/views/drive.vue:
marked-as-sensitive: "Marqué comme sensible"
unmarked-as-sensitive: "Marqué comme non sensible"
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"
admin/views/users.vue:
operation: "Actions"
@ -1416,6 +1427,7 @@ admin/views/emoji.vue:
title: "Ajouter un émoji"
name: "Nom de lémoji"
name-desc: "Vous pouvez utiliser les caractères a~z 0~9 _"
category: "Catégories"
aliases: "Aliases"
aliases-desc: "Vous pouvez définir plus dun, séparés par des espaces."
url: "URL de limage"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -48,14 +48,15 @@
</ui-select>
</ui-horizon-group>
<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>
<template v-if="domain === 'deliver'">
<span>{{ job.data.to }}</span>
</template>
<template v-if="domain === 'inbox'">
<span>{{ job.activity.id }}</span>
<span>{{ job.data.activity.id }}</span>
</template>
<span>{{ `(${job.attempts}/${job.maxAttempts}, ${Math.floor((jobsFetched - job.timestamp) / 1000 / 60)}min)` }}</span>
</div>
</sequential-entrance>
<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,
jobs: [],
jobsLimit: 50,
jobsFetched: Date.now(),
domain: 'deliver',
state: 'delayed',
faTasks, faPaperPlane, faInbox, faChartBar, faDatabase, faCloud
@ -140,6 +142,7 @@ export default Vue.extend({
state: this.state,
limit: this.jobsLimit
}).then(jobs => {
this.jobsFetched = Date.now(),
this.jobs = jobs;
});
},
@ -149,7 +152,8 @@ export default Vue.extend({
<style lang="stylus" scoped>
.xvvuvgsv
> b
margin-right 16px
margin-left -6px
> b, span
margin 0 6px
</style>

View File

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

View File

@ -76,13 +76,15 @@ export default Vue.extend({
this.$root.api('ap/show', {
uri: acct
}).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({
type: 'error',
text: 'acct is not an user'
text: 'Not supported'
});
} else {
this.user = res.object;
}
}).catch((e: any) => {
this.$root.dialog({

View File

@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as request from 'request';
import config from '../config';
import chalk from 'chalk';
import * as chalk from 'chalk';
import Logger from '../services/logger';
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 { queueLogger } from './logger';
import { DriveFile } from '../models/entities/drive-file';
import { getJobInfo } from './get-job-info';
function initializeQueue(name: string) {
return new Queue(name, {
@ -44,19 +45,19 @@ const objectStorageLogger = queueLogger.createSubLogger('objectStorage');
deliverQueue
.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => deliverLogger.debug(`active id=${job.id} to=${job.data.to}`))
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) id=${job.id} to=${job.data.to}`))
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) id=${job.id} to=${job.data.to}`, { job, e: renderError(err) }))
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} 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}) ${getJobInfo(job)} to=${job.data.to}`))
.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
.on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => inboxLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) id=${job.id}`))
.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('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
.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('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
.on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`))

View File

@ -5,6 +5,8 @@ import Logger from '../../services/logger';
import { Instances } from '../../models';
import { instanceChart } from '../../services/chart';
import { fetchNodeinfo } from '../../services/fetch-nodeinfo';
import { fetchMeta } from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host';
const logger = new Logger('deliver');
@ -13,6 +15,23 @@ let latest: string | null = null;
export default async (job: Bull.Job) => {
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 {
if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) {
logger.debug(`delivering ${latest}`);
@ -48,8 +67,6 @@ export default async (job: Bull.Job) => {
});
if (res != null && res.hasOwnProperty('statusCode')) {
logger.warn(`deliver failed: ${res.statusCode} ${res.statusMessage} to=${job.data.to}`);
// 4xx
if (res.statusCode >= 400 && res.statusCode < 500) {
// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
@ -61,7 +78,6 @@ export default async (job: Bull.Job) => {
throw `${res.statusCode} ${res.statusMessage}`;
} else {
// DNS error, socket error, timeout ...
logger.warn(`deliver failed: ${res} to=${job.data.to}`);
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();
let object;
try {
object = await resolver.resolve(activity.object);
} catch (e) {
const object = await resolver.resolve(activity.object).catch(e => {
logger.error(`Resolution failed: ${e}`);
throw e;
}
});
switch (object.type) {
case 'Follow':

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import config from '../../../config';
import Resolver from '../resolver';
import { resolveImage } from './image';
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId } from '../type';
import { DriveFile } from '../../../models/entities/drive-file';
import { fromHtml } from '../../../mfm/fromHtml';
import { resolveNote, extractEmojis } from './note';
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);
//#region アバターとヘッダー画像をフェッチ
const [avatar, banner] = (await Promise.all<DriveFile | null>([
const [avatar, banner] = await Promise.all([
person.icon,
person.image
].map(img =>
img == null
? Promise.resolve(null)
: resolveImage(user!, img).catch(() => null)
)));
));
const avatarId = avatar ? avatar.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 avatarColor = avatar && avatar.properties.avgColor ? avatar.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}`);
// アバターとヘッダー画像をフェッチ
const [avatar, banner] = (await Promise.all<DriveFile | null>([
const [avatar, banner] = await Promise.all([
person.icon,
person.image
].map(img =>
img == null
? Promise.resolve(null)
: resolveImage(exist, img).catch(() => null)
)));
));
// カスタム絵文字取得
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) {
updates.avatarId = avatar.id;
updates.avatarUrl = DriveFiles.getPublicUrl(avatar);
updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true);
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 { ILocalUser } from '../../models/entities/user';
import { publishApLogStream } from '../../services/stream';
import { apLogger } from './logger';
import { UserKeypairs, Instances } from '../../models';
import { fetchMeta } from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host';
import { UserKeypairs } from '../../models';
import { ensure } from '../../prelude/ensure';
import * as httpsProxyAgent from 'https-proxy-agent';
export const logger = apLogger.createSubLogger('deliver');
const agent = config.proxy
? new httpsProxyAgent(config.proxy)
: new https.Agent({
@ -24,28 +19,7 @@ const agent = config.proxy
export default async (user: ILocalUser, url: string, object: any) => {
const timeout = 10 * 1000;
const { protocol, host, 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 { protocol, hostname, port, pathname, search } = new URL(url);
const data = JSON.stringify(object);
@ -73,10 +47,8 @@ export default async (user: ILocalUser, url: string, object: any) => {
}
}, res => {
if (res.statusCode! >= 400) {
logger.warn(`${url} --> ${res.statusCode}`);
reject(res);
} else {
logger.succ(`${url} --> ${res.statusCode}`);
resolve();
}
});

View File

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

View File

@ -5,7 +5,7 @@ import authenticate from './authenticate';
import call from './call';
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 reply = (x?: any, y?: ApiError) => {

View File

@ -6,7 +6,7 @@ import { Signins } from '../../../models';
import { genId } from '../../../misc/gen-id';
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) {
//#region Cookie
const expires = 1000 * 60 * 60 * 24 * 365; // One Year

View File

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

View File

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

View File

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

View File

@ -25,29 +25,19 @@ export default class extends Channel {
@autobind
private async onNote(note: PackedNote) {
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, {
// リプライなら再pack
if (note.replyId != null) {
note.reply = await Notes.pack(note.replyId, this.user, {
detail: true
});
}
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await Notes.pack(note.renoteId, this.user, {
detail: true
});
if (note.isHidden) {
return;
}
} else {
// リプライなら再pack
if (note.replyId != null) {
note.reply = await Notes.pack(note.replyId, this.user, {
detail: true
});
}
// Renoteなら再pack
if (note.renoteId != null) {
note.renote = await Notes.pack(note.renoteId, this.user, {
detail: true
});
}
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する

View File

@ -8,12 +8,12 @@ import { InternalStorage } from '../../services/drive/internal-storage';
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);
ctx.status = 500;
};
export default async function(ctx: Koa.BaseContext) {
export default async function(ctx: Koa.Context) {
const key = ctx.params.key;
// Fetch drive file

View File

@ -6,7 +6,7 @@ import { createTemp } from '../../misc/create-temp';
import { downloadUrl } from '../../misc/donwload-url';
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;
// Create temp file

View File

@ -2,7 +2,7 @@ import * as Koa from 'koa';
import * as manifest from '../../client/assets/manifest.json';
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 instance = await fetchMeta(true);

View File

@ -8,7 +8,7 @@ import { query } from '../../prelude/url';
const logger = new Logger('url-preview');
module.exports = async (ctx: Koa.BaseContext) => {
module.exports = async (ctx: Koa.Context) => {
const meta = await fetchMeta();
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);
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
Users.decrement({ id: follower.id }, 'followingCount', 1);
//#endregion
@ -47,16 +63,4 @@ export default async function(follower: User, followee: User, silent = false) {
//#endregion
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 { publishMainStream } from '../../stream';
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) {
if (Users.isRemoteUser(follower)) {
@ -17,11 +18,25 @@ export default async function(followee: User, follower: User) {
deliver(followee as ILocalUser, content, follower.inbox);
}
await FollowRequests.delete({
const request = await FollowRequests.findOne({
followeeId: followee.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, {
detail: true
}).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 renderRemove from '../../remote/activitypub/renderer/remove';
import { renderActivity } from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
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 { Notes, UserNotePinings, Users, Followings } from '../../models';
import { Notes, UserNotePinings, Users } from '../../models';
import { UserNotePining } from '../../models/entities/user-note-pinings';
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;
const queue = await CreateRemoteInboxes(user);
if (queue.length < 1) return;
const target = `${config.url}/users/${user.id}/collections/featured`;
const item = `${config.url}/notes/${noteId}`;
const content = renderActivity(isAddition ? renderAdd(user, target, item) : renderRemove(user, target, item));
for (const inbox of queue) {
deliver(user, content, inbox);
}
}
/**
* ローカルユーザーのリモートフォロワーの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;
deliverToFollowers(user, content);
}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import es from '../../db/elasticsearch';
import { publishMainStream, publishNotesStream } from '../stream';
import { deliver } from '../../queue';
import DeliverManager from '../../remote/activitypub/deliver-manager';
import renderNote from '../../remote/activitypub/renderer/note';
import renderCreate from '../../remote/activitypub/renderer/create';
import renderAnnounce from '../../remote/activitypub/renderer/announce';
@ -17,7 +17,7 @@ import extractMentions from '../../misc/extract-mentions';
import extractEmojis from '../../misc/extract-emojis';
import extractHashtags from '../../misc/extract-hashtags';
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 { App } from '../../models/entities/app';
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 nmRelatedPromises = [];
createMentionedEvents(mentionedUsers, note, nm);
const noteActivity = await renderNoteOrRenoteActivity(data, note);
if (Users.isLocalUser(user)) {
deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
}
await createMentionedEvents(mentionedUsers, note, nm);
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(() => {
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
@ -320,29 +345,6 @@ function incRenoteCount(renote: Note) {
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[]) {
const insert = new Note({
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) {
for (const u of mentionedUsers.filter(u => Users.isLocalUser(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 renderUndo from '../../remote/activitypub/renderer/undo';
import { renderActivity } from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
import config from '../../config';
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
import { User } from '../../models/entities/user';
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 { 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)
: renderDelete(renderTombstone(`${config.url}/notes/${note.id}`), user));
const queue: string[] = [];
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);
}
deliverToFollowers(user, content);
}
//#endregion

View File

@ -1,9 +1,9 @@
import renderUpdate from '../../../remote/activitypub/renderer/update';
import { renderActivity } from '../../../remote/activitypub/renderer';
import { deliver } from '../../../queue';
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 { deliverToFollowers } from '../../../remote/activitypub/deliver-manager';
export async function deliverQuestionUpdate(noteId: Note['id']) {
const note = await Notes.findOne(noteId);
@ -12,26 +12,9 @@ export async function deliverQuestionUpdate(noteId: Note['id']) {
const user = await Users.findOne(note.userId);
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)) {
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));
for (const inbox of queue) {
deliver(user, content, inbox);
}
}
const content = renderActivity(renderUpdate(await renderNote(note, false), user));
deliverToFollowers(user, content);
}
}

View File

@ -1,35 +1,36 @@
{
"compilerOptions": {
"allowJs": true,
"noEmitOnError": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedParameters": false,
"noUnusedLocals": true,
"noFallthroughCasesInSwitch": true,
"declaration": false,
"sourceMap": true,
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"removeComments": false,
"noLib": false,
"strict": true,
"strictNullChecks": true,
"strictPropertyInitialization": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"typeRoots": [
"node_modules/@types",
"src/@types"
]
},
"compileOnSave": false,
"include": [
"./src/**/*.ts"
],
"exclude": [
"./src/client/app/**/*.ts"
]
"compilerOptions": {
"allowJs": true,
"noEmitOnError": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedParameters": false,
"noUnusedLocals": true,
"noFallthroughCasesInSwitch": true,
"declaration": false,
"sourceMap": true,
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"removeComments": false,
"noLib": false,
"strict": true,
"strictNullChecks": true,
"strictPropertyInitialization": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"isolatedModules": true,
"typeRoots": [
"node_modules/@types",
"src/@types"
]
},
"compileOnSave": false,
"include": [
"./src/**/*.ts"
],
"exclude": [
"./src/client/app/**/*.ts"
]
}

View File

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

1125
yarn.lock

File diff suppressed because it is too large Load Diff