Merge branch 'develop'
This commit is contained in:
		
						commit
						7d70126072
					
				
					 24 changed files with 602 additions and 269 deletions
				
			
		| 
						 | 
					@ -6,8 +6,6 @@ mongodb:
 | 
				
			||||||
  db: misskey
 | 
					  db: misskey
 | 
				
			||||||
  user: syuilo
 | 
					  user: syuilo
 | 
				
			||||||
  pass: ''
 | 
					  pass: ''
 | 
				
			||||||
drive:
 | 
					 | 
				
			||||||
  storage: 'db'
 | 
					 | 
				
			||||||
redis:
 | 
					redis:
 | 
				
			||||||
  host: localhost
 | 
					  host: localhost
 | 
				
			||||||
  port: 6379
 | 
					  port: 6379
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,6 @@ mongodb:
 | 
				
			||||||
  db: test-misskey
 | 
					  db: test-misskey
 | 
				
			||||||
  user: admin
 | 
					  user: admin
 | 
				
			||||||
  pass: ''
 | 
					  pass: ''
 | 
				
			||||||
drive:
 | 
					 | 
				
			||||||
  storage: 'db'
 | 
					 | 
				
			||||||
# __REDIS__
 | 
					# __REDIS__
 | 
				
			||||||
redis:
 | 
					redis:
 | 
				
			||||||
  host: localhost
 | 
					  host: localhost
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,61 +78,6 @@ redis:
 | 
				
			||||||
#  port: 9200
 | 
					#  port: 9200
 | 
				
			||||||
#  pass: null
 | 
					#  pass: null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#   ┌────────────────────────────────────┐
 | 
					 | 
				
			||||||
#───┘ File storage (Drive) configuration └──────────────────────
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
drive:
 | 
					 | 
				
			||||||
  storage: 'fs'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# OR
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#drive:
 | 
					 | 
				
			||||||
#  storage: 'minio'
 | 
					 | 
				
			||||||
#  bucket:
 | 
					 | 
				
			||||||
#  prefix:
 | 
					 | 
				
			||||||
#  config:
 | 
					 | 
				
			||||||
#    endPoint:
 | 
					 | 
				
			||||||
#    port:
 | 
					 | 
				
			||||||
#    useSSL:
 | 
					 | 
				
			||||||
#    accessKey:
 | 
					 | 
				
			||||||
#    secretKey:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# S3/GCS example
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# * Replace <endpoint> to
 | 
					 | 
				
			||||||
#     S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
 | 
					 | 
				
			||||||
#     GCS: use 'storage.googleapis.com'
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# * Replace <region> to
 | 
					 | 
				
			||||||
#     S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
 | 
					 | 
				
			||||||
#     GCS: not needed (just delete the region line)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#drive:
 | 
					 | 
				
			||||||
#  storage: 'minio'
 | 
					 | 
				
			||||||
#  bucket: bucket-name
 | 
					 | 
				
			||||||
#  prefix: files
 | 
					 | 
				
			||||||
#  baseUrl: https://bucket-name.<endpoint>
 | 
					 | 
				
			||||||
#  config:
 | 
					 | 
				
			||||||
#    endPoint: <endpoint>
 | 
					 | 
				
			||||||
#    region: <region>
 | 
					 | 
				
			||||||
#    useSSL: true
 | 
					 | 
				
			||||||
#    accessKey: XXX
 | 
					 | 
				
			||||||
#    secretKey: YYY
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# S3/GCS example (with CDN, custom domain)
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#drive:
 | 
					 | 
				
			||||||
#  storage: 'minio'
 | 
					 | 
				
			||||||
#  bucket: drive.example.com
 | 
					 | 
				
			||||||
#  prefix: files
 | 
					 | 
				
			||||||
#  baseUrl: https://drive.example.com
 | 
					 | 
				
			||||||
#  config:
 | 
					 | 
				
			||||||
#    endPoint: <endpoint>
 | 
					 | 
				
			||||||
#    region: <region>
 | 
					 | 
				
			||||||
#    useSSL: true
 | 
					 | 
				
			||||||
#    accessKey: XXX
 | 
					 | 
				
			||||||
#    secretKey: YYY
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#   ┌───────────────┐
 | 
					#   ┌───────────────┐
 | 
				
			||||||
#───┘ ID generation └───────────────────────────────────────────
 | 
					#───┘ ID generation └───────────────────────────────────────────
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										40
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										40
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
					@ -8,32 +8,13 @@ If you encounter any problems with updating, please try the following:
 | 
				
			||||||
Migration
 | 
					Migration
 | 
				
			||||||
------------------------------
 | 
					------------------------------
 | 
				
			||||||
#### 1
 | 
					#### 1
 | 
				
			||||||
`ormconfig.json`という名前で、Misskeyのインストール場所(package.jsonとかがあるディレクトリ)に新たなファイルを作る。中身は次のようにします:
 | 
					 | 
				
			||||||
``` json
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	"type": "postgres",
 | 
					 | 
				
			||||||
	"host": "PostgreSQLのホスト",
 | 
					 | 
				
			||||||
	"port": 5432,
 | 
					 | 
				
			||||||
	"username": "PostgreSQLのユーザー名",
 | 
					 | 
				
			||||||
	"password": "PostgreSQLのパスワード",
 | 
					 | 
				
			||||||
	"database": "PostgreSQLのデータベース名",
 | 
					 | 
				
			||||||
	"entities": ["src/models/entities/*.ts"],
 | 
					 | 
				
			||||||
	"migrations": ["migration/*.ts"],
 | 
					 | 
				
			||||||
	"cli": {
 | 
					 | 
				
			||||||
		"migrationsDir": "migration"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
上記の各種PostgreSQLの設定(ポートも)は、設定ファイルに書いてあるものをコピーしてください。
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### 2
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
npm i -g ts-node
 | 
					npm i -g ts-node
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### 3
 | 
					#### 2
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
ts-node ./node_modules/typeorm/cli.js migration:run
 | 
					npm run migrate
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
How to migrate to v11 from v10
 | 
					How to migrate to v11 from v10
 | 
				
			||||||
| 
						 | 
					@ -73,6 +54,20 @@ mongodb:
 | 
				
			||||||
8. master ブランチに戻す
 | 
					8. master ブランチに戻す
 | 
				
			||||||
9. enjoy
 | 
					9. enjoy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					11.14.0 (2019/05/16)
 | 
				
			||||||
 | 
					--------------------
 | 
				
			||||||
 | 
					### 注意
 | 
				
			||||||
 | 
					このバージョンからオブジェクトストレージの設定は設定ファイルではなく管理画面から行うようになりました。
 | 
				
			||||||
 | 
					オブジェクトストレージを使用している場合、アップデートした後管理画面にアクセスしオブジェクトストレージの設定を再度行ってください。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### ✨Improvements
 | 
				
			||||||
 | 
					* 特定のユーザーのファイルをすべて削除できるように
 | 
				
			||||||
 | 
					* インスタンスの設定画面を整理
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🐛Fixes
 | 
				
			||||||
 | 
					* GIF画像のサムネイルが生成されないのを修正
 | 
				
			||||||
 | 
					* 管理画面の「ログ」で複数の除外条件を設定できない問題を修正
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.13.0 (2019/05/14)
 | 
					11.13.0 (2019/05/14)
 | 
				
			||||||
--------------------
 | 
					--------------------
 | 
				
			||||||
### 注意
 | 
					### 注意
 | 
				
			||||||
| 
						 | 
					@ -85,12 +80,13 @@ mongodb:
 | 
				
			||||||
* ユーザーや外部インスタンスが生成するリンクにnofollowを追加
 | 
					* ユーザーや外部インスタンスが生成するリンクにnofollowを追加
 | 
				
			||||||
* リモートのユーザーページやノートページにnoindexを追加
 | 
					* リモートのユーザーページやノートページにnoindexを追加
 | 
				
			||||||
* 自分のユーザーメニューにはミュートなどを表示しないように
 | 
					* 自分のユーザーメニューにはミュートなどを表示しないように
 | 
				
			||||||
 | 
					* デザインの調整
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 🐛Fixes
 | 
					### 🐛Fixes
 | 
				
			||||||
* インスタンスブロックを設定できない問題を修正
 | 
					* インスタンスブロックを設定できない問題を修正
 | 
				
			||||||
* ピン留め投稿の表示順がおかしい問題を修正
 | 
					* ピン留め投稿の表示順がおかしい問題を修正
 | 
				
			||||||
* 設定の「アップデートを確認」でメッセージが正しく表示されない問題を修正
 | 
					* 設定の「アップデートを確認」でメッセージが正しく表示されない問題を修正
 | 
				
			||||||
* FFirefoxで自分のメニューが開けない問題を修正
 | 
					* Firefoxで自分のメニューが開けない問題を修正
 | 
				
			||||||
* Welcomeページのタグクラウドが動かない問題を修正
 | 
					* Welcomeページのタグクラウドが動かない問題を修正
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.12.0 (2019/05/10)
 | 
					11.12.0 (2019/05/10)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -199,7 +199,7 @@ const user = await Users.findOne(userId).then(ensure);
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Migration作成方法
 | 
					### Migration作成方法
 | 
				
			||||||
コードの変更をした後、`ormconfig.json`(書き方はCONTRIBUTING.mdを参照)を用意し、
 | 
					コードの変更をした後、`ormconfig.json`(`npm run ormconfig`で生成)を用意し、
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
npm i -g ts-node
 | 
					npm i -g ts-node
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1187,7 +1187,6 @@ admin/views/index.vue:
 | 
				
			||||||
  users: "ユーザー"
 | 
					  users: "ユーザー"
 | 
				
			||||||
  federation: "連合"
 | 
					  federation: "連合"
 | 
				
			||||||
  announcements: "お知らせ"
 | 
					  announcements: "お知らせ"
 | 
				
			||||||
  hashtags: "ハッシュタグ"
 | 
					 | 
				
			||||||
  abuse: "スパム報告"
 | 
					  abuse: "スパム報告"
 | 
				
			||||||
  queue: "ジョブキュー"
 | 
					  queue: "ジョブキュー"
 | 
				
			||||||
  logs: "ログ"
 | 
					  logs: "ログ"
 | 
				
			||||||
| 
						 | 
					@ -1230,7 +1229,22 @@ admin/views/instance.vue:
 | 
				
			||||||
  maintainer-config: "管理者情報"
 | 
					  maintainer-config: "管理者情報"
 | 
				
			||||||
  maintainer-name: "管理者名"
 | 
					  maintainer-name: "管理者名"
 | 
				
			||||||
  maintainer-email: "管理者の連絡先"
 | 
					  maintainer-email: "管理者の連絡先"
 | 
				
			||||||
 | 
					  advanced-config: "その他の設定"
 | 
				
			||||||
 | 
					  note-and-tl: "投稿とタイムライン"
 | 
				
			||||||
  drive-config: "ドライブの設定"
 | 
					  drive-config: "ドライブの設定"
 | 
				
			||||||
 | 
					  use-object-storage: "オブジェクトストレージを使用する"
 | 
				
			||||||
 | 
					  object-storage-base-url: "URL"
 | 
				
			||||||
 | 
					  object-storage-bucket: "バケット名"
 | 
				
			||||||
 | 
					  object-storage-prefix: "プレフィックス"
 | 
				
			||||||
 | 
					  object-storage-endpoint: "エンドポイント"
 | 
				
			||||||
 | 
					  object-storage-region: "リージョン"
 | 
				
			||||||
 | 
					  object-storage-port: "ポート"
 | 
				
			||||||
 | 
					  object-storage-access-key: "アクセスキー"
 | 
				
			||||||
 | 
					  object-storage-secret-key: "シークレットキー"
 | 
				
			||||||
 | 
					  object-storage-use-ssl: "SSLを使用"
 | 
				
			||||||
 | 
					  object-storage-s3-info: "Amazon S3をオブジェクトストレージとして使用する場合の「エンドポイント」と「リージョン」の設定については{0}をご確認ください。"
 | 
				
			||||||
 | 
					  object-storage-s3-info-here: "こちら"
 | 
				
			||||||
 | 
					  object-storage-gcs-info: "Google Cloud Storageをオブジェクトストレージとして使用する場合、「エンドポイント」は storage.googleapis.com に設定し、「リージョン」は空欄にします。"
 | 
				
			||||||
  cache-remote-files: "リモートのファイルをキャッシュする"
 | 
					  cache-remote-files: "リモートのファイルをキャッシュする"
 | 
				
			||||||
  cache-remote-files-desc: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。そのためサーバーのストレージを節約できますが、プライバシー設定で直リンクを無効にしているユーザーにはファイルが見えなくなったり、サムネイルが生成されないので通信量が増加します。通常はこの設定をオンにしておくことをおすすめします。"
 | 
					  cache-remote-files-desc: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。そのためサーバーのストレージを節約できますが、プライバシー設定で直リンクを無効にしているユーザーにはファイルが見えなくなったり、サムネイルが生成されないので通信量が増加します。通常はこの設定をオンにしておくことをおすすめします。"
 | 
				
			||||||
  local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量"
 | 
					  local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量"
 | 
				
			||||||
| 
						 | 
					@ -1241,6 +1255,9 @@ admin/views/instance.vue:
 | 
				
			||||||
  enable-recaptcha: "reCAPTCHAを有効にする"
 | 
					  enable-recaptcha: "reCAPTCHAを有効にする"
 | 
				
			||||||
  recaptcha-site-key: "reCAPTCHA site key"
 | 
					  recaptcha-site-key: "reCAPTCHA site key"
 | 
				
			||||||
  recaptcha-secret-key: "reCAPTCHA secret key"
 | 
					  recaptcha-secret-key: "reCAPTCHA secret key"
 | 
				
			||||||
 | 
					  hidden-tags: "非表示ハッシュタグ"
 | 
				
			||||||
 | 
					  hidden-tags-info: "集計から除外するハッシュタグを改行で区切って記述します。"
 | 
				
			||||||
 | 
					  external-service-integration-config: "外部サービス連携"
 | 
				
			||||||
  twitter-integration-config: "Twitter連携の設定"
 | 
					  twitter-integration-config: "Twitter連携の設定"
 | 
				
			||||||
  twitter-integration-info: "コールバックURLは {url} に設定します。"
 | 
					  twitter-integration-info: "コールバックURLは {url} に設定します。"
 | 
				
			||||||
  enable-twitter-integration: "Twitter連携を有効にする"
 | 
					  enable-twitter-integration: "Twitter連携を有効にする"
 | 
				
			||||||
| 
						 | 
					@ -1361,6 +1378,8 @@ admin/views/users.vue:
 | 
				
			||||||
  unsilence-confirm: "サイレンスを解除しますか?"
 | 
					  unsilence-confirm: "サイレンスを解除しますか?"
 | 
				
			||||||
  update-remote-user: "リモートユーザー情報の更新"
 | 
					  update-remote-user: "リモートユーザー情報の更新"
 | 
				
			||||||
  remote-user-updated: "リモートユーザー情報を更新しました"
 | 
					  remote-user-updated: "リモートユーザー情報を更新しました"
 | 
				
			||||||
 | 
					  delete-all-files: "すべてのファイルを削除"
 | 
				
			||||||
 | 
					  delete-all-files-confirm: "すべてのファイルを削除しますか?"
 | 
				
			||||||
  users:
 | 
					  users:
 | 
				
			||||||
    title: "ユーザー"
 | 
					    title: "ユーザー"
 | 
				
			||||||
    sort:
 | 
					    sort:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										31
									
								
								migration/1557932705754-ObjectStorageSetting.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								migration/1557932705754-ObjectStorageSetting.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					import {MigrationInterface, QueryRunner} from "typeorm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ObjectStorageSetting1557932705754 implements MigrationInterface {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async up(queryRunner: QueryRunner): Promise<any> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "useObjectStorage" boolean NOT NULL DEFAULT false`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageBucket" character varying(512)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStoragePrefix" character varying(512)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageBaseUrl" character varying(512)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageEndpoint" character varying(512)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageRegion" character varying(512)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageAccessKey" character varying(512)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageSecretKey" character varying(512)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStoragePort" integer`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" ADD "objectStorageUseSSL" boolean NOT NULL DEFAULT true`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async down(queryRunner: QueryRunner): Promise<any> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageUseSSL"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStoragePort"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageSecretKey"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageAccessKey"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageRegion"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageEndpoint"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageBaseUrl"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStoragePrefix"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "objectStorageBucket"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "useObjectStorage"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"author": "syuilo <i@syuilo.com>",
 | 
						"author": "syuilo <i@syuilo.com>",
 | 
				
			||||||
	"version": "11.13.0",
 | 
						"version": "11.14.0",
 | 
				
			||||||
	"codename": "daybreak",
 | 
						"codename": "daybreak",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,8 @@
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
		"start": "node ./index.js",
 | 
							"start": "node ./index.js",
 | 
				
			||||||
		"init": "node ./built/init.js",
 | 
							"init": "node ./built/init.js",
 | 
				
			||||||
 | 
							"ormconfig": "node ./built/ormconfig.js",
 | 
				
			||||||
 | 
							"migrate": "npm run ormconfig && ts-node ./node_modules/typeorm/cli.js migration:run",
 | 
				
			||||||
		"build": "webpack && gulp build",
 | 
							"build": "webpack && gulp build",
 | 
				
			||||||
		"webpack": "webpack",
 | 
							"webpack": "webpack",
 | 
				
			||||||
		"watch": "webpack --watch",
 | 
							"watch": "webpack --watch",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,41 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<div>
 | 
					 | 
				
			||||||
	<ui-card>
 | 
					 | 
				
			||||||
		<template #title>{{ $t('hided-tags') }}</template>
 | 
					 | 
				
			||||||
		<section>
 | 
					 | 
				
			||||||
			<textarea class="jdnqwkzlnxcfftthoybjxrebyolvoucw" v-model="hiddenTags"></textarea>
 | 
					 | 
				
			||||||
			<ui-button @click="save">{{ $t('save') }}</ui-button>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
	</ui-card>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../i18n';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n('admin/views/hashtags.vue'),
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			hiddenTags: '',
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.$root.getMeta().then(meta => {
 | 
					 | 
				
			||||||
			this.hiddenTags = meta.hiddenTags.join('\n');
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		save() {
 | 
					 | 
				
			||||||
			this.$root.api('admin/update-meta', {
 | 
					 | 
				
			||||||
				hiddenTags: this.hiddenTags.split('\n')
 | 
					 | 
				
			||||||
			}).then(() => {
 | 
					 | 
				
			||||||
				//this.$root.os.apis.dialog({ text: `Saved` });
 | 
					 | 
				
			||||||
			}).catch(e => {
 | 
					 | 
				
			||||||
				//this.$root.os.apis.dialog({ text: `Failed ${e}` });
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,6 @@
 | 
				
			||||||
			<li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faGlobe" fixed-width/>{{ $t('federation') }}</li>
 | 
								<li @click="nav('federation')" :class="{ active: page == 'federation' }"><fa :icon="faGlobe" fixed-width/>{{ $t('federation') }}</li>
 | 
				
			||||||
			<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li>
 | 
								<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="faGrin" fixed-width/>{{ $t('emoji') }}</li>
 | 
				
			||||||
			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li>
 | 
								<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>{{ $t('announcements') }}</li>
 | 
				
			||||||
			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>{{ $t('hashtags') }}</li>
 | 
					 | 
				
			||||||
			<li @click="nav('abuse')" :class="{ active: page == 'abuse' }"><fa :icon="faExclamationCircle" fixed-width/>{{ $t('abuse') }}</li>
 | 
								<li @click="nav('abuse')" :class="{ active: page == 'abuse' }"><fa :icon="faExclamationCircle" fixed-width/>{{ $t('abuse') }}</li>
 | 
				
			||||||
		</ul>
 | 
							</ul>
 | 
				
			||||||
		<div class="back-to-misskey">
 | 
							<div class="back-to-misskey">
 | 
				
			||||||
| 
						 | 
					@ -48,7 +47,6 @@
 | 
				
			||||||
			<div v-if="page == 'users'"><x-users/></div>
 | 
								<div v-if="page == 'users'"><x-users/></div>
 | 
				
			||||||
			<div v-if="page == 'emoji'"><x-emoji/></div>
 | 
								<div v-if="page == 'emoji'"><x-emoji/></div>
 | 
				
			||||||
			<div v-if="page == 'announcements'"><x-announcements/></div>
 | 
								<div v-if="page == 'announcements'"><x-announcements/></div>
 | 
				
			||||||
			<div v-if="page == 'hashtags'"><x-hashtags/></div>
 | 
					 | 
				
			||||||
			<div v-if="page == 'drive'"><x-drive/></div>
 | 
								<div v-if="page == 'drive'"><x-drive/></div>
 | 
				
			||||||
			<div v-if="page == 'federation'"><x-federation/></div>
 | 
								<div v-if="page == 'federation'"><x-federation/></div>
 | 
				
			||||||
			<div v-if="page == 'abuse'"><x-abuse/></div>
 | 
								<div v-if="page == 'abuse'"><x-abuse/></div>
 | 
				
			||||||
| 
						 | 
					@ -68,7 +66,6 @@ import XLogs from "./logs.vue";
 | 
				
			||||||
import XModerators from "./moderators.vue";
 | 
					import XModerators from "./moderators.vue";
 | 
				
			||||||
import XEmoji from "./emoji.vue";
 | 
					import XEmoji from "./emoji.vue";
 | 
				
			||||||
import XAnnouncements from "./announcements.vue";
 | 
					import XAnnouncements from "./announcements.vue";
 | 
				
			||||||
import XHashtags from "./hashtags.vue";
 | 
					 | 
				
			||||||
import XUsers from "./users.vue";
 | 
					import XUsers from "./users.vue";
 | 
				
			||||||
import XDrive from "./drive.vue";
 | 
					import XDrive from "./drive.vue";
 | 
				
			||||||
import XAbuse from "./abuse.vue";
 | 
					import XAbuse from "./abuse.vue";
 | 
				
			||||||
| 
						 | 
					@ -91,7 +88,6 @@ export default Vue.extend({
 | 
				
			||||||
		XModerators,
 | 
							XModerators,
 | 
				
			||||||
		XEmoji,
 | 
							XEmoji,
 | 
				
			||||||
		XAnnouncements,
 | 
							XAnnouncements,
 | 
				
			||||||
		XHashtags,
 | 
					 | 
				
			||||||
		XUsers,
 | 
							XUsers,
 | 
				
			||||||
		XDrive,
 | 
							XDrive,
 | 
				
			||||||
		XAbuse,
 | 
							XAbuse,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<ui-card>
 | 
						<ui-card>
 | 
				
			||||||
		<template #title><fa icon="cog"/> {{ $t('instance') }}</template>
 | 
							<template #title><fa icon="cog"/> {{ $t('instance') }}</template>
 | 
				
			||||||
		<section class="fit-top fit-bottom">
 | 
							<section class="fit-top">
 | 
				
			||||||
			<ui-input :value="host" readonly>{{ $t('host') }}</ui-input>
 | 
								<ui-input :value="host" readonly>{{ $t('host') }}</ui-input>
 | 
				
			||||||
			<ui-input v-model="name">{{ $t('instance-name') }}</ui-input>
 | 
								<ui-input v-model="name">{{ $t('instance-name') }}</ui-input>
 | 
				
			||||||
			<ui-textarea v-model="description">{{ $t('instance-description') }}</ui-textarea>
 | 
								<ui-textarea v-model="description">{{ $t('instance-description') }}</ui-textarea>
 | 
				
			||||||
| 
						 | 
					@ -11,77 +11,83 @@
 | 
				
			||||||
			<ui-input v-model="bannerUrl"><template #icon><fa icon="link"/></template>{{ $t('banner-url') }}</ui-input>
 | 
								<ui-input v-model="bannerUrl"><template #icon><fa icon="link"/></template>{{ $t('banner-url') }}</ui-input>
 | 
				
			||||||
			<ui-input v-model="errorImageUrl"><template #icon><fa icon="link"/></template>{{ $t('error-image-url') }}</ui-input>
 | 
								<ui-input v-model="errorImageUrl"><template #icon><fa icon="link"/></template>{{ $t('error-image-url') }}</ui-input>
 | 
				
			||||||
			<ui-input v-model="ToSUrl"><template #icon><fa icon="link"/></template>{{ $t('tos-url') }}</ui-input>
 | 
								<ui-input v-model="ToSUrl"><template #icon><fa icon="link"/></template>{{ $t('tos-url') }}</ui-input>
 | 
				
			||||||
			<ui-input v-model="repositoryUrl"><template #icon><fa icon="link"/></template>{{ $t('repository-url') }}</ui-input>
 | 
					 | 
				
			||||||
			<ui-input v-model="feedbackUrl"><template #icon><fa icon="link"/></template>{{ $t('feedback-url') }}</ui-input>
 | 
					 | 
				
			||||||
			<ui-input v-model="languages"><template #icon><fa icon="language"/></template>{{ $t('languages') }}<template #desc>{{ $t('languages-desc') }}</template></ui-input>
 | 
								<ui-input v-model="languages"><template #icon><fa icon="language"/></template>{{ $t('languages') }}<template #desc>{{ $t('languages-desc') }}</template></ui-input>
 | 
				
			||||||
 | 
								<details>
 | 
				
			||||||
 | 
									<summary>{{ $t('advanced-config') }}</summary>
 | 
				
			||||||
 | 
									<ui-input v-model="repositoryUrl"><template #icon><fa icon="link"/></template>{{ $t('repository-url') }}</ui-input>
 | 
				
			||||||
 | 
									<ui-input v-model="feedbackUrl"><template #icon><fa icon="link"/></template>{{ $t('feedback-url') }}</ui-input>
 | 
				
			||||||
 | 
								</details>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
		<section class="fit-bottom">
 | 
							<section class="fit-bottom">
 | 
				
			||||||
			<header><fa :icon="faHeadset"/> {{ $t('maintainer-config') }}</header>
 | 
								<header><fa :icon="faHeadset"/> {{ $t('maintainer-config') }}</header>
 | 
				
			||||||
			<ui-input v-model="maintainerName">{{ $t('maintainer-name') }}</ui-input>
 | 
								<ui-input v-model="maintainerName">{{ $t('maintainer-name') }}</ui-input>
 | 
				
			||||||
			<ui-input v-model="maintainerEmail" type="email"><template #icon><fa :icon="farEnvelope"/></template>{{ $t('maintainer-email') }}</ui-input>
 | 
								<ui-input v-model="maintainerEmail" type="email"><template #icon><fa :icon="farEnvelope"/></template>{{ $t('maintainer-email') }}</ui-input>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-switch v-model="disableRegistration">{{ $t('disable-registration') }}</ui-switch>
 | 
				
			||||||
 | 
								<ui-button v-if="disableRegistration" @click="invite">{{ $t('invite') }}</ui-button>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<ui-card>
 | 
				
			||||||
 | 
							<template #title><fa :icon="faPencilAlt"/> {{ $t('note-and-tl') }}</template>
 | 
				
			||||||
		<section class="fit-top fit-bottom">
 | 
							<section class="fit-top fit-bottom">
 | 
				
			||||||
			<ui-input v-model="maxNoteTextLength">{{ $t('max-note-text-length') }}</ui-input>
 | 
								<ui-input v-model="maxNoteTextLength">{{ $t('max-note-text-length') }}</ui-input>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
		<section>
 | 
							<section>
 | 
				
			||||||
			<ui-switch v-model="disableRegistration">{{ $t('disable-registration') }}</ui-switch>
 | 
					 | 
				
			||||||
			<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
 | 
								<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
 | 
				
			||||||
			<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch>
 | 
								<ui-switch v-model="disableGlobalTimeline">{{ $t('disable-global-timeline') }}</ui-switch>
 | 
				
			||||||
			<ui-info>{{ $t('disabling-timelines-info') }}</ui-info>
 | 
								<ui-info>{{ $t('disabling-timelines-info') }}</ui-info>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
			<ui-switch v-model="enableEmojiReaction">{{ $t('enable-emoji-reaction') }}</ui-switch>
 | 
								<ui-switch v-model="enableEmojiReaction">{{ $t('enable-emoji-reaction') }}</ui-switch>
 | 
				
			||||||
			<ui-switch v-model="useStarForReactionFallback">{{ $t('use-star-for-reaction-fallback') }}</ui-switch>
 | 
								<ui-switch v-model="useStarForReactionFallback">{{ $t('use-star-for-reaction-fallback') }}</ui-switch>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
		<section class="fit-bottom">
 | 
							<section>
 | 
				
			||||||
			<header><fa icon="cloud"/> {{ $t('drive-config') }}</header>
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<ui-card>
 | 
				
			||||||
 | 
							<template #title><fa icon="cloud"/> {{ $t('drive-config') }}</template>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-switch v-model="useObjectStorage">{{ $t('use-object-storage') }}</ui-switch>
 | 
				
			||||||
 | 
								<template v-if="useObjectStorage">
 | 
				
			||||||
 | 
									<ui-info>
 | 
				
			||||||
 | 
										<i18n path="object-storage-s3-info">
 | 
				
			||||||
 | 
											<a href="https://docs.aws.amazon.com/general/latest/gr/rande.html" target="_blank">{{ $t('object-storage-s3-info-here') }}</a>
 | 
				
			||||||
 | 
										</i18n>
 | 
				
			||||||
 | 
									</ui-info>
 | 
				
			||||||
 | 
									<ui-info>{{ $t('object-storage-gcs-info') }}</ui-info>
 | 
				
			||||||
 | 
									<ui-input v-model="objectStorageBaseUrl" :disabled="!useObjectStorage">{{ $t('object-storage-base-url') }}</ui-input>
 | 
				
			||||||
 | 
									<ui-horizon-group inputs>
 | 
				
			||||||
 | 
										<ui-input v-model="objectStorageBucket" :disabled="!useObjectStorage">{{ $t('object-storage-bucket') }}</ui-input>
 | 
				
			||||||
 | 
										<ui-input v-model="objectStoragePrefix" :disabled="!useObjectStorage">{{ $t('object-storage-prefix') }}</ui-input>
 | 
				
			||||||
 | 
									</ui-horizon-group>
 | 
				
			||||||
 | 
									<ui-input v-model="objectStorageEndpoint" :disabled="!useObjectStorage">{{ $t('object-storage-endpoint') }}</ui-input>
 | 
				
			||||||
 | 
									<ui-horizon-group inputs>
 | 
				
			||||||
 | 
										<ui-input v-model="objectStorageRegion" :disabled="!useObjectStorage">{{ $t('object-storage-region') }}</ui-input>
 | 
				
			||||||
 | 
										<ui-input v-model="objectStoragePort" type="number" :disabled="!useObjectStorage">{{ $t('object-storage-port') }}</ui-input>
 | 
				
			||||||
 | 
									</ui-horizon-group>
 | 
				
			||||||
 | 
									<ui-horizon-group inputs>
 | 
				
			||||||
 | 
										<ui-input v-model="objectStorageAccessKey" :disabled="!useObjectStorage"><template #icon><fa icon="key"/></template>{{ $t('object-storage-access-key') }}</ui-input>
 | 
				
			||||||
 | 
										<ui-input v-model="objectStorageSecretKey" :disabled="!useObjectStorage"><template #icon><fa icon="key"/></template>{{ $t('object-storage-secret-key') }}</ui-input>
 | 
				
			||||||
 | 
									</ui-horizon-group>
 | 
				
			||||||
 | 
									<ui-switch v-model="objectStorageUseSSL" :disabled="!useObjectStorage">{{ $t('object-storage-use-ssl') }}</ui-switch>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
			<ui-switch v-model="cacheRemoteFiles">{{ $t('cache-remote-files') }}<template #desc>{{ $t('cache-remote-files-desc') }}</template></ui-switch>
 | 
								<ui-switch v-model="cacheRemoteFiles">{{ $t('cache-remote-files') }}<template #desc>{{ $t('cache-remote-files-desc') }}</template></ui-switch>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section class="fit-top fit-bottom">
 | 
				
			||||||
			<ui-input v-model="localDriveCapacityMb" type="number">{{ $t('local-drive-capacity-mb') }}<template #suffix>MB</template><template #desc>{{ $t('mb') }}</template></ui-input>
 | 
								<ui-input v-model="localDriveCapacityMb" type="number">{{ $t('local-drive-capacity-mb') }}<template #suffix>MB</template><template #desc>{{ $t('mb') }}</template></ui-input>
 | 
				
			||||||
			<ui-input v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles">{{ $t('remote-drive-capacity-mb') }}<template #suffix>MB</template><template #desc>{{ $t('mb') }}</template></ui-input>
 | 
								<ui-input v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles">{{ $t('remote-drive-capacity-mb') }}<template #suffix>MB</template><template #desc>{{ $t('mb') }}</template></ui-input>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
		<section class="fit-bottom">
 | 
					 | 
				
			||||||
			<header><fa :icon="faShieldAlt"/> {{ $t('recaptcha-config') }}</header>
 | 
					 | 
				
			||||||
			<ui-switch v-model="enableRecaptcha">{{ $t('enable-recaptcha') }}</ui-switch>
 | 
					 | 
				
			||||||
			<ui-info>{{ $t('recaptcha-info') }}</ui-info>
 | 
					 | 
				
			||||||
			<ui-horizon-group inputs>
 | 
					 | 
				
			||||||
				<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-site-key') }}</ui-input>
 | 
					 | 
				
			||||||
				<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-secret-key') }}</ui-input>
 | 
					 | 
				
			||||||
			</ui-horizon-group>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section>
 | 
							<section>
 | 
				
			||||||
			<header><fa :icon="faGhost"/> {{ $t('proxy-account-config') }}</header>
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
			<ui-info>{{ $t('proxy-account-info') }}</ui-info>
 | 
					 | 
				
			||||||
			<ui-input v-model="proxyAccount"><template #prefix>@</template>{{ $t('proxy-account-username') }}<template #desc>{{ $t('proxy-account-username-desc') }}</template></ui-input>
 | 
					 | 
				
			||||||
			<ui-info warn>{{ $t('proxy-account-warn') }}</ui-info>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section>
 | 
					 | 
				
			||||||
			<header><fa :icon="farEnvelope"/> {{ $t('email-config') }}</header>
 | 
					 | 
				
			||||||
			<ui-switch v-model="enableEmail">{{ $t('enable-email') }}<template #desc>{{ $t('email-config-info') }}</template></ui-switch>
 | 
					 | 
				
			||||||
			<ui-input v-model="email" type="email" :disabled="!enableEmail">{{ $t('email') }}</ui-input>
 | 
					 | 
				
			||||||
			<ui-horizon-group inputs>
 | 
					 | 
				
			||||||
				<ui-input v-model="smtpHost" :disabled="!enableEmail">{{ $t('smtp-host') }}</ui-input>
 | 
					 | 
				
			||||||
				<ui-input v-model="smtpPort" type="number" :disabled="!enableEmail">{{ $t('smtp-port') }}</ui-input>
 | 
					 | 
				
			||||||
			</ui-horizon-group>
 | 
					 | 
				
			||||||
			<ui-switch v-model="smtpAuth">{{ $t('smtp-auth') }}</ui-switch>
 | 
					 | 
				
			||||||
			<ui-horizon-group inputs>
 | 
					 | 
				
			||||||
				<ui-input v-model="smtpUser" :disabled="!enableEmail || !smtpAuth">{{ $t('smtp-user') }}</ui-input>
 | 
					 | 
				
			||||||
				<ui-input v-model="smtpPass" type="password" :withPasswordToggle="true" :disabled="!enableEmail || !smtpAuth">{{ $t('smtp-pass') }}</ui-input>
 | 
					 | 
				
			||||||
			</ui-horizon-group>
 | 
					 | 
				
			||||||
			<ui-switch v-model="smtpSecure" :disabled="!enableEmail">{{ $t('smtp-secure') }}<template #desc>{{ $t('smtp-secure-info') }}</template></ui-switch>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section>
 | 
					 | 
				
			||||||
			<header><fa :icon="faBolt"/> {{ $t('serviceworker-config') }}</header>
 | 
					 | 
				
			||||||
			<ui-switch v-model="enableServiceWorker">{{ $t('enable-serviceworker') }}<template #desc>{{ $t('serviceworker-info') }}</template></ui-switch>
 | 
					 | 
				
			||||||
			<ui-info>{{ $t('vapid-info') }}<br><code>npm i web-push -g<br>web-push generate-vapid-keys</code></ui-info>
 | 
					 | 
				
			||||||
			<ui-horizon-group inputs class="fit-bottom">
 | 
					 | 
				
			||||||
				<ui-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa icon="key"/></template>{{ $t('vapid-publickey') }}</ui-input>
 | 
					 | 
				
			||||||
				<ui-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa icon="key"/></template>{{ $t('vapid-privatekey') }}</ui-input>
 | 
					 | 
				
			||||||
			</ui-horizon-group>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section>
 | 
					 | 
				
			||||||
			<header>summaly Proxy</header>
 | 
					 | 
				
			||||||
			<ui-input v-model="summalyProxy">URL</ui-input>
 | 
					 | 
				
			||||||
		</section>
 | 
					 | 
				
			||||||
		<section>
 | 
					 | 
				
			||||||
			<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
 | 
					 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
	</ui-card>
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -91,56 +97,142 @@
 | 
				
			||||||
			<ui-textarea v-model="pinnedUsers">
 | 
								<ui-textarea v-model="pinnedUsers">
 | 
				
			||||||
				<template #desc>{{ $t('pinned-users-info') }}</template>
 | 
									<template #desc>{{ $t('pinned-users-info') }}</template>
 | 
				
			||||||
			</ui-textarea>
 | 
								</ui-textarea>
 | 
				
			||||||
			<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
	</ui-card>
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ui-card>
 | 
						<ui-card>
 | 
				
			||||||
		<template #title>{{ $t('invite') }}</template>
 | 
							<template #title><fa :icon="faGhost"/> {{ $t('proxy-account-config') }}</template>
 | 
				
			||||||
		<section>
 | 
							<section>
 | 
				
			||||||
			<ui-button @click="invite">{{ $t('invite') }}</ui-button>
 | 
								<ui-info>{{ $t('proxy-account-info') }}</ui-info>
 | 
				
			||||||
			<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
 | 
								<ui-input v-model="proxyAccount"><template #prefix>@</template>{{ $t('proxy-account-username') }}<template #desc>{{ $t('proxy-account-username-desc') }}</template></ui-input>
 | 
				
			||||||
 | 
								<ui-info warn>{{ $t('proxy-account-warn') }}</ui-info>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
	</ui-card>
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ui-card>
 | 
						<ui-card>
 | 
				
			||||||
		<template #title><fa :icon="['fab', 'twitter']"/> {{ $t('twitter-integration-config') }}</template>
 | 
							<template #title><fa :icon="farEnvelope"/> {{ $t('email-config') }}</template>
 | 
				
			||||||
		<section>
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-switch v-model="enableEmail">{{ $t('enable-email') }}<template #desc>{{ $t('email-config-info') }}</template></ui-switch>
 | 
				
			||||||
 | 
								<template v-if="enableEmail">
 | 
				
			||||||
 | 
									<ui-input v-model="email" type="email" :disabled="!enableEmail">{{ $t('email') }}</ui-input>
 | 
				
			||||||
 | 
									<ui-horizon-group inputs>
 | 
				
			||||||
 | 
										<ui-input v-model="smtpHost" :disabled="!enableEmail">{{ $t('smtp-host') }}</ui-input>
 | 
				
			||||||
 | 
										<ui-input v-model="smtpPort" type="number" :disabled="!enableEmail">{{ $t('smtp-port') }}</ui-input>
 | 
				
			||||||
 | 
									</ui-horizon-group>
 | 
				
			||||||
 | 
									<ui-switch v-model="smtpAuth">{{ $t('smtp-auth') }}</ui-switch>
 | 
				
			||||||
 | 
									<ui-horizon-group inputs>
 | 
				
			||||||
 | 
										<ui-input v-model="smtpUser" :disabled="!enableEmail || !smtpAuth">{{ $t('smtp-user') }}</ui-input>
 | 
				
			||||||
 | 
										<ui-input v-model="smtpPass" type="password" :with-password-toggle="true" :disabled="!enableEmail || !smtpAuth">{{ $t('smtp-pass') }}</ui-input>
 | 
				
			||||||
 | 
									</ui-horizon-group>
 | 
				
			||||||
 | 
									<ui-switch v-model="smtpSecure" :disabled="!enableEmail">{{ $t('smtp-secure') }}<template #desc>{{ $t('smtp-secure-info') }}</template></ui-switch>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<ui-card>
 | 
				
			||||||
 | 
							<template #title><fa :icon="faBolt"/> {{ $t('serviceworker-config') }}</template>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-switch v-model="enableServiceWorker">{{ $t('enable-serviceworker') }}<template #desc>{{ $t('serviceworker-info') }}</template></ui-switch>
 | 
				
			||||||
 | 
								<template v-if="enableServiceWorker">
 | 
				
			||||||
 | 
									<ui-info>{{ $t('vapid-info') }}<br><code>npm i web-push -g<br>web-push generate-vapid-keys</code></ui-info>
 | 
				
			||||||
 | 
									<ui-horizon-group inputs class="fit-bottom">
 | 
				
			||||||
 | 
										<ui-input v-model="swPublicKey" :disabled="!enableServiceWorker"><template #icon><fa icon="key"/></template>{{ $t('vapid-publickey') }}</ui-input>
 | 
				
			||||||
 | 
										<ui-input v-model="swPrivateKey" :disabled="!enableServiceWorker"><template #icon><fa icon="key"/></template>{{ $t('vapid-privatekey') }}</ui-input>
 | 
				
			||||||
 | 
									</ui-horizon-group>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<ui-card>
 | 
				
			||||||
 | 
							<template #title><fa :icon="faShieldAlt"/> {{ $t('recaptcha-config') }}</template>
 | 
				
			||||||
 | 
							<section :class="enableRecaptcha ? 'fit-bottom' : ''">
 | 
				
			||||||
 | 
								<ui-switch v-model="enableRecaptcha">{{ $t('enable-recaptcha') }}</ui-switch>
 | 
				
			||||||
 | 
								<template v-if="enableRecaptcha">
 | 
				
			||||||
 | 
									<ui-info>{{ $t('recaptcha-info') }}</ui-info>
 | 
				
			||||||
 | 
									<ui-horizon-group inputs>
 | 
				
			||||||
 | 
										<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-site-key') }}</ui-input>
 | 
				
			||||||
 | 
										<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><template #icon><fa icon="key"/></template>{{ $t('recaptcha-secret-key') }}</ui-input>
 | 
				
			||||||
 | 
									</ui-horizon-group>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<ui-card>
 | 
				
			||||||
 | 
							<template #title><fa :icon="faShieldAlt"/> {{ $t('external-service-integration-config') }}</template>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<header><fa :icon="['fab', 'twitter']"/> {{ $t('twitter-integration-config') }}</header>
 | 
				
			||||||
			<ui-switch v-model="enableTwitterIntegration">{{ $t('enable-twitter-integration') }}</ui-switch>
 | 
								<ui-switch v-model="enableTwitterIntegration">{{ $t('enable-twitter-integration') }}</ui-switch>
 | 
				
			||||||
			<ui-horizon-group>
 | 
								<template v-if="enableTwitterIntegration">
 | 
				
			||||||
				<ui-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa icon="key"/></template>{{ $t('twitter-integration-consumer-key') }}</ui-input>
 | 
									<ui-horizon-group>
 | 
				
			||||||
				<ui-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa icon="key"/></template>{{ $t('twitter-integration-consumer-secret') }}</ui-input>
 | 
										<ui-input v-model="twitterConsumerKey" :disabled="!enableTwitterIntegration"><template #icon><fa icon="key"/></template>{{ $t('twitter-integration-consumer-key') }}</ui-input>
 | 
				
			||||||
			</ui-horizon-group>
 | 
										<ui-input v-model="twitterConsumerSecret" :disabled="!enableTwitterIntegration"><template #icon><fa icon="key"/></template>{{ $t('twitter-integration-consumer-secret') }}</ui-input>
 | 
				
			||||||
			<ui-info>{{ $t('twitter-integration-info', { url: `${url}/api/tw/cb` }) }}</ui-info>
 | 
									</ui-horizon-group>
 | 
				
			||||||
			<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
 | 
									<ui-info>{{ $t('twitter-integration-info', { url: `${url}/api/tw/cb` }) }}</ui-info>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
	</ui-card>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<ui-card>
 | 
					 | 
				
			||||||
		<template #title><fa :icon="['fab', 'github']"/> {{ $t('github-integration-config') }}</template>
 | 
					 | 
				
			||||||
		<section>
 | 
							<section>
 | 
				
			||||||
 | 
								<header><fa :icon="['fab', 'github']"/> {{ $t('github-integration-config') }}</header>
 | 
				
			||||||
			<ui-switch v-model="enableGithubIntegration">{{ $t('enable-github-integration') }}</ui-switch>
 | 
								<ui-switch v-model="enableGithubIntegration">{{ $t('enable-github-integration') }}</ui-switch>
 | 
				
			||||||
			<ui-horizon-group>
 | 
								<template v-if="enableGithubIntegration">
 | 
				
			||||||
				<ui-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa icon="key"/></template>{{ $t('github-integration-client-id') }}</ui-input>
 | 
									<ui-horizon-group>
 | 
				
			||||||
				<ui-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa icon="key"/></template>{{ $t('github-integration-client-secret') }}</ui-input>
 | 
										<ui-input v-model="githubClientId" :disabled="!enableGithubIntegration"><template #icon><fa icon="key"/></template>{{ $t('github-integration-client-id') }}</ui-input>
 | 
				
			||||||
			</ui-horizon-group>
 | 
										<ui-input v-model="githubClientSecret" :disabled="!enableGithubIntegration"><template #icon><fa icon="key"/></template>{{ $t('github-integration-client-secret') }}</ui-input>
 | 
				
			||||||
			<ui-info>{{ $t('github-integration-info', { url: `${url}/api/gh/cb` }) }}</ui-info>
 | 
									</ui-horizon-group>
 | 
				
			||||||
			<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
 | 
									<ui-info>{{ $t('github-integration-info', { url: `${url}/api/gh/cb` }) }}</ui-info>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<header><fa :icon="['fab', 'discord']"/> {{ $t('discord-integration-config') }}</header>
 | 
				
			||||||
 | 
								<ui-switch v-model="enableDiscordIntegration">{{ $t('enable-discord-integration') }}</ui-switch>
 | 
				
			||||||
 | 
								<template v-if="enableDiscordIntegration">
 | 
				
			||||||
 | 
									<ui-horizon-group>
 | 
				
			||||||
 | 
										<ui-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa icon="key"/></template>{{ $t('discord-integration-client-id') }}</ui-input>
 | 
				
			||||||
 | 
										<ui-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa icon="key"/></template>{{ $t('discord-integration-client-secret') }}</ui-input>
 | 
				
			||||||
 | 
									</ui-horizon-group>
 | 
				
			||||||
 | 
									<ui-info>{{ $t('discord-integration-info', { url: `${url}/api/dc/cb` }) }}</ui-info>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
	</ui-card>
 | 
						</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ui-card>
 | 
						<details>
 | 
				
			||||||
		<template #title><fa :icon="['fab', 'discord']"/> {{ $t('discord-integration-config') }}</template>
 | 
							<summary style="color:var(--text);">{{ $t('advanced-config') }}</summary>
 | 
				
			||||||
		<section>
 | 
					
 | 
				
			||||||
			<ui-switch v-model="enableDiscordIntegration">{{ $t('enable-discord-integration') }}</ui-switch>
 | 
							<ui-card>
 | 
				
			||||||
			<ui-horizon-group>
 | 
								<template #title><fa :icon="faHashtag"/> {{ $t('hidden-tags') }}</template>
 | 
				
			||||||
				<ui-input v-model="discordClientId" :disabled="!enableDiscordIntegration"><template #icon><fa icon="key"/></template>{{ $t('discord-integration-client-id') }}</ui-input>
 | 
								<section class="fit-top">
 | 
				
			||||||
				<ui-input v-model="discordClientSecret" :disabled="!enableDiscordIntegration"><template #icon><fa icon="key"/></template>{{ $t('discord-integration-client-secret') }}</ui-input>
 | 
									<ui-textarea v-model="hiddenTags">
 | 
				
			||||||
			</ui-horizon-group>
 | 
										<template #desc>{{ $t('hidden-tags-info') }}</template>
 | 
				
			||||||
			<ui-info>{{ $t('discord-integration-info', { url: `${url}/api/dc/cb` }) }}</ui-info>
 | 
									</ui-textarea>
 | 
				
			||||||
			<ui-button @click="updateMeta">{{ $t('save') }}</ui-button>
 | 
									<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
		</section>
 | 
								</section>
 | 
				
			||||||
	</ui-card>
 | 
							</ui-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<ui-card>
 | 
				
			||||||
 | 
								<template #title>summaly Proxy</template>
 | 
				
			||||||
 | 
								<section class="fit-top fit-bottom">
 | 
				
			||||||
 | 
									<ui-input v-model="summalyProxy">URL</ui-input>
 | 
				
			||||||
 | 
								</section>
 | 
				
			||||||
 | 
								<section>
 | 
				
			||||||
 | 
									<ui-button @click="updateMeta"><fa :icon="faSave"/> {{ $t('save') }}</ui-button>
 | 
				
			||||||
 | 
								</section>
 | 
				
			||||||
 | 
							</ui-card>
 | 
				
			||||||
 | 
						</details>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,8 +241,8 @@ import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../i18n';
 | 
					import i18n from '../../i18n';
 | 
				
			||||||
import { url, host } from '../../config';
 | 
					import { url, host } from '../../config';
 | 
				
			||||||
import { toUnicode } from 'punycode';
 | 
					import { toUnicode } from 'punycode';
 | 
				
			||||||
import { faHeadset, faShieldAlt, faGhost, faUserPlus, faBolt, faThumbtack } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faHeadset, faShieldAlt, faGhost, faUserPlus, faBolt, faThumbtack, faPencilAlt, faHashtag } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faEnvelope as farEnvelope } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faEnvelope as farEnvelope, faSave } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('admin/views/instance.vue'),
 | 
						i18n: i18n('admin/views/instance.vue'),
 | 
				
			||||||
| 
						 | 
					@ -193,7 +285,6 @@ export default Vue.extend({
 | 
				
			||||||
			discordClientId: null,
 | 
								discordClientId: null,
 | 
				
			||||||
			discordClientSecret: null,
 | 
								discordClientSecret: null,
 | 
				
			||||||
			proxyAccount: null,
 | 
								proxyAccount: null,
 | 
				
			||||||
			inviteCode: null,
 | 
					 | 
				
			||||||
			summalyProxy: null,
 | 
								summalyProxy: null,
 | 
				
			||||||
			enableEmail: false,
 | 
								enableEmail: false,
 | 
				
			||||||
			email: null,
 | 
								email: null,
 | 
				
			||||||
| 
						 | 
					@ -207,7 +298,18 @@ export default Vue.extend({
 | 
				
			||||||
			swPublicKey: null,
 | 
								swPublicKey: null,
 | 
				
			||||||
			swPrivateKey: null,
 | 
								swPrivateKey: null,
 | 
				
			||||||
			pinnedUsers: '',
 | 
								pinnedUsers: '',
 | 
				
			||||||
			faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt, faThumbtack
 | 
								hiddenTags: '',
 | 
				
			||||||
 | 
								useObjectStorage: false,
 | 
				
			||||||
 | 
								objectStorageBaseUrl: null,
 | 
				
			||||||
 | 
								objectStorageBucket: null,
 | 
				
			||||||
 | 
								objectStoragePrefix: null,
 | 
				
			||||||
 | 
								objectStorageEndpoint: null,
 | 
				
			||||||
 | 
								objectStorageRegion: null,
 | 
				
			||||||
 | 
								objectStoragePort: null,
 | 
				
			||||||
 | 
								objectStorageAccessKey: null,
 | 
				
			||||||
 | 
								objectStorageSecretKey: null,
 | 
				
			||||||
 | 
								objectStorageUseSSL: false,
 | 
				
			||||||
 | 
								faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt, faThumbtack, faPencilAlt, faSave, faHashtag
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -260,13 +362,27 @@ export default Vue.extend({
 | 
				
			||||||
			this.swPublicKey = meta.swPublickey;
 | 
								this.swPublicKey = meta.swPublickey;
 | 
				
			||||||
			this.swPrivateKey = meta.swPrivateKey;
 | 
								this.swPrivateKey = meta.swPrivateKey;
 | 
				
			||||||
			this.pinnedUsers = meta.pinnedUsers.join('\n');
 | 
								this.pinnedUsers = meta.pinnedUsers.join('\n');
 | 
				
			||||||
 | 
								this.hiddenTags = meta.hiddenTags.join('\n');
 | 
				
			||||||
 | 
								this.useObjectStorage = meta.useObjectStorage;
 | 
				
			||||||
 | 
								this.objectStorageBaseUrl = meta.objectStorageBaseUrl;
 | 
				
			||||||
 | 
								this.objectStorageBucket = meta.objectStorageBucket;
 | 
				
			||||||
 | 
								this.objectStoragePrefix = meta.objectStoragePrefix;
 | 
				
			||||||
 | 
								this.objectStorageEndpoint = meta.objectStorageEndpoint;
 | 
				
			||||||
 | 
								this.objectStorageRegion = meta.objectStorageRegion;
 | 
				
			||||||
 | 
								this.objectStoragePort = meta.objectStoragePort;
 | 
				
			||||||
 | 
								this.objectStorageAccessKey = meta.objectStorageAccessKey;
 | 
				
			||||||
 | 
								this.objectStorageSecretKey = meta.objectStorageSecretKey;
 | 
				
			||||||
 | 
								this.objectStorageUseSSL = meta.objectStorageUseSSL;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		invite() {
 | 
							invite() {
 | 
				
			||||||
			this.$root.api('admin/invite').then(x => {
 | 
								this.$root.api('admin/invite').then(x => {
 | 
				
			||||||
				this.inviteCode = x.code;
 | 
									this.$root.dialog({
 | 
				
			||||||
 | 
										type: 'info',
 | 
				
			||||||
 | 
										text: x.code
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
			}).catch(e => {
 | 
								}).catch(e => {
 | 
				
			||||||
				this.$root.dialog({
 | 
									this.$root.dialog({
 | 
				
			||||||
					type: 'error',
 | 
										type: 'error',
 | 
				
			||||||
| 
						 | 
					@ -322,7 +438,18 @@ export default Vue.extend({
 | 
				
			||||||
				enableServiceWorker: this.enableServiceWorker,
 | 
									enableServiceWorker: this.enableServiceWorker,
 | 
				
			||||||
				swPublicKey: this.swPublicKey,
 | 
									swPublicKey: this.swPublicKey,
 | 
				
			||||||
				swPrivateKey: this.swPrivateKey,
 | 
									swPrivateKey: this.swPrivateKey,
 | 
				
			||||||
				pinnedUsers: this.pinnedUsers.split('\n')
 | 
									pinnedUsers: this.pinnedUsers.split('\n'),
 | 
				
			||||||
 | 
									hiddenTags: this.hiddenTags.split('\n'),
 | 
				
			||||||
 | 
									useObjectStorage: this.useObjectStorage,
 | 
				
			||||||
 | 
									objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null,
 | 
				
			||||||
 | 
									objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null,
 | 
				
			||||||
 | 
									objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null,
 | 
				
			||||||
 | 
									objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null,
 | 
				
			||||||
 | 
									objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null,
 | 
				
			||||||
 | 
									objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null,
 | 
				
			||||||
 | 
									objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null,
 | 
				
			||||||
 | 
									objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null,
 | 
				
			||||||
 | 
									objectStorageUseSSL: this.objectStorageUseSSL,
 | 
				
			||||||
			}).then(() => {
 | 
								}).then(() => {
 | 
				
			||||||
				this.$root.dialog({
 | 
									this.$root.dialog({
 | 
				
			||||||
					type: 'success',
 | 
										type: 'success',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,9 @@
 | 
				
			||||||
			<ui-button @click="showUser"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button>
 | 
								<ui-button @click="showUser"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			<div class="user" v-if="user">
 | 
								<div class="user" v-if="user">
 | 
				
			||||||
				<x-user :user='user'/>
 | 
									<x-user :user="user"/>
 | 
				
			||||||
				<div class="actions">
 | 
									<div class="actions">
 | 
				
			||||||
 | 
										<ui-button v-if="user.host != null" @click="updateRemoteUser"><fa :icon="faSync"/> {{ $t('update-remote-user') }}</ui-button>
 | 
				
			||||||
					<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button>
 | 
										<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button>
 | 
				
			||||||
					<ui-horizon-group>
 | 
										<ui-horizon-group>
 | 
				
			||||||
						<ui-button @click="silenceUser"><fa :icon="faMicrophoneSlash"/> {{ $t('make-silence') }}</ui-button>
 | 
											<ui-button @click="silenceUser"><fa :icon="faMicrophoneSlash"/> {{ $t('make-silence') }}</ui-button>
 | 
				
			||||||
| 
						 | 
					@ -20,7 +21,7 @@
 | 
				
			||||||
						<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button>
 | 
											<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button>
 | 
				
			||||||
						<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button>
 | 
											<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button>
 | 
				
			||||||
					</ui-horizon-group>
 | 
										</ui-horizon-group>
 | 
				
			||||||
					<ui-button v-if="user.host != null" @click="updateRemoteUser"><fa :icon="faSync"/> {{ $t('update-remote-user') }}</ui-button>
 | 
										<ui-button @click="deleteAllFiles"><fa :icon="faTrashAlt"/> {{ $t('delete-all-files') }}</ui-button>
 | 
				
			||||||
					<ui-textarea v-if="user" :value="user | json5" readonly tall style="margin-top:16px;"></ui-textarea>
 | 
										<ui-textarea v-if="user" :value="user | json5" readonly tall style="margin-top:16px;"></ui-textarea>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
| 
						 | 
					@ -67,7 +68,7 @@ import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../i18n';
 | 
					import i18n from '../../i18n';
 | 
				
			||||||
import parseAcct from "../../../../misc/acct/parse";
 | 
					import parseAcct from "../../../../misc/acct/parse";
 | 
				
			||||||
import { faUsers, faTerminal, faSearch, faKey, faSync, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faUsers, faTerminal, faSearch, faKey, faSync, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faSnowflake, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
import XUser from './users.user.vue';
 | 
					import XUser from './users.user.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
| 
						 | 
					@ -88,7 +89,7 @@ export default Vue.extend({
 | 
				
			||||||
			offset: 0,
 | 
								offset: 0,
 | 
				
			||||||
			users: [],
 | 
								users: [],
 | 
				
			||||||
			existMore: false,
 | 
								existMore: false,
 | 
				
			||||||
			faTerminal, faUsers, faSnowflake, faSearch, faKey, faSync, faMicrophoneSlash
 | 
								faTerminal, faUsers, faSnowflake, faSearch, faKey, faSync, faMicrophoneSlash, faTrashAlt
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -277,6 +278,25 @@ export default Vue.extend({
 | 
				
			||||||
			this.refreshUser();
 | 
								this.refreshUser();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							async deleteAllFiles() {
 | 
				
			||||||
 | 
								if (!await this.getConfirmed(this.$t('delete-all-files-confirm'))) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const process = async () => {
 | 
				
			||||||
 | 
									await this.$root.api('admin/delete-all-files-of-a-user', { userId: this.user.id });
 | 
				
			||||||
 | 
									this.$root.dialog({
 | 
				
			||||||
 | 
										type: 'success',
 | 
				
			||||||
 | 
										splash: true
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								await process().catch(e => {
 | 
				
			||||||
 | 
									this.$root.dialog({
 | 
				
			||||||
 | 
										type: 'error',
 | 
				
			||||||
 | 
										text: e.toString()
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		async getConfirmed(text: string): Promise<Boolean> {
 | 
							async getConfirmed(text: string): Promise<Boolean> {
 | 
				
			||||||
			const confirm = await this.$root.dialog({
 | 
								const confirm = await this.$root.dialog({
 | 
				
			||||||
				type: 'warning',
 | 
									type: 'warning',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,13 +27,6 @@ export type Source = {
 | 
				
			||||||
		port: number;
 | 
							port: number;
 | 
				
			||||||
		pass: string;
 | 
							pass: string;
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	drive?: {
 | 
					 | 
				
			||||||
		storage: string;
 | 
					 | 
				
			||||||
		bucket?: string;
 | 
					 | 
				
			||||||
		prefix?: string;
 | 
					 | 
				
			||||||
		baseUrl?: string;
 | 
					 | 
				
			||||||
		config?: any;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	autoAdmin?: boolean;
 | 
						autoAdmin?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -288,4 +288,61 @@ export class Meta {
 | 
				
			||||||
		nullable: true
 | 
							nullable: true
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public feedbackUrl: string | null;
 | 
						public feedbackUrl: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('boolean', {
 | 
				
			||||||
 | 
							default: false,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public useObjectStorage: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 512,
 | 
				
			||||||
 | 
							nullable: true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStorageBucket: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 512,
 | 
				
			||||||
 | 
							nullable: true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStoragePrefix: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 512,
 | 
				
			||||||
 | 
							nullable: true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStorageBaseUrl: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 512,
 | 
				
			||||||
 | 
							nullable: true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStorageEndpoint: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 512,
 | 
				
			||||||
 | 
							nullable: true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStorageRegion: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 512,
 | 
				
			||||||
 | 
							nullable: true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStorageAccessKey: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 512,
 | 
				
			||||||
 | 
							nullable: true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStorageSecretKey: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('integer', {
 | 
				
			||||||
 | 
							nullable: true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStoragePort: number | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('boolean', {
 | 
				
			||||||
 | 
							default: true,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public objectStorageUseSSL: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/ormconfig.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/ormconfig.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					import * as fs from 'fs';
 | 
				
			||||||
 | 
					import config from './config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const json = {
 | 
				
			||||||
 | 
						type: 'postgres',
 | 
				
			||||||
 | 
						host: config.db.host,
 | 
				
			||||||
 | 
						port: config.db.port,
 | 
				
			||||||
 | 
						username: config.db.user,
 | 
				
			||||||
 | 
						password: config.db.pass,
 | 
				
			||||||
 | 
						database: config.db.db,
 | 
				
			||||||
 | 
						entities: ['src/models/entities/*.ts'],
 | 
				
			||||||
 | 
						migrations: ['migration/*.ts'],
 | 
				
			||||||
 | 
						cli: {
 | 
				
			||||||
 | 
							migrationsDir: 'migration'
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fs.writeFileSync('ormconfig.json', JSON.stringify(json));
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import define from '../../define';
 | 
				
			||||||
 | 
					import del from '../../../../services/drive/delete-file';
 | 
				
			||||||
 | 
					import { DriveFiles } from '../../../../models';
 | 
				
			||||||
 | 
					import { ID } from '../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						tags: ['admin'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
						requireModerator: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							userId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
								desc: {
 | 
				
			||||||
 | 
									'ja-JP': '対象のユーザーID',
 | 
				
			||||||
 | 
									'en-US': 'The user ID which you want to suspend'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, me) => {
 | 
				
			||||||
 | 
						const files = await DriveFiles.find({
 | 
				
			||||||
 | 
							userId: ps.userId
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (const file of files) {
 | 
				
			||||||
 | 
							del(file);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -53,16 +53,18 @@ export default define(meta, async (ps) => {
 | 
				
			||||||
		if (blackDomains.length > 0) {
 | 
							if (blackDomains.length > 0) {
 | 
				
			||||||
			query.andWhere(new Brackets(qb => {
 | 
								query.andWhere(new Brackets(qb => {
 | 
				
			||||||
				for (const blackDomain of blackDomains) {
 | 
									for (const blackDomain of blackDomains) {
 | 
				
			||||||
					const subDomains = blackDomain.split('.');
 | 
										qb.andWhere(new Brackets(qb => {
 | 
				
			||||||
					let i = 0;
 | 
											const subDomains = blackDomain.split('.');
 | 
				
			||||||
					for (const subDomain of subDomains) {
 | 
											let i = 0;
 | 
				
			||||||
						const p = `blackSubDomain_${subDomain}_${i}`;
 | 
											for (const subDomain of subDomains) {
 | 
				
			||||||
						// 全体で否定できないのでド・モルガンの法則で
 | 
												const p = `blackSubDomain_${subDomain}_${i}`;
 | 
				
			||||||
						// !(P && Q) を !P || !Q で表す
 | 
												// 全体で否定できないのでド・モルガンの法則で
 | 
				
			||||||
						// SQL is 1 based, so we need '+ 1'
 | 
												// !(P && Q) を !P || !Q で表す
 | 
				
			||||||
						qb.orWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain });
 | 
												// SQL is 1 based, so we need '+ 1'
 | 
				
			||||||
						i++;
 | 
												qb.orWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain });
 | 
				
			||||||
					}
 | 
												i++;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}));
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}));
 | 
								}));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -357,7 +357,47 @@ export const meta = {
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
				'ja-JP': 'フィードバックのURL'
 | 
									'ja-JP': 'フィードバックのURL'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							useObjectStorage: {
 | 
				
			||||||
 | 
								validator: $.optional.bool
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStorageBaseUrl: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.str
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStorageBucket: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.str
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStoragePrefix: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.str
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStorageEndpoint: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.str
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStorageRegion: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.str
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStoragePort: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.num
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStorageAccessKey: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.str
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStorageSecretKey: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.str
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							objectStorageUseSSL: {
 | 
				
			||||||
 | 
								validator: $.optional.bool
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -560,6 +600,46 @@ export default define(meta, async (ps) => {
 | 
				
			||||||
		set.feedbackUrl = ps.feedbackUrl;
 | 
							set.feedbackUrl = ps.feedbackUrl;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.useObjectStorage !== undefined) {
 | 
				
			||||||
 | 
							set.useObjectStorage = ps.useObjectStorage;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStorageBaseUrl !== undefined) {
 | 
				
			||||||
 | 
							set.objectStorageBaseUrl = ps.objectStorageBaseUrl;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStorageBucket !== undefined) {
 | 
				
			||||||
 | 
							set.objectStorageBucket = ps.objectStorageBucket;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStoragePrefix !== undefined) {
 | 
				
			||||||
 | 
							set.objectStoragePrefix = ps.objectStoragePrefix;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStorageEndpoint !== undefined) {
 | 
				
			||||||
 | 
							set.objectStorageEndpoint = ps.objectStorageEndpoint;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStorageRegion !== undefined) {
 | 
				
			||||||
 | 
							set.objectStorageRegion = ps.objectStorageRegion;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStoragePort !== undefined) {
 | 
				
			||||||
 | 
							set.objectStoragePort = ps.objectStoragePort;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStorageAccessKey !== undefined) {
 | 
				
			||||||
 | 
							set.objectStorageAccessKey = ps.objectStorageAccessKey;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStorageSecretKey !== undefined) {
 | 
				
			||||||
 | 
							set.objectStorageSecretKey = ps.objectStorageSecretKey;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.objectStorageUseSSL !== undefined) {
 | 
				
			||||||
 | 
							set.objectStorageUseSSL = ps.objectStorageUseSSL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await getConnection().transaction(async transactionalEntityManager => {
 | 
						await getConnection().transaction(async transactionalEntityManager => {
 | 
				
			||||||
		const meta = await transactionalEntityManager.findOne(Meta, {
 | 
							const meta = await transactionalEntityManager.findOne(Meta, {
 | 
				
			||||||
			order: {
 | 
								order: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -153,7 +153,7 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
			globalTimeLine: !instance.disableGlobalTimeline,
 | 
								globalTimeLine: !instance.disableGlobalTimeline,
 | 
				
			||||||
			elasticsearch: config.elasticsearch ? true : false,
 | 
								elasticsearch: config.elasticsearch ? true : false,
 | 
				
			||||||
			recaptcha: instance.enableRecaptcha,
 | 
								recaptcha: instance.enableRecaptcha,
 | 
				
			||||||
			objectStorage: config.drive && config.drive.storage === 'minio',
 | 
								objectStorage: instance.useObjectStorage,
 | 
				
			||||||
			twitter: instance.enableTwitterIntegration,
 | 
								twitter: instance.enableTwitterIntegration,
 | 
				
			||||||
			github: instance.enableGithubIntegration,
 | 
								github: instance.enableGithubIntegration,
 | 
				
			||||||
			discord: instance.enableDiscordIntegration,
 | 
								discord: instance.enableDiscordIntegration,
 | 
				
			||||||
| 
						 | 
					@ -182,6 +182,16 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
		response.smtpUser = instance.smtpUser;
 | 
							response.smtpUser = instance.smtpUser;
 | 
				
			||||||
		response.smtpPass = instance.smtpPass;
 | 
							response.smtpPass = instance.smtpPass;
 | 
				
			||||||
		response.swPrivateKey = instance.swPrivateKey;
 | 
							response.swPrivateKey = instance.swPrivateKey;
 | 
				
			||||||
 | 
							response.useObjectStorage = instance.useObjectStorage;
 | 
				
			||||||
 | 
							response.objectStorageBaseUrl = instance.objectStorageBaseUrl;
 | 
				
			||||||
 | 
							response.objectStorageBucket = instance.objectStorageBucket;
 | 
				
			||||||
 | 
							response.objectStoragePrefix = instance.objectStoragePrefix;
 | 
				
			||||||
 | 
							response.objectStorageEndpoint = instance.objectStorageEndpoint;
 | 
				
			||||||
 | 
							response.objectStorageRegion = instance.objectStorageRegion;
 | 
				
			||||||
 | 
							response.objectStoragePort = instance.objectStoragePort;
 | 
				
			||||||
 | 
							response.objectStorageAccessKey = instance.objectStorageAccessKey;
 | 
				
			||||||
 | 
							response.objectStorageSecretKey = instance.objectStorageSecretKey;
 | 
				
			||||||
 | 
							response.objectStorageUseSSL = instance.objectStorageUseSSL;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return response;
 | 
						return response;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import * as fs from 'fs';
 | 
					import * as fs from 'fs';
 | 
				
			||||||
import * as Koa from 'koa';
 | 
					import * as Koa from 'koa';
 | 
				
			||||||
import { serverLogger } from '..';
 | 
					import { serverLogger } from '..';
 | 
				
			||||||
import { IImage, ConvertToPng, ConvertToJpeg } from '../../services/drive/image-processor';
 | 
					import { IImage, convertToPng, convertToJpeg } from '../../services/drive/image-processor';
 | 
				
			||||||
import { createTemp } from '../../misc/create-temp';
 | 
					import { createTemp } from '../../misc/create-temp';
 | 
				
			||||||
import { downloadUrl } from '../../misc/donwload-url';
 | 
					import { downloadUrl } from '../../misc/donwload-url';
 | 
				
			||||||
import { detectMine } from '../../misc/detect-mine';
 | 
					import { detectMine } from '../../misc/detect-mine';
 | 
				
			||||||
| 
						 | 
					@ -20,9 +20,9 @@ export async function proxyMedia(ctx: Koa.BaseContext) {
 | 
				
			||||||
		let image: IImage;
 | 
							let image: IImage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if ('static' in ctx.query && ['image/png', 'image/gif'].includes(type)) {
 | 
							if ('static' in ctx.query && ['image/png', 'image/gif'].includes(type)) {
 | 
				
			||||||
			image = await ConvertToPng(path, 498, 280);
 | 
								image = await convertToPng(path, 498, 280);
 | 
				
			||||||
		} else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif'].includes(type)) {
 | 
							} else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif'].includes(type)) {
 | 
				
			||||||
			image = await ConvertToJpeg(path, 200, 200);
 | 
								image = await convertToJpeg(path, 200, 200);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			image = {
 | 
								image = {
 | 
				
			||||||
				data: fs.readFileSync(path),
 | 
									data: fs.readFileSync(path),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,11 +8,10 @@ import * as sharp from 'sharp';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { publishMainStream, publishDriveStream } from '../stream';
 | 
					import { publishMainStream, publishDriveStream } from '../stream';
 | 
				
			||||||
import delFile from './delete-file';
 | 
					import delFile from './delete-file';
 | 
				
			||||||
import config from '../../config';
 | 
					 | 
				
			||||||
import { fetchMeta } from '../../misc/fetch-meta';
 | 
					import { fetchMeta } from '../../misc/fetch-meta';
 | 
				
			||||||
import { GenerateVideoThumbnail } from './generate-video-thumbnail';
 | 
					import { GenerateVideoThumbnail } from './generate-video-thumbnail';
 | 
				
			||||||
import { driveLogger } from './logger';
 | 
					import { driveLogger } from './logger';
 | 
				
			||||||
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
 | 
					import { IImage, convertToJpeg, convertToWebp, convertToPng, convertToGif, convertToApng } from './image-processor';
 | 
				
			||||||
import { contentDisposition } from '../../misc/content-disposition';
 | 
					import { contentDisposition } from '../../misc/content-disposition';
 | 
				
			||||||
import { detectMine } from '../../misc/detect-mine';
 | 
					import { detectMine } from '../../misc/detect-mine';
 | 
				
			||||||
import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '../../models';
 | 
					import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '../../models';
 | 
				
			||||||
| 
						 | 
					@ -37,7 +36,9 @@ async function save(file: DriveFile, path: string, name: string, type: string, h
 | 
				
			||||||
	// thunbnail, webpublic を必要なら生成
 | 
						// thunbnail, webpublic を必要なら生成
 | 
				
			||||||
	const alts = await generateAlts(path, type, !file.uri);
 | 
						const alts = await generateAlts(path, type, !file.uri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (config.drive && config.drive.storage == 'minio') {
 | 
						const meta = await fetchMeta();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (meta.useObjectStorage) {
 | 
				
			||||||
		//#region ObjectStorage params
 | 
							//#region ObjectStorage params
 | 
				
			||||||
		let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) || ['']);
 | 
							let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) || ['']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,11 +48,11 @@ async function save(file: DriveFile, path: string, name: string, type: string, h
 | 
				
			||||||
			if (type === 'image/webp') ext = '.webp';
 | 
								if (type === 'image/webp') ext = '.webp';
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const baseUrl = config.drive.baseUrl
 | 
							const baseUrl = meta.objectStorageBaseUrl
 | 
				
			||||||
			|| `${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? `:${config.drive.config.port}` : '' }/${ config.drive.bucket }`;
 | 
								|| `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// for original
 | 
							// for original
 | 
				
			||||||
		const key = `${config.drive.prefix}/${uuid.v4()}${ext}`;
 | 
							const key = `${meta.objectStoragePrefix}/${uuid.v4()}${ext}`;
 | 
				
			||||||
		const url = `${ baseUrl }/${ key }`;
 | 
							const url = `${ baseUrl }/${ key }`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// for alts
 | 
							// for alts
 | 
				
			||||||
| 
						 | 
					@ -68,7 +69,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h
 | 
				
			||||||
		];
 | 
							];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (alts.webpublic) {
 | 
							if (alts.webpublic) {
 | 
				
			||||||
			webpublicKey = `${config.drive.prefix}/${uuid.v4()}.${alts.webpublic.ext}`;
 | 
								webpublicKey = `${meta.objectStoragePrefix}/${uuid.v4()}.${alts.webpublic.ext}`;
 | 
				
			||||||
			webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
 | 
								webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			logger.info(`uploading webpublic: ${webpublicKey}`);
 | 
								logger.info(`uploading webpublic: ${webpublicKey}`);
 | 
				
			||||||
| 
						 | 
					@ -76,7 +77,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (alts.thumbnail) {
 | 
							if (alts.thumbnail) {
 | 
				
			||||||
			thumbnailKey = `${config.drive.prefix}/${uuid.v4()}.${alts.thumbnail.ext}`;
 | 
								thumbnailKey = `${meta.objectStoragePrefix}/${uuid.v4()}.${alts.thumbnail.ext}`;
 | 
				
			||||||
			thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
 | 
								thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			logger.info(`uploading thumbnail: ${thumbnailKey}`);
 | 
								logger.info(`uploading thumbnail: ${thumbnailKey}`);
 | 
				
			||||||
| 
						 | 
					@ -149,11 +150,15 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
 | 
				
			||||||
		logger.info(`creating web image`);
 | 
							logger.info(`creating web image`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (['image/jpeg'].includes(type)) {
 | 
							if (['image/jpeg'].includes(type)) {
 | 
				
			||||||
			webpublic = await ConvertToJpeg(path, 2048, 2048);
 | 
								webpublic = await convertToJpeg(path, 2048, 2048);
 | 
				
			||||||
		} else if (['image/webp'].includes(type)) {
 | 
							} else if (['image/webp'].includes(type)) {
 | 
				
			||||||
			webpublic = await ConvertToWebp(path, 2048, 2048);
 | 
								webpublic = await convertToWebp(path, 2048, 2048);
 | 
				
			||||||
		} else if (['image/png'].includes(type)) {
 | 
							} else if (['image/png'].includes(type)) {
 | 
				
			||||||
			webpublic = await ConvertToPng(path, 2048, 2048);
 | 
								webpublic = await convertToPng(path, 2048, 2048);
 | 
				
			||||||
 | 
							} else if (['image/apng', 'image/vnd.mozilla.apng'].includes(type)) {
 | 
				
			||||||
 | 
								webpublic = await convertToApng(path);
 | 
				
			||||||
 | 
							} else if (['image/gif'].includes(type)) {
 | 
				
			||||||
 | 
								webpublic = await convertToGif(path);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			logger.info(`web image not created (not an image)`);
 | 
								logger.info(`web image not created (not an image)`);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -166,9 +171,11 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
 | 
				
			||||||
	let thumbnail: IImage | null = null;
 | 
						let thumbnail: IImage | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (['image/jpeg', 'image/webp'].includes(type)) {
 | 
						if (['image/jpeg', 'image/webp'].includes(type)) {
 | 
				
			||||||
		thumbnail = await ConvertToJpeg(path, 498, 280);
 | 
							thumbnail = await convertToJpeg(path, 498, 280);
 | 
				
			||||||
	} else if (['image/png'].includes(type)) {
 | 
						} else if (['image/png'].includes(type)) {
 | 
				
			||||||
		thumbnail = await ConvertToPng(path, 498, 280);
 | 
							thumbnail = await convertToPng(path, 498, 280);
 | 
				
			||||||
 | 
						} else if (['image/gif'].includes(type)) {
 | 
				
			||||||
 | 
							thumbnail = await convertToGif(path);
 | 
				
			||||||
	} else if (type.startsWith('video/')) {
 | 
						} else if (type.startsWith('video/')) {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			thumbnail = await GenerateVideoThumbnail(path);
 | 
								thumbnail = await GenerateVideoThumbnail(path);
 | 
				
			||||||
| 
						 | 
					@ -188,7 +195,15 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
 | 
				
			||||||
 * Upload to ObjectStorage
 | 
					 * Upload to ObjectStorage
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
 | 
					async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
 | 
				
			||||||
	const minio = new Minio.Client(config.drive!.config);
 | 
						const meta = await fetchMeta();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const minio = new Minio.Client({
 | 
				
			||||||
 | 
							endPoint: meta.objectStorageEndpoint!,
 | 
				
			||||||
 | 
							port: meta.objectStoragePort ? meta.objectStoragePort : undefined,
 | 
				
			||||||
 | 
							useSSL: meta.objectStorageUseSSL,
 | 
				
			||||||
 | 
							accessKey: meta.objectStorageAccessKey!,
 | 
				
			||||||
 | 
							secretKey: meta.objectStorageSecretKey!,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const metadata = {
 | 
						const metadata = {
 | 
				
			||||||
		'Content-Type': type,
 | 
							'Content-Type': type,
 | 
				
			||||||
| 
						 | 
					@ -197,7 +212,7 @@ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (filename) metadata['Content-Disposition'] = contentDisposition('inline', filename);
 | 
						if (filename) metadata['Content-Disposition'] = contentDisposition('inline', filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await minio.putObject(config.drive!.bucket!, key, stream, undefined, metadata);
 | 
						await minio.putObject(meta.objectStorageBucket!, key, stream, undefined, metadata);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function deleteOldFile(user: IRemoteUser) {
 | 
					async function deleteOldFile(user: IRemoteUser) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
import * as Minio from 'minio';
 | 
					import * as Minio from 'minio';
 | 
				
			||||||
import config from '../../config';
 | 
					 | 
				
			||||||
import { DriveFile } from '../../models/entities/drive-file';
 | 
					import { DriveFile } from '../../models/entities/drive-file';
 | 
				
			||||||
import { InternalStorage } from './internal-storage';
 | 
					import { InternalStorage } from './internal-storage';
 | 
				
			||||||
import { DriveFiles, Instances, Notes } from '../../models';
 | 
					import { DriveFiles, Instances, Notes } from '../../models';
 | 
				
			||||||
import { driveChart, perUserDriveChart, instanceChart } from '../chart';
 | 
					import { driveChart, perUserDriveChart, instanceChart } from '../chart';
 | 
				
			||||||
 | 
					import { fetchMeta } from '../../misc/fetch-meta';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function(file: DriveFile, isExpired = false) {
 | 
					export default async function(file: DriveFile, isExpired = false) {
 | 
				
			||||||
	if (file.storedInternal) {
 | 
						if (file.storedInternal) {
 | 
				
			||||||
| 
						 | 
					@ -17,16 +17,24 @@ export default async function(file: DriveFile, isExpired = false) {
 | 
				
			||||||
			InternalStorage.del(file.webpublicAccessKey!);
 | 
								InternalStorage.del(file.webpublicAccessKey!);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else if (!file.isLink) {
 | 
						} else if (!file.isLink) {
 | 
				
			||||||
		const minio = new Minio.Client(config.drive!.config);
 | 
							const meta = await fetchMeta();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		await minio.removeObject(config.drive!.bucket!, file.accessKey!);
 | 
							const minio = new Minio.Client({
 | 
				
			||||||
 | 
								endPoint: meta.objectStorageEndpoint!,
 | 
				
			||||||
 | 
								port: meta.objectStoragePort ? meta.objectStoragePort : undefined,
 | 
				
			||||||
 | 
								useSSL: meta.objectStorageUseSSL,
 | 
				
			||||||
 | 
								accessKey: meta.objectStorageAccessKey!,
 | 
				
			||||||
 | 
								secretKey: meta.objectStorageSecretKey!,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							await minio.removeObject(meta.objectStorageBucket!, file.accessKey!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (file.thumbnailUrl) {
 | 
							if (file.thumbnailUrl) {
 | 
				
			||||||
			await minio.removeObject(config.drive!.bucket!, file.thumbnailAccessKey!);
 | 
								await minio.removeObject(meta.objectStorageBucket!, file.thumbnailAccessKey!);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (file.webpublicUrl) {
 | 
							if (file.webpublicUrl) {
 | 
				
			||||||
			await minio.removeObject(config.drive!.bucket!, file.webpublicAccessKey!);
 | 
								await minio.removeObject(meta.objectStorageBucket!, file.webpublicAccessKey!);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import * as fs from 'fs';
 | 
					import * as fs from 'fs';
 | 
				
			||||||
import * as tmp from 'tmp';
 | 
					import * as tmp from 'tmp';
 | 
				
			||||||
import { IImage, ConvertToJpeg } from './image-processor';
 | 
					import { IImage, convertToJpeg } from './image-processor';
 | 
				
			||||||
const ThumbnailGenerator = require('video-thumbnail-generator').default;
 | 
					const ThumbnailGenerator = require('video-thumbnail-generator').default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function GenerateVideoThumbnail(path: string): Promise<IImage> {
 | 
					export async function GenerateVideoThumbnail(path: string): Promise<IImage> {
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ export async function GenerateVideoThumbnail(path: string): Promise<IImage> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const outPath = `${outDir}/output.png`;
 | 
						const outPath = `${outDir}/output.png`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const thumbnail = await ConvertToJpeg(outPath, 498, 280);
 | 
						const thumbnail = await convertToJpeg(outPath, 498, 280);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// cleanup
 | 
						// cleanup
 | 
				
			||||||
	fs.unlinkSync(outPath);
 | 
						fs.unlinkSync(outPath);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import * as sharp from 'sharp';
 | 
					import * as sharp from 'sharp';
 | 
				
			||||||
 | 
					import * as fs from 'fs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type IImage = {
 | 
					export type IImage = {
 | 
				
			||||||
	data: Buffer;
 | 
						data: Buffer;
 | 
				
			||||||
| 
						 | 
					@ -10,7 +11,7 @@ export type IImage = {
 | 
				
			||||||
 * Convert to JPEG
 | 
					 * Convert to JPEG
 | 
				
			||||||
 *   with resize, remove metadata, resolve orientation, stop animation
 | 
					 *   with resize, remove metadata, resolve orientation, stop animation
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function ConvertToJpeg(path: string, width: number, height: number): Promise<IImage> {
 | 
					export async function convertToJpeg(path: string, width: number, height: number): Promise<IImage> {
 | 
				
			||||||
	const data = await sharp(path)
 | 
						const data = await sharp(path)
 | 
				
			||||||
		.resize(width, height, {
 | 
							.resize(width, height, {
 | 
				
			||||||
			fit: 'inside',
 | 
								fit: 'inside',
 | 
				
			||||||
| 
						 | 
					@ -34,7 +35,7 @@ export async function ConvertToJpeg(path: string, width: number, height: number)
 | 
				
			||||||
 * Convert to WebP
 | 
					 * Convert to WebP
 | 
				
			||||||
 *   with resize, remove metadata, resolve orientation, stop animation
 | 
					 *   with resize, remove metadata, resolve orientation, stop animation
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function ConvertToWebp(path: string, width: number, height: number): Promise<IImage> {
 | 
					export async function convertToWebp(path: string, width: number, height: number): Promise<IImage> {
 | 
				
			||||||
	const data = await sharp(path)
 | 
						const data = await sharp(path)
 | 
				
			||||||
		.resize(width, height, {
 | 
							.resize(width, height, {
 | 
				
			||||||
			fit: 'inside',
 | 
								fit: 'inside',
 | 
				
			||||||
| 
						 | 
					@ -57,7 +58,7 @@ export async function ConvertToWebp(path: string, width: number, height: number)
 | 
				
			||||||
 * Convert to PNG
 | 
					 * Convert to PNG
 | 
				
			||||||
 *   with resize, remove metadata, resolve orientation, stop animation
 | 
					 *   with resize, remove metadata, resolve orientation, stop animation
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function ConvertToPng(path: string, width: number, height: number): Promise<IImage> {
 | 
					export async function convertToPng(path: string, width: number, height: number): Promise<IImage> {
 | 
				
			||||||
	const data = await sharp(path)
 | 
						const data = await sharp(path)
 | 
				
			||||||
		.resize(width, height, {
 | 
							.resize(width, height, {
 | 
				
			||||||
			fit: 'inside',
 | 
								fit: 'inside',
 | 
				
			||||||
| 
						 | 
					@ -73,3 +74,29 @@ export async function ConvertToPng(path: string, width: number, height: number):
 | 
				
			||||||
		type: 'image/png'
 | 
							type: 'image/png'
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert to GIF (Actually just NOP)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function convertToGif(path: string): Promise<IImage> {
 | 
				
			||||||
 | 
						const data = await fs.promises.readFile(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return {
 | 
				
			||||||
 | 
							data,
 | 
				
			||||||
 | 
							ext: 'gif',
 | 
				
			||||||
 | 
							type: 'image/gif'
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert to APNG (Actually just NOP)
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function convertToApng(path: string): Promise<IImage> {
 | 
				
			||||||
 | 
						const data = await fs.promises.readFile(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return {
 | 
				
			||||||
 | 
							data,
 | 
				
			||||||
 | 
							ext: 'apng',
 | 
				
			||||||
 | 
							type: 'image/apng'
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue