Compare commits
71 Commits
11.0.0-bet
...
11.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
f6f8cdbcf2 | |||
f46f53b8a3 | |||
a2fcae4383 | |||
483f043768 | |||
f8e0f4f21f | |||
9c3613e96d | |||
d9cacdc86d | |||
aa3d2deeaa | |||
e64912545a | |||
b247be80cc | |||
343f2f1f33 | |||
2d590df900 | |||
ba56b2b9fd | |||
bf472b0c5e | |||
f7961f34c5 | |||
e369031a28 | |||
186d7bbfd9 | |||
60a11f8da5 | |||
1181fcdceb | |||
8cb9852058 | |||
53d46d1cbe | |||
275e1c8de9 | |||
d46eca4c87 | |||
2927fb1597 | |||
8c72e011d2 | |||
69662e24c3 | |||
96099ffe98 | |||
ae16b45c11 | |||
cfee87d3ef | |||
5fcf5bc635 | |||
14bcb813cc | |||
6af19794b6 | |||
8316186695 | |||
85d3023cd5 | |||
78414dee29 | |||
084135141f | |||
467a21f028 | |||
6e284c44d6 | |||
daf9a449e8 | |||
960268fd33 | |||
8c331da315 | |||
b0d626d862 | |||
a51fbd7316 | |||
063f372f3c | |||
987168b863 | |||
4ee40c3345 | |||
654daff7ce | |||
64e10e9619 | |||
b89cffe98d | |||
bd76ba702f | |||
5d52e9ce6b | |||
3c29027ca3 | |||
2ff3069d23 | |||
4198246351 | |||
2b6389b4dc | |||
d7df75ae6c | |||
11c30eccb3 | |||
ab8c6515b8 | |||
d4ad36fa41 | |||
4d688be3df | |||
d2b75f3501 | |||
46b78cb4ff | |||
5d6e0d0f37 | |||
e19d0a37bb | |||
dea3e2132e | |||
91c1ceefbd | |||
5c50989970 | |||
2a7e3b9c51 | |||
ab302df0ae | |||
21e5809993 | |||
c58afc67e8 |
@ -65,10 +65,10 @@ db:
|
|||||||
# ┌─────────────────────┐
|
# ┌─────────────────────┐
|
||||||
#───┘ Redis configuration └─────────────────────────────────────
|
#───┘ Redis configuration └─────────────────────────────────────
|
||||||
|
|
||||||
#redis:
|
redis:
|
||||||
# host: localhost
|
host: localhost
|
||||||
# port: 6379
|
port: 6379
|
||||||
# pass: example-pass
|
#pass: example-pass
|
||||||
|
|
||||||
# ┌─────────────────────────────┐
|
# ┌─────────────────────────────┐
|
||||||
#───┘ Elasticsearch configuration └─────────────────────────────
|
#───┘ Elasticsearch configuration └─────────────────────────────
|
||||||
@ -127,28 +127,12 @@ drive:
|
|||||||
# change it according to your preferences.
|
# change it according to your preferences.
|
||||||
|
|
||||||
# Available methods:
|
# Available methods:
|
||||||
# aid1 ... Use AID for ID generation (with random 1 char)
|
# aid ... Short, Millisecond accuracy
|
||||||
# aid2 ... Use AID for ID generation (with random 2 chars)
|
# meid ... Similar to ObjectID, Millisecond accuracy
|
||||||
# aid3 ... Use AID for ID generation (with random 3 chars)
|
# ulid ... Millisecond accuracy
|
||||||
# aid4 ... Use AID for ID generation (with random 4 chars)
|
# objectid ... This is left for backward compatibility
|
||||||
# ulid ... Use ulid for ID generation
|
|
||||||
# objectid ... This is left for backward compatibility.
|
|
||||||
|
|
||||||
# AID(n) is the original ID generation method.
|
id: 'aid'
|
||||||
# The trailing n represents the number of random characters that
|
|
||||||
# will be suffixed.
|
|
||||||
# The larger n is the safer. If n is small, the possibility of
|
|
||||||
# collision at the same time increases, but there are also
|
|
||||||
# advantages such as shortening of the URL.
|
|
||||||
|
|
||||||
# ULID: Universally Unique Lexicographically Sortable Identifier.
|
|
||||||
# for more details: https://github.com/ulid/spec
|
|
||||||
# * Normally, AID should be sufficient.
|
|
||||||
|
|
||||||
# ObjectID is the method used in previous versions of Misskey.
|
|
||||||
# * Choose this if you are migrating from a previous Misskey.
|
|
||||||
|
|
||||||
id: 'aid2'
|
|
||||||
|
|
||||||
# ┌─────────────────────┐
|
# ┌─────────────────────┐
|
||||||
#───┘ Other configuration └─────────────────────────────────────
|
#───┘ Other configuration └─────────────────────────────────────
|
||||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -7,11 +7,37 @@ If you encounter any problems with updating, please try the following:
|
|||||||
|
|
||||||
11.0.0
|
11.0.0
|
||||||
----------
|
----------
|
||||||
* データベースがMongoDBからPostgreSQLに変更されました
|
* **データベースがMongoDBからPostgreSQLに変更されました**
|
||||||
|
* **Redisが必須に**
|
||||||
|
* アカウントを完全に削除できるように
|
||||||
|
* ミュート/ブロック時にそのユーザーの投稿のウォッチをすべて解除するように
|
||||||
|
* フォロー申請数が実際より1すくなくなる問題を修正
|
||||||
|
* リストからアカウント削除したユーザーを削除できない問題を修正
|
||||||
|
* リストTLでフォローしていないユーザーの非公開投稿が流れる問題を修正
|
||||||
|
* リストTLでダイレクト投稿が流れない問題を修正
|
||||||
|
* ミュートしているユーザーの投稿がタイムラインに流れてくることがある問題を修正
|
||||||
|
|
||||||
### APIの破壊的変更
|
### APIの破壊的変更
|
||||||
* v10時点で deprecated だったパラメータなどを削除
|
* v10時点で deprecated だったパラメータなどを削除
|
||||||
* ユーザーリストの title が name に
|
* ユーザーリストの title が name に
|
||||||
|
* リバーシの対局の`settings`プロパティがなくなり、その中にあったプロパティがすべて上の階層に
|
||||||
|
* 例えば`game.settings.map`は`game.map`になる
|
||||||
|
|
||||||
|
### 既知の問題
|
||||||
|
* アプリが作成できない
|
||||||
|
* 依存ライブラリの問題と思わるため、対応が難しい
|
||||||
|
|
||||||
|
### Migration
|
||||||
|
coming soon...
|
||||||
|
|
||||||
|
10.100.0
|
||||||
|
----------
|
||||||
|
* ユーザーリストでフォローボタンを表示するように
|
||||||
|
* ドライブのファイルのサムネイルを修正
|
||||||
|
* 投稿ウィジットでローカルのみの公開範囲で投稿できない問題を修正
|
||||||
|
* TLを遡った時に抜けがある時がある問題を修正
|
||||||
|
* ユーザータイムラインが投稿日時順ではなくなっているのを修正
|
||||||
|
* 10.99.0 でチャートのレンダリングがおかしい問題を修正
|
||||||
|
|
||||||
10.99.0
|
10.99.0
|
||||||
----------
|
----------
|
||||||
|
@ -130,6 +130,40 @@ const users = userIds.length > 0 ? await Users.find({
|
|||||||
}) : [];
|
}) : [];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 配列のインデックス in SQL
|
||||||
|
SQLでは配列のインデックスは**1始まり**。
|
||||||
|
`[a, b, c]`の `a`にアクセスしたいなら`[0]`ではなく`[1]`と書く
|
||||||
|
|
||||||
### `undefined`にご用心
|
### `undefined`にご用心
|
||||||
MongoDBの時とは違い、findOneでレコードを取得する時に対象レコードが存在しない場合 **`undefined`** が返ってくるので注意。
|
MongoDBの時とは違い、findOneでレコードを取得する時に対象レコードが存在しない場合 **`undefined`** が返ってくるので注意。
|
||||||
MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください
|
MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください
|
||||||
|
|
||||||
|
### 簡素な`undefined`チェック
|
||||||
|
データベースからレコードを取得するときに、プログラムの流れ的に(ほぼ)絶対`undefined`にはならない場合でも、`undefined`チェックしないとTypeScriptに怒られます。
|
||||||
|
でもいちいち複数行を費やして、発生するはずのない`undefined`をチェックするのも面倒なので、`ensure`というユーティリティ関数を用意しています。
|
||||||
|
例えば、
|
||||||
|
``` ts
|
||||||
|
const user = await Users.findOne(userId);
|
||||||
|
// この時点で user の型は User | undefined
|
||||||
|
if (user == null) {
|
||||||
|
throw 'missing user';
|
||||||
|
}
|
||||||
|
// この時点で user の型は User
|
||||||
|
```
|
||||||
|
という処理を`ensure`を使うと
|
||||||
|
``` ts
|
||||||
|
const user = await Users.findOne(userId).then(ensure);
|
||||||
|
// この時点で user の型は User
|
||||||
|
```
|
||||||
|
という風に書けます。
|
||||||
|
もちろん`ensure`内部でエラーを握りつぶすようなことはしておらず、万が一`undefined`だった場合はPromiseがRejectされ後続の処理は実行されません。
|
||||||
|
``` ts
|
||||||
|
const user = await Users.findOne(userId).then(ensure);
|
||||||
|
// 万が一 Users.findOne の結果が undefined だったら、ensure でエラーが発生するので
|
||||||
|
// この行に到達することは無い
|
||||||
|
// なので、.then(ensure) は
|
||||||
|
// if (user == null) {
|
||||||
|
// throw 'missing user';
|
||||||
|
// }
|
||||||
|
// の糖衣構文のような扱いです
|
||||||
|
```
|
||||||
|
@ -24,18 +24,13 @@ Please install and setup these softwares:
|
|||||||
#### Dependencies :package:
|
#### Dependencies :package:
|
||||||
* **[Node.js](https://nodejs.org/en/)** >= 11.7.0
|
* **[Node.js](https://nodejs.org/en/)** >= 11.7.0
|
||||||
* **[PostgreSQL](https://www.postgresql.org/)** >= 10
|
* **[PostgreSQL](https://www.postgresql.org/)** >= 10
|
||||||
|
* **[Redis](https://redis.io/)**
|
||||||
|
|
||||||
##### Optional
|
##### Optional
|
||||||
* [Redis](https://redis.io/)
|
|
||||||
* Redis is optional, but we strongly recommended to install it
|
|
||||||
* [Elasticsearch](https://www.elastic.co/) - required to enable the search feature
|
* [Elasticsearch](https://www.elastic.co/) - required to enable the search feature
|
||||||
* [FFmpeg](https://www.ffmpeg.org/)
|
* [FFmpeg](https://www.ffmpeg.org/)
|
||||||
|
|
||||||
*3.* Setup PostgreSQL
|
*3.* Install Misskey
|
||||||
----------------------------------------------------------------
|
|
||||||
:)
|
|
||||||
|
|
||||||
*4.* Install Misskey
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
1. `su - misskey` Connect to misskey user.
|
1. `su - misskey` Connect to misskey user.
|
||||||
2. `git clone -b master git://github.com/syuilo/misskey.git` Clone the misskey repo from master branch.
|
2. `git clone -b master git://github.com/syuilo/misskey.git` Clone the misskey repo from master branch.
|
||||||
@ -43,12 +38,12 @@ Please install and setup these softwares:
|
|||||||
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
|
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
|
||||||
5. `npm install` Install misskey dependencies.
|
5. `npm install` Install misskey dependencies.
|
||||||
|
|
||||||
*5.* Configure Misskey
|
*4.* Configure Misskey
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
|
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
|
||||||
2. Edit `default.yml`
|
2. Edit `default.yml`
|
||||||
|
|
||||||
*6.* Build Misskey
|
*5.* Build Misskey
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
Build misskey with the following:
|
Build misskey with the following:
|
||||||
@ -64,13 +59,13 @@ If you're still encountering errors about some modules, use node-gyp:
|
|||||||
3. `node-gyp build`
|
3. `node-gyp build`
|
||||||
4. `NODE_ENV=production npm run build`
|
4. `NODE_ENV=production npm run build`
|
||||||
|
|
||||||
*7.* Init DB
|
*6.* Init DB
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
``` shell
|
``` shell
|
||||||
npm run init
|
npm run init
|
||||||
```
|
```
|
||||||
|
|
||||||
*8.* That is it.
|
*7.* That is it.
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
Well done! Now, you have an environment that run to Misskey.
|
Well done! Now, you have an environment that run to Misskey.
|
||||||
|
|
||||||
|
@ -24,18 +24,13 @@ Installez les paquets suivants :
|
|||||||
#### Dépendences :package:
|
#### Dépendences :package:
|
||||||
* **[Node.js](https://nodejs.org/en/)** >= 11.7.0
|
* **[Node.js](https://nodejs.org/en/)** >= 11.7.0
|
||||||
* **[PostgreSQL](https://www.postgresql.org/)** >= 10
|
* **[PostgreSQL](https://www.postgresql.org/)** >= 10
|
||||||
|
* **[Redis](https://redis.io/)**
|
||||||
|
|
||||||
##### Optionnels
|
##### Optionnels
|
||||||
* [Redis](https://redis.io/)
|
|
||||||
* Redis est optionnel mais nous vous recommandons vivement de l'installer
|
|
||||||
* [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche
|
* [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche
|
||||||
* [FFmpeg](https://www.ffmpeg.org/)
|
* [FFmpeg](https://www.ffmpeg.org/)
|
||||||
|
|
||||||
*3.* Paramètrage de PostgreSQL
|
*3.* Installation de Misskey
|
||||||
----------------------------------------------------------------
|
|
||||||
:)
|
|
||||||
|
|
||||||
*4.* Installation de Misskey
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
1. `su - misskey` Basculez vers l'utilisateur misskey.
|
1. `su - misskey` Basculez vers l'utilisateur misskey.
|
||||||
2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey.
|
2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey.
|
||||||
@ -43,12 +38,12 @@ Installez les paquets suivants :
|
|||||||
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
|
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
|
||||||
5. `npm install` Installez les dépendances de misskey.
|
5. `npm install` Installez les dépendances de misskey.
|
||||||
|
|
||||||
*5.* Création du fichier de configuration
|
*4.* Création du fichier de configuration
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le`default.yml`.
|
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le`default.yml`.
|
||||||
2. Editez le fichier `default.yml`
|
2. Editez le fichier `default.yml`
|
||||||
|
|
||||||
*6.* Construction de Misskey
|
*5.* Construction de Misskey
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
Construisez Misskey comme ceci :
|
Construisez Misskey comme ceci :
|
||||||
@ -64,7 +59,7 @@ Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp:
|
|||||||
3. `node-gyp build`
|
3. `node-gyp build`
|
||||||
4. `NODE_ENV=production npm run build`
|
4. `NODE_ENV=production npm run build`
|
||||||
|
|
||||||
*7.* C'est tout.
|
*6.* C'est tout.
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey
|
Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey
|
||||||
|
|
||||||
|
@ -24,25 +24,14 @@ adduser --disabled-password --disabled-login misskey
|
|||||||
#### 依存関係 :package:
|
#### 依存関係 :package:
|
||||||
* **[Node.js](https://nodejs.org/en/)** (11.7.0以上)
|
* **[Node.js](https://nodejs.org/en/)** (11.7.0以上)
|
||||||
* **[PostgreSQL](https://www.postgresql.org/)** (10以上)
|
* **[PostgreSQL](https://www.postgresql.org/)** (10以上)
|
||||||
|
* **[Redis](https://redis.io/)**
|
||||||
|
|
||||||
##### オプション
|
##### オプション
|
||||||
* [Redis](https://redis.io/)
|
|
||||||
* Redisはオプションですが、インストールすることを強く推奨します。
|
|
||||||
* インストールしなくていいのは、あなたのインスタンスが自分専用のときだけとお考えください。
|
|
||||||
* 具体的には、Redisをインストールしないと、次の事が出来なくなります:
|
|
||||||
* Misskeyプロセスを複数起動しての負荷分散
|
|
||||||
* レートリミット
|
|
||||||
* ジョブキュー
|
|
||||||
* Twitter連携
|
|
||||||
* [Elasticsearch](https://www.elastic.co/)
|
* [Elasticsearch](https://www.elastic.co/)
|
||||||
* 検索機能を有効にするためにはインストールが必要です。
|
* 検索機能を有効にするためにはインストールが必要です。
|
||||||
* [FFmpeg](https://www.ffmpeg.org/)
|
* [FFmpeg](https://www.ffmpeg.org/)
|
||||||
|
|
||||||
*3.* PostgreSQLの設定
|
*3.* Misskeyのインストール
|
||||||
----------------------------------------------------------------
|
|
||||||
:)
|
|
||||||
|
|
||||||
*4.* Misskeyのインストール
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
1. `su - misskey` misskeyユーザーを使用
|
1. `su - misskey` misskeyユーザーを使用
|
||||||
2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン
|
2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン
|
||||||
@ -50,12 +39,12 @@ adduser --disabled-password --disabled-login misskey
|
|||||||
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
|
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
|
||||||
5. `npm install` Misskeyの依存パッケージをインストール
|
5. `npm install` Misskeyの依存パッケージをインストール
|
||||||
|
|
||||||
*5.* 設定ファイルを作成する
|
*4.* 設定ファイルを作成する
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。
|
1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。
|
||||||
2. `default.yml` を編集する。
|
2. `default.yml` を編集する。
|
||||||
|
|
||||||
*6.* Misskeyのビルド
|
*5.* Misskeyのビルド
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
次のコマンドでMisskeyをビルドしてください:
|
次のコマンドでMisskeyをビルドしてください:
|
||||||
@ -70,13 +59,13 @@ Debianをお使いであれば、`build-essential`パッケージをインスト
|
|||||||
3. `node-gyp build`
|
3. `node-gyp build`
|
||||||
4. `NODE_ENV=production npm run build`
|
4. `NODE_ENV=production npm run build`
|
||||||
|
|
||||||
*7.* データベースを初期化
|
*6.* データベースを初期化
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
``` shell
|
``` shell
|
||||||
npm run init
|
npm run init
|
||||||
```
|
```
|
||||||
|
|
||||||
*8.* 以上です!
|
*7.* 以上です!
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
お疲れ様でした。これでMisskeyを動かす準備は整いました。
|
お疲れ様でした。これでMisskeyを動かす準備は整いました。
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ gulp.task('copy:client', () =>
|
|||||||
])
|
])
|
||||||
.pipe(isProduction ? (imagemin as any)() : gutil.noop())
|
.pipe(isProduction ? (imagemin as any)() : gutil.noop())
|
||||||
.pipe(rename(path => {
|
.pipe(rename(path => {
|
||||||
path.dirname = path.dirname.replace('assets', '.');
|
path.dirname = path.dirname!.replace('assets', '.');
|
||||||
}))
|
}))
|
||||||
.pipe(gulp.dest('./built/client/assets/'))
|
.pipe(gulp.dest('./built/client/assets/'))
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "11.0.0-beta.2",
|
"version": "11.0.0-beta.13",
|
||||||
"codename": "daybreak",
|
"codename": "daybreak",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -106,7 +106,7 @@
|
|||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bootstrap-vue": "2.0.0-rc.13",
|
"bootstrap-vue": "2.0.0-rc.13",
|
||||||
"bull": "3.7.0",
|
"bull": "3.7.0",
|
||||||
"cafy": "15.1.0",
|
"cafy": "15.1.1",
|
||||||
"chai": "4.2.0",
|
"chai": "4.2.0",
|
||||||
"chai-http": "4.2.1",
|
"chai-http": "4.2.1",
|
||||||
"chalk": "2.4.2",
|
"chalk": "2.4.2",
|
||||||
|
@ -44,7 +44,7 @@ function greet() {
|
|||||||
export async function masterMain() {
|
export async function masterMain() {
|
||||||
greet();
|
greet();
|
||||||
|
|
||||||
let config: Config;
|
let config!: Config;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// initialize app
|
// initialize app
|
||||||
|
@ -15,6 +15,6 @@ export async function workerMain() {
|
|||||||
|
|
||||||
if (cluster.isWorker) {
|
if (cluster.isWorker) {
|
||||||
// Send a 'ready' message to parent process
|
// Send a 'ready' message to parent process
|
||||||
process.send('ready');
|
process.send!('ready');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="is-remote" v-if="user.host != null">
|
<div class="is-remote" v-if="user.host != null">
|
||||||
<details>
|
<details>
|
||||||
<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary>
|
<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary>
|
||||||
<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a>
|
<a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
<header :style="bannerStyle">
|
<header :style="bannerStyle">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}
|
<fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
|
<div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
|
||||||
<fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a>
|
<fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<x-header class="header" :user="user"/>
|
<x-header class="header" :user="user"/>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="wwtwuxyh" v-if="!fetching">
|
<div class="wwtwuxyh" v-if="!fetching">
|
||||||
<div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div>
|
<div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div>
|
||||||
<div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div>
|
<div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div>
|
||||||
<header>
|
<header>
|
||||||
<div class="banner" :style="style"></div>
|
<div class="banner" :style="style"></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
kind: 'light',
|
kind: 'light',
|
||||||
|
|
||||||
vars: {
|
vars: {
|
||||||
primary: '#fb4e4e',
|
primary: '#f18570',
|
||||||
secondary: '#fff',
|
secondary: '#fff',
|
||||||
text: '#666',
|
text: '#666',
|
||||||
},
|
},
|
||||||
|
@ -29,7 +29,7 @@ export default function load() {
|
|||||||
|
|
||||||
config.url = url.origin;
|
config.url = url.origin;
|
||||||
|
|
||||||
config.port = config.port || parseInt(process.env.PORT, 10);
|
config.port = config.port || parseInt(process.env.PORT || '', 10);
|
||||||
|
|
||||||
mixin.host = url.host;
|
mixin.host = url.host;
|
||||||
mixin.hostname = url.hostname;
|
mixin.hostname = url.hostname;
|
||||||
|
@ -19,7 +19,7 @@ initDb().then(() => {
|
|||||||
all, local
|
all, local
|
||||||
};
|
};
|
||||||
|
|
||||||
process.send(stats);
|
process.send!(stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
|
@ -76,7 +76,7 @@ class MyCustomLogger implements Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initDb(justBorrow = false, sync = false, log = false) {
|
export function initDb(justBorrow = false, sync = false, log = false) {
|
||||||
const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV);
|
const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV || '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const conn = getConnection();
|
const conn = getConnection();
|
||||||
@ -93,7 +93,7 @@ export function initDb(justBorrow = false, sync = false, log = false) {
|
|||||||
synchronize: process.env.NODE_ENV === 'test' || sync,
|
synchronize: process.env.NODE_ENV === 'test' || sync,
|
||||||
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
|
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
|
||||||
logging: enableLogging,
|
logging: enableLogging,
|
||||||
logger: enableLogging ? new MyCustomLogger() : null,
|
logger: enableLogging ? new MyCustomLogger() : undefined,
|
||||||
entities: [
|
entities: [
|
||||||
Meta,
|
Meta,
|
||||||
Instance,
|
Instance,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as redis from 'redis';
|
import * as redis from 'redis';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
|
||||||
export default config.redis ? redis.createClient(
|
export default redis.createClient(
|
||||||
config.redis.port,
|
config.redis.port,
|
||||||
config.redis.host,
|
config.redis.host,
|
||||||
{
|
{
|
||||||
@ -9,4 +9,4 @@ export default config.redis ? redis.createClient(
|
|||||||
prefix: config.redis.prefix,
|
prefix: config.redis.prefix,
|
||||||
db: config.redis.db || 0
|
db: config.redis.db || 0
|
||||||
}
|
}
|
||||||
) : null;
|
);
|
||||||
|
@ -37,7 +37,7 @@ export type Undo = {
|
|||||||
/**
|
/**
|
||||||
* ターン
|
* ターン
|
||||||
*/
|
*/
|
||||||
turn: Color;
|
turn: Color | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,12 +47,12 @@ export default class Reversi {
|
|||||||
public map: MapPixel[];
|
public map: MapPixel[];
|
||||||
public mapWidth: number;
|
public mapWidth: number;
|
||||||
public mapHeight: number;
|
public mapHeight: number;
|
||||||
public board: Color[];
|
public board: (Color | null | undefined)[];
|
||||||
public turn: Color = BLACK;
|
public turn: Color | null = BLACK;
|
||||||
public opts: Options;
|
public opts: Options;
|
||||||
|
|
||||||
public prevPos = -1;
|
public prevPos = -1;
|
||||||
public prevColor: Color = null;
|
public prevColor: Color | null = null;
|
||||||
|
|
||||||
private logs: Undo[] = [];
|
private logs: Undo[] = [];
|
||||||
|
|
||||||
@ -145,12 +145,12 @@ export default class Reversi {
|
|||||||
// ターン計算
|
// ターン計算
|
||||||
this.turn =
|
this.turn =
|
||||||
this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
|
this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
|
||||||
this.canPutSomewhere(this.prevColor) ? this.prevColor :
|
this.canPutSomewhere(this.prevColor!) ? this.prevColor :
|
||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public undo() {
|
public undo() {
|
||||||
const undo = this.logs.pop();
|
const undo = this.logs.pop()!;
|
||||||
this.prevColor = undo.color;
|
this.prevColor = undo.color;
|
||||||
this.prevPos = undo.pos;
|
this.prevPos = undo.pos;
|
||||||
this.board[undo.pos] = null;
|
this.board[undo.pos] = null;
|
||||||
@ -254,10 +254,10 @@ export default class Reversi {
|
|||||||
/**
|
/**
|
||||||
* ゲームの勝者 (null = 引き分け)
|
* ゲームの勝者 (null = 引き分け)
|
||||||
*/
|
*/
|
||||||
public get winner(): Color {
|
public get winner(): Color | null {
|
||||||
return this.isEnded ?
|
return this.isEnded ?
|
||||||
this.blackCount == this.whiteCount ? null :
|
this.blackCount == this.whiteCount ? null :
|
||||||
this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
|
this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
|
||||||
undefined;
|
undefined as never;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ import { URL } from 'url';
|
|||||||
import { urlRegex } from './prelude';
|
import { urlRegex } from './prelude';
|
||||||
|
|
||||||
export function fromHtml(html: string): string {
|
export function fromHtml(html: string): string {
|
||||||
if (html == null) return null;
|
|
||||||
|
|
||||||
const dom = parseFragment(html) as DefaultTreeDocumentFragment;
|
const dom = parseFragment(html) as DefaultTreeDocumentFragment;
|
||||||
|
|
||||||
let text = '';
|
let text = '';
|
||||||
|
@ -2,7 +2,7 @@ import { mfmLanguage } from './language';
|
|||||||
import { MfmForest } from './prelude';
|
import { MfmForest } from './prelude';
|
||||||
import { normalize } from './normalize';
|
import { normalize } from './normalize';
|
||||||
|
|
||||||
export function parse(source: string): MfmForest {
|
export function parse(source: string | null): MfmForest | null {
|
||||||
if (source == null || source == '') {
|
if (source == null || source == '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -10,7 +10,7 @@ export function parse(source: string): MfmForest {
|
|||||||
return normalize(mfmLanguage.root.tryParse(source));
|
return normalize(mfmLanguage.root.tryParse(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parsePlain(source: string): MfmForest {
|
export function parsePlain(source: string | null): MfmForest | null {
|
||||||
if (source == null || source == '') {
|
if (source == null || source == '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { intersperse } from '../prelude/array';
|
|||||||
import { MfmForest, MfmTree } from './prelude';
|
import { MfmForest, MfmTree } from './prelude';
|
||||||
import { IMentionedRemoteUsers } from '../models/entities/note';
|
import { IMentionedRemoteUsers } from '../models/entities/note';
|
||||||
|
|
||||||
export function toHtml(tokens: MfmForest, mentionedRemoteUsers: IMentionedRemoteUsers = []) {
|
export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) {
|
||||||
if (tokens == null) {
|
if (tokens == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import { InternalStorage } from './services/drive/internal-storage';
|
|||||||
import { createTemp } from './misc/create-temp';
|
import { createTemp } from './misc/create-temp';
|
||||||
import { Note } from './models/entities/note';
|
import { Note } from './models/entities/note';
|
||||||
import { Following } from './models/entities/following';
|
import { Following } from './models/entities/following';
|
||||||
import { genId } from './misc/gen-id';
|
|
||||||
import { Poll } from './models/entities/poll';
|
import { Poll } from './models/entities/poll';
|
||||||
import { PollVote } from './models/entities/poll-vote';
|
import { PollVote } from './models/entities/poll-vote';
|
||||||
import { NoteFavorite } from './models/entities/note-favorite';
|
import { NoteFavorite } from './models/entities/note-favorite';
|
||||||
@ -25,9 +24,14 @@ import { UserPublickey } from './models/entities/user-publickey';
|
|||||||
import { UserKeypair } from './models/entities/user-keypair';
|
import { UserKeypair } from './models/entities/user-keypair';
|
||||||
import { extractPublic } from './crypto_key';
|
import { extractPublic } from './crypto_key';
|
||||||
import { Emoji } from './models/entities/emoji';
|
import { Emoji } from './models/entities/emoji';
|
||||||
import { toPuny } from './misc/convert-host';
|
import { toPuny as _toPuny } from './misc/convert-host';
|
||||||
import { UserProfile } from './models/entities/user-profile';
|
import { UserProfile } from './models/entities/user-profile';
|
||||||
|
|
||||||
|
function toPuny(x: string | null): string | null {
|
||||||
|
if (x == null) return null;
|
||||||
|
return _toPuny(x);
|
||||||
|
}
|
||||||
|
|
||||||
const u = (config as any).mongodb.user ? encodeURIComponent((config as any).mongodb.user) : null;
|
const u = (config as any).mongodb.user ? encodeURIComponent((config as any).mongodb.user) : null;
|
||||||
const p = (config as any).mongodb.pass ? encodeURIComponent((config as any).mongodb.pass) : null;
|
const p = (config as any).mongodb.pass ? encodeURIComponent((config as any).mongodb.pass) : null;
|
||||||
|
|
||||||
@ -36,6 +40,9 @@ const uri = `mongodb://${u && p ? `${u}:${p}@` : ''}${(config as any).mongodb.ho
|
|||||||
const db = monk(uri);
|
const db = monk(uri);
|
||||||
let mdb: mongo.Db;
|
let mdb: mongo.Db;
|
||||||
|
|
||||||
|
const test = false;
|
||||||
|
const limit = 500;
|
||||||
|
|
||||||
const nativeDbConn = async (): Promise<mongo.Db> => {
|
const nativeDbConn = async (): Promise<mongo.Db> => {
|
||||||
if (mdb) return mdb;
|
if (mdb) return mdb;
|
||||||
|
|
||||||
@ -92,14 +99,14 @@ async function main() {
|
|||||||
usernameLower: user.username.toLowerCase(),
|
usernameLower: user.username.toLowerCase(),
|
||||||
host: toPuny(user.host),
|
host: toPuny(user.host),
|
||||||
token: generateUserToken(),
|
token: generateUserToken(),
|
||||||
isAdmin: user.isAdmin,
|
isAdmin: user.isAdmin || false,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
followersCount: user.followersCount,
|
followersCount: user.followersCount || 0,
|
||||||
followingCount: user.followingCount,
|
followingCount: user.followingCount || 0,
|
||||||
notesCount: user.notesCount,
|
notesCount: user.notesCount || 0,
|
||||||
isBot: user.isBot,
|
isBot: user.isBot || false,
|
||||||
isCat: user.isCat,
|
isCat: user.isCat || false,
|
||||||
isVerified: user.isVerified,
|
isVerified: user.isVerified || false,
|
||||||
inbox: user.inbox,
|
inbox: user.inbox,
|
||||||
sharedInbox: user.sharedInbox,
|
sharedInbox: user.sharedInbox,
|
||||||
uri: user.uri,
|
uri: user.uri,
|
||||||
@ -133,7 +140,7 @@ async function main() {
|
|||||||
async function migrateFollowing(following: any) {
|
async function migrateFollowing(following: any) {
|
||||||
await Followings.save({
|
await Followings.save({
|
||||||
id: following._id.toHexString(),
|
id: following._id.toHexString(),
|
||||||
createdAt: following.createdAt || new Date(),
|
createdAt: new Date(),
|
||||||
followerId: following.followerId.toHexString(),
|
followerId: following.followerId.toHexString(),
|
||||||
followeeId: following.followeeId.toHexString(),
|
followeeId: following.followeeId.toHexString(),
|
||||||
|
|
||||||
@ -160,6 +167,7 @@ async function main() {
|
|||||||
const user = await _User.findOne({
|
const user = await _User.findOne({
|
||||||
_id: file.metadata.userId
|
_id: file.metadata.userId
|
||||||
});
|
});
|
||||||
|
if (user == null) return;
|
||||||
if (file.metadata.storage && file.metadata.storage.key) { // when object storage
|
if (file.metadata.storage && file.metadata.storage.key) { // when object storage
|
||||||
await DriveFiles.save({
|
await DriveFiles.save({
|
||||||
id: file._id.toHexString(),
|
id: file._id.toHexString(),
|
||||||
@ -169,7 +177,7 @@ async function main() {
|
|||||||
md5: file.md5,
|
md5: file.md5,
|
||||||
name: file.filename,
|
name: file.filename,
|
||||||
type: file.contentType,
|
type: file.contentType,
|
||||||
properties: file.metadata.properties,
|
properties: file.metadata.properties || {},
|
||||||
size: file.length,
|
size: file.length,
|
||||||
url: file.metadata.url,
|
url: file.metadata.url,
|
||||||
uri: file.metadata.uri,
|
uri: file.metadata.uri,
|
||||||
@ -255,7 +263,6 @@ async function main() {
|
|||||||
|
|
||||||
if (note.poll) {
|
if (note.poll) {
|
||||||
await Polls.save({
|
await Polls.save({
|
||||||
id: genId(),
|
|
||||||
noteId: note._id.toHexString(),
|
noteId: note._id.toHexString(),
|
||||||
choices: note.poll.choices.map((x: any) => x.text),
|
choices: note.poll.choices.map((x: any) => x.text),
|
||||||
expiresAt: note.poll.expiresAt,
|
expiresAt: note.poll.expiresAt,
|
||||||
@ -301,13 +308,13 @@ async function main() {
|
|||||||
const u = await _User.findOne({
|
const u = await _User.findOne({
|
||||||
_id: new mongo.ObjectId(user.id)
|
_id: new mongo.ObjectId(user.id)
|
||||||
});
|
});
|
||||||
const avatar = await DriveFiles.findOne(u.avatarId.toHexString());
|
const avatar = u.avatarId ? await DriveFiles.findOne(u.avatarId.toHexString()) : null;
|
||||||
const banner = await DriveFiles.findOne(u.bannerId.toHexString());
|
const banner = u.bannerId ? await DriveFiles.findOne(u.bannerId.toHexString()) : null;
|
||||||
await Users.update(user.id, {
|
await Users.update(user.id, {
|
||||||
avatarId: avatar.id,
|
avatarId: avatar ? avatar.id : null,
|
||||||
bannerId: banner.id,
|
bannerId: banner ? banner.id : null,
|
||||||
avatarUrl: avatar.url,
|
avatarUrl: avatar ? avatar.url : null,
|
||||||
bannerUrl: banner.url
|
bannerUrl: banner ? banner.url : null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,9 +330,14 @@ async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const allUsersCount = await _User.count();
|
let allUsersCount = await _User.count({
|
||||||
|
deletedAt: { $exists: false }
|
||||||
|
});
|
||||||
|
if (test && allUsersCount > limit) allUsersCount = limit;
|
||||||
for (let i = 0; i < allUsersCount; i++) {
|
for (let i = 0; i < allUsersCount; i++) {
|
||||||
const user = await _User.findOne({}, {
|
const user = await _User.findOne({
|
||||||
|
deletedAt: { $exists: false }
|
||||||
|
}, {
|
||||||
skip: i
|
skip: i
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@ -337,7 +349,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allFollowingsCount = await _Following.count();
|
let allFollowingsCount = await _Following.count();
|
||||||
|
if (test && allFollowingsCount > limit) allFollowingsCount = limit;
|
||||||
for (let i = 0; i < allFollowingsCount; i++) {
|
for (let i = 0; i < allFollowingsCount; i++) {
|
||||||
const following = await _Following.findOne({}, {
|
const following = await _Following.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@ -351,7 +364,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allDriveFoldersCount = await _DriveFolder.count();
|
let allDriveFoldersCount = await _DriveFolder.count();
|
||||||
|
if (test && allDriveFoldersCount > limit) allDriveFoldersCount = limit;
|
||||||
for (let i = 0; i < allDriveFoldersCount; i++) {
|
for (let i = 0; i < allDriveFoldersCount; i++) {
|
||||||
const folder = await _DriveFolder.findOne({}, {
|
const folder = await _DriveFolder.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@ -365,9 +379,16 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allDriveFilesCount = await _DriveFile.count();
|
let allDriveFilesCount = await _DriveFile.count({
|
||||||
|
'metadata._user.host': null,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
|
});
|
||||||
|
if (test && allDriveFilesCount > limit) allDriveFilesCount = limit;
|
||||||
for (let i = 0; i < allDriveFilesCount; i++) {
|
for (let i = 0; i < allDriveFilesCount; i++) {
|
||||||
const file = await _DriveFile.findOne({}, {
|
const file = await _DriveFile.findOne({
|
||||||
|
'metadata._user.host': null,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
|
}, {
|
||||||
skip: i
|
skip: i
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@ -379,12 +400,15 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allNotesCount = await _Note.count({
|
let allNotesCount = await _Note.count({
|
||||||
'_user.host': null
|
'_user.host': null,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
});
|
});
|
||||||
|
if (test && allNotesCount > limit) allNotesCount = limit;
|
||||||
for (let i = 0; i < allNotesCount; i++) {
|
for (let i = 0; i < allNotesCount; i++) {
|
||||||
const note = await _Note.findOne({
|
const note = await _Note.findOne({
|
||||||
'_user.host': null
|
'_user.host': null,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
}, {
|
}, {
|
||||||
skip: i
|
skip: i
|
||||||
});
|
});
|
||||||
@ -397,7 +421,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPollVotesCount = await _PollVote.count();
|
let allPollVotesCount = await _PollVote.count();
|
||||||
|
if (test && allPollVotesCount > limit) allPollVotesCount = limit;
|
||||||
for (let i = 0; i < allPollVotesCount; i++) {
|
for (let i = 0; i < allPollVotesCount; i++) {
|
||||||
const vote = await _PollVote.findOne({}, {
|
const vote = await _PollVote.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@ -411,7 +436,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allNoteFavoritesCount = await _Favorite.count();
|
let allNoteFavoritesCount = await _Favorite.count();
|
||||||
|
if (test && allNoteFavoritesCount > limit) allNoteFavoritesCount = limit;
|
||||||
for (let i = 0; i < allNoteFavoritesCount; i++) {
|
for (let i = 0; i < allNoteFavoritesCount; i++) {
|
||||||
const favorite = await _Favorite.findOne({}, {
|
const favorite = await _Favorite.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@ -425,7 +451,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allNoteReactionsCount = await _NoteReaction.count();
|
let allNoteReactionsCount = await _NoteReaction.count();
|
||||||
|
if (test && allNoteReactionsCount > limit) allNoteReactionsCount = limit;
|
||||||
for (let i = 0; i < allNoteReactionsCount; i++) {
|
for (let i = 0; i < allNoteReactionsCount; i++) {
|
||||||
const reaction = await _NoteReaction.findOne({}, {
|
const reaction = await _NoteReaction.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@ -439,7 +466,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allActualUsersCount = await Users.count();
|
let allActualUsersCount = await Users.count();
|
||||||
|
if (test && allActualUsersCount > limit) allActualUsersCount = limit;
|
||||||
for (let i = 0; i < allActualUsersCount; i++) {
|
for (let i = 0; i < allActualUsersCount; i++) {
|
||||||
const [user] = await Users.find({
|
const [user] = await Users.find({
|
||||||
take: 1,
|
take: 1,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
type Acct = {
|
type Acct = {
|
||||||
username: string;
|
username: string;
|
||||||
host: string;
|
host: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Acct;
|
export default Acct;
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
// AID
|
|
||||||
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さnの[ランダムな文字列]
|
|
||||||
|
|
||||||
const CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
||||||
const TIME2000 = 946684800000;
|
|
||||||
|
|
||||||
function getTime(time: number) {
|
|
||||||
time = time - TIME2000;
|
|
||||||
if (time < 0) time = 0;
|
|
||||||
|
|
||||||
return time.toString(36);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRandom(length: number) {
|
|
||||||
let str = '';
|
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
str += CHARS[Math.floor(Math.random() * CHARS.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function genAid(date: Date, rand: number): string {
|
|
||||||
return getTime(date.getTime()).padStart(8, CHARS[0]) + getRandom(rand);
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
// AID(Cheep)
|
|
||||||
// 長さ6の[2000年1月1日からの経過秒をbase36でエンコードしたもの] + 長さ3の[ランダムな文字列]
|
|
||||||
|
|
||||||
const CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
||||||
const TIME2000 = 946684800000;
|
|
||||||
|
|
||||||
function getTime(time: number) {
|
|
||||||
time = time - TIME2000;
|
|
||||||
if (time < 0) time = 0;
|
|
||||||
time = Math.floor(time / 1000);
|
|
||||||
return time.toString(36);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRandom() {
|
|
||||||
let str = '';
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
str += CHARS[Math.floor(Math.random() * CHARS.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function genAidc(date: Date): string {
|
|
||||||
return getTime(date.getTime()).padStart(6, CHARS[0]) + getRandom();
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ import config from '../config';
|
|||||||
import { toASCII } from 'punycode';
|
import { toASCII } from 'punycode';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
|
|
||||||
export function getFullApAccount(username: string, host: string) {
|
export function getFullApAccount(username: string, host: string | null) {
|
||||||
return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`;
|
return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,6 +17,10 @@ export function extractDbHost(uri: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function toPuny(host: string) {
|
export function toPuny(host: string) {
|
||||||
|
return toASCII(host.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toPunyNullable(host: string | null | undefined): string | null {
|
||||||
if (host == null) return null;
|
if (host == null) return null;
|
||||||
return toASCII(host.toLowerCase());
|
return toASCII(host.toLowerCase());
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import fileType from 'file-type';
|
|||||||
import checkSvg from '../misc/check-svg';
|
import checkSvg from '../misc/check-svg';
|
||||||
|
|
||||||
export async function detectMine(path: string) {
|
export async function detectMine(path: string) {
|
||||||
return new Promise<[string, string]>((res, rej) => {
|
return new Promise<[string, string | null]>((res, rej) => {
|
||||||
const readable = fs.createReadStream(path);
|
const readable = fs.createReadStream(path);
|
||||||
readable
|
readable
|
||||||
.on('error', rej)
|
.on('error', rej)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as URL from 'url';
|
|
||||||
import * as request from 'request';
|
import * as request from 'request';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
@ -26,7 +25,7 @@ export async function downloadUrl(url: string, path: string) {
|
|||||||
rej(error);
|
rej(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
|
const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
|
||||||
|
|
||||||
const req = request({
|
const req = request({
|
||||||
url: requestUrl,
|
url: requestUrl,
|
||||||
|
@ -9,7 +9,6 @@ export default async function(): Promise<Meta> {
|
|||||||
} else {
|
} else {
|
||||||
return Metas.save({
|
return Metas.save({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
hiddenTags: []
|
|
||||||
} as Meta);
|
} as Meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import fetchMeta from './fetch-meta';
|
import fetchMeta from './fetch-meta';
|
||||||
import { ILocalUser } from '../models/entities/user';
|
import { ILocalUser } from '../models/entities/user';
|
||||||
import { Users } from '../models';
|
import { Users } from '../models';
|
||||||
|
import { ensure } from '../prelude/ensure';
|
||||||
|
|
||||||
export async function fetchProxyAccount(): Promise<ILocalUser> {
|
export async function fetchProxyAccount(): Promise<ILocalUser> {
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
return await Users.findOne({ username: meta.proxyAccount, host: null }) as ILocalUser;
|
return await Users.findOne({ username: meta.proxyAccount!, host: null }).then(ensure) as ILocalUser;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ulid } from 'ulid';
|
import { ulid } from 'ulid';
|
||||||
import { genAid } from './aid';
|
import { genAid } from './id/aid';
|
||||||
import { genAidc } from './aidc';
|
import { genMeid } from './id/meid';
|
||||||
import { genObjectId } from './object-id';
|
import { genObjectId } from './id/object-id';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
|
||||||
const metohd = config.id.toLowerCase();
|
const metohd = config.id.toLowerCase();
|
||||||
@ -10,11 +10,8 @@ export function genId(date?: Date): string {
|
|||||||
if (!date || (date > new Date())) date = new Date();
|
if (!date || (date > new Date())) date = new Date();
|
||||||
|
|
||||||
switch (metohd) {
|
switch (metohd) {
|
||||||
case 'aidc': return genAidc(date);
|
case 'aid': return genAid(date);
|
||||||
case 'aid1': return genAid(date, 1);
|
case 'meid': return genMeid(date);
|
||||||
case 'aid2': return genAid(date, 2);
|
|
||||||
case 'aid3': return genAid(date, 3);
|
|
||||||
case 'aid4': return genAid(date, 4);
|
|
||||||
case 'ulid': return ulid(date.getTime());
|
case 'ulid': return ulid(date.getTime());
|
||||||
case 'objectid': return genObjectId(date);
|
case 'objectid': return genObjectId(date);
|
||||||
default: throw 'unknown id generation method';
|
default: throw 'unknown id generation method';
|
||||||
|
23
src/misc/id/aid.ts
Normal file
23
src/misc/id/aid.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// AID
|
||||||
|
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
|
||||||
|
|
||||||
|
import * as cluster from 'cluster';
|
||||||
|
|
||||||
|
const TIME2000 = 946684800000;
|
||||||
|
let counter = process.pid + (cluster.isMaster ? 0 : cluster.worker.id);
|
||||||
|
|
||||||
|
function getTime(time: number) {
|
||||||
|
time = time - TIME2000;
|
||||||
|
if (time < 0) time = 0;
|
||||||
|
|
||||||
|
return time.toString(36).padStart(8, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNoise() {
|
||||||
|
return counter.toString(36).padStart(2, '0').slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genAid(date: Date): string {
|
||||||
|
counter++;
|
||||||
|
return getTime(date.getTime()) + getNoise();
|
||||||
|
}
|
26
src/misc/id/meid.ts
Normal file
26
src/misc/id/meid.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const CHARS = '0123456789abcdef';
|
||||||
|
|
||||||
|
function getTime(time: number) {
|
||||||
|
if (time < 0) time = 0;
|
||||||
|
if (time === 0) {
|
||||||
|
return CHARS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
time += 0x800000000000;
|
||||||
|
|
||||||
|
return time.toString(16).padStart(12, CHARS[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandom() {
|
||||||
|
let str = '';
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
str += CHARS[Math.floor(Math.random() * CHARS.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genMeid(date: Date): string {
|
||||||
|
return 'f' + getTime(date.getTime()) + getRandom();
|
||||||
|
}
|
@ -8,7 +8,7 @@ function getTime(time: number) {
|
|||||||
|
|
||||||
time = Math.floor(time / 1000);
|
time = Math.floor(time / 1000);
|
||||||
|
|
||||||
return time.toString(16);
|
return time.toString(16).padStart(8, CHARS[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandom() {
|
function getRandom() {
|
@ -7,7 +7,7 @@ export class IdentifiableError extends Error {
|
|||||||
|
|
||||||
constructor(id: string, message?: string) {
|
constructor(id: string, message?: string) {
|
||||||
super(message);
|
super(message);
|
||||||
this.message = message;
|
this.message = message || '';
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ export function nyaize(text: string): string {
|
|||||||
// ja-JP
|
// ja-JP
|
||||||
.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ')
|
.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ')
|
||||||
// ko-KR
|
// ko-KR
|
||||||
.replace(/[나-낳]/g, (match: string) => String.fromCharCode(
|
.replace(/[나-낳]/g, match => String.fromCharCode(
|
||||||
match.codePointAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0)
|
match.codePointAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export async function getFallbackReaction(): Promise<string> {
|
|||||||
return meta.useStarForReactionFallback ? 'star' : 'like';
|
return meta.useStarForReactionFallback ? 'star' : 'like';
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toDbReaction(reaction: string, enableEmoji = true): Promise<string> {
|
export async function toDbReaction(reaction?: string | null, enableEmoji = true): Promise<string> {
|
||||||
if (reaction == null) return await getFallbackReaction();
|
if (reaction == null) return await getFallbackReaction();
|
||||||
|
|
||||||
// 既存の文字列リアクションはそのまま
|
// 既存の文字列リアクションはそのまま
|
||||||
|
@ -19,8 +19,8 @@ type MyType<T extends Schema> = {
|
|||||||
export type SchemaType<p extends Schema> =
|
export type SchemaType<p extends Schema> =
|
||||||
p['type'] extends 'number' ? number :
|
p['type'] extends 'number' ? number :
|
||||||
p['type'] extends 'string' ? string :
|
p['type'] extends 'string' ? string :
|
||||||
p['type'] extends 'array' ? MyType<p['items']>[] :
|
p['type'] extends 'array' ? MyType<NonNullable<p['items']>>[] :
|
||||||
p['type'] extends 'object' ? ObjType<p['properties']> :
|
p['type'] extends 'object' ? ObjType<NonNullable<p['properties']>> :
|
||||||
any;
|
any;
|
||||||
|
|
||||||
export function convertOpenApiSchema(schema: Schema) {
|
export function convertOpenApiSchema(schema: Schema) {
|
||||||
|
@ -53,7 +53,7 @@ export class App {
|
|||||||
public permission: string[];
|
public permission: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The callbackUrl of the App.'
|
comment: 'The callbackUrl of the App.'
|
||||||
})
|
})
|
||||||
public callbackUrl: string | null;
|
public callbackUrl: string | null;
|
||||||
|
@ -19,11 +19,15 @@ export class AuthSession {
|
|||||||
})
|
})
|
||||||
public token: string;
|
public token: string;
|
||||||
|
|
||||||
@Column(id())
|
@Column({
|
||||||
|
...id(),
|
||||||
|
nullable: true
|
||||||
|
})
|
||||||
public userId: User['id'];
|
public userId: User['id'];
|
||||||
|
|
||||||
@ManyToOne(type => User, {
|
@ManyToOne(type => User, {
|
||||||
onDelete: 'CASCADE'
|
onDelete: 'CASCADE',
|
||||||
|
nullable: true
|
||||||
})
|
})
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
public user: User | null;
|
public user: User | null;
|
||||||
|
@ -25,12 +25,12 @@ export class Emoji {
|
|||||||
public host: string | null;
|
public host: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
})
|
})
|
||||||
public url: string;
|
public url: string;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true
|
length: 512, nullable: true
|
||||||
})
|
})
|
||||||
public uri: string | null;
|
public uri: string | null;
|
||||||
|
|
||||||
|
@ -53,13 +53,13 @@ export class FollowRequest {
|
|||||||
public followerHost: string | null;
|
public followerHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followerInbox: string | null;
|
public followerInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followerSharedInbox: string | null;
|
public followerSharedInbox: string | null;
|
||||||
@ -71,13 +71,13 @@ export class FollowRequest {
|
|||||||
public followeeHost: string | null;
|
public followeeHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followeeInbox: string | null;
|
public followeeInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followeeSharedInbox: string | null;
|
public followeeSharedInbox: string | null;
|
||||||
|
@ -48,13 +48,13 @@ export class Following {
|
|||||||
public followerHost: string | null;
|
public followerHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followerInbox: string | null;
|
public followerInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followerSharedInbox: string | null;
|
public followerSharedInbox: string | null;
|
||||||
@ -66,13 +66,13 @@ export class Following {
|
|||||||
public followeeHost: string | null;
|
public followeeHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followeeInbox: string | null;
|
public followeeInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followeeSharedInbox: string | null;
|
public followeeSharedInbox: string | null;
|
||||||
|
@ -78,27 +78,27 @@ export class Meta {
|
|||||||
public blockedHosts: string[];
|
public blockedHosts: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
default: '/assets/ai.png'
|
default: '/assets/ai.png'
|
||||||
})
|
})
|
||||||
public mascotImageUrl: string | null;
|
public mascotImageUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
nullable: true
|
nullable: true
|
||||||
})
|
})
|
||||||
public bannerUrl: string | null;
|
public bannerUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
default: 'https://ai.misskey.xyz/aiart/yubitun.png'
|
default: 'https://ai.misskey.xyz/aiart/yubitun.png'
|
||||||
})
|
})
|
||||||
public errorImageUrl: string | null;
|
public errorImageUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
nullable: true
|
nullable: true
|
||||||
})
|
})
|
||||||
public iconUrl: string | null;
|
public iconUrl: string | null;
|
||||||
|
@ -15,13 +15,6 @@ export class Note {
|
|||||||
})
|
})
|
||||||
public createdAt: Date;
|
public createdAt: Date;
|
||||||
|
|
||||||
@Index()
|
|
||||||
@Column('timestamp with time zone', {
|
|
||||||
nullable: true,
|
|
||||||
comment: 'The updated date of the Note.'
|
|
||||||
})
|
|
||||||
public updatedAt: Date | null;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
@ -126,7 +119,7 @@ export class Note {
|
|||||||
|
|
||||||
@Index({ unique: true })
|
@Index({ unique: true })
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The URI of a note. it will be null when the note is local.'
|
comment: 'The URI of a note. it will be null when the note is local.'
|
||||||
})
|
})
|
||||||
public uri: string | null;
|
public uri: string | null;
|
||||||
@ -195,12 +188,6 @@ export class Note {
|
|||||||
})
|
})
|
||||||
public userHost: string | null;
|
public userHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
|
||||||
length: 128, nullable: true,
|
|
||||||
comment: '[Denormalized]'
|
|
||||||
})
|
|
||||||
public userInbox: string | null;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
nullable: true,
|
nullable: true,
|
||||||
@ -227,6 +214,14 @@ export class Note {
|
|||||||
})
|
})
|
||||||
public renoteUserHost: string | null;
|
public renoteUserHost: string | null;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
constructor(data: Partial<Note>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IMentionedRemoteUsers = {
|
export type IMentionedRemoteUsers = {
|
||||||
|
@ -53,11 +53,19 @@ export class Poll {
|
|||||||
})
|
})
|
||||||
public userHost: string | null;
|
public userHost: string | null;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
constructor(data: Partial<Poll>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IPoll = {
|
export type IPoll = {
|
||||||
choices: string[];
|
choices: string[];
|
||||||
votes?: number[];
|
votes?: number[];
|
||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
expiresAt: Date;
|
expiresAt: Date | null;
|
||||||
};
|
};
|
||||||
|
@ -21,7 +21,7 @@ export class SwSubscription {
|
|||||||
public user: User | null;
|
public user: User | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
})
|
})
|
||||||
public endpoint: string;
|
public endpoint: string;
|
||||||
|
|
||||||
|
@ -22,4 +22,12 @@ export class UserKeypair {
|
|||||||
length: 4096,
|
length: 4096,
|
||||||
})
|
})
|
||||||
public privateKey: string;
|
public privateKey: string;
|
||||||
|
|
||||||
|
constructor(data: Partial<UserKeypair>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,12 @@ export class UserProfile {
|
|||||||
value: string;
|
value: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512, nullable: true,
|
||||||
|
comment: 'Remote URL of the user.'
|
||||||
|
})
|
||||||
|
public url: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, nullable: true,
|
length: 128, nullable: true,
|
||||||
comment: 'The email address of the User.'
|
comment: 'The email address of the User.'
|
||||||
@ -192,4 +198,12 @@ export class UserProfile {
|
|||||||
})
|
})
|
||||||
public userHost: string | null;
|
public userHost: string | null;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
constructor(data: Partial<UserProfile>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,4 +23,12 @@ export class UserPublickey {
|
|||||||
length: 4096,
|
length: 4096,
|
||||||
})
|
})
|
||||||
public keyPem: string;
|
public keyPem: string;
|
||||||
|
|
||||||
|
constructor(data: Partial<UserPublickey>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,12 +96,12 @@ export class User {
|
|||||||
public tags: string[];
|
public tags: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
})
|
})
|
||||||
public avatarUrl: string | null;
|
public avatarUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
})
|
})
|
||||||
public bannerUrl: string | null;
|
public bannerUrl: string | null;
|
||||||
|
|
||||||
@ -175,26 +175,26 @@ export class User {
|
|||||||
public host: string | null;
|
public host: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The inbox of the User. It will be null if the origin of the user is local.'
|
comment: 'The inbox URL of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public inbox: string | null;
|
public inbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The sharedInbox of the User. It will be null if the origin of the user is local.'
|
comment: 'The sharedInbox URL of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public sharedInbox: string | null;
|
public sharedInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The featured of the User. It will be null if the origin of the user is local.'
|
comment: 'The featured URL of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public featured: string | null;
|
public featured: string | null;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The URI of the User. It will be null if the origin of the user is local.'
|
comment: 'The URI of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public uri: string | null;
|
public uri: string | null;
|
||||||
@ -205,6 +205,14 @@ export class User {
|
|||||||
comment: 'The native access token of the User. It will be null if the origin of the user is local.'
|
comment: 'The native access token of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public token: string | null;
|
public token: string | null;
|
||||||
|
|
||||||
|
constructor(data: Partial<User>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILocalUser extends User {
|
export interface ILocalUser extends User {
|
||||||
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||||||
import { Users } from '..';
|
import { Users } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { AbuseUserReport } from '../entities/abuse-user-report';
|
import { AbuseUserReport } from '../entities/abuse-user-report';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(AbuseUserReport)
|
@EntityRepository(AbuseUserReport)
|
||||||
export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
|
export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
|
||||||
@ -14,7 +15,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: AbuseUserReport['id'] | AbuseUserReport,
|
src: AbuseUserReport['id'] | AbuseUserReport,
|
||||||
) {
|
) {
|
||||||
const report = typeof src === 'object' ? src : await this.findOne(src);
|
const report = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: report.id,
|
id: report.id,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { EntityRepository, Repository } from 'typeorm';
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
import { App } from '../entities/app';
|
import { App } from '../entities/app';
|
||||||
import { AccessTokens } from '..';
|
import { AccessTokens } from '..';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(App)
|
@EntityRepository(App)
|
||||||
export class AppRepository extends Repository<App> {
|
export class AppRepository extends Repository<App> {
|
||||||
@ -19,7 +20,7 @@ export class AppRepository extends Repository<App> {
|
|||||||
includeProfileImageIds: false
|
includeProfileImageIds: false
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const app = typeof src === 'object' ? src : await this.findOne(src);
|
const app = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: app.id,
|
id: app.id,
|
||||||
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||||||
import { Apps } from '..';
|
import { Apps } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { AuthSession } from '../entities/auth-session';
|
import { AuthSession } from '../entities/auth-session';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(AuthSession)
|
@EntityRepository(AuthSession)
|
||||||
export class AuthSessionRepository extends Repository<AuthSession> {
|
export class AuthSessionRepository extends Repository<AuthSession> {
|
||||||
@ -9,7 +10,7 @@ export class AuthSessionRepository extends Repository<AuthSession> {
|
|||||||
src: AuthSession['id'] | AuthSession,
|
src: AuthSession['id'] | AuthSession,
|
||||||
me?: any
|
me?: any
|
||||||
) {
|
) {
|
||||||
const session = typeof src === 'object' ? src : await this.findOne(src);
|
const session = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: session.id,
|
id: session.id,
|
||||||
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||||||
import { Users } from '..';
|
import { Users } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { Blocking } from '../entities/blocking';
|
import { Blocking } from '../entities/blocking';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(Blocking)
|
@EntityRepository(Blocking)
|
||||||
export class BlockingRepository extends Repository<Blocking> {
|
export class BlockingRepository extends Repository<Blocking> {
|
||||||
@ -16,7 +17,7 @@ export class BlockingRepository extends Repository<Blocking> {
|
|||||||
src: Blocking['id'] | Blocking,
|
src: Blocking['id'] | Blocking,
|
||||||
me?: any
|
me?: any
|
||||||
) {
|
) {
|
||||||
const blocking = typeof src === 'object' ? src : await this.findOne(src);
|
const blocking = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: blocking.id,
|
id: blocking.id,
|
||||||
|
@ -4,6 +4,7 @@ import { Users, DriveFolders } from '..';
|
|||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { User } from '../entities/user';
|
import { User } from '../entities/user';
|
||||||
import { toPuny } from '../../misc/convert-host';
|
import { toPuny } from '../../misc/convert-host';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(DriveFile)
|
@EntityRepository(DriveFile)
|
||||||
export class DriveFileRepository extends Repository<DriveFile> {
|
export class DriveFileRepository extends Repository<DriveFile> {
|
||||||
@ -91,7 +92,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
|
|||||||
self: false
|
self: false
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const file = typeof src === 'object' ? src : await this.findOne(src);
|
const file = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: file.id,
|
id: file.id,
|
||||||
@ -108,7 +109,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
|
|||||||
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
|
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
|
||||||
detail: true
|
detail: true
|
||||||
}) : null,
|
}) : null,
|
||||||
user: opts.withUser ? Users.pack(file.userId) : null
|
user: opts.withUser ? Users.pack(file.userId!) : null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||||||
import { DriveFolders, DriveFiles } from '..';
|
import { DriveFolders, DriveFiles } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { DriveFolder } from '../entities/drive-folder';
|
import { DriveFolder } from '../entities/drive-folder';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(DriveFolder)
|
@EntityRepository(DriveFolder)
|
||||||
export class DriveFolderRepository extends Repository<DriveFolder> {
|
export class DriveFolderRepository extends Repository<DriveFolder> {
|
||||||
@ -22,7 +23,7 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
|
|||||||
detail: false
|
detail: false
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const folder = typeof src === 'object' ? src : await this.findOne(src);
|
const folder = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: folder.id,
|
id: folder.id,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { EntityRepository, Repository } from 'typeorm';
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
import { FollowRequest } from '../entities/follow-request';
|
import { FollowRequest } from '../entities/follow-request';
|
||||||
import { Users } from '..';
|
import { Users } from '..';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(FollowRequest)
|
@EntityRepository(FollowRequest)
|
||||||
export class FollowRequestRepository extends Repository<FollowRequest> {
|
export class FollowRequestRepository extends Repository<FollowRequest> {
|
||||||
@ -8,7 +9,7 @@ export class FollowRequestRepository extends Repository<FollowRequest> {
|
|||||||
src: FollowRequest['id'] | FollowRequest,
|
src: FollowRequest['id'] | FollowRequest,
|
||||||
me?: any
|
me?: any
|
||||||
) {
|
) {
|
||||||
const request = typeof src === 'object' ? src : await this.findOne(src);
|
const request = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: request.id,
|
id: request.id,
|
||||||
|
@ -2,9 +2,50 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||||||
import { Users } from '..';
|
import { Users } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { Following } from '../entities/following';
|
import { Following } from '../entities/following';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
|
type LocalFollowerFollowing = Following & {
|
||||||
|
followerHost: null;
|
||||||
|
followerInbox: null;
|
||||||
|
followerSharedInbox: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RemoteFollowerFollowing = Following & {
|
||||||
|
followerHost: string;
|
||||||
|
followerInbox: string;
|
||||||
|
followerSharedInbox: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LocalFolloweeFollowing = Following & {
|
||||||
|
followeeHost: null;
|
||||||
|
followeeInbox: null;
|
||||||
|
followeeSharedInbox: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RemoteFolloweeFollowing = Following & {
|
||||||
|
followeeHost: string;
|
||||||
|
followeeInbox: string;
|
||||||
|
followeeSharedInbox: string;
|
||||||
|
};
|
||||||
|
|
||||||
@EntityRepository(Following)
|
@EntityRepository(Following)
|
||||||
export class FollowingRepository extends Repository<Following> {
|
export class FollowingRepository extends Repository<Following> {
|
||||||
|
public isLocalFollower(following: Following): following is LocalFollowerFollowing {
|
||||||
|
return following.followerHost == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRemoteFollower(following: Following): following is RemoteFollowerFollowing {
|
||||||
|
return following.followerHost != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isLocalFollowee(following: Following): following is LocalFolloweeFollowing {
|
||||||
|
return following.followeeHost == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing {
|
||||||
|
return following.followeeHost != null;
|
||||||
|
}
|
||||||
|
|
||||||
public packMany(
|
public packMany(
|
||||||
followings: any[],
|
followings: any[],
|
||||||
me?: any,
|
me?: any,
|
||||||
@ -24,7 +65,7 @@ export class FollowingRepository extends Repository<Following> {
|
|||||||
populateFollower?: boolean;
|
populateFollower?: boolean;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const following = typeof src === 'object' ? src : await this.findOne(src);
|
const following = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
if (opts == null) opts = {};
|
if (opts == null) opts = {};
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { EntityRepository, Repository } from 'typeorm';
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
import { Users } from '../../..';
|
import { Users } from '../../..';
|
||||||
import { ReversiGame } from '../../../entities/games/reversi/game';
|
import { ReversiGame } from '../../../entities/games/reversi/game';
|
||||||
|
import { ensure } from '../../../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(ReversiGame)
|
@EntityRepository(ReversiGame)
|
||||||
export class ReversiGameRepository extends Repository<ReversiGame> {
|
export class ReversiGameRepository extends Repository<ReversiGame> {
|
||||||
@ -15,7 +16,7 @@ export class ReversiGameRepository extends Repository<ReversiGame> {
|
|||||||
detail: true
|
detail: true
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const game = typeof src === 'object' ? src : await this.findOne(src);
|
const game = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { ReversiMatching } from '../../../entities/games/reversi/matching';
|
import { ReversiMatching } from '../../../entities/games/reversi/matching';
|
||||||
import { Users } from '../../..';
|
import { Users } from '../../..';
|
||||||
|
import { ensure } from '../../../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(ReversiMatching)
|
@EntityRepository(ReversiMatching)
|
||||||
export class ReversiMatchingRepository extends Repository<ReversiMatching> {
|
export class ReversiMatchingRepository extends Repository<ReversiMatching> {
|
||||||
@ -9,7 +10,7 @@ export class ReversiMatchingRepository extends Repository<ReversiMatching> {
|
|||||||
src: ReversiMatching['id'] | ReversiMatching,
|
src: ReversiMatching['id'] | ReversiMatching,
|
||||||
me: any
|
me: any
|
||||||
) {
|
) {
|
||||||
const matching = typeof src === 'object' ? src : await this.findOne(src);
|
const matching = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: matching.id,
|
id: matching.id,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { EntityRepository, Repository } from 'typeorm';
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
import { MessagingMessage } from '../entities/messaging-message';
|
import { MessagingMessage } from '../entities/messaging-message';
|
||||||
import { Users, DriveFiles } from '..';
|
import { Users, DriveFiles } from '..';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(MessagingMessage)
|
@EntityRepository(MessagingMessage)
|
||||||
export class MessagingMessageRepository extends Repository<MessagingMessage> {
|
export class MessagingMessageRepository extends Repository<MessagingMessage> {
|
||||||
@ -19,7 +20,7 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
|
|||||||
populateRecipient: true
|
populateRecipient: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const message = typeof src === 'object' ? src : await this.findOne(src);
|
const message = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: message.id,
|
id: message.id,
|
||||||
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||||||
import { Users } from '..';
|
import { Users } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { Muting } from '../entities/muting';
|
import { Muting } from '../entities/muting';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(Muting)
|
@EntityRepository(Muting)
|
||||||
export class MutingRepository extends Repository<Muting> {
|
export class MutingRepository extends Repository<Muting> {
|
||||||
@ -16,7 +17,7 @@ export class MutingRepository extends Repository<Muting> {
|
|||||||
src: Muting['id'] | Muting,
|
src: Muting['id'] | Muting,
|
||||||
me?: any
|
me?: any
|
||||||
) {
|
) {
|
||||||
const muting = typeof src === 'object' ? src : await this.findOne(src);
|
const muting = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: muting.id,
|
id: muting.id,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { EntityRepository, Repository } from 'typeorm';
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
import { NoteFavorite } from '../entities/note-favorite';
|
import { NoteFavorite } from '../entities/note-favorite';
|
||||||
import { Notes } from '..';
|
import { Notes } from '..';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(NoteFavorite)
|
@EntityRepository(NoteFavorite)
|
||||||
export class NoteFavoriteRepository extends Repository<NoteFavorite> {
|
export class NoteFavoriteRepository extends Repository<NoteFavorite> {
|
||||||
@ -15,7 +16,7 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> {
|
|||||||
src: NoteFavorite['id'] | NoteFavorite,
|
src: NoteFavorite['id'] | NoteFavorite,
|
||||||
me?: any
|
me?: any
|
||||||
) {
|
) {
|
||||||
const favorite = typeof src === 'object' ? src : await this.findOne(src);
|
const favorite = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: favorite.id,
|
id: favorite.id,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { EntityRepository, Repository } from 'typeorm';
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
import { NoteReaction } from '../entities/note-reaction';
|
import { NoteReaction } from '../entities/note-reaction';
|
||||||
import { Users } from '..';
|
import { Users } from '..';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(NoteReaction)
|
@EntityRepository(NoteReaction)
|
||||||
export class NoteReactionRepository extends Repository<NoteReaction> {
|
export class NoteReactionRepository extends Repository<NoteReaction> {
|
||||||
@ -8,7 +9,7 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
|
|||||||
src: NoteReaction['id'] | NoteReaction,
|
src: NoteReaction['id'] | NoteReaction,
|
||||||
me?: any
|
me?: any
|
||||||
) {
|
) {
|
||||||
const reaction = typeof src === 'object' ? src : await this.findOne(src);
|
const reaction = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: reaction.id,
|
id: reaction.id,
|
||||||
|
@ -5,6 +5,7 @@ import { unique, concat } from '../../prelude/array';
|
|||||||
import { nyaize } from '../../misc/nyaize';
|
import { nyaize } from '../../misc/nyaize';
|
||||||
import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
|
import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(Note)
|
@EntityRepository(Note)
|
||||||
export class NoteRepository extends Repository<Note> {
|
export class NoteRepository extends Repository<Note> {
|
||||||
@ -12,7 +13,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
return x.trim().length <= 100;
|
return x.trim().length <= 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async hideNote(packedNote: any, meId: User['id']) {
|
private async hideNote(packedNote: any, meId: User['id'] | null) {
|
||||||
let hide = false;
|
let hide = false;
|
||||||
|
|
||||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||||
@ -75,7 +76,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
|
|
||||||
public packMany(
|
public packMany(
|
||||||
notes: (Note['id'] | Note)[],
|
notes: (Note['id'] | Note)[],
|
||||||
me?: User['id'] | User,
|
me?: User['id'] | User | null | undefined,
|
||||||
options?: {
|
options?: {
|
||||||
detail?: boolean;
|
detail?: boolean;
|
||||||
skipHide?: boolean;
|
skipHide?: boolean;
|
||||||
@ -86,7 +87,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
|
|
||||||
public async pack(
|
public async pack(
|
||||||
src: Note['id'] | Note,
|
src: Note['id'] | Note,
|
||||||
me?: User['id'] | User,
|
me?: User['id'] | User | null | undefined,
|
||||||
options?: {
|
options?: {
|
||||||
detail?: boolean;
|
detail?: boolean;
|
||||||
skipHide?: boolean;
|
skipHide?: boolean;
|
||||||
@ -98,11 +99,11 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
||||||
const note = typeof src === 'object' ? src : await this.findOne(src);
|
const note = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
const host = note.userHost;
|
const host = note.userHost;
|
||||||
|
|
||||||
async function populatePoll() {
|
async function populatePoll() {
|
||||||
const poll = await Polls.findOne({ noteId: note.id });
|
const poll = await Polls.findOne({ noteId: note.id }).then(ensure);
|
||||||
const choices = poll.choices.map(c => ({
|
const choices = poll.choices.map(c => ({
|
||||||
text: c,
|
text: c,
|
||||||
votes: poll.votes[poll.choices.indexOf(c)],
|
votes: poll.votes[poll.choices.indexOf(c)],
|
||||||
@ -111,7 +112,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
|
|
||||||
if (poll.multiple) {
|
if (poll.multiple) {
|
||||||
const votes = await PollVotes.find({
|
const votes = await PollVotes.find({
|
||||||
userId: meId,
|
userId: meId!,
|
||||||
noteId: note.id
|
noteId: note.id
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const vote = await PollVotes.findOne({
|
const vote = await PollVotes.findOne({
|
||||||
userId: meId,
|
userId: meId!,
|
||||||
noteId: note.id
|
noteId: note.id
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
|
|
||||||
async function populateMyReaction() {
|
async function populateMyReaction() {
|
||||||
const reaction = await NoteReactions.findOne({
|
const reaction = await NoteReactions.findOne({
|
||||||
userId: meId,
|
userId: meId!,
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -147,7 +148,7 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
return reaction.reaction;
|
return reaction.reaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = note.text;
|
let text = note.text;
|
||||||
@ -161,15 +162,15 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
const packed = await rap({
|
const packed = await rap({
|
||||||
id: note.id,
|
id: note.id,
|
||||||
createdAt: note.createdAt,
|
createdAt: note.createdAt,
|
||||||
app: note.appId ? Apps.pack(note.appId) : null,
|
app: note.appId ? Apps.pack(note.appId) : undefined,
|
||||||
userId: note.userId,
|
userId: note.userId,
|
||||||
user: Users.pack(note.user || note.userId, meId),
|
user: Users.pack(note.user || note.userId, meId),
|
||||||
text: text,
|
text: text,
|
||||||
cw: note.cw,
|
cw: note.cw,
|
||||||
visibility: note.visibility,
|
visibility: note.visibility,
|
||||||
localOnly: note.localOnly,
|
localOnly: note.localOnly || undefined,
|
||||||
visibleUserIds: note.visibleUserIds,
|
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
|
||||||
viaMobile: note.viaMobile,
|
viaMobile: note.viaMobile || undefined,
|
||||||
renoteCount: note.renoteCount,
|
renoteCount: note.renoteCount,
|
||||||
repliesCount: note.repliesCount,
|
repliesCount: note.repliesCount,
|
||||||
reactions: note.reactions,
|
reactions: note.reactions,
|
||||||
@ -182,17 +183,18 @@ export class NoteRepository extends Repository<Note> {
|
|||||||
files: DriveFiles.packMany(note.fileIds),
|
files: DriveFiles.packMany(note.fileIds),
|
||||||
replyId: note.replyId,
|
replyId: note.replyId,
|
||||||
renoteId: note.renoteId,
|
renoteId: note.renoteId,
|
||||||
|
uri: note.uri,
|
||||||
|
|
||||||
...(opts.detail ? {
|
...(opts.detail ? {
|
||||||
reply: note.replyId ? this.pack(note.replyId, meId, {
|
reply: note.replyId ? this.pack(note.replyId, meId, {
|
||||||
detail: false
|
detail: false
|
||||||
}) : null,
|
}) : undefined,
|
||||||
|
|
||||||
renote: note.renoteId ? this.pack(note.renoteId, meId, {
|
renote: note.renoteId ? this.pack(note.renoteId, meId, {
|
||||||
detail: true
|
detail: true
|
||||||
}) : null,
|
}) : undefined,
|
||||||
|
|
||||||
poll: note.hasPoll ? populatePoll() : null,
|
poll: note.hasPoll ? populatePoll() : undefined,
|
||||||
|
|
||||||
...(meId ? {
|
...(meId ? {
|
||||||
myReaction: populateMyReaction()
|
myReaction: populateMyReaction()
|
||||||
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm';
|
|||||||
import { Users, Notes } from '..';
|
import { Users, Notes } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { Notification } from '../entities/notification';
|
import { Notification } from '../entities/notification';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(Notification)
|
@EntityRepository(Notification)
|
||||||
export class NotificationRepository extends Repository<Notification> {
|
export class NotificationRepository extends Repository<Notification> {
|
||||||
@ -14,7 +15,7 @@ export class NotificationRepository extends Repository<Notification> {
|
|||||||
public async pack(
|
public async pack(
|
||||||
src: Notification['id'] | Notification,
|
src: Notification['id'] | Notification,
|
||||||
) {
|
) {
|
||||||
const notification = typeof src === 'object' ? src : await this.findOne(src);
|
const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: notification.id,
|
id: notification.id,
|
||||||
@ -23,23 +24,23 @@ export class NotificationRepository extends Repository<Notification> {
|
|||||||
userId: notification.notifierId,
|
userId: notification.notifierId,
|
||||||
user: Users.pack(notification.notifier || notification.notifierId),
|
user: Users.pack(notification.notifier || notification.notifierId),
|
||||||
...(notification.type === 'mention' ? {
|
...(notification.type === 'mention' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId),
|
note: Notes.pack(notification.note || notification.noteId!),
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'reply' ? {
|
...(notification.type === 'reply' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId),
|
note: Notes.pack(notification.note || notification.noteId!),
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'renote' ? {
|
...(notification.type === 'renote' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId),
|
note: Notes.pack(notification.note || notification.noteId!),
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'quote' ? {
|
...(notification.type === 'quote' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId),
|
note: Notes.pack(notification.note || notification.noteId!),
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'reaction' ? {
|
...(notification.type === 'reaction' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId),
|
note: Notes.pack(notification.note || notification.noteId!),
|
||||||
reaction: notification.reaction
|
reaction: notification.reaction
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'pollVote' ? {
|
...(notification.type === 'pollVote' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId),
|
note: Notes.pack(notification.note || notification.noteId!),
|
||||||
choice: notification.choice
|
choice: notification.choice
|
||||||
} : {})
|
} : {})
|
||||||
});
|
});
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import { EntityRepository, Repository } from 'typeorm';
|
import { EntityRepository, Repository } from 'typeorm';
|
||||||
import { UserList } from '../entities/user-list';
|
import { UserList } from '../entities/user-list';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
import { UserListJoinings } from '..';
|
||||||
|
|
||||||
@EntityRepository(UserList)
|
@EntityRepository(UserList)
|
||||||
export class UserListRepository extends Repository<UserList> {
|
export class UserListRepository extends Repository<UserList> {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: any,
|
src: UserList['id'] | UserList,
|
||||||
) {
|
) {
|
||||||
const userList = typeof src === 'object' ? src : await this.findOne(src);
|
const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
|
|
||||||
|
const users = await UserListJoinings.find({
|
||||||
|
userListId: userList.id
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: userList.id,
|
id: userList.id,
|
||||||
name: userList.name
|
name: userList.name,
|
||||||
|
userIds: users.map(x => x.userId)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { EntityRepository, Repository, In } from 'typeorm';
|
|||||||
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
||||||
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
|
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
@EntityRepository(User)
|
@EntityRepository(User)
|
||||||
export class UserRepository extends Repository<User> {
|
export class UserRepository extends Repository<User> {
|
||||||
@ -51,7 +52,7 @@ export class UserRepository extends Repository<User> {
|
|||||||
|
|
||||||
public packMany(
|
public packMany(
|
||||||
users: (User['id'] | User)[],
|
users: (User['id'] | User)[],
|
||||||
me?: User['id'] | User,
|
me?: User['id'] | User | null | undefined,
|
||||||
options?: {
|
options?: {
|
||||||
detail?: boolean,
|
detail?: boolean,
|
||||||
includeSecrets?: boolean,
|
includeSecrets?: boolean,
|
||||||
@ -63,7 +64,7 @@ export class UserRepository extends Repository<User> {
|
|||||||
|
|
||||||
public async pack(
|
public async pack(
|
||||||
src: User['id'] | User,
|
src: User['id'] | User,
|
||||||
me?: User['id'] | User,
|
me?: User['id'] | User | null | undefined,
|
||||||
options?: {
|
options?: {
|
||||||
detail?: boolean,
|
detail?: boolean,
|
||||||
includeSecrets?: boolean,
|
includeSecrets?: boolean,
|
||||||
@ -75,26 +76,24 @@ export class UserRepository extends Repository<User> {
|
|||||||
includeSecrets: false
|
includeSecrets: false
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const user = typeof src === 'object' ? src : await this.findOne(src);
|
const user = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||||
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
||||||
|
|
||||||
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
|
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
|
||||||
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
|
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
|
||||||
const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }) : null;
|
const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }).then(ensure) : null;
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
host: user.host,
|
host: user.host,
|
||||||
uri: user.uri,
|
|
||||||
avatarUrl: user.avatarUrl,
|
avatarUrl: user.avatarUrl,
|
||||||
bannerUrl: user.bannerUrl,
|
|
||||||
avatarColor: user.avatarColor,
|
avatarColor: user.avatarColor,
|
||||||
bannerColor: user.bannerColor,
|
isAdmin: user.isAdmin || undefined,
|
||||||
isAdmin: user.isAdmin,
|
isBot: user.isBot || undefined,
|
||||||
isBot: user.isBot,
|
isCat: user.isCat || undefined,
|
||||||
isCat: user.isCat,
|
isVerified: user.isVerified || undefined,
|
||||||
|
|
||||||
// カスタム絵文字添付
|
// カスタム絵文字添付
|
||||||
emojis: user.emojis.length > 0 ? Emojis.find({
|
emojis: user.emojis.length > 0 ? Emojis.find({
|
||||||
@ -117,9 +116,14 @@ export class UserRepository extends Repository<User> {
|
|||||||
} : {}),
|
} : {}),
|
||||||
|
|
||||||
...(opts.detail ? {
|
...(opts.detail ? {
|
||||||
description: profile.description,
|
url: profile!.url,
|
||||||
location: profile.location,
|
createdAt: user.createdAt,
|
||||||
birthday: profile.birthday,
|
updatedAt: user.updatedAt,
|
||||||
|
bannerUrl: user.bannerUrl,
|
||||||
|
bannerColor: user.bannerColor,
|
||||||
|
description: profile!.description,
|
||||||
|
location: profile!.location,
|
||||||
|
birthday: profile!.birthday,
|
||||||
followersCount: user.followersCount,
|
followersCount: user.followersCount,
|
||||||
followingCount: user.followingCount,
|
followingCount: user.followingCount,
|
||||||
notesCount: user.notesCount,
|
notesCount: user.notesCount,
|
||||||
@ -132,9 +136,9 @@ export class UserRepository extends Repository<User> {
|
|||||||
...(opts.detail && meId === user.id ? {
|
...(opts.detail && meId === user.id ? {
|
||||||
avatarId: user.avatarId,
|
avatarId: user.avatarId,
|
||||||
bannerId: user.bannerId,
|
bannerId: user.bannerId,
|
||||||
autoWatch: profile.autoWatch,
|
autoWatch: profile!.autoWatch,
|
||||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
alwaysMarkNsfw: profile!.alwaysMarkNsfw,
|
||||||
carefulBot: profile.carefulBot,
|
carefulBot: profile!.carefulBot,
|
||||||
hasUnreadMessagingMessage: MessagingMessages.count({
|
hasUnreadMessagingMessage: MessagingMessages.count({
|
||||||
where: {
|
where: {
|
||||||
recipientId: user.id,
|
recipientId: user.id,
|
||||||
@ -155,9 +159,9 @@ export class UserRepository extends Repository<User> {
|
|||||||
} : {}),
|
} : {}),
|
||||||
|
|
||||||
...(opts.includeSecrets ? {
|
...(opts.includeSecrets ? {
|
||||||
clientData: profile.clientData,
|
clientData: profile!.clientData,
|
||||||
email: profile.email,
|
email: profile!.email,
|
||||||
emailVerified: profile.emailVerified,
|
emailVerified: profile!.emailVerified,
|
||||||
} : {}),
|
} : {}),
|
||||||
|
|
||||||
...(relation ? {
|
...(relation ? {
|
||||||
|
10
src/prelude/ensure.ts
Normal file
10
src/prelude/ensure.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* 値が null または undefined の場合はエラーを発生させ、そうでない場合は値をそのまま返します
|
||||||
|
*/
|
||||||
|
export function ensure<T>(x: T): NonNullable<T> {
|
||||||
|
if (x == null) {
|
||||||
|
throw 'ぬるぽ';
|
||||||
|
} else {
|
||||||
|
return x!;
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ import { queueLogger } from './logger';
|
|||||||
import { DriveFile } from '../models/entities/drive-file';
|
import { DriveFile } from '../models/entities/drive-file';
|
||||||
|
|
||||||
function initializeQueue(name: string) {
|
function initializeQueue(name: string) {
|
||||||
return new Queue(name, config.redis != null ? {
|
return new Queue(name, {
|
||||||
redis: {
|
redis: {
|
||||||
port: config.redis.port,
|
port: config.redis.port,
|
||||||
host: config.redis.host,
|
host: config.redis.host,
|
||||||
@ -20,7 +20,15 @@ function initializeQueue(name: string) {
|
|||||||
db: config.redis.db || 0,
|
db: config.redis.db || 0,
|
||||||
},
|
},
|
||||||
prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue'
|
prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue'
|
||||||
} : null);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderError(e: Error): any {
|
||||||
|
return {
|
||||||
|
stack: e.stack,
|
||||||
|
message: e.message,
|
||||||
|
name: e.name
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deliverQueue = initializeQueue('deliver');
|
export const deliverQueue = initializeQueue('deliver');
|
||||||
@ -34,16 +42,16 @@ 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 id=${job.id} 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}) id=${job.id} to=${job.data.to}`))
|
||||||
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) id=${job.id} to=${job.data.to}`))
|
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) id=${job.id} to=${job.data.to}`, renderError(err)))
|
||||||
.on('error', (error) => deliverLogger.error(`error ${error}`))
|
.on('error', (error) => deliverLogger.error(`error ${error}`, renderError(error)))
|
||||||
.on('stalled', (job) => deliverLogger.warn(`stalled id=${job.id} to=${job.data.to}`));
|
.on('stalled', (job) => deliverLogger.warn(`stalled id=${job.id} 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 id=${job.id}`))
|
||||||
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) 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'}`))
|
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`, renderError(err)))
|
||||||
.on('error', (error) => inboxLogger.error(`error ${error}`))
|
.on('error', (error) => inboxLogger.error(`error ${error}`, renderError(error)))
|
||||||
.on('stalled', (job) => inboxLogger.warn(`stalled id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`));
|
.on('stalled', (job) => inboxLogger.warn(`stalled id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`));
|
||||||
|
|
||||||
export function deliver(user: ILocalUser, content: any, to: any) {
|
export function deliver(user: ILocalUser, content: any, to: any) {
|
||||||
|
@ -10,9 +10,11 @@ const logger = queueLogger.createSubLogger('delete-drive-files');
|
|||||||
export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> {
|
export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> {
|
||||||
logger.info(`Deleting drive files of ${job.data.user.id} ...`);
|
logger.info(`Deleting drive files of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne(job.data.user.id);
|
||||||
id: job.data.user.id
|
if (user == null) {
|
||||||
});
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let deletedCount = 0;
|
let deletedCount = 0;
|
||||||
let ended = false;
|
let ended = false;
|
||||||
|
@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-blocking');
|
|||||||
export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
|
export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
|
||||||
logger.info(`Exporting blocking of ${job.data.user.id} ...`);
|
logger.info(`Exporting blocking of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne(job.data.user.id);
|
||||||
id: job.data.user.id
|
if (user == null) {
|
||||||
});
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
||||||
@ -56,6 +58,10 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
|
|||||||
|
|
||||||
for (const block of blockings) {
|
for (const block of blockings) {
|
||||||
const u = await Users.findOne({ id: block.blockeeId });
|
const u = await Users.findOne({ id: block.blockeeId });
|
||||||
|
if (u == null) {
|
||||||
|
exportedCount++; continue;
|
||||||
|
}
|
||||||
|
|
||||||
const content = getFullApAccount(u.username, u.host);
|
const content = getFullApAccount(u.username, u.host);
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
|
@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-following');
|
|||||||
export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
|
export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
|
||||||
logger.info(`Exporting following of ${job.data.user.id} ...`);
|
logger.info(`Exporting following of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne(job.data.user.id);
|
||||||
id: job.data.user.id
|
if (user == null) {
|
||||||
});
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
||||||
@ -56,6 +58,10 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
|
|||||||
|
|
||||||
for (const following of followings) {
|
for (const following of followings) {
|
||||||
const u = await Users.findOne({ id: following.followeeId });
|
const u = await Users.findOne({ id: following.followeeId });
|
||||||
|
if (u == null) {
|
||||||
|
exportedCount++; continue;
|
||||||
|
}
|
||||||
|
|
||||||
const content = getFullApAccount(u.username, u.host);
|
const content = getFullApAccount(u.username, u.host);
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
|
@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-mute');
|
|||||||
export async function exportMute(job: Bull.Job, done: any): Promise<void> {
|
export async function exportMute(job: Bull.Job, done: any): Promise<void> {
|
||||||
logger.info(`Exporting mute of ${job.data.user.id} ...`);
|
logger.info(`Exporting mute of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne(job.data.user.id);
|
||||||
id: job.data.user.id
|
if (user == null) {
|
||||||
});
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
||||||
@ -56,6 +58,10 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> {
|
|||||||
|
|
||||||
for (const mute of mutes) {
|
for (const mute of mutes) {
|
||||||
const u = await Users.findOne({ id: mute.muteeId });
|
const u = await Users.findOne({ id: mute.muteeId });
|
||||||
|
if (u == null) {
|
||||||
|
exportedCount++; continue;
|
||||||
|
}
|
||||||
|
|
||||||
const content = getFullApAccount(u.username, u.host);
|
const content = getFullApAccount(u.username, u.host);
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
stream.write(content + '\n', err => {
|
stream.write(content + '\n', err => {
|
||||||
|
@ -9,15 +9,18 @@ import { Users, Notes, Polls } from '../../../models';
|
|||||||
import { MoreThan } from 'typeorm';
|
import { MoreThan } from 'typeorm';
|
||||||
import { Note } from '../../../models/entities/note';
|
import { Note } from '../../../models/entities/note';
|
||||||
import { Poll } from '../../../models/entities/poll';
|
import { Poll } from '../../../models/entities/poll';
|
||||||
|
import { ensure } from '../../../prelude/ensure';
|
||||||
|
|
||||||
const logger = queueLogger.createSubLogger('export-notes');
|
const logger = queueLogger.createSubLogger('export-notes');
|
||||||
|
|
||||||
export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
|
export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
|
||||||
logger.info(`Exporting notes of ${job.data.user.id} ...`);
|
logger.info(`Exporting notes of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne(job.data.user.id);
|
||||||
id: job.data.user.id
|
if (user == null) {
|
||||||
});
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create temp file
|
// Create temp file
|
||||||
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
|
||||||
@ -67,9 +70,9 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
|
|||||||
cursor = notes[notes.length - 1].id;
|
cursor = notes[notes.length - 1].id;
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
let poll: Poll;
|
let poll: Poll | undefined;
|
||||||
if (note.hasPoll) {
|
if (note.hasPoll) {
|
||||||
poll = await Polls.findOne({ noteId: note.id });
|
poll = await Polls.findOne({ noteId: note.id }).then(ensure);
|
||||||
}
|
}
|
||||||
const content = JSON.stringify(serialize(note, poll));
|
const content = JSON.stringify(serialize(note, poll));
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
@ -114,7 +117,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
|
|||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
|
||||||
function serialize(note: Note, poll: Poll): any {
|
function serialize(note: Note, poll: Poll | null = null): any {
|
||||||
return {
|
return {
|
||||||
id: note.id,
|
id: note.id,
|
||||||
text: note.text,
|
text: note.text,
|
||||||
|
@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-user-lists');
|
|||||||
export async function exportUserLists(job: Bull.Job, done: any): Promise<void> {
|
export async function exportUserLists(job: Bull.Job, done: any): Promise<void> {
|
||||||
logger.info(`Exporting user lists of ${job.data.user.id} ...`);
|
logger.info(`Exporting user lists of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne(job.data.user.id);
|
||||||
id: job.data.user.id
|
if (user == null) {
|
||||||
});
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lists = await UserLists.find({
|
const lists = await UserLists.find({
|
||||||
userId: user.id
|
userId: user.id
|
||||||
|
@ -13,13 +13,19 @@ const logger = queueLogger.createSubLogger('import-following');
|
|||||||
export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
|
export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
|
||||||
logger.info(`Importing following of ${job.data.user.id} ...`);
|
logger.info(`Importing following of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne(job.data.user.id);
|
||||||
id: job.data.user.id
|
if (user == null) {
|
||||||
});
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const file = await DriveFiles.findOne({
|
const file = await DriveFiles.findOne({
|
||||||
id: job.data.fileId
|
id: job.data.fileId
|
||||||
});
|
});
|
||||||
|
if (file == null) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const csv = await downloadTextFile(file.url);
|
const csv = await downloadTextFile(file.url);
|
||||||
|
|
||||||
@ -31,11 +37,11 @@ export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
const { username, host } = parseAcct(line.trim());
|
const { username, host } = parseAcct(line.trim());
|
||||||
|
|
||||||
let target = isSelfHost(host) ? await Users.findOne({
|
let target = isSelfHost(host!) ? await Users.findOne({
|
||||||
host: null,
|
host: null,
|
||||||
usernameLower: username.toLowerCase()
|
usernameLower: username.toLowerCase()
|
||||||
}) : await Users.findOne({
|
}) : await Users.findOne({
|
||||||
host: toPuny(host),
|
host: toPuny(host!),
|
||||||
usernameLower: username.toLowerCase()
|
usernameLower: username.toLowerCase()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,13 +14,19 @@ const logger = queueLogger.createSubLogger('import-user-lists');
|
|||||||
export async function importUserLists(job: Bull.Job, done: any): Promise<void> {
|
export async function importUserLists(job: Bull.Job, done: any): Promise<void> {
|
||||||
logger.info(`Importing user lists of ${job.data.user.id} ...`);
|
logger.info(`Importing user lists of ${job.data.user.id} ...`);
|
||||||
|
|
||||||
const user = await Users.findOne({
|
const user = await Users.findOne(job.data.user.id);
|
||||||
id: job.data.user.id
|
if (user == null) {
|
||||||
});
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const file = await DriveFiles.findOne({
|
const file = await DriveFiles.findOne({
|
||||||
id: job.data.fileId
|
id: job.data.fileId
|
||||||
});
|
});
|
||||||
|
if (file == null) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const csv = await downloadTextFile(file.url);
|
const csv = await downloadTextFile(file.url);
|
||||||
|
|
||||||
@ -43,22 +49,20 @@ export async function importUserLists(job: Bull.Job, done: any): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = isSelfHost(host) ? await Users.findOne({
|
let target = isSelfHost(host!) ? await Users.findOne({
|
||||||
host: null,
|
host: null,
|
||||||
usernameLower: username.toLowerCase()
|
usernameLower: username.toLowerCase()
|
||||||
}) : await Users.findOne({
|
}) : await Users.findOne({
|
||||||
host: toPuny(host),
|
host: toPuny(host!),
|
||||||
usernameLower: username.toLowerCase()
|
usernameLower: username.toLowerCase()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (host == null && target == null) continue;
|
|
||||||
|
|
||||||
if (await UserListJoinings.findOne({ userListId: list.id, userId: target.id }) != null) continue;
|
|
||||||
|
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
target = await resolveUser(username, host);
|
target = await resolveUser(username, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await UserListJoinings.findOne({ userListId: list.id, userId: target.id }) != null) continue;
|
||||||
|
|
||||||
pushUserToUserList(target, list);
|
pushUserToUserList(target, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { instanceChart } from '../../services/chart';
|
|||||||
|
|
||||||
const logger = new Logger('deliver');
|
const logger = new Logger('deliver');
|
||||||
|
|
||||||
let latest: string = null;
|
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);
|
||||||
|
@ -12,7 +12,9 @@ import { Instances, Users, UserPublickeys } from '../../models';
|
|||||||
import { instanceChart } from '../../services/chart';
|
import { instanceChart } from '../../services/chart';
|
||||||
import { UserPublickey } from '../../models/entities/user-publickey';
|
import { UserPublickey } from '../../models/entities/user-publickey';
|
||||||
import fetchMeta from '../../misc/fetch-meta';
|
import fetchMeta from '../../misc/fetch-meta';
|
||||||
import { toPuny } from '../../misc/convert-host';
|
import { toPuny, toPunyNullable } from '../../misc/convert-host';
|
||||||
|
import { validActor } from '../../remote/activitypub/type';
|
||||||
|
import { ensure } from '../../prelude/ensure';
|
||||||
|
|
||||||
const logger = new Logger('inbox');
|
const logger = new Logger('inbox');
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
|
|
||||||
if (keyIdLower.startsWith('acct:')) {
|
if (keyIdLower.startsWith('acct:')) {
|
||||||
const acct = parseAcct(keyIdLower.slice('acct:'.length));
|
const acct = parseAcct(keyIdLower.slice('acct:'.length));
|
||||||
const host = toPuny(acct.host);
|
const host = toPunyNullable(acct.host);
|
||||||
const username = toPuny(acct.username);
|
const username = toPuny(acct.username);
|
||||||
|
|
||||||
if (host === null) {
|
if (host === null) {
|
||||||
@ -63,9 +65,7 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
host: host
|
host: host
|
||||||
}) as IRemoteUser;
|
}) as IRemoteUser;
|
||||||
|
|
||||||
key = await UserPublickeys.findOne({
|
key = await UserPublickeys.findOne(user.id).then(ensure);
|
||||||
userId: user.id
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// アクティビティ内のホストの検証
|
// アクティビティ内のホストの検証
|
||||||
const host = toPuny(new URL(signature.keyId).hostname);
|
const host = toPuny(new URL(signature.keyId).hostname);
|
||||||
@ -86,14 +86,14 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
|
|
||||||
key = await UserPublickeys.findOne({
|
key = await UserPublickeys.findOne({
|
||||||
keyId: signature.keyId
|
keyId: signature.keyId
|
||||||
});
|
}).then(ensure);
|
||||||
|
|
||||||
user = await Users.findOne(key.userId) as IRemoteUser;
|
user = await Users.findOne(key.userId) as IRemoteUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
|
// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
|
||||||
if (activity.type === 'Update') {
|
if (activity.type === 'Update') {
|
||||||
if (activity.object && activity.object.type === 'Person') {
|
if (activity.object && validActor.includes(activity.object.type)) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
logger.warn('Update activity received, but user not registed.');
|
logger.warn('Update activity received, but user not registed.');
|
||||||
} else if (!httpSignature.verifySignature(signature, key.keyPem)) {
|
} else if (!httpSignature.verifySignature(signature, key.keyPem)) {
|
||||||
|
@ -6,9 +6,10 @@ import { Users } from '../../../../models';
|
|||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||||
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
|
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
|
||||||
|
if (id == null) throw 'missing id';
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const follower = await Users.findOne({
|
const follower = await Users.findOne({
|
||||||
|
@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
|
|||||||
|
|
||||||
if (activity.target === actor.featured) {
|
if (activity.target === actor.featured) {
|
||||||
const note = await resolveNote(activity.object);
|
const note = await resolveNote(activity.object);
|
||||||
|
if (note == null) throw new Error('note not found');
|
||||||
await addPinned(actor, note.id);
|
await addPinned(actor, note.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -53,16 +53,16 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
|
|||||||
logger.info(`Creating the (Re)Note: ${uri}`);
|
logger.info(`Creating the (Re)Note: ${uri}`);
|
||||||
|
|
||||||
//#region Visibility
|
//#region Visibility
|
||||||
const visibility = getVisibility(activity.to, activity.cc, actor);
|
const visibility = getVisibility(activity.to || [], activity.cc || [], actor);
|
||||||
|
|
||||||
let visibleUsers: User[] = [];
|
let visibleUsers: User[] = [];
|
||||||
if (visibility == 'specified') {
|
if (visibility == 'specified') {
|
||||||
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri)));
|
visibleUsers = await Promise.all((note.to || []).map(uri => resolvePerson(uri)));
|
||||||
}
|
}
|
||||||
//#endergion
|
//#endergion
|
||||||
|
|
||||||
await post(actor, {
|
await post(actor, {
|
||||||
createdAt: new Date(activity.published),
|
createdAt: activity.published ? new Date(activity.published) : null,
|
||||||
renote,
|
renote,
|
||||||
visibility,
|
visibility,
|
||||||
visibleUsers,
|
visibleUsers,
|
||||||
@ -75,9 +75,6 @@ type visibility = 'public' | 'home' | 'followers' | 'specified';
|
|||||||
function getVisibility(to: string[], cc: string[], actor: IRemoteUser): visibility {
|
function getVisibility(to: string[], cc: string[], actor: IRemoteUser): visibility {
|
||||||
const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
|
const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
|
||||||
|
|
||||||
to = to || [];
|
|
||||||
cc = cc || [];
|
|
||||||
|
|
||||||
if (to.includes(PUBLIC)) {
|
if (to.includes(PUBLIC)) {
|
||||||
return 'public';
|
return 'public';
|
||||||
} else if (cc.includes(PUBLIC)) {
|
} else if (cc.includes(PUBLIC)) {
|
||||||
|
@ -9,13 +9,14 @@ const logger = apLogger;
|
|||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||||
|
if (id == null) throw 'missing id';
|
||||||
|
|
||||||
const uri = activity.id || activity;
|
const uri = activity.id || activity;
|
||||||
|
|
||||||
logger.info(`Block: ${uri}`);
|
logger.info(`Block: ${uri}`);
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockee = await Users.findOne(id.split('/').pop());
|
const blockee = await Users.findOne(id.split('/').pop());
|
||||||
|
@ -6,9 +6,10 @@ import { Users } from '../../../models';
|
|||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||||
|
if (id == null) throw 'missing id';
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const followee = await Users.findOne(id.split('/').pop());
|
const followee = await Users.findOne(id.split('/').pop());
|
||||||
|
@ -71,7 +71,7 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
apLogger.warn(`unknown activity type: ${(activity as any).type}`);
|
apLogger.warn(`unknown activity type: ${(activity as any).type}`);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { Notes } from '../../../models';
|
|||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: ILike) => {
|
export default async (actor: IRemoteUser, activity: ILike) => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||||
|
if (id == null) throw 'missing id';
|
||||||
|
|
||||||
// Transform:
|
// Transform:
|
||||||
// https://misskey.ex/notes/xxxx to
|
// https://misskey.ex/notes/xxxx to
|
||||||
|
@ -6,9 +6,10 @@ import { Users } from '../../../../models';
|
|||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||||
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
|
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
|
||||||
|
if (id == null) throw 'missing id';
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const follower = await Users.findOne(id.split('/').pop());
|
const follower = await Users.findOne(id.split('/').pop());
|
||||||
|
@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => {
|
|||||||
|
|
||||||
if (activity.target === actor.featured) {
|
if (activity.target === actor.featured) {
|
||||||
const note = await resolveNote(activity.object);
|
const note = await resolveNote(activity.object);
|
||||||
|
if (note == null) throw new Error('note not found');
|
||||||
await removePinned(actor, note.id);
|
await removePinned(actor, note.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,14 @@ const logger = apLogger;
|
|||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||||
|
if (id == null) throw 'missing id';
|
||||||
|
|
||||||
const uri = activity.id || activity;
|
const uri = activity.id || activity;
|
||||||
|
|
||||||
logger.info(`UnBlock: ${uri}`);
|
logger.info(`UnBlock: ${uri}`);
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockee = await Users.findOne(id.split('/').pop());
|
const blockee = await Users.findOne(id.split('/').pop());
|
||||||
|
@ -7,9 +7,10 @@ import { Users, FollowRequests, Followings } from '../../../../models';
|
|||||||
|
|
||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||||
|
if (id == null) throw 'missing id';
|
||||||
|
|
||||||
if (!id.startsWith(config.url + '/')) {
|
if (!id.startsWith(config.url + '/')) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const followee = await Users.findOne(id.split('/').pop());
|
const followee = await Users.findOne(id.split('/').pop());
|
||||||
|
@ -39,6 +39,4 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
|
|||||||
undoLike(actor, object as ILike);
|
undoLike(actor, object as ILike);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@ import { Notes } from '../../../../models';
|
|||||||
*/
|
*/
|
||||||
export default async (actor: IRemoteUser, activity: ILike): Promise<void> => {
|
export default async (actor: IRemoteUser, activity: ILike): Promise<void> => {
|
||||||
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
|
||||||
|
if (id == null) throw 'missing id';
|
||||||
|
|
||||||
const noteId = id.split('/').pop();
|
const noteId = id.split('/').pop();
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import fetchMeta from '../../../misc/fetch-meta';
|
|||||||
import { apLogger } from '../logger';
|
import { apLogger } from '../logger';
|
||||||
import { DriveFile } from '../../../models/entities/drive-file';
|
import { DriveFile } from '../../../models/entities/drive-file';
|
||||||
import { DriveFiles } from '../../../models';
|
import { DriveFiles } from '../../../models';
|
||||||
|
import { ensure } from '../../../prelude/ensure';
|
||||||
|
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ const logger = apLogger;
|
|||||||
export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
|
export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
|
||||||
// 投稿者が凍結されていたらスキップ
|
// 投稿者が凍結されていたらスキップ
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
return null;
|
throw new Error('actor has been suspended');
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = await new Resolver().resolve(value) as any;
|
const image = await new Resolver().resolve(value) as any;
|
||||||
@ -28,17 +29,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
|
|||||||
const instance = await fetchMeta();
|
const instance = await fetchMeta();
|
||||||
const cache = instance.cacheRemoteFiles;
|
const cache = instance.cacheRemoteFiles;
|
||||||
|
|
||||||
let file;
|
let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache);
|
||||||
try {
|
|
||||||
file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache);
|
|
||||||
} catch (e) {
|
|
||||||
// 4xxの場合は添付されてなかったことにする
|
|
||||||
if (e >= 400 && e < 500) {
|
|
||||||
logger.warn(`Ignored image: ${image.url} - ${e}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.isLink) {
|
if (file.isLink) {
|
||||||
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
|
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
|
||||||
@ -49,7 +40,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
|
|||||||
uri: image.url
|
uri: image.url
|
||||||
});
|
});
|
||||||
|
|
||||||
file = DriveFiles.findOne(file.id);
|
file = await DriveFiles.findOne(file.id).then(ensure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import { IObject, INote } from '../type';
|
|||||||
import { Emoji } from '../../../models/entities/emoji';
|
import { Emoji } from '../../../models/entities/emoji';
|
||||||
import { genId } from '../../../misc/gen-id';
|
import { genId } from '../../../misc/gen-id';
|
||||||
import fetchMeta from '../../../misc/fetch-meta';
|
import fetchMeta from '../../../misc/fetch-meta';
|
||||||
|
import { ensure } from '../../../prelude/ensure';
|
||||||
|
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
@ -29,13 +30,14 @@ const logger = apLogger;
|
|||||||
*
|
*
|
||||||
* Misskeyに対象のNoteが登録されていればそれを返します。
|
* Misskeyに対象のNoteが登録されていればそれを返します。
|
||||||
*/
|
*/
|
||||||
export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<Note> {
|
export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> {
|
||||||
const uri = typeof value == 'string' ? value : value.id;
|
const uri = typeof value == 'string' ? value : value.id;
|
||||||
|
if (uri == null) throw 'missing uri';
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならデータベースからフェッチ
|
// URIがこのサーバーを指しているならデータベースからフェッチ
|
||||||
if (uri.startsWith(config.url + '/')) {
|
if (uri.startsWith(config.url + '/')) {
|
||||||
const id = uri.split('/').pop();
|
const id = uri.split('/').pop();
|
||||||
return await Notes.findOne(id);
|
return await Notes.findOne(id).then(x => x || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region このサーバーに既に登録されていたらそれを返す
|
//#region このサーバーに既に登録されていたらそれを返す
|
||||||
@ -52,7 +54,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P
|
|||||||
/**
|
/**
|
||||||
* Noteを作成します。
|
* Noteを作成します。
|
||||||
*/
|
*/
|
||||||
export async function createNote(value: any, resolver?: Resolver, silent = false): Promise<Note> {
|
export async function createNote(value: any, resolver?: Resolver, silent = false): Promise<Note | null> {
|
||||||
if (resolver == null) resolver = new Resolver();
|
if (resolver == null) resolver = new Resolver();
|
||||||
|
|
||||||
const object: any = await resolver.resolve(value);
|
const object: any = await resolver.resolve(value);
|
||||||
@ -65,7 +67,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
value: value,
|
value: value,
|
||||||
object: object
|
object: object
|
||||||
});
|
});
|
||||||
return null;
|
throw 'invalid note';
|
||||||
}
|
}
|
||||||
|
|
||||||
const note: INote = object;
|
const note: INote = object;
|
||||||
@ -75,11 +77,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
logger.info(`Creating the Note: ${note.id}`);
|
logger.info(`Creating the Note: ${note.id}`);
|
||||||
|
|
||||||
// 投稿者をフェッチ
|
// 投稿者をフェッチ
|
||||||
const actor = await resolvePerson(note.attributedTo, null, resolver) as IRemoteUser;
|
const actor = await resolvePerson(note.attributedTo, resolver) as IRemoteUser;
|
||||||
|
|
||||||
// 投稿者が凍結されていたらスキップ
|
// 投稿者が凍結されていたらスキップ
|
||||||
if (actor.isSuspended) {
|
if (actor.isSuspended) {
|
||||||
return null;
|
throw 'actor has been suspended';
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Visibility
|
//#region Visibility
|
||||||
@ -95,9 +97,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
visibility = 'followers';
|
visibility = 'followers';
|
||||||
} else {
|
} else {
|
||||||
visibility = 'specified';
|
visibility = 'specified';
|
||||||
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, null, resolver)));
|
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, resolver)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endergion
|
//#endergion
|
||||||
|
|
||||||
const apMentions = await extractMentionedUsers(actor, note.to, note.cc, resolver);
|
const apMentions = await extractMentionedUsers(actor, note.to, note.cc, resolver);
|
||||||
@ -118,20 +120,22 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
// リプライ
|
// リプライ
|
||||||
const reply: Note = note.inReplyTo
|
const reply: Note | null = note.inReplyTo
|
||||||
? await resolveNote(note.inReplyTo, resolver).catch(e => {
|
? await resolveNote(note.inReplyTo, resolver).then(x => {
|
||||||
// 4xxの場合はリプライしてないことにする
|
if (x == null) {
|
||||||
if (e.statusCode >= 400 && e.statusCode < 500) {
|
logger.warn(`Specified inReplyTo, but nout found`);
|
||||||
logger.warn(`Ignored inReplyTo ${note.inReplyTo} - ${e.statusCode} `);
|
throw 'inReplyTo not found';
|
||||||
return null;
|
} else {
|
||||||
|
return x;
|
||||||
}
|
}
|
||||||
|
}).catch(e => {
|
||||||
logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`);
|
logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`);
|
||||||
throw e;
|
throw e;
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// 引用
|
// 引用
|
||||||
let quote: Note;
|
let quote: Note | undefined | null;
|
||||||
|
|
||||||
if (note._misskey_quote && typeof note._misskey_quote == 'string') {
|
if (note._misskey_quote && typeof note._misskey_quote == 'string') {
|
||||||
quote = await resolveNote(note._misskey_quote).catch(e => {
|
quote = await resolveNote(note._misskey_quote).catch(e => {
|
||||||
@ -148,11 +152,12 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
const cw = note.summary === '' ? null : note.summary;
|
const cw = note.summary === '' ? null : note.summary;
|
||||||
|
|
||||||
// テキストのパース
|
// テキストのパース
|
||||||
const text = note._misskey_content || fromHtml(note.content);
|
const text = note._misskey_content || (note.content ? fromHtml(note.content) : null);
|
||||||
|
|
||||||
// vote
|
// vote
|
||||||
if (reply && reply.hasPoll) {
|
if (reply && reply.hasPoll) {
|
||||||
const poll = await Polls.findOne({ noteId: reply.id });
|
const poll = await Polls.findOne(reply.id).then(ensure);
|
||||||
|
|
||||||
const tryCreateVote = async (name: string, index: number): Promise<null> => {
|
const tryCreateVote = async (name: string, index: number): Promise<null> => {
|
||||||
if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
|
if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
|
||||||
logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
|
||||||
@ -180,7 +185,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojis = await extractEmojis(note.tag, actor.host).catch(e => {
|
const emojis = await extractEmojis(note.tag || [], actor.host).catch(e => {
|
||||||
logger.info(`extractEmojis: ${e}`);
|
logger.info(`extractEmojis: ${e}`);
|
||||||
return [] as Emoji[];
|
return [] as Emoji[];
|
||||||
});
|
});
|
||||||
@ -196,7 +201,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await post(actor, {
|
return await post(actor, {
|
||||||
createdAt: new Date(note.published),
|
createdAt: note.published ? new Date(note.published) : null,
|
||||||
files,
|
files,
|
||||||
reply,
|
reply,
|
||||||
renote: quote,
|
renote: quote,
|
||||||
@ -223,8 +228,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
|
|||||||
* Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ
|
* Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ
|
||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||||
*/
|
*/
|
||||||
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note> {
|
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> {
|
||||||
const uri = typeof value == 'string' ? value : value.id;
|
const uri = typeof value == 'string' ? value : value.id;
|
||||||
|
if (uri == null) throw 'missing uri';
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
||||||
@ -242,71 +248,81 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
|
|||||||
// リモートサーバーからフェッチしてきて登録
|
// リモートサーバーからフェッチしてきて登録
|
||||||
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
|
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
|
||||||
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
||||||
return await createNote(uri, resolver);
|
return await createNote(uri, resolver).catch(e => {
|
||||||
|
if (e.name === 'duplicated') {
|
||||||
|
return fetchNote(uri).then(note => {
|
||||||
|
if (note == null) {
|
||||||
|
throw 'something happened';
|
||||||
|
} else {
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractEmojis(tags: ITag[], host: string) {
|
export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {
|
||||||
host = toPuny(host);
|
host = toPuny(host);
|
||||||
|
|
||||||
if (!tags) return [];
|
if (!tags) return [];
|
||||||
|
|
||||||
const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url);
|
const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name);
|
||||||
|
|
||||||
return await Promise.all(
|
return await Promise.all(eomjiTags.map(async tag => {
|
||||||
eomjiTags.map(async tag => {
|
const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
|
||||||
const name = tag.name.replace(/^:/, '').replace(/:$/, '');
|
|
||||||
|
|
||||||
const exists = await Emojis.findOne({
|
const exists = await Emojis.findOne({
|
||||||
host,
|
host,
|
||||||
name
|
name
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
if ((tag.updated != null && exists.updatedAt == null)
|
if ((tag.updated != null && exists.updatedAt == null)
|
||||||
|| (tag.id != null && exists.uri == null)
|
|| (tag.id != null && exists.uri == null)
|
||||||
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
|
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
|
||||||
) {
|
) {
|
||||||
await Emojis.update({
|
await Emojis.update({
|
||||||
host,
|
host,
|
||||||
name,
|
name,
|
||||||
}, {
|
}, {
|
||||||
uri: tag.id,
|
uri: tag.id,
|
||||||
url: tag.icon.url,
|
url: tag.icon!.url,
|
||||||
updatedAt: new Date(tag.updated),
|
updatedAt: new Date(tag.updated!),
|
||||||
});
|
});
|
||||||
|
|
||||||
return await Emojis.findOne({
|
return await Emojis.findOne({
|
||||||
host,
|
host,
|
||||||
name
|
name
|
||||||
});
|
}) as Emoji;
|
||||||
}
|
|
||||||
|
|
||||||
return exists;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`register emoji host=${host}, name=${name}`);
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
return await Emojis.save({
|
logger.info(`register emoji host=${host}, name=${name}`);
|
||||||
id: genId(),
|
|
||||||
host,
|
return await Emojis.save({
|
||||||
name,
|
id: genId(),
|
||||||
uri: tag.id,
|
host,
|
||||||
url: tag.icon.url,
|
name,
|
||||||
updatedAt: tag.updated ? new Date(tag.updated) : undefined,
|
uri: tag.id,
|
||||||
aliases: []
|
url: tag.icon!.url,
|
||||||
} as Emoji);
|
updatedAt: tag.updated ? new Date(tag.updated) : undefined,
|
||||||
})
|
aliases: []
|
||||||
);
|
} as Partial<Emoji>);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) {
|
async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) {
|
||||||
const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`];
|
const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`];
|
||||||
const uris = difference(unique(concat([to || [], cc || []])), ignoreUris);
|
const uris = difference(unique(concat([to || [], cc || []])), ignoreUris);
|
||||||
|
|
||||||
const limit = promiseLimit(2);
|
const limit = promiseLimit<User | null>(2);
|
||||||
const users = await Promise.all(
|
const users = await Promise.all(
|
||||||
uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise<User>)
|
uris.map(uri => limit(() => resolvePerson(uri, resolver).catch(() => null)) as Promise<User | null>)
|
||||||
);
|
);
|
||||||
|
|
||||||
return users.filter(x => x != null);
|
return users.filter(x => x != null) as User[];
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,9 @@ import { UserPublickey } from '../../../models/entities/user-publickey';
|
|||||||
import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error';
|
import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error';
|
||||||
import { toPuny } from '../../../misc/convert-host';
|
import { toPuny } from '../../../misc/convert-host';
|
||||||
import { UserProfile } from '../../../models/entities/user-profile';
|
import { UserProfile } from '../../../models/entities/user-profile';
|
||||||
|
import { validActor } from '../../../remote/activitypub/type';
|
||||||
|
import { getConnection } from 'typeorm';
|
||||||
|
import { ensure } from '../../../prelude/ensure';
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +41,7 @@ function validatePerson(x: any, uri: string) {
|
|||||||
return new Error('invalid person: object is null');
|
return new Error('invalid person: object is null');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x.type != 'Person' && x.type != 'Service') {
|
if (!validActor.includes(x.type)) {
|
||||||
return new Error(`invalid person: object is not a person or service '${x.type}'`);
|
return new Error(`invalid person: object is not a person or service '${x.type}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,13 +87,13 @@ function validatePerson(x: any, uri: string) {
|
|||||||
*
|
*
|
||||||
* Misskeyに対象のPersonが登録されていればそれを返します。
|
* Misskeyに対象のPersonが登録されていればそれを返します。
|
||||||
*/
|
*/
|
||||||
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User> {
|
export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
|
||||||
if (typeof uri !== 'string') throw 'uri is not string';
|
if (typeof uri !== 'string') throw 'uri is not string';
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならデータベースからフェッチ
|
// URIがこのサーバーを指しているならデータベースからフェッチ
|
||||||
if (uri.startsWith(config.url + '/')) {
|
if (uri.startsWith(config.url + '/')) {
|
||||||
const id = uri.split('/').pop();
|
const id = uri.split('/').pop();
|
||||||
return await Users.findOne(id);
|
return await Users.findOne(id).then(x => x || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region このサーバーに既に登録されていたらそれを返す
|
//#region このサーバーに既に登録されていたらそれを返す
|
||||||
@ -126,7 +129,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
|
|
||||||
const host = toPuny(new URL(object.id).hostname);
|
const host = toPuny(new URL(object.id).hostname);
|
||||||
|
|
||||||
const { fields } = analyzeAttachments(person.attachment);
|
const { fields } = analyzeAttachments(person.attachment || []);
|
||||||
|
|
||||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase());
|
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase());
|
||||||
|
|
||||||
@ -135,27 +138,42 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
// Create user
|
// Create user
|
||||||
let user: IRemoteUser;
|
let user: IRemoteUser;
|
||||||
try {
|
try {
|
||||||
user = await Users.save({
|
// Start transaction
|
||||||
id: genId(),
|
await getConnection().transaction(async transactionalEntityManager => {
|
||||||
avatarId: null,
|
user = await transactionalEntityManager.save(new User({
|
||||||
bannerId: null,
|
id: genId(),
|
||||||
createdAt: Date.parse(person.published) || new Date(),
|
avatarId: null,
|
||||||
lastFetchedAt: new Date(),
|
bannerId: null,
|
||||||
name: person.name,
|
createdAt: new Date(),
|
||||||
isLocked: person.manuallyApprovesFollowers,
|
lastFetchedAt: new Date(),
|
||||||
username: person.preferredUsername,
|
name: person.name,
|
||||||
usernameLower: person.preferredUsername.toLowerCase(),
|
isLocked: person.manuallyApprovesFollowers,
|
||||||
host,
|
username: person.preferredUsername,
|
||||||
inbox: person.inbox,
|
usernameLower: person.preferredUsername.toLowerCase(),
|
||||||
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
host,
|
||||||
featured: person.featured,
|
inbox: person.inbox,
|
||||||
endpoints: person.endpoints,
|
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
uri: person.id,
|
featured: person.featured,
|
||||||
url: person.url,
|
uri: person.id,
|
||||||
tags,
|
tags,
|
||||||
isBot,
|
isBot,
|
||||||
isCat: (person as any).isCat === true
|
isCat: (person as any).isCat === true
|
||||||
} as Partial<User>) as IRemoteUser;
|
})) as IRemoteUser;
|
||||||
|
|
||||||
|
await transactionalEntityManager.save(new UserProfile({
|
||||||
|
userId: user.id,
|
||||||
|
description: person.summary ? fromHtml(person.summary) : null,
|
||||||
|
url: person.url,
|
||||||
|
fields,
|
||||||
|
userHost: host
|
||||||
|
}));
|
||||||
|
|
||||||
|
await transactionalEntityManager.save(new UserPublickey({
|
||||||
|
userId: user.id,
|
||||||
|
keyId: person.publicKey.id,
|
||||||
|
keyPem: person.publicKey.publicKeyPem
|
||||||
|
}));
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// duplicate key error
|
// duplicate key error
|
||||||
if (isDuplicateKeyValueError(e)) {
|
if (isDuplicateKeyValueError(e)) {
|
||||||
@ -166,39 +184,26 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
await UserProfiles.save({
|
|
||||||
userId: user.id,
|
|
||||||
description: fromHtml(person.summary),
|
|
||||||
fields,
|
|
||||||
userHost: host
|
|
||||||
} as Partial<UserProfile>);
|
|
||||||
|
|
||||||
await UserPublickeys.save({
|
|
||||||
userId: user.id,
|
|
||||||
keyId: person.publicKey.id,
|
|
||||||
keyPem: person.publicKey.publicKeyPem
|
|
||||||
} as UserPublickey);
|
|
||||||
|
|
||||||
// Register host
|
// Register host
|
||||||
registerOrFetchInstanceDoc(host).then(i => {
|
registerOrFetchInstanceDoc(host).then(i => {
|
||||||
Instances.increment({ id: i.id }, 'usersCount', 1);
|
Instances.increment({ id: i.id }, 'usersCount', 1);
|
||||||
instanceChart.newUser(i.host);
|
instanceChart.newUser(i.host);
|
||||||
});
|
});
|
||||||
|
|
||||||
usersChart.update(user, true);
|
usersChart.update(user!, true);
|
||||||
|
|
||||||
// ハッシュタグ更新
|
// ハッシュタグ更新
|
||||||
for (const tag of tags) updateHashtag(user, tag, true, true);
|
for (const tag of tags) updateHashtag(user!, tag, true, true);
|
||||||
for (const tag of (user.tags || []).filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false);
|
for (const tag of (user!.tags || []).filter(x => !tags.includes(x))) updateHashtag(user!, tag, true, false);
|
||||||
|
|
||||||
//#region アイコンとヘッダー画像をフェッチ
|
//#region アイコンとヘッダー画像をフェッチ
|
||||||
const [avatar, banner] = (await Promise.all<DriveFile>([
|
const [avatar, banner] = (await Promise.all<DriveFile | null>([
|
||||||
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;
|
||||||
@ -206,9 +211,9 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar) : null;
|
const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar) : 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 && avatar.properties.avgColor ? banner.properties.avgColor : null;
|
const bannerColor = banner && banner.properties.avgColor ? banner.properties.avgColor : null;
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await Users.update(user!.id, {
|
||||||
avatarId,
|
avatarId,
|
||||||
bannerId,
|
bannerId,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
@ -217,30 +222,30 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
bannerColor
|
bannerColor
|
||||||
});
|
});
|
||||||
|
|
||||||
user.avatarId = avatarId;
|
user!.avatarId = avatarId;
|
||||||
user.bannerId = bannerId;
|
user!.bannerId = bannerId;
|
||||||
user.avatarUrl = avatarUrl;
|
user!.avatarUrl = avatarUrl;
|
||||||
user.bannerUrl = bannerUrl;
|
user!.bannerUrl = bannerUrl;
|
||||||
user.avatarColor = avatarColor;
|
user!.avatarColor = avatarColor;
|
||||||
user.bannerColor = bannerColor;
|
user!.bannerColor = bannerColor;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region カスタム絵文字取得
|
//#region カスタム絵文字取得
|
||||||
const emojis = await extractEmojis(person.tag, host).catch(e => {
|
const emojis = await extractEmojis(person.tag || [], host).catch(e => {
|
||||||
logger.info(`extractEmojis: ${e}`);
|
logger.info(`extractEmojis: ${e}`);
|
||||||
return [] as Emoji[];
|
return [] as Emoji[];
|
||||||
});
|
});
|
||||||
|
|
||||||
const emojiNames = emojis.map(emoji => emoji.name);
|
const emojiNames = emojis.map(emoji => emoji.name);
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await Users.update(user!.id, {
|
||||||
emojis: emojiNames
|
emojis: emojiNames
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
await updateFeatured(user.id).catch(err => logger.error(err));
|
await updateFeatured(user!.id).catch(err => logger.error(err));
|
||||||
|
|
||||||
return user;
|
return user!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -250,7 +255,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
* @param resolver Resolver
|
* @param resolver Resolver
|
||||||
* @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します)
|
* @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します)
|
||||||
*/
|
*/
|
||||||
export async function updatePerson(uri: string, resolver?: Resolver, hint?: object): Promise<void> {
|
export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: object): Promise<void> {
|
||||||
if (typeof uri !== 'string') throw 'uri is not string';
|
if (typeof uri !== 'string') throw 'uri is not string';
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならスキップ
|
// URIがこのサーバーを指しているならスキップ
|
||||||
@ -286,7 +291,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
logger.info(`Updating the Person: ${person.id}`);
|
logger.info(`Updating the Person: ${person.id}`);
|
||||||
|
|
||||||
// アイコンとヘッダー画像をフェッチ
|
// アイコンとヘッダー画像をフェッチ
|
||||||
const [avatar, banner] = (await Promise.all<DriveFile>([
|
const [avatar, banner] = (await Promise.all<DriveFile | null>([
|
||||||
person.icon,
|
person.icon,
|
||||||
person.image
|
person.image
|
||||||
].map(img =>
|
].map(img =>
|
||||||
@ -296,14 +301,14 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
)));
|
)));
|
||||||
|
|
||||||
// カスタム絵文字取得
|
// カスタム絵文字取得
|
||||||
const emojis = await extractEmojis(person.tag, exist.host).catch(e => {
|
const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => {
|
||||||
logger.info(`extractEmojis: ${e}`);
|
logger.info(`extractEmojis: ${e}`);
|
||||||
return [] as Emoji[];
|
return [] as Emoji[];
|
||||||
});
|
});
|
||||||
|
|
||||||
const emojiNames = emojis.map(emoji => emoji.name);
|
const emojiNames = emojis.map(emoji => emoji.name);
|
||||||
|
|
||||||
const { fields, services } = analyzeAttachments(person.attachment);
|
const { fields, services } = analyzeAttachments(person.attachment || []);
|
||||||
|
|
||||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase());
|
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase());
|
||||||
|
|
||||||
@ -313,7 +318,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
featured: person.featured,
|
featured: person.featured,
|
||||||
emojis: emojiNames,
|
emojis: emojiNames,
|
||||||
description: fromHtml(person.summary),
|
description: person.summary ? fromHtml(person.summary) : null,
|
||||||
name: person.name,
|
name: person.name,
|
||||||
url: person.url,
|
url: person.url,
|
||||||
endpoints: person.endpoints,
|
endpoints: person.endpoints,
|
||||||
@ -322,7 +327,6 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
isBot: object.type == 'Service',
|
isBot: object.type == 'Service',
|
||||||
isCat: (person as any).isCat === true,
|
isCat: (person as any).isCat === true,
|
||||||
isLocked: person.manuallyApprovesFollowers,
|
isLocked: person.manuallyApprovesFollowers,
|
||||||
createdAt: new Date(Date.parse(person.published)) || null,
|
|
||||||
} as Partial<User>;
|
} as Partial<User>;
|
||||||
|
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
@ -375,7 +379,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
|
* Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
|
||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||||
*/
|
*/
|
||||||
export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise<User> {
|
export async function resolvePerson(uri: string, resolver?: Resolver): Promise<User> {
|
||||||
if (typeof uri !== 'string') throw 'uri is not string';
|
if (typeof uri !== 'string') throw 'uri is not string';
|
||||||
|
|
||||||
//#region このサーバーに既に登録されていたらそれを返す
|
//#region このサーバーに既に登録されていたらそれを返す
|
||||||
@ -435,21 +439,24 @@ export function analyzeAttachments(attachments: ITag[]) {
|
|||||||
}[] = [];
|
}[] = [];
|
||||||
const services: { [x: string]: any } = {};
|
const services: { [x: string]: any } = {};
|
||||||
|
|
||||||
if (Array.isArray(attachments))
|
if (Array.isArray(attachments)) {
|
||||||
for (const attachment of attachments.filter(isPropertyValue))
|
for (const attachment of attachments.filter(isPropertyValue)) {
|
||||||
if (isPropertyValue(attachment.identifier))
|
if (isPropertyValue(attachment.identifier!)) {
|
||||||
addService(services, attachment.identifier);
|
addService(services, attachment.identifier!);
|
||||||
else
|
} else {
|
||||||
fields.push({
|
fields.push({
|
||||||
name: attachment.name,
|
name: attachment.name!,
|
||||||
value: fromHtml(attachment.value)
|
value: fromHtml(attachment.value!)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { fields, services };
|
return { fields, services };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateFeatured(userId: User['id']) {
|
export async function updateFeatured(userId: User['id']) {
|
||||||
const user = await Users.findOne(userId);
|
const user = await Users.findOne(userId).then(ensure);
|
||||||
if (!Users.isRemoteUser(user)) return;
|
if (!Users.isRemoteUser(user)) return;
|
||||||
if (!user.featured) return;
|
if (!user.featured) return;
|
||||||
|
|
||||||
@ -467,18 +474,18 @@ export async function updateFeatured(userId: User['id']) {
|
|||||||
if (!Array.isArray(items)) throw new Error(`Collection items is not an array`);
|
if (!Array.isArray(items)) throw new Error(`Collection items is not an array`);
|
||||||
|
|
||||||
// Resolve and regist Notes
|
// Resolve and regist Notes
|
||||||
const limit = promiseLimit(2);
|
const limit = promiseLimit<Note | null>(2);
|
||||||
const featuredNotes = await Promise.all(items
|
const featuredNotes = await Promise.all(items
|
||||||
.filter(item => item.type === 'Note')
|
.filter(item => item.type === 'Note')
|
||||||
.slice(0, 5)
|
.slice(0, 5)
|
||||||
.map(item => limit(() => resolveNote(item, resolver)) as Promise<Note>));
|
.map(item => limit(() => resolveNote(item, resolver))));
|
||||||
|
|
||||||
for (const note of featuredNotes.filter(note => note != null)) {
|
for (const note of featuredNotes.filter(note => note != null)) {
|
||||||
UserNotePinings.save({
|
UserNotePinings.save({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
noteId: note.id
|
noteId: note!.id
|
||||||
} as UserNotePining);
|
} as UserNotePining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,10 @@ export async function extractPollFromQuestion(source: string | IQuestion): Promi
|
|||||||
throw 'invalid question';
|
throw 'invalid question';
|
||||||
}
|
}
|
||||||
|
|
||||||
const choices = question[multiple ? 'anyOf' : 'oneOf']
|
const choices = question[multiple ? 'anyOf' : 'oneOf']!
|
||||||
.map((x, i) => x.name);
|
.map((x, i) => x.name!);
|
||||||
|
|
||||||
const votes = question[multiple ? 'anyOf' : 'oneOf']
|
const votes = question[multiple ? 'anyOf' : 'oneOf']!
|
||||||
.map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0);
|
.map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -60,7 +60,7 @@ export async function updateQuestion(value: any) {
|
|||||||
|
|
||||||
for (const choice of poll.choices) {
|
for (const choice of poll.choices) {
|
||||||
const oldCount = poll.votes[poll.choices.indexOf(choice)];
|
const oldCount = poll.votes[poll.choices.indexOf(choice)];
|
||||||
const newCount = apChoices.filter(ap => ap.name === choice)[0].replies.totalItems;
|
const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems;
|
||||||
|
|
||||||
if (oldCount != newCount) {
|
if (oldCount != newCount) {
|
||||||
changed = true;
|
changed = true;
|
||||||
@ -68,10 +68,6 @@ export async function updateQuestion(value: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Notes.update(note.id, {
|
|
||||||
updatedAt: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await Polls.update({ noteId: note.id }, {
|
await Polls.update({ noteId: note.id }, {
|
||||||
votes: poll.votes
|
votes: poll.votes
|
||||||
});
|
});
|
||||||
|
@ -14,13 +14,13 @@ export type ITag = {
|
|||||||
identifier?: IIdentifier;
|
identifier?: IIdentifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function extractHashtags(tags: ITag[]) {
|
export function extractHashtags(tags: ITag[] | null | undefined): string[] {
|
||||||
if (!tags) return [];
|
if (tags == null) return [];
|
||||||
|
|
||||||
const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string');
|
const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string');
|
||||||
|
|
||||||
return hashtags.map(tag => {
|
return hashtags.map(tag => {
|
||||||
const m = tag.name.match(/^#(.+)/);
|
const m = tag.name ? tag.name.match(/^#(.+)/) : null;
|
||||||
return m ? m[1] : null;
|
return m ? m[1] : null;
|
||||||
}).filter(x => x != null);
|
}).filter(x => x != null) as string[];
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user