Compare commits
	
		
			117 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a73da3cd70 | ||
|   | 9c27d0ae3f | ||
|   | 525d5218c1 | ||
|   | e23b13ec7f | ||
|   | 29b000e03c | ||
|   | 26c9d8ff6f | ||
|   | 5e3372e932 | ||
|   | f7069dcd18 | ||
|   | 560bb65384 | ||
|   | 50cd6a036e | ||
|   | 441ab2b5f8 | ||
|   | ba5ed188a1 | ||
|   | 72e672f08d | ||
|   | 120474ec6a | ||
|   | eee57c47f5 | ||
|   | 4c160869b8 | ||
|   | 3720a7fbe0 | ||
|   | 7afa541a53 | ||
|   | 6f979c8275 | ||
|   | d399241e65 | ||
|   | e85dec030a | ||
|   | 64277b7157 | ||
|   | 4a72543f65 | ||
|   | 5b84d29807 | ||
|   | a11061ec2b | ||
|   | 24cfb93b2e | ||
|   | 502b42d63a | ||
|   | 612672b79c | ||
|   | abc670e1b1 | ||
|   | d589ccdd01 | ||
|   | acb07d9f7d | ||
|   | f4d2186719 | ||
|   | d0ede5c665 | ||
|   | 554cbb5e9b | ||
|   | dbd32a56bf | ||
|   | 7f500235c6 | ||
|   | 39a58084c8 | ||
|   | cde0fde836 | ||
|   | e70cca0fda | ||
|   | 919bd7eb82 | ||
|   | 312cff3d6f | ||
|   | 0d86eef3d7 | ||
|   | 13acf570e7 | ||
|   | fa17623fa8 | ||
|   | 06fd525950 | ||
|   | 4805b5115a | ||
|   | 108dcb3e61 | ||
|   | 780d272535 | ||
|   | 02ea4b81a5 | ||
|   | 7c1bdc6d36 | ||
|   | 78c7b8b836 | ||
|   | 227da30acb | ||
|   | 610805026f | ||
|   | c02399c3d2 | ||
|   | e0799d4153 | ||
|   | 6df83f1aa9 | ||
|   | efb5ad1d9b | ||
|   | 716976f016 | ||
|   | 7892f41b84 | ||
|   | d549e03b3f | ||
|   | c511ef21ff | ||
|   | d64dc45899 | ||
|   | bcb0588409 | ||
|   | 0975959eb9 | ||
|   | e985a6d9d3 | ||
|   | b893305974 | ||
|   | 724fdd44e4 | ||
|   | b480ef669c | ||
|   | 4b145da046 | ||
|   | 83d168ece3 | ||
|   | ae44fe7818 | ||
|   | f8981b3acb | ||
|   | 050b324885 | ||
|   | e74c0df6c6 | ||
|   | 22d0d11895 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 80d0c0cf74 | ||
|   | 518646b925 | ||
|   | 479d7e0087 | ||
|   | 8ea1a555f4 | ||
|   | 04024dc37c | ||
|   | 060ff9288f | ||
|   | 197116ee78 | ||
|   | a1e0015257 | ||
|   | 7e701ef9e0 | ||
|   | 3d6fb661bb | ||
|   | fc372496da | ||
|   | ad7258fe9c | ||
|   | bd707cb2a8 | ||
|   | 1839b5f205 | ||
|   | 02b47f963c | ||
|   | f8a7f9378a | ||
|   | 65cb253be4 | ||
|   | a12356b24b | ||
|   | 6a67ad7f93 | ||
|   | 140a7f0b1c | ||
|   | 00159bc6b5 | ||
|   | 9542260103 | ||
|   | 72074578df | ||
|   | 3b4750a988 | ||
|   | aeec5f0163 | ||
|   | 9c94d8c8d6 | ||
|   | 581712a2c8 | ||
|   | b25b51aaca | ||
|   | fb97e13a61 | ||
|   | 36e154fdb2 | ||
|   | ca273a24b4 | ||
|   | d828bf2889 | ||
|   | 87efccef18 | ||
|   | e0bf522e7f | ||
|   | 5b1cd3bd3c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | f00489196d | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | dd53bf7e51 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 35a6da26d2 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c8c8748a0b | ||
|   | 46d0065a90 | ||
|   | 990b0180a8 | ||
|   | f3bfb72251 | 
							
								
								
									
										129
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| version: 2.1 | ||||
|  | ||||
| general: | ||||
|   branches: | ||||
|     ignore: | ||||
|       - l10n_develop | ||||
|       - imgbot | ||||
|  | ||||
| executors: | ||||
|   default: | ||||
|     working_directory: /tmp/workspace | ||||
|     docker: | ||||
|       - image: misskey/ci:latest | ||||
|       - image: circleci/mongo:latest | ||||
|       - image: circleci/redis:latest | ||||
|   docker: | ||||
|     working_directory: /tmp/workspace | ||||
|     docker: | ||||
|       - image: docker:latest | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|     executor: default | ||||
|     steps: | ||||
|       - checkout | ||||
|       - restore_cache: | ||||
|           name: Restore npm package caches | ||||
|           keys: | ||||
|             - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}- | ||||
|             - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}- | ||||
|             - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}- | ||||
|             - npm-v1-arch-{{ arch }}- | ||||
|             - npm-v1- | ||||
|       - run: | ||||
|           name: Install Dependencies | ||||
|           command: | | ||||
|             npm install | ||||
|       - run: | ||||
|           name: Configure | ||||
|           command: | | ||||
|             cp .ci/default.yml .config | ||||
|             cp .ci/test.yml .config | ||||
|       - run: | ||||
|           name: Build | ||||
|           command: | | ||||
|             npm run build || (echo -e '\033[0;34mRebuild modules\033[0;39m' && ls -1A node_modules | grep '^[^@]' | xargs npm rebuild && ls -1A node_modules | grep '^@' | xargs -I%1 sh -c 'ls -1A node_modules/'%1' | xargs -P0 -I%2 npm rebuild node_modules/'%1'/%2' && npm run build) | ||||
|             ls -1ARl node_modules > ls | ||||
|       - save_cache: | ||||
|           name: Cache npm packages | ||||
|           key: npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}-ls-{{ checksum "ls" }} | ||||
|           paths: | ||||
|             - node_modules | ||||
|       - store_artifacts: | ||||
|           path: built | ||||
|       - persist_to_workspace: | ||||
|           root: . | ||||
|           paths: | ||||
|             - . | ||||
|   test: | ||||
|     parameters: | ||||
|       without_redis: | ||||
|         type: string | ||||
|         default: "" | ||||
|     executor: default | ||||
|     steps: | ||||
|       - attach_workspace: | ||||
|           at: /tmp/workspace | ||||
|       - when: | ||||
|           condition: <<parameters.without_redis>> | ||||
|           steps: | ||||
|             - run: | ||||
|                 name: Configure | ||||
|                 command: | | ||||
|                   mv .config/test.yml .config/test_redis.yml | ||||
|                   touch .config/test.yml | ||||
|                   cat .config/test_redis.yml | while IFS= read line; do if [[ "$line" = '# __REDIS__' ]]; then break; else echo "$line" >> .config/test.yml; fi; done | ||||
|       - run: | ||||
|           name: Test | ||||
|           command: | | ||||
|             npm run test || (npm rebuild && npm run test) || ((node-gyp configure && node-gyp build && npm run build || (echo -e '\033[0;34mRebuild modules\033[0;39m' && ls -1A node_modules | grep '^[^@]' | xargs npm rebuild && ls -1A node_modules | grep '^@' | xargs -I%1 sh -c 'ls -1A node_modules/'%1' | xargs -P0 -I%2 npm rebuild node_modules/'%1'/%2' && npm run build)) && npm run test) | ||||
|             ls -1ARl node_modules > ls | ||||
|       - save_cache: | ||||
|           name: Cache npm packages | ||||
|           key: npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}-ls-{{ checksum "ls" }} | ||||
|           paths: | ||||
|             - node_modules | ||||
|  | ||||
|   docker: | ||||
|     parameters: | ||||
|       with_deploy: | ||||
|         type: string | ||||
|         default: "" | ||||
|     executor: docker | ||||
|     steps: | ||||
|       - checkout | ||||
|       - setup_remote_docker | ||||
|       - run: | ||||
|           name: Build | ||||
|           command: | | ||||
|             docker build . | ||||
|       - when: | ||||
|           condition: <<parameters.with_deploy>> | ||||
|           steps: | ||||
|             - run: | ||||
|                 name: Deploy | ||||
|                 command: | | ||||
|                   docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD && docker push misskey/misskey | ||||
|  | ||||
| workflows: | ||||
|   version: 2 | ||||
|   build-and-test: | ||||
|     jobs: | ||||
|       - build | ||||
|       - test: | ||||
|           requires: | ||||
|             - build | ||||
|       - test: | ||||
|           without_redis: "true" | ||||
|           requires: | ||||
|             - build | ||||
|       - docker: | ||||
|           filters: | ||||
|             branches: | ||||
|               ignore: master | ||||
|       - docker: | ||||
|           with_deploy: "true" | ||||
|           filters: | ||||
|             branches: | ||||
|               only: master | ||||
| @@ -35,7 +35,7 @@ before_script: | ||||
|   - npm install | ||||
|  | ||||
|   # 設定ファイルを配置 | ||||
|   - cp ./.travis/default.yml ./.config | ||||
|   - cp ./.travis/test.yml ./.config | ||||
|   - cp ./.ci/default.yml ./.config | ||||
|   - cp ./.ci/test.yml ./.config | ||||
|  | ||||
|   - travis_wait npm run build | ||||
|   | ||||
							
								
								
									
										24
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -3,8 +3,9 @@ FROM alpine:3.8 AS base | ||||
| ENV NODE_ENV=production | ||||
|  | ||||
| RUN apk add --no-cache nodejs nodejs-npm zlib | ||||
| RUN npm i -g npm@latest | ||||
|  | ||||
| WORKDIR /misskey | ||||
| COPY . ./ | ||||
|  | ||||
| FROM base AS builder | ||||
|  | ||||
| @@ -21,18 +22,23 @@ RUN apk add --no-cache \ | ||||
|     pkgconfig \ | ||||
|     libtool \ | ||||
|     zlib-dev | ||||
| RUN npm install \ | ||||
|     && npm install -g node-gyp \ | ||||
|     && node-gyp configure \ | ||||
|     && node-gyp build \ | ||||
|     && npm run build | ||||
| RUN npm i -g node-gyp | ||||
|  | ||||
| COPY ./package.json ./ | ||||
| RUN npm i | ||||
|  | ||||
| COPY . ./ | ||||
| RUN node-gyp configure \ | ||||
|  && node-gyp build \ | ||||
|  && npm run build | ||||
|  | ||||
| FROM base AS runner | ||||
|  | ||||
| COPY --from=builder /misskey/built ./built | ||||
| COPY --from=builder /misskey/node_modules ./node_modules | ||||
|  | ||||
| RUN apk add --no-cache tini | ||||
| ENTRYPOINT ["/sbin/tini", "--"] | ||||
|  | ||||
| COPY --from=builder /misskey/node_modules ./node_modules | ||||
| COPY --from=builder /misskey/built ./built | ||||
| COPY . ./ | ||||
|  | ||||
| CMD ["npm", "start"] | ||||
|   | ||||
							
								
								
									
										26
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,6 +3,7 @@ | ||||
| [](https://misskey.xyz/) | ||||
| ================================================================ | ||||
|  | ||||
| [](https://circleci.com/gh/syuilo/misskey) | ||||
| [![][travis-badge]][travis-link] | ||||
| [![][dependencies-badge]][dependencies-link] | ||||
| [](http://makeapullrequest.com) | ||||
| @@ -71,39 +72,46 @@ Please see [Contribution guide](./CONTRIBUTING.md). | ||||
| ---------------------------------------------------------------- | ||||
| <!-- PATREON_START --> | ||||
| <table><tr> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=CXe9AqlZy9AsYfiWd3OBYVOzvODoN47Litz0Tu4BFpU%3D" alt="Gargron"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1?token-time=2145916800&token-hash=d6P5MWHHsCMxUuBAEPAoVc5wLUR19mIhqAq7Ma9h9rI%3D" alt="ne_moni"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/2?token-time=2145916800&token-hash=mgPdX9TqZxEg4TTPuc477dxhIgYk9246qafjWZEqZ7g%3D" alt="Melilot"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12999811/5f349fafcce44dd1824a8b1ebbec4564/2?token-time=2145916800&token-hash=rwZ8qvbm_kpA4ib3kc07tVKupXeySpY5ATQFGxfL9v0%3D" alt="Xeltica"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1?token-time=2145916800&token-hash=I8lJVM8LeW6TSo5W6uIIRZ42cw83zp1wK_FsbzY0mcQ%3D" alt="mydarkstar"></td> | ||||
| <td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td> | ||||
| </tr><tr> | ||||
| <td><a href="https://www.patreon.com/mastodon">Gargron</a></td> | ||||
| <td><a href="https://www.patreon.com/negao">negao</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> | ||||
| <td><a href="https://www.patreon.com/AxellaMC">Xeltica</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td> | ||||
| <td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td> | ||||
| <td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> | ||||
| </tr></table> | ||||
| <table><tr> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=zwSu01tOtn5xTUucDZHuPsCxF2HBEMVs9ROJKTlEV_o%3D" alt="nemu"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/8241184/39e18850e87a449e9c9a71acb3310ebd/2?token-time=2145916800&token-hash=iUXOQzRyJDv3PJxwS7Mjwg1459dzh2trOq6NFtXu_OM%3D" alt="Acid Chicken"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=UERBN4OyP7Nh5XwwdDg0N0IE5cD6_qUQMO81Z5Wizso%3D" alt="Hiratake"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/10789744/97175095d8f04c0f86225ff47cb98d40/1?token-time=2145916800&token-hash=P4BIzCX2I1CkEP66ottfhsC8Wr6BUSamjA-vq3pLqFI%3D" alt="Naoki Hirayama"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D" alt="Gargron"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1?token-time=2145916800&token-hash=VZUtwrjQa8Jml4twCjHYQQZ64wHEY4oIlGl7Kc-VYUQ%3D" alt="Nokotaro Takeda"></td> | ||||
| <td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td> | ||||
| </tr><tr> | ||||
| <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td> | ||||
| <td><a href="https://www.patreon.com/acid_chicken">Acid Chicken</a></td> | ||||
| <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> | ||||
| <td><a href="https://www.patreon.com/spinlock">Naoki Hirayama</a></td> | ||||
| <td><a href="https://www.patreon.com/dansup">dansup</a></td> | ||||
| <td><a href="https://www.patreon.com/mastodon">Gargron</a></td> | ||||
| <td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> | ||||
| </tr></table> | ||||
| <table><tr> | ||||
| </tr><tr> | ||||
| </tr></table> | ||||
|  | ||||
| **Last updated:** Tue, 02 Oct 2018 09:25:07 UTC | ||||
| **Last updated:** Sat, 27 Oct 2018 04:36:06 UTC | ||||
| <!-- PATREON_END --> | ||||
|  | ||||
| :four_leaf_clover: Copyright | ||||
|   | ||||
							
								
								
									
										38
									
								
								appveyor.yml
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								appveyor.yml
									
									
									
									
									
								
							| @@ -1,38 +0,0 @@ | ||||
| # appveyor file | ||||
| # http://www.appveyor.com/docs/appveyor-yml | ||||
|  | ||||
| environment: | ||||
|   matrix: | ||||
|     - nodejs_version: 11.0.0 | ||||
|  | ||||
| platform: | ||||
|   - x64 | ||||
|   - Any CPU | ||||
|  | ||||
| build: off | ||||
|  | ||||
| install: | ||||
|   # Get the latest stable version of Node.js or io.js | ||||
|   - ps: Install-Product node $env:nodejs_version | ||||
|  | ||||
|   # Update node-gyp | ||||
|   # 必須! node-gyp のバージョンを上げないと、ネイティブモジュールのコンパイルに失敗します | ||||
|   - npm install -g node-gyp | ||||
|  | ||||
|   - npm install | ||||
|  | ||||
| init: | ||||
|   # git clone の際の改行を変換しないようにします | ||||
|   - git config --global core.autocrlf false | ||||
|  | ||||
| before_test: | ||||
|   # 設定ファイルを配置 | ||||
|   - cp ./.travis/default.yml ./.config | ||||
|   - cp ./.travis/test.yml ./.config | ||||
|  | ||||
|   - npm run build | ||||
|  | ||||
| test_script: | ||||
|   - node --version | ||||
|   - npm --version | ||||
|   - npm test | ||||
| @@ -1,13 +0,0 @@ | ||||
| const deleteUser = require('../built/models/user').deleteUser; | ||||
|  | ||||
| const args = process.argv.slice(2); | ||||
|  | ||||
| const userId = args[0]; | ||||
|  | ||||
| console.log(`deleting ${userId}...`); | ||||
|  | ||||
| deleteUser(userId).then(() => { | ||||
| 	console.log('done'); | ||||
| }, e => { | ||||
| 	console.error(e); | ||||
| }); | ||||
| @@ -1,23 +0,0 @@ | ||||
| const mongo = require('mongodb'); | ||||
| const User = require('../built/models/user').default; | ||||
|  | ||||
| const args = process.argv.slice(2); | ||||
|  | ||||
| const user = args[0]; | ||||
|  | ||||
| const q = user.startsWith('@') ? { | ||||
| 	username: user.split('@')[1], | ||||
| 	host: user.split('@')[2] || null | ||||
| } : { _id: new mongo.ObjectID(user) }; | ||||
|  | ||||
| console.log(`Mark as verfied ${user}...`); | ||||
|  | ||||
| User.update(q, { | ||||
| 	$set: { | ||||
| 		isVerified: true | ||||
| 	} | ||||
| }).then(() => { | ||||
| 	console.log(`Done ${user}`); | ||||
| }, e => { | ||||
| 	console.error(e); | ||||
| }); | ||||
| @@ -1,42 +0,0 @@ | ||||
| const { default: Note } = require('../built/models/note'); | ||||
| const { default: Meta } = require('../built/models/meta'); | ||||
| const { default: User } = require('../built/models/user'); | ||||
|  | ||||
| async function main() { | ||||
| 	const meta = await Meta.findOne({}); | ||||
|  | ||||
| 	const notesCount = await Note.count(); | ||||
|  | ||||
| 	const usersCount = await User.count(); | ||||
|  | ||||
| 	const originalNotesCount = await Note.count({ | ||||
| 		'_user.host': null | ||||
| 	}); | ||||
|  | ||||
| 	const originalUsersCount = await User.count({ | ||||
| 		host: null | ||||
| 	}); | ||||
|  | ||||
| 	const stats = { | ||||
| 		notesCount, | ||||
| 		usersCount, | ||||
| 		originalNotesCount, | ||||
| 		originalUsersCount | ||||
| 	}; | ||||
|  | ||||
| 	if (meta) { | ||||
| 		await Meta.update({}, { | ||||
| 			$set: { | ||||
| 				stats | ||||
| 			} | ||||
| 		}); | ||||
| 	} else { | ||||
| 		await Meta.insert({ | ||||
| 			stats | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| main().then(() => { | ||||
| 	console.log('done'); | ||||
| }).catch(console.error); | ||||
| @@ -18,6 +18,10 @@ If you find an untranslated part on Misskey: | ||||
| 4. Add the text property using the `foo` keyword below the path that you found or created in step 2. Make sure to type your text in quotation marks. Text should always be inside of quotes. | ||||
| 	-   For example, in this case we add timeline: `timeline: "タイムライン"` to `locales/ja-JP.yml`. | ||||
|  | ||||
| 5. And done! | ||||
| 5. When you add text to the ja-JP file (of syuilo/misskey), it will automatically be applied to all other local language files within 24-48 hours. Translations added in ja-JP file should contain the original Japanese strings (example see step 4).  | ||||
|  | ||||
| 6. The new strings will automatically appear in the localized language files in the original Japanese text. After that, please go to [CrowdIn](https://crowdin.com/project/misskey) to do the localized translations in your language. | ||||
|  | ||||
| 7. And done! | ||||
|  | ||||
| For more details, please refer to this [commit](https://github.com/syuilo/misskey/commit/10f6d5980fa7692ccb45fbc5f843458b69b7607c). | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了しました!" | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了しました!" | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "Bitte Passwort eingeben" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "Settings saved!" | ||||
|   failed: "Failed to setup. Please ensure that the token is correct." | ||||
|   info: "From the next time you sign in to Misskey, the token displayed on your device will be necessary too, as well as the password." | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "To access the API, set this token as the key 'i' of request parameters." | ||||
|   caution: "Do not enter this token to any apps nor tell this token to others otherwise your account may get compromised." | ||||
|   regeneration-of-token: "If your token gets leaked, you can regenerate it." | ||||
|   regenerate-token: "Regenerate the token" | ||||
|   token: "Token:" | ||||
|   enter-password: "Please enter the password" | ||||
|   enter-password: "Enter the password" | ||||
|   console: | ||||
|     title: 'API console' | ||||
|     endpoint: 'Endpoint' | ||||
|     parameter: 'Parameters' | ||||
|     send: 'Send' | ||||
|     sending: 'Sending' | ||||
|     response: 'Result' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "No linked applications" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "Mute" | ||||
|   muted: "Muting" | ||||
|   unmute: "Unmute" | ||||
|   block: "Block" | ||||
|   unblock: "Unblock" | ||||
|   block-confirm: "Are you sure block this user?" | ||||
|   push-to-a-list: "Add to list" | ||||
|   list-pushed: "Successfully added {user} to {list}." | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "Timeline" | ||||
|   media: "Media" | ||||
|   is-suspended: "This account has been suspended." | ||||
|   mute: "Mute" | ||||
|   unmute: "Unmute" | ||||
|   block: "Block" | ||||
|   unblock: "Unblock" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "Recent notes" | ||||
|   images: "Images" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "¡Configuraciones guardadas!" | ||||
|   failed: "Error al configurar. Por favor asegúrate de que el token es correcto." | ||||
|   info: "Desde ahora, ingresa el token que se muestra en tu dispositivo adicionalmente a tu contraseña cuando inicies sesión en Misskey" | ||||
| desktop/views/components/settings.api.vue: | ||||
|   intro: "Para acceder al API, configura este token como la letra \"i\" de los parámetros requeridos." | ||||
|   caution: "Por favor no muestres este token a otros (no lo ingreses en otro lugar que no sea aquí). De otra forma, tu cuenta puede llegar a ser comprometida." | ||||
|   regeneration-of-token: "En el caso no deseado de que este token lo tenga otra persona, puedes regenerarlo." | ||||
|   regenerate-token: "Regenerar el token" | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "Por favor ingresa tu contraseña" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "No hay aplicaciones asociadas" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "L'operation a été complétée avec succès!" | ||||
|   failed: "L'operation a échoué. Veuillez vous assurer que le token a été entrer correctement." | ||||
|   info: "À partir de maintenant, à chaque fois que vous vous connecter entrez votre mot de passe ainsi que le token généré sur votre appareil." | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "Si votre jeton est compromis, vous pouvez le régénérer." | ||||
|   regenerate-token: "Regenerer le token" | ||||
|   token: "Jeton :" | ||||
|   enter-password: "Veuillez entrer le mot de passe" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "Aucune application autorisée" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "Mettre en sourdine" | ||||
|   muted: "Muting" | ||||
|   unmute: "Enlever la sourdine" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "Ajouter à la liste" | ||||
|   list-pushed: "Vous avez ajouté {user} à la liste {list}." | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "Fil d'actualité" | ||||
|   media: "Media" | ||||
|   is-suspended: "This account has been suspended." | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "Notes récentes" | ||||
|   images: "Images" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了しました!" | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -832,7 +832,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "通知" | ||||
|   apps: "アプリ" | ||||
|   mute: "ミュート" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "パスワード" | ||||
| @@ -950,13 +951,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
|  | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
|  | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| @@ -966,8 +974,12 @@ common/views/components/drive-settings.vue: | ||||
|   in-use: "使用中" | ||||
|   stats: "統計" | ||||
|  | ||||
| desktop/views/components/settings.mute.vue: | ||||
|   no-users: "ミュートしているユーザーはいません" | ||||
| common/views/components/mute-and-block.vue: | ||||
|   mute-and-block: "ミュートとブロック" | ||||
|   mute: "ミュート" | ||||
|   block: "ブロック" | ||||
|   no-muted-users: "ミュートしているユーザーはいません" | ||||
|   no-blocked-users: "ブロックしているユーザーはいません" | ||||
|  | ||||
| desktop/views/components/settings.password.vue: | ||||
|   reset: "パスワードを変更する" | ||||
| @@ -1196,6 +1208,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
|  | ||||
| @@ -1524,6 +1539,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
|  | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   | ||||
| @@ -847,18 +847,25 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了したで!" | ||||
|   failed: "なんか設定に失敗したで。トークンを間違えとらんか確認してや。" | ||||
|   info: "次のサインインからは、パスワードに加えてデバイスに出とるトークンを入力してな。" | ||||
| desktop/views/components/settings.api.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」っちゅうキーでパラメータに付加してリクエストしてや。" | ||||
|   caution: "アカウントを不正利用されるかも知れんから、このトークンは第三者に教えたらあかんで(アプリなどにも入力しんといてな)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたとかその可能性があったらトークンを再生成できるで。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "トークン:" | ||||
|   enter-password: "パスワードを入力してや" | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "API使うんやったらこのトークンを「i」っちゅうパラメータにくっつけてリクエストできるで。" | ||||
|   caution: "アカウント勝手にいじられるかも知れんから、このトークンは教えたらあかんし、アプリにも書いたらあかんで(これはフリちゃうで)" | ||||
|   regeneration-of-token: "トークン漏れてもうたんやったらもっかい生成できるで。" | ||||
|   regenerate-token: "トークンもっかい生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入れてや" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送る' | ||||
|     sending: '応答待っとる' | ||||
|     response: 'こんなん返ってきたわ' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはあらへんで" | ||||
| common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   in-use: "使用中" | ||||
|   in-use: "使うとる" | ||||
|   stats: "統計" | ||||
| desktop/views/components/settings.mute.vue: | ||||
|   no-users: "ミュートしているユーザーはおらんで" | ||||
| @@ -938,7 +945,7 @@ desktop/views/pages/admin/admin.vue: | ||||
|   dashboard: "ダッシュボード" | ||||
|   users: "ユーザー" | ||||
|   update: "更新" | ||||
|   announcements: "お知らせ" | ||||
|   announcements: "知っといてや" | ||||
|   hashtags: "ハッシュタグ" | ||||
| desktop/views/pages/admin/admin.dashboard.vue: | ||||
|   dashboard: "ダッシュボード" | ||||
| @@ -967,7 +974,7 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify: "公式アカウントにはさせへんで" | ||||
|   unverified: "公式アカウントを解除したで" | ||||
| desktop/views/pages/admin/admin.announcements.vue: | ||||
|   announcements: "お知らせ" | ||||
|   announcements: "知っといてや" | ||||
| desktop/views/pages/admin/admin.hashtags.vue: | ||||
|   hided-tags: "Hidden Tags" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
| @@ -979,10 +986,10 @@ desktop/views/pages/deck/deck.user-column.vue: | ||||
|   following: "フォロー" | ||||
|   followers: "フォロワー" | ||||
|   images: "画像" | ||||
|   activity: "アクティビティ" | ||||
|   activity: "やっとること" | ||||
|   timeline: "タイムライン" | ||||
|   pinned-notes: "ピン留めされた投稿" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   pinned-notes: "ピン留めしはった投稿" | ||||
|   push-to-a-list: "リストに入れたる" | ||||
| desktop/views/pages/stats/stats.vue: | ||||
|   all-users: "全てのユーザー" | ||||
|   original-users: "ここの人らだけ" | ||||
| @@ -1035,7 +1042,7 @@ desktop/views/pages/user/user.friends.vue: | ||||
|   no-users: "よう話すツレは居らん" | ||||
| desktop/views/pages/user/user.vue: | ||||
|   is-suspended: "このユーザーはあかんわ。凍結されとる。" | ||||
|   last-used-at: "最終アクセス" | ||||
|   last-used-at: "最後いつ来はった?" | ||||
| desktop/views/pages/user/user.photos.vue: | ||||
|   title: "写真" | ||||
|   loading: "読み込んどります" | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしとるで" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加したで。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1117,8 +1127,8 @@ mobile/views/components/drive.file-detail.vue: | ||||
|   hash: "ハッシュ(md5)" | ||||
|   exif: "EXIF" | ||||
|   nsfw: "ちょっと見せられへんわ" | ||||
|   mark-as-sensitive: "閲覧注意に設定" | ||||
|   unmark-as-sensitive: "閲覧注意を解除" | ||||
|   mark-as-sensitive: "見たらあかん感じにしとく" | ||||
|   unmark-as-sensitive: "やっぱ見せたるわ" | ||||
| mobile/views/components/media-image.vue: | ||||
|   sensitive: "見たらあかんで" | ||||
|   click-to-show: "押してみ、見せたるわ" | ||||
| @@ -1312,7 +1322,7 @@ mobile/views/pages/settings.vue: | ||||
|   signout: "さいなら" | ||||
|   sound: "サウンド" | ||||
|   enable-sounds: "サウンド鳴らす" | ||||
|   mark-as-read-all-unread-notes: "すべての投稿を既読にする" | ||||
|   mark-as-read-all-unread-notes: "全部もう読んだわ" | ||||
| mobile/views/pages/user.vue: | ||||
|   follows-you: "フォローされとるで" | ||||
|   following: "フォロー" | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーはあかんわ。凍結されとる。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近儲かりまっか?" | ||||
|   images: "画像" | ||||
| @@ -1369,27 +1383,27 @@ dev/views/index.vue: | ||||
|   manage-apps: "アプリの管理" | ||||
| dev/views/apps.vue: | ||||
|   manage-apps: "アプリを管理" | ||||
|   create-app: "アプリ作成" | ||||
|   app-missing: "アプリなし" | ||||
|   create-app: "アプリ作る" | ||||
|   app-missing: "アプリあらへん" | ||||
| dev/views/new-app.vue: | ||||
|   create-app: "アプリケーションの作成" | ||||
|   app-name: "アプリケーション名" | ||||
|   app-name-desc: "あなたのアプリの名称。" | ||||
|   app-name-ex: "ex) Misskey for iOS" | ||||
|   app-overview: "アプリの概要" | ||||
|   app-desc: "あなたのアプリの簡単な説明や紹介。" | ||||
|   app-desc-ex: "ex) Misskey iOSクライアント。" | ||||
|   callback-url: "コールバックURL (オプション)" | ||||
|   callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。" | ||||
|   create-app: "アプリケーション作る" | ||||
|   app-name: "アプリケーションの名前" | ||||
|   app-name-desc: "あんたのアプリの名前。" | ||||
|   app-name-ex: "ex) 関西ミスキー保安協会" | ||||
|   app-overview: "このアプリどんなん?" | ||||
|   app-desc: "あんたのアプリどんなんか教えて" | ||||
|   app-desc-ex: "ex) 関西人なら誰でも口ずさめるこのCMがついにMisskeyへ。" | ||||
|   callback-url: "コールバックURL (無くてもええで)" | ||||
|   callback-url-desc: "ユーザーが認証フォームで認証した後どこに連れてくかを設定できるで" | ||||
|   authority: "権限" | ||||
|   authority-desc: "ここで要求した機能だけがAPIからアクセスできます。" | ||||
|   authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。" | ||||
|   account-read: "アカウントの情報を見る。" | ||||
|   account-write: "アカウントの情報を操作する。" | ||||
|   note-write: "投稿する。" | ||||
|   reaction-write: "リアクションしたりリアクションをキャンセルする。" | ||||
|   following-write: "フォローしたりフォロー解除する。" | ||||
|   drive-read: "ドライブを見る。" | ||||
|   drive-write: "ドライブを操作する。" | ||||
|   notification-read: "通知を見る。" | ||||
|   notification-write: "通知を操作する。" | ||||
|   authority-desc: "ここにチェックした機能しかAPIからアクセスできひんから気ぃつけてな" | ||||
|   authority-warning: "アプリ作った後でも変えれるけど、新しいやつ追加したらそん時関連付いてるユーザーキーは全部ほかされるで。" | ||||
|   account-read: "アカウントの情報見せて" | ||||
|   account-write: "アカウントの情報いじらせて" | ||||
|   note-write: "投稿させて" | ||||
|   reaction-write: "リアクションしたりそれをキャンセルさせて" | ||||
|   following-write: "フォローとかフォロー解除させて" | ||||
|   drive-read: "ドライブ見せて" | ||||
|   drive-write: "ドライブいじらせて" | ||||
|   notification-read: "通知見せて" | ||||
|   notification-write: "通知いじらせて" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了しました!" | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "Instellen voltooid!" | ||||
|   failed: "Instellen mislukt. Zorg ervoor dat de sleutel juist is." | ||||
|   info: "Vanaf nu moet je ook de op je apparaat getoonde sleutel tonen bij het inloggen op Misskey." | ||||
| desktop/views/components/settings.api.vue: | ||||
|   intro: "Als je toegang wilt tot de API, stel deze sleutel dan in als 'i' bij de verzoekparameters." | ||||
|   caution: "Laat deze sleutel niet zien aan derde partijen (en voer hem nergens anders in dan hier), anders kan je account gehackt worden." | ||||
|   regeneration-of-token: "Mocht deze sleutel tóch uitlekken, dan kun je hem opnieuw genereren." | ||||
|   regenerate-token: "Sleutel opnieuw genereren" | ||||
|   token: "Sleutel:" | ||||
|   enter-password: "Voer je wachtwoord in" | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "Dempen" | ||||
|   muted: "Dempend" | ||||
|   unmute: "Ontdempen" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "Tijdlijn" | ||||
|   media: "Media" | ||||
|   is-suspended: "Dit account is geschorst." | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "Recente notities" | ||||
|   images: "Afbeeldingen" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了しました!" | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "Media" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "Nylige innlegg" | ||||
|   images: "Bilder" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "Pomyślnie ukończono konfigurację!" | ||||
|   failed: "Nie udało się skonfigurować uwierzytelniania dwuetapowego, upewnij się że wprowadziłeś prawidłowy token." | ||||
|   info: "Od teraz, wprowadzaj token wyświetlany na urządzeniu przy każdym logowaniu do Misskey." | ||||
| desktop/views/components/settings.api.vue: | ||||
|   intro: "Aby uzyskać dostęp do API, ustaw ten token jako klucz 'i' parametrów żądań." | ||||
|   caution: "Nie pokazuj tego tokenu osobom trzecim (nie wprowadzaj go nigdzie indziej), aby konto nie trafiło w niepowołane ręce." | ||||
|   regeneration-of-token: "W przypadku wycieku tokenu, możesz wygenerować nowy." | ||||
|   regenerate-token: "Wygeneruj nowy token" | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "Wprowadź hasło" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "Brak zautoryzowanych aplikacji" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "Wycisz" | ||||
|   muted: "Wyciszyłeś" | ||||
|   unmute: "Cofnij wyciszenie" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "Dodaj do listy" | ||||
|   list-pushed: "Dodałeś(-aś) {user} do {list}." | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "Oś czasu" | ||||
|   media: "Multimedia" | ||||
|   is-suspended: "To konto zostało zablokowane" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "Ostatnie wpisy" | ||||
|   images: "Zdjęcia" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了しました!" | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "Linha do tempo" | ||||
|   media: "Mídia" | ||||
|   is-suspended: "Esta conta foi suspensa" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "Notas recentes" | ||||
|   images: "Imagens" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了しました!" | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -847,13 +847,20 @@ desktop/views/components/settings.2fa.vue: | ||||
|   success: "設定が完了しました!" | ||||
|   failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" | ||||
|   info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" | ||||
| desktop/views/components/settings.api.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはありません" | ||||
| common/views/components/drive-settings.vue: | ||||
| @@ -1048,6 +1055,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "ミュートする" | ||||
|   muted: "ミュートしています" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロックする" | ||||
|   unblock: "ブロック解除" | ||||
|   block-confirm: "このユーザーをブロックしますか?" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   list-pushed: "{user}を{list}に追加しました。" | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1322,6 +1332,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
							
								
								
									
										62
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										62
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"version": "10.31.0", | ||||
| 	"version": "10.33.0", | ||||
| 	"lockfileVersion": 1, | ||||
| 	"requires": true, | ||||
| 	"dependencies": { | ||||
| @@ -544,9 +544,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"@types/mocha": { | ||||
| 			"version": "5.2.3", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.3.tgz", | ||||
| 			"integrity": "sha512-C1wVVr7xhKu6c3Mb27dFzNYR05qvHwgtpN+JOYTGc1pKA7dCEDDYpscn7kul+bCUwa3NoGDbzI1pdznSOa397w==" | ||||
| 			"version": "5.2.5", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz", | ||||
| 			"integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==" | ||||
| 		}, | ||||
| 		"@types/mongodb": { | ||||
| 			"version": "3.1.12", | ||||
| @@ -1259,9 +1259,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"apexcharts": { | ||||
| 			"version": "2.1.5", | ||||
| 			"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-2.1.5.tgz", | ||||
| 			"integrity": "sha512-4eKh2HyQVr5ct2t7cWkvDSUyJM9KGw6dRHAlojqo6HJz+XtrnnvL8uP18rzNq4M80P3Ul+yDjpCt5EXlYPfo5Q==", | ||||
| 			"version": "2.1.6", | ||||
| 			"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-2.1.6.tgz", | ||||
| 			"integrity": "sha512-kIb4Q07bWwTGuTWhyzhDAOz6nrltDgyP8VUUwqetxr0o11mNH6PA6YVnR/e9nyd9HU6q3bFZN8eVuSatnqdxAQ==", | ||||
| 			"requires": { | ||||
| 				"babel-polyfill": "^6.26.0", | ||||
| 				"core-js": "^2.5.7", | ||||
| @@ -4755,9 +4755,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"eslint": { | ||||
| 			"version": "5.7.0", | ||||
| 			"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.7.0.tgz", | ||||
| 			"integrity": "sha512-zYCeFQahsxffGl87U2aJ7DPyH8CbWgxBC213Y8+TCanhUTf2gEvfq3EKpHmEcozTLyPmGe9LZdMAwC/CpJBM5A==", | ||||
| 			"version": "5.8.0", | ||||
| 			"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.8.0.tgz", | ||||
| 			"integrity": "sha512-Zok6Bru3y2JprqTNm14mgQ15YQu/SMDkWdnmHfFg770DIUlmMFd/gqqzCHekxzjHZJxXv3tmTpH0C1icaYJsRQ==", | ||||
| 			"requires": { | ||||
| 				"@babel/code-frame": "^7.0.0", | ||||
| 				"ajv": "^6.5.3", | ||||
| @@ -7224,14 +7224,14 @@ | ||||
| 			"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" | ||||
| 		}, | ||||
| 		"html-minifier": { | ||||
| 			"version": "3.5.20", | ||||
| 			"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", | ||||
| 			"integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", | ||||
| 			"version": "3.5.21", | ||||
| 			"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", | ||||
| 			"integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", | ||||
| 			"requires": { | ||||
| 				"camel-case": "3.0.x", | ||||
| 				"clean-css": "4.2.x", | ||||
| 				"commander": "2.17.x", | ||||
| 				"he": "1.1.x", | ||||
| 				"he": "1.2.x", | ||||
| 				"param-case": "2.1.x", | ||||
| 				"relateurl": "0.2.x", | ||||
| 				"uglify-js": "3.4.x" | ||||
| @@ -7242,14 +7242,10 @@ | ||||
| 					"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", | ||||
| 					"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" | ||||
| 				}, | ||||
| 				"uglify-js": { | ||||
| 					"version": "3.4.9", | ||||
| 					"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", | ||||
| 					"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", | ||||
| 					"requires": { | ||||
| 						"commander": "~2.17.1", | ||||
| 						"source-map": "~0.6.1" | ||||
| 					} | ||||
| 				"he": { | ||||
| 					"version": "1.2.0", | ||||
| 					"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", | ||||
| 					"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| @@ -15911,6 +15907,22 @@ | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"uglify-js": { | ||||
| 			"version": "3.4.9", | ||||
| 			"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", | ||||
| 			"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", | ||||
| 			"requires": { | ||||
| 				"commander": "~2.17.1", | ||||
| 				"source-map": "~0.6.1" | ||||
| 			}, | ||||
| 			"dependencies": { | ||||
| 				"commander": { | ||||
| 					"version": "2.17.1", | ||||
| 					"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", | ||||
| 					"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"uglify-to-browserify": { | ||||
| 			"version": "1.0.2", | ||||
| 			"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", | ||||
| @@ -16808,9 +16820,9 @@ | ||||
| 			"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" | ||||
| 		}, | ||||
| 		"webpack": { | ||||
| 			"version": "4.23.0", | ||||
| 			"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.0.tgz", | ||||
| 			"integrity": "sha512-Osh/3U9y4swhEKDjy8fF48v2Qx5VC6VvdQ8bEm1HMaVVddiQBw4+mIyDrzVcVRCPT/+4uJFOcklPuoB+I3Zw0w==", | ||||
| 			"version": "4.23.1", | ||||
| 			"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.1.tgz", | ||||
| 			"integrity": "sha512-iE5Cu4rGEDk7ONRjisTOjVHv3dDtcFfwitSxT7evtYj/rANJpt1OuC/Kozh1pBa99AUBr1L/LsaNB+D9Xz3CEg==", | ||||
| 			"requires": { | ||||
| 				"@webassemblyjs/ast": "1.7.10", | ||||
| 				"@webassemblyjs/helper-module-context": "1.7.10", | ||||
|   | ||||
							
								
								
									
										21
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <i@syuilo.com>", | ||||
| 	"version": "10.32.0", | ||||
| 	"clientVersion": "1.0.11138", | ||||
| 	"version": "10.35.0", | ||||
| 	"clientVersion": "1.0.11255", | ||||
| 	"codename": "nighthike", | ||||
| 	"main": "./built/index.js", | ||||
| 	"private": true, | ||||
| @@ -58,7 +58,7 @@ | ||||
| 		"@types/koa__cors": "2.2.3", | ||||
| 		"@types/minio": "7.0.0", | ||||
| 		"@types/mkdirp": "0.5.2", | ||||
| 		"@types/mocha": "5.2.3", | ||||
| 		"@types/mocha": "5.2.5", | ||||
| 		"@types/mongodb": "3.1.12", | ||||
| 		"@types/ms": "0.7.30", | ||||
| 		"@types/node": "10.12.0", | ||||
| @@ -84,7 +84,7 @@ | ||||
| 		"@types/websocket": "0.0.40", | ||||
| 		"@types/ws": "6.0.1", | ||||
| 		"animejs": "2.2.0", | ||||
| 		"apexcharts": "2.1.5", | ||||
| 		"apexcharts": "2.1.6", | ||||
| 		"autobind-decorator": "2.1.0", | ||||
| 		"autosize": "4.0.2", | ||||
| 		"autwh": "0.1.0", | ||||
| @@ -108,7 +108,7 @@ | ||||
| 		"elasticsearch": "15.1.1", | ||||
| 		"emojilib": "2.3.0", | ||||
| 		"escape-regexp": "0.0.1", | ||||
| 		"eslint": "5.7.0", | ||||
| 		"eslint": "5.8.0", | ||||
| 		"eslint-plugin-vue": "4.7.1", | ||||
| 		"eventemitter3": "3.1.0", | ||||
| 		"file-loader": "2.0.0", | ||||
| @@ -129,7 +129,7 @@ | ||||
| 		"gulp-uglify": "3.0.1", | ||||
| 		"gulp-util": "3.0.8", | ||||
| 		"hard-source-webpack-plugin": "0.12.0", | ||||
| 		"html-minifier": "3.5.20", | ||||
| 		"html-minifier": "3.5.21", | ||||
| 		"http-signature": "1.2.0", | ||||
| 		"insert-text-at-cursor": "0.1.1", | ||||
| 		"is-root": "2.0.0", | ||||
| @@ -229,17 +229,10 @@ | ||||
| 		"vuex-persistedstate": "2.5.4", | ||||
| 		"web-push": "3.3.3", | ||||
| 		"webfinger.js": "2.6.6", | ||||
| 		"webpack": "4.23.0", | ||||
| 		"webpack": "4.23.1", | ||||
| 		"webpack-cli": "3.1.2", | ||||
| 		"websocket": "1.0.28", | ||||
| 		"ws": "6.1.0", | ||||
| 		"xev": "2.0.1" | ||||
| 	}, | ||||
| 	"greenkeeper": { | ||||
| 		"ignore": [ | ||||
| 			"deepcopy", | ||||
| 			"cafy", | ||||
| 			"@types/gulp" | ||||
| 		] | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										72
									
								
								src/client/app/common/views/components/api-settings.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/client/app/common/views/components/api-settings.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| <template> | ||||
| <ui-card> | ||||
| 	<div slot="title">%fa:key% API</div> | ||||
|  | ||||
| 	<section class="fit-top"> | ||||
| 		<ui-input :value="$store.state.i.token" readonly> | ||||
| 			<span>%i18n:@token%</span> | ||||
| 		</ui-input> | ||||
| 		<p>%i18n:@intro%</p> | ||||
| 		<ui-info warn>%i18n:@caution%</ui-info> | ||||
| 		<p>%i18n:@regeneration-of-token%</p> | ||||
| 		<ui-button @click="regenerateToken">%fa:sync-alt% %i18n:@regenerate-token%</ui-button> | ||||
| 	</section> | ||||
|  | ||||
| 	<section> | ||||
| 		<header>%fa:terminal% %i18n:@console.title%</header> | ||||
| 		<ui-input v-model="endpoint"> | ||||
| 			<span>%i18n:@console.endpoint%</span> | ||||
| 		</ui-input> | ||||
| 		<ui-textarea v-model="body"> | ||||
| 			<span>%i18n:@console.parameter% (JSON or JSON5)</span> | ||||
| 		</ui-textarea> | ||||
| 		<ui-button @click="send" :disabled="sending"> | ||||
| 			<template v-if="sending">%i18n:@console.sending%</template> | ||||
| 			<template v-else>%fa:paper-plane% %i18n:@console.send%</template> | ||||
| 		</ui-button> | ||||
| 		<ui-textarea v-if="res" v-model="res" readonly tall> | ||||
| 			<span>%i18n:@console.response%</span> | ||||
| 		</ui-textarea> | ||||
| 	</section> | ||||
| </ui-card> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as JSON5 from 'json5'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			endpoint: '', | ||||
| 			body: '{}', | ||||
| 			res: null, | ||||
| 			sending: false | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		regenerateToken() { | ||||
| 			(this as any).apis.input({ | ||||
| 				title: '%i18n:@enter-password%', | ||||
| 				type: 'password' | ||||
| 			}).then(password => { | ||||
| 				(this as any).api('i/regenerate_token', { | ||||
| 					password: password | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		send() { | ||||
| 			this.sending = true; | ||||
| 			(this as any).api(this.endpoint, JSON5.parse(this.body)).then(res => { | ||||
| 				this.sending = false; | ||||
| 				this.res = JSON5.stringify(res, null, 2); | ||||
| 			}, err => { | ||||
| 				this.sending = false; | ||||
| 				this.res = JSON5.stringify(err, null, 2); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
							
								
								
									
										19
									
								
								src/client/app/common/views/components/error.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/client/app/common/views/components/error.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <template> | ||||
| <div class="wjqjnyhzogztorhrdgcpqlkxhkmuetgj"> | ||||
| 	<p>%fa:exclamation-triangle% %i18n:common.error.title%</p> | ||||
| 	<ui-button @click="() => $emit('retry')">%i18n:common.error.retry%</ui-button> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .wjqjnyhzogztorhrdgcpqlkxhkmuetgj | ||||
| 	max-width 350px | ||||
| 	margin 0 auto | ||||
| 	padding 32px | ||||
| 	text-align center | ||||
| 	color var(--text) | ||||
|  | ||||
| 	> p | ||||
| 		margin 0 0 8px 0 | ||||
|  | ||||
| </style> | ||||
| @@ -1,5 +1,8 @@ | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| import muteAndBlock from './mute-and-block.vue'; | ||||
| import error from './error.vue'; | ||||
| import apiSettings from './api-settings.vue'; | ||||
| import driveSettings from './drive-settings.vue'; | ||||
| import profileEditor from './profile-editor.vue'; | ||||
| import noteSkeleton from './note-skeleton.vue'; | ||||
| @@ -44,9 +47,13 @@ import uiTextarea from './ui/textarea.vue'; | ||||
| import uiSwitch from './ui/switch.vue'; | ||||
| import uiRadio from './ui/radio.vue'; | ||||
| import uiSelect from './ui/select.vue'; | ||||
| import uiInfo from './ui/info.vue'; | ||||
| import formButton from './ui/form/button.vue'; | ||||
| import formRadio from './ui/form/radio.vue'; | ||||
|  | ||||
| Vue.component('mk-mute-and-block', muteAndBlock); | ||||
| Vue.component('mk-error', error); | ||||
| Vue.component('mk-api-settings', apiSettings); | ||||
| Vue.component('mk-drive-settings', driveSettings); | ||||
| Vue.component('mk-profile-editor', profileEditor); | ||||
| Vue.component('mk-note-skeleton', noteSkeleton); | ||||
| @@ -91,5 +98,6 @@ Vue.component('ui-textarea', uiTextarea); | ||||
| Vue.component('ui-switch', uiSwitch); | ||||
| Vue.component('ui-radio', uiRadio); | ||||
| Vue.component('ui-select', uiSelect); | ||||
| Vue.component('ui-info', uiInfo); | ||||
| Vue.component('form-button', formButton); | ||||
| Vue.component('form-radio', formRadio); | ||||
|   | ||||
							
								
								
									
										56
									
								
								src/client/app/common/views/components/mute-and-block.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/client/app/common/views/components/mute-and-block.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| <template> | ||||
| <ui-card> | ||||
| 	<div slot="title">%fa:ban% %i18n:@mute-and-block%</div> | ||||
|  | ||||
| 	<section> | ||||
| 		<header>%i18n:@mute%</header> | ||||
| 		<ui-info v-if="!muteFetching && mute.length == 0"> | ||||
| 			<p>%i18n:@no-muted-users%</p> | ||||
| 		</ui-info> | ||||
| 		<div class="users" v-if="mute.length != 0"> | ||||
| 			<div v-for="user in mute" :key="user.id"> | ||||
| 				<p><b>{{ user | userName }}</b> @{{ user | acct }}</p> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</section> | ||||
|  | ||||
| 	<section> | ||||
| 		<header>%i18n:@block%</header> | ||||
| 		<ui-info v-if="!blockFetching && block.length == 0"> | ||||
| 			<p>%i18n:@no-blocked-users%</p> | ||||
| 		</ui-info> | ||||
| 		<div class="users" v-if="block.length != 0"> | ||||
| 			<div v-for="user in block" :key="user.id"> | ||||
| 				<p><b>{{ user | userName }}</b> @{{ user | acct }}</p> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</section> | ||||
| </ui-card> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			muteFetching: true, | ||||
| 			blockFetching: true, | ||||
| 			mute: [], | ||||
| 			block: [] | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		(this as any).api('mute/list').then(mute => { | ||||
| 			this.mute = mute.map(x => x.mutee); | ||||
| 			this.muteFetching = false; | ||||
| 		}); | ||||
|  | ||||
| 		(this as any).api('blocking/list').then(blocking => { | ||||
| 			this.block = blocking.map(x => x.blockee); | ||||
| 			this.blockFetching = false; | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -2,11 +2,11 @@ | ||||
| <header class="bvonvjxbwzaiskogyhbwgyxvcgserpmu"> | ||||
| 	<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle == 'smart'"/> | ||||
| 	<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link> | ||||
| 	<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:star%</span> | ||||
| 	<span class="is-admin" v-if="note.user.isAdmin">admin</span> | ||||
| 	<span class="is-bot" v-if="note.user.isBot">bot</span> | ||||
| 	<span class="is-cat" v-if="note.user.isCat">cat</span> | ||||
| 	<span class="username"><mk-acct :user="note.user"/></span> | ||||
| 	<span class="is-verified" v-if="note.user.isVerified" title="%i18n:common.verified-user%">%fa:star%</span> | ||||
| 	<div class="info"> | ||||
| 		<span class="app" v-if="note.app && !mini">via <b>{{ note.app.name }}</b></span> | ||||
| 		<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span> | ||||
| @@ -68,10 +68,6 @@ export default Vue.extend({ | ||||
| 		&:hover | ||||
| 			text-decoration underline | ||||
|  | ||||
| 	> .is-verified | ||||
| 		margin-right 8px | ||||
| 		color #4dabf7 | ||||
|  | ||||
| 	> .is-admin | ||||
| 	> .is-bot | ||||
| 	> .is-cat | ||||
| @@ -95,6 +91,10 @@ export default Vue.extend({ | ||||
| 		color var(--noteHeaderAcct) | ||||
| 		flex-shrink 2147483647 | ||||
|  | ||||
| 	> .is-verified | ||||
| 		margin 0 .5em 0 0 | ||||
| 		color #4dabf7 | ||||
|  | ||||
| 	> .info | ||||
| 		margin-left auto | ||||
| 		font-size 0.9em | ||||
|   | ||||
							
								
								
									
										33
									
								
								src/client/app/common/views/components/ui/info.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/client/app/common/views/components/ui/info.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <template> | ||||
| <div class="ymxyweixqwsxauxldgpvecjepnwxbylu" :class="{ warn }"> | ||||
| 	<i v-if="warn">%fa:exclamation-triangle%</i> | ||||
| 	<slot></slot> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		warn: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .ymxyweixqwsxauxldgpvecjepnwxbylu | ||||
| 	margin 16px 0 | ||||
| 	padding 16px | ||||
| 	font-size 90% | ||||
|  | ||||
| 	> i | ||||
| 		margin-right 4px | ||||
|  | ||||
| 	&.warn | ||||
| 		background var(--infoWarnBg) | ||||
| 		color var(--infoWarnFg) | ||||
| </style> | ||||
| @@ -1,17 +1,17 @@ | ||||
| <template> | ||||
| <div class="ui-textarea" :class="{ focused, filled }"> | ||||
| <div class="ui-textarea" :class="{ focused, filled, tall }"> | ||||
| 	<div class="input"> | ||||
| 		<span class="label" ref="label"><slot></slot></span> | ||||
| 		<textarea ref="input" | ||||
| 				:value="value" | ||||
| 				:required="required" | ||||
| 				:readonly="readonly" | ||||
| 				:pattern="pattern" | ||||
| 				:autocomplete="autocomplete" | ||||
| 				@input="$emit('input', $event.target.value)" | ||||
| 				@focus="focused = true" | ||||
| 				@blur="focused = false"> | ||||
| 		</textarea> | ||||
| 			:value="value" | ||||
| 			:required="required" | ||||
| 			:readonly="readonly" | ||||
| 			:pattern="pattern" | ||||
| 			:autocomplete="autocomplete" | ||||
| 			@input="$emit('input', $event.target.value)" | ||||
| 			@focus="focused = true" | ||||
| 			@blur="focused = false" | ||||
| 		></textarea> | ||||
| 	</div> | ||||
| 	<div class="text"><slot name="text"></slot></div> | ||||
| </div> | ||||
| @@ -41,7 +41,12 @@ export default Vue.extend({ | ||||
| 		autocomplete: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		} | ||||
| 		}, | ||||
| 		tall: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| @@ -66,6 +71,9 @@ export default Vue.extend({ | ||||
| root(fill) | ||||
| 	margin 42px 0 32px 0 | ||||
|  | ||||
| 	&:last-child | ||||
| 		margin-bottom 0 | ||||
|  | ||||
| 	> .input | ||||
| 		padding 12px | ||||
|  | ||||
| @@ -157,6 +165,11 @@ root(fill) | ||||
| 				left 0 !important | ||||
| 				transform scale(0.75) | ||||
|  | ||||
| 	&.tall | ||||
| 		> .input | ||||
| 			> textarea | ||||
| 				min-height 200px | ||||
|  | ||||
| .ui-textarea.fill | ||||
| 	root(true) | ||||
|  | ||||
|   | ||||
| @@ -67,12 +67,12 @@ export default Vue.extend({ | ||||
| 				text: '%i18n:@contextmenu.rename%', | ||||
| 				icon: '%fa:i-cursor%', | ||||
| 				action: this.rename | ||||
| 			}/*, null, { | ||||
| 			}, null, { | ||||
| 				type: 'item', | ||||
| 				text: '%i18n:common.delete%', | ||||
| 				icon: '%fa:R trash-alt%', | ||||
| 				action: this.deleteFolder | ||||
| 			}*/], { | ||||
| 			}], { | ||||
| 					closed: () => { | ||||
| 						this.isContextmenuShowing = false; | ||||
| 					} | ||||
| @@ -207,7 +207,9 @@ export default Vue.extend({ | ||||
| 		}, | ||||
|  | ||||
| 		deleteFolder() { | ||||
| 			alert('not implemented yet'); | ||||
| 			(this as any).api('drive/folders/delete', { | ||||
| 				folderId: this.folder.id | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|   | ||||
| @@ -98,7 +98,7 @@ export default Vue.extend({ | ||||
| 			hierarchyFolders: [], | ||||
| 			selectedFiles: [], | ||||
| 			uploadings: [], | ||||
| 			connection: null | ||||
| 			connection: null, | ||||
|  | ||||
| 			/** | ||||
| 			 * ドロップされようとしているか | ||||
| @@ -122,6 +122,7 @@ export default Vue.extend({ | ||||
| 		this.connection.on('fileDeleted', this.onStreamDriveFileDeleted); | ||||
| 		this.connection.on('folderCreated', this.onStreamDriveFolderCreated); | ||||
| 		this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated); | ||||
| 		this.connection.on('folderDeleted', this.onStreamDriveFolderDeleted); | ||||
|  | ||||
| 		if (this.initFolder) { | ||||
| 			this.move(this.initFolder); | ||||
| @@ -182,6 +183,10 @@ export default Vue.extend({ | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onStreamDriveFolderDeleted(folderId) { | ||||
| 			this.removeFolder(folderId); | ||||
| 		}, | ||||
|  | ||||
| 		onChangeUploaderUploads(uploads) { | ||||
| 			this.uploadings = uploads; | ||||
| 		}, | ||||
|   | ||||
| @@ -40,8 +40,8 @@ export default Vue.extend({ | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.connection = (this as any).os.stream.useSharedConnection('main'); | ||||
| 		this.connection.on('follow', this.onFollow); | ||||
| 		this.connection.on('unfollow', this.onUnfollow); | ||||
| 		this.connection.on('follow', this.onFollowChange); | ||||
| 		this.connection.on('unfollow', this.onFollowChange); | ||||
| 	}, | ||||
|  | ||||
| 	beforeDestroy() { | ||||
| @@ -49,17 +49,11 @@ export default Vue.extend({ | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		onFollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onUnfollow(user) { | ||||
| 		onFollowChange(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 				this.$forceUpdate(); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
|   | ||||
| @@ -4,10 +4,7 @@ | ||||
|  | ||||
| 	<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot> | ||||
|  | ||||
| 	<div v-if="!fetching && requestInitPromise != null" class="error"> | ||||
| 		<p>%fa:exclamation-triangle% %i18n:common.error.title%</p> | ||||
| 		<ui-button @click="resolveInitPromise">%i18n:common.error.retry%</ui-button> | ||||
| 	</div> | ||||
| 	<mk-error v-if="!fetching && requestInitPromise != null" @retry="resolveInitPromise"/> | ||||
|  | ||||
| 	<div class="placeholder" v-if="fetching"> | ||||
| 		<template v-for="i in 10"> | ||||
| @@ -215,16 +212,6 @@ export default Vue.extend({ | ||||
| 		> * | ||||
| 			transition transform .3s ease, opacity .3s ease | ||||
|  | ||||
| 	> .error | ||||
| 		max-width 300px | ||||
| 		margin 0 auto | ||||
| 		padding 32px | ||||
| 		text-align center | ||||
| 		color var(--text) | ||||
|  | ||||
| 		> p | ||||
| 			margin 0 0 8px 0 | ||||
|  | ||||
| 	> .placeholder | ||||
| 		padding 32px | ||||
| 		opacity 0.3 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <template> | ||||
| <div class="2fa"> | ||||
| 	<p>%i18n:@intro%<a href="%i18n:@url%" target="_blank">%i18n:@detail%</a></p> | ||||
| 	<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:@caution%</p></div> | ||||
| 	<p style="margin-top:0;">%i18n:@intro%<a href="%i18n:@url%" target="_blank">%i18n:@detail%</a></p> | ||||
| 	<ui-info warn>%i18n:@caution%</ui-info> | ||||
| 	<p v-if="!data && !$store.state.i.twoFactorEnabled"><ui-button @click="register">%i18n:@register%</ui-button></p> | ||||
| 	<template v-if="$store.state.i.twoFactorEnabled"> | ||||
| 		<p>%i18n:@already-registered%</p> | ||||
| @@ -72,9 +72,3 @@ export default Vue.extend({ | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .2fa | ||||
| 	color #4a535a | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -1,40 +0,0 @@ | ||||
| <template> | ||||
| <div class="root api"> | ||||
| 	<ui-input :value="$store.state.i.token" readonly> | ||||
| 		<span>%i18n:@token%</span> | ||||
| 	</ui-input> | ||||
| 	<p>%i18n:@intro%</p> | ||||
| 	<div class="ui info warn"><p>%fa:exclamation-triangle%%i18n:@caution%</p></div> | ||||
| 	<p>%i18n:@regeneration-of-token%</p> | ||||
| 	<ui-button @click="regenerateToken">%i18n:@regenerate-token%</ui-button> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	methods: { | ||||
| 		regenerateToken() { | ||||
| 			(this as any).apis.input({ | ||||
| 				title: '%i18n:@enter-password%', | ||||
| 				type: 'password' | ||||
| 			}).then(password => { | ||||
| 				(this as any).api('i/regenerate_token', { | ||||
| 					password: password | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .root.api | ||||
| 	code | ||||
| 		display inline-block | ||||
| 		padding 4px 6px | ||||
| 		color #555 | ||||
| 		background #eee | ||||
| 		border-radius 2px | ||||
| </style> | ||||
| @@ -1,31 +0,0 @@ | ||||
| <template> | ||||
| <div> | ||||
| 	<div class="none ui info" v-if="!fetching && users.length == 0"> | ||||
| 		<p>%fa:info-circle%%i18n:@no-users%</p> | ||||
| 	</div> | ||||
| 	<div class="users" v-if="users.length != 0"> | ||||
| 		<div v-for="user in users" :key="user.id"> | ||||
| 			<p><b>{{ user | userName }}</b> @{{ user | acct }}</p> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			users: [] | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).api('mute/list').then(x => { | ||||
| 			this.users = x.users; | ||||
| 			this.fetching = false; | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -7,7 +7,7 @@ | ||||
| 		<p :class="{ active: page == 'notification' }" @mousedown="page = 'notification'">%fa:R bell .fw%%i18n:@notification%</p> | ||||
| 		<p :class="{ active: page == 'drive' }" @mousedown="page = 'drive'">%fa:cloud .fw%%i18n:common.drive%</p> | ||||
| 		<p :class="{ active: page == 'hashtags' }" @mousedown="page = 'hashtags'">%fa:hashtag .fw%%i18n:@tags%</p> | ||||
| 		<p :class="{ active: page == 'mute' }" @mousedown="page = 'mute'">%fa:ban .fw%%i18n:@mute%</p> | ||||
| 		<p :class="{ active: page == 'muteAndBlock' }" @mousedown="page = 'muteAndBlock'">%fa:ban .fw%%i18n:@mute-and-block%</p> | ||||
| 		<p :class="{ active: page == 'apps' }" @mousedown="page = 'apps'">%fa:puzzle-piece .fw%%i18n:@apps%</p> | ||||
| 		<p :class="{ active: page == 'security' }" @mousedown="page = 'security'">%fa:unlock-alt .fw%%i18n:@security%</p> | ||||
| 		<p :class="{ active: page == 'api' }" @mousedown="page = 'api'">%fa:key .fw%API</p> | ||||
| @@ -200,12 +200,9 @@ | ||||
| 			</section> | ||||
| 		</ui-card> | ||||
|  | ||||
| 		<ui-card class="mute" v-show="page == 'mute'"> | ||||
| 			<div slot="title">%fa:ban% %i18n:@mute%</div> | ||||
| 			<section> | ||||
| 				<x-mute/> | ||||
| 			</section> | ||||
| 		</ui-card> | ||||
| 		<div class="muteAndBlock" v-show="page == 'muteAndBlock'"> | ||||
| 			<mk-mute-and-block/> | ||||
| 		</div> | ||||
|  | ||||
| 		<ui-card class="apps" v-show="page == 'apps'"> | ||||
| 			<div slot="title">%fa:puzzle-piece% %i18n:@apps%</div> | ||||
| @@ -235,12 +232,9 @@ | ||||
| 			</section> | ||||
| 		</ui-card> | ||||
|  | ||||
| 		<ui-card class="api" v-show="page == 'api'"> | ||||
| 			<div slot="title">%fa:key% API</div> | ||||
| 			<section class="fit-top"> | ||||
| 				<x-api/> | ||||
| 			</section> | ||||
| 		</ui-card> | ||||
| 		<div class="api" v-show="page == 'api'"> | ||||
| 			<mk-api-settings/> | ||||
| 		</div> | ||||
|  | ||||
| 		<ui-card class="other" v-show="page == 'other'"> | ||||
| 			<div slot="title">%fa:info-circle% %i18n:@about%</div> | ||||
| @@ -292,10 +286,8 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import XMute from './settings.mute.vue'; | ||||
| import XPassword from './settings.password.vue'; | ||||
| import X2fa from './settings.2fa.vue'; | ||||
| import XApi from './settings.api.vue'; | ||||
| import XApps from './settings.apps.vue'; | ||||
| import XSignins from './settings.signins.vue'; | ||||
| import XTags from './settings.tags.vue'; | ||||
| @@ -304,10 +296,8 @@ import checkForUpdate from '../../../common/scripts/check-for-update'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XMute, | ||||
| 		XPassword, | ||||
| 		X2fa, | ||||
| 		XApi, | ||||
| 		XApps, | ||||
| 		XSignins, | ||||
| 		XTags | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
| <div class="dnpfarvgbnfmyzbdquhhzyxcmstpdqzs" :class="{ naked, narrow, active, isStacked, draghover, dragging, dropready }" | ||||
| 		@dragover.prevent.stop="onDragover" | ||||
| 		@dragenter.prevent.stop="onDragenter" | ||||
| 		@dragleave="onDragleave" | ||||
| 		@drop.prevent.stop="onDrop" | ||||
| 		v-hotkey="keymap"> | ||||
| @@ -269,7 +269,7 @@ export default Vue.extend({ | ||||
| 			this.dragging = false; | ||||
| 		}, | ||||
|  | ||||
| 		onDragover(e) { | ||||
| 		onDragenter(e) { | ||||
| 			// テンポラリカラムにはドロップさせない | ||||
| 			if (this.isTemporaryColumn) { | ||||
| 				e.dataTransfer.dropEffect = 'none'; | ||||
| @@ -287,7 +287,7 @@ export default Vue.extend({ | ||||
|  | ||||
| 			e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; | ||||
|  | ||||
| 			if (!this.dragging) this.draghover = true; | ||||
| 			if (!this.dragging && isDeckColumn) this.draghover = true; | ||||
| 		}, | ||||
|  | ||||
| 		onDragleave() { | ||||
|   | ||||
| @@ -8,10 +8,7 @@ | ||||
| 		</template> | ||||
| 	</div> | ||||
|  | ||||
| 	<div v-if="!fetching && requestInitPromise != null" class="error"> | ||||
| 		<p>%fa:exclamation-triangle% %i18n:common.error.title%</p> | ||||
| 		<ui-button @click="resolveInitPromise">%i18n:common.error.retry%</ui-button> | ||||
| 	</div> | ||||
| 	<mk-error v-if="!fetching && requestInitPromise != null" @retry="resolveInitPromise"/> | ||||
|  | ||||
| 	<!-- トランジションを有効にするとなぜかメモリリークする --> | ||||
| 	<!--<transition-group name="mk-notes" class="transition" ref="notes">--> | ||||
| @@ -221,13 +218,6 @@ export default Vue.extend({ | ||||
| 		> * | ||||
| 			transition transform .3s ease, opacity .3s ease | ||||
|  | ||||
| 	> .error | ||||
| 		max-width 300px | ||||
| 		margin 0 auto | ||||
| 		padding 16px | ||||
| 		text-align center | ||||
| 		color var(--text) | ||||
|  | ||||
| 	> .placeholder | ||||
| 		padding 16px | ||||
| 		opacity 0.3 | ||||
|   | ||||
| @@ -11,7 +11,11 @@ | ||||
| 	<div class="action-form"> | ||||
| 		<ui-button @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id"> | ||||
| 			<span v-if="user.isMuted">%fa:eye% %i18n:@unmute%</span> | ||||
| 			<span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span> | ||||
| 			<span v-else>%fa:eye-slash% %i18n:@mute%</span> | ||||
| 		</ui-button> | ||||
| 		<ui-button @click="user.isBlocking ? unblock() : block()" v-if="$store.state.i.id != user.id"> | ||||
| 			<span v-if="user.isBlocking">%fa:user% %i18n:@unblock%</span> | ||||
| 			<span v-else>%fa:user-slash% %i18n:@block%</span> | ||||
| 		</ui-button> | ||||
| 		<ui-button @click="list">%fa:list% %i18n:@push-to-a-list%</ui-button> | ||||
| 	</div> | ||||
| @@ -66,6 +70,27 @@ export default Vue.extend({ | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		block() { | ||||
| 			if (!window.confirm('%i18n:@block-confirm%')) return; | ||||
| 			(this as any).api('blocking/create', { | ||||
| 				userId: this.user.id | ||||
| 			}).then(() => { | ||||
| 				this.user.isBlocking = true; | ||||
| 			}, () => { | ||||
| 				alert('error'); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		unblock() { | ||||
| 			(this as any).api('blocking/delete', { | ||||
| 				userId: this.user.id | ||||
| 			}).then(() => { | ||||
| 				this.user.isBlocking = false; | ||||
| 			}, () => { | ||||
| 				alert('error'); | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		list() { | ||||
| 			const w = (this as any).os.new(MkUserListsWindow); | ||||
| 			w.$once('choosen', async list => { | ||||
| @@ -114,7 +139,6 @@ export default Vue.extend({ | ||||
| 	> .action-form | ||||
| 		padding 16px | ||||
| 		text-align center | ||||
| 		border-bottom solid 1px var(--faceDivider) | ||||
|  | ||||
| 		> * | ||||
| 			width 100% | ||||
|   | ||||
| @@ -17,6 +17,7 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		user: { | ||||
| @@ -24,6 +25,7 @@ export default Vue.extend({ | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	data() { | ||||
| 		return { | ||||
| 			u: this.user, | ||||
| @@ -31,28 +33,24 @@ export default Vue.extend({ | ||||
| 			connection: null | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	mounted() { | ||||
| 		this.connection = (this as any).os.stream.useSharedConnection('main'); | ||||
|  | ||||
| 		this.connection.on('follow', this.onFollow); | ||||
| 		this.connection.on('unfollow', this.onUnfollow); | ||||
| 		this.connection.on('follow', this.onFollowChange); | ||||
| 		this.connection.on('unfollow', this.onFollowChange); | ||||
| 	}, | ||||
|  | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.dispose(); | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
|  | ||||
| 		onFollow(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		onUnfollow(user) { | ||||
| 		onFollowChange(user) { | ||||
| 			if (user.id == this.u.id) { | ||||
| 				this.u.isFollowing = user.isFollowing; | ||||
| 				this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; | ||||
| 				this.$forceUpdate(); | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| @@ -90,8 +88,6 @@ export default Vue.extend({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
|  | ||||
|  | ||||
| .mk-follow-button | ||||
| 	display block | ||||
| 	user-select none | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import noteCard from './note-card.vue'; | ||||
| import userCard from './user-card.vue'; | ||||
| import noteDetail from './note-detail.vue'; | ||||
| import followButton from './follow-button.vue'; | ||||
| import muteButton from './mute-button.vue'; | ||||
| import friendsMaker from './friends-maker.vue'; | ||||
| import notification from './notification.vue'; | ||||
| import notifications from './notifications.vue'; | ||||
| @@ -37,7 +36,6 @@ Vue.component('mk-note-card', noteCard); | ||||
| Vue.component('mk-user-card', userCard); | ||||
| Vue.component('mk-note-detail', noteDetail); | ||||
| Vue.component('mk-follow-button', followButton); | ||||
| Vue.component('mk-mute-button', muteButton); | ||||
| Vue.component('mk-friends-maker', friendsMaker); | ||||
| Vue.component('mk-notification', notification); | ||||
| Vue.component('mk-notifications', notifications); | ||||
|   | ||||
| @@ -1,79 +0,0 @@ | ||||
| <template> | ||||
| <button | ||||
|   class="mk-mute-button" | ||||
|   :class="{ active: user.isMuted }" | ||||
|   @click="onClick"> | ||||
|   <span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span> | ||||
|   <span v-else>%fa:eye% %i18n:@unmute%</span> | ||||
| </button> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue' | ||||
| export default Vue.extend({ | ||||
|   props: { | ||||
|     user: { | ||||
|       type: Object, | ||||
|       required: true | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     onClick() { | ||||
|       if (!this.user.isMuted) { | ||||
|         this.mute(); | ||||
|       } else { | ||||
|         this.unmute(); | ||||
|       } | ||||
|     }, | ||||
|     mute() { | ||||
|       (this as any).api('mute/create', { userId: this.user.id}) | ||||
|         .then(() => { this.user.isMuted = true }) | ||||
|         .catch(() => { alert('error')}) | ||||
|     }, | ||||
|     unmute() { | ||||
|       (this as any).api('mute/delete', { userId: this.user.id }) | ||||
|         .then(() => { this.user.isMuted = false }) | ||||
|         .catch(() => { alert('error') }) | ||||
|     } | ||||
|   }, | ||||
| }) | ||||
| </script> | ||||
|  | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
|  | ||||
|  | ||||
| .mk-mute-button | ||||
|   display block | ||||
|   user-select none | ||||
|   cursor pointer | ||||
|   padding 0 16px | ||||
|   margin 0 | ||||
|   min-width 100px | ||||
|   line-height 36px | ||||
|   font-size 14px | ||||
|   font-weight bold | ||||
|   color var(--primary) | ||||
|   background transparent | ||||
|   outline none | ||||
|   border solid 1px var(--primary) | ||||
|   border-radius 36px | ||||
|  | ||||
|   &:hover | ||||
|     background var(--primaryAlpha01) | ||||
|  | ||||
|   &:active | ||||
|     background var(--primaryAlpha02) | ||||
|  | ||||
|   &.active | ||||
|     color var(--primaryForeground) | ||||
|     background var(--primary) | ||||
|  | ||||
|     &:hover | ||||
|       background var(--primaryLighten10) | ||||
|       border-color var(--primaryLighten10) | ||||
|     &:active | ||||
|       background var(--primaryDarken10) | ||||
|       border-color var(--primaryDarken10) | ||||
|  | ||||
| </style> | ||||
| @@ -26,6 +26,9 @@ | ||||
| 					<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch> | ||||
| 					<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch> | ||||
| 					<ui-switch v-model="alwaysShowNsfw">%i18n:common.always-show-nsfw% (%i18n:common.this-setting-is-this-device-only%)</ui-switch> | ||||
| 				</section> | ||||
|  | ||||
| 				<section> | ||||
| 					<ui-switch v-model="games_reversi_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch> | ||||
| 					<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch> | ||||
| 				</section> | ||||
| @@ -82,6 +85,8 @@ | ||||
|  | ||||
| 			<mk-drive-settings/> | ||||
|  | ||||
| 			<mk-mute-and-block/> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:volume-up% %i18n:@sound%</div> | ||||
|  | ||||
| @@ -120,6 +125,8 @@ | ||||
| 				</section> | ||||
| 			</ui-card> | ||||
|  | ||||
| 			<mk-api-settings /> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:sync-alt% %i18n:@update%</div> | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| 					<a class="avatar"> | ||||
| 						<img :src="user.avatarUrl" alt="avatar"/> | ||||
| 					</a> | ||||
| 					<mk-mute-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> | ||||
| 					<button class="menu" ref="menu" @click="menu">%fa:ellipsis-h%</button> | ||||
| 					<mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> | ||||
| 				</div> | ||||
| 				<div class="title"> | ||||
| @@ -67,6 +67,7 @@ import Vue from 'vue'; | ||||
| import * as age from 's-age'; | ||||
| import parseAcct from '../../../../../misc/acct/parse'; | ||||
| import Progress from '../../../common/scripts/loading'; | ||||
| import Menu from '../../../common/views/components/menu.vue'; | ||||
| import XHome from './user/home.vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| @@ -109,7 +110,61 @@ export default Vue.extend({ | ||||
| 				Progress.done(); | ||||
| 				document.title = `${Vue.filter('userName')(this.user)} | ${(this as any).os.instanceName}`; | ||||
| 			}); | ||||
| 		} | ||||
| 		}, | ||||
|  | ||||
| 		menu() { | ||||
| 			let menu = [{ | ||||
| 				icon: this.user.isMuted ? '%fa:eye%' : '%fa:eye-slash%', | ||||
| 				text: this.user.isMuted ? '%i18n:@unmute%' : '%i18n:@mute%', | ||||
| 				action: () => { | ||||
| 					if (this.user.isMuted) { | ||||
| 						(this as any).api('mute/delete', { | ||||
| 							userId: this.user.id | ||||
| 						}).then(() => { | ||||
| 							this.user.isMuted = false; | ||||
| 						}, () => { | ||||
| 							alert('error'); | ||||
| 						}); | ||||
| 					} else { | ||||
| 						(this as any).api('mute/create', { | ||||
| 							userId: this.user.id | ||||
| 						}).then(() => { | ||||
| 							this.user.isMuted = true; | ||||
| 						}, () => { | ||||
| 							alert('error'); | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: this.user.isBlocking ? '%fa:user%' : '%fa:user-slash%', | ||||
| 				text: this.user.isBlocking ? '%i18n:@unblock%' : '%i18n:@block%', | ||||
| 				action: () => { | ||||
| 					if (this.user.isBlocking) { | ||||
| 						(this as any).api('blocking/delete', { | ||||
| 							userId: this.user.id | ||||
| 						}).then(() => { | ||||
| 							this.user.isBlocking = false; | ||||
| 						}, () => { | ||||
| 							alert('error'); | ||||
| 						}); | ||||
| 					} else { | ||||
| 						(this as any).api('blocking/create', { | ||||
| 							userId: this.user.id | ||||
| 						}).then(() => { | ||||
| 							this.user.isBlocking = true; | ||||
| 						}, () => { | ||||
| 							alert('error'); | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 			}]; | ||||
|  | ||||
| 			this.os.new(Menu, { | ||||
| 				source: this.$refs.menu, | ||||
| 				compact: true, | ||||
| 				items: menu | ||||
| 			}); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| @@ -156,14 +211,10 @@ main | ||||
| 			max-width 600px | ||||
|  | ||||
| 			> .top | ||||
| 				&:after | ||||
| 					content '' | ||||
| 					display block | ||||
| 					clear both | ||||
| 				display flex | ||||
|  | ||||
| 				> .avatar | ||||
| 					display block | ||||
| 					float left | ||||
| 					width 25% | ||||
| 					height 40px | ||||
|  | ||||
| @@ -183,11 +234,15 @@ main | ||||
| 							border 4px solid $bg | ||||
| 							border-radius 12px | ||||
|  | ||||
| 				> .mk-mute-button | ||||
| 					float right | ||||
| 				> .menu | ||||
| 					margin 0 0 0 auto | ||||
| 					padding 8px | ||||
| 					margin-right 8px | ||||
| 					font-size 18px | ||||
| 					color var(--text) | ||||
|  | ||||
| 				> .mk-follow-button | ||||
| 					float right | ||||
| 					margin 0 | ||||
|  | ||||
| 			> .title | ||||
| 				margin 8px 0 | ||||
|   | ||||
| @@ -131,6 +131,9 @@ | ||||
| 		remoteInfoBg: '#42321c', | ||||
| 		remoteInfoFg: '#ffbd3e', | ||||
|  | ||||
| 		infoWarnBg: '#42321c', | ||||
| 		infoWarnFg: '#ffbd3e', | ||||
|  | ||||
| 		messagingRoomBg: '@bg', | ||||
| 		messagingRoomInfo: '#fff', | ||||
| 		messagingRoomDateDividerLine: 'rgba(255, 255, 255, 0.1)', | ||||
|   | ||||
| @@ -131,6 +131,9 @@ | ||||
| 		remoteInfoBg: '#fff0db', | ||||
| 		remoteInfoFg: '#573c08', | ||||
|  | ||||
| 		infoWarnBg: '#fff0db', | ||||
| 		infoWarnFg: '#573c08', | ||||
|  | ||||
| 		messagingRoomBg: '#fff', | ||||
| 		messagingRoomInfo: '#000', | ||||
| 		messagingRoomDateDividerLine: 'rgba(0, 0, 0, 0.1)', | ||||
|   | ||||
| @@ -14,11 +14,13 @@ export type Source = { | ||||
| 		 * メンテナの連絡先(URLかmailto形式のURL) | ||||
| 		 */ | ||||
| 		url: string; | ||||
| 		email?: string; | ||||
| 		repository_url?: string; | ||||
| 		feedback_url?: string; | ||||
| 	}; | ||||
| 	name?: string; | ||||
| 	description?: string; | ||||
| 	languages?: string[]; | ||||
| 	welcome_bg_url?: string; | ||||
| 	url: string; | ||||
| 	port: number; | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|  | ||||
| 以下のURLに、`i`というパラメータ名で認証情報を含めて、websocket接続してください。例: | ||||
| ``` | ||||
| %URL%/streaming?i=xxxxxxxxxxxxxxx | ||||
| %WS_URL%/streaming?i=xxxxxxxxxxxxxxx | ||||
| ``` | ||||
|  | ||||
| 認証情報は、自分のAPIキーや、アプリケーションからストリームに接続する際はユーザーのアクセストークンのことを指します。 | ||||
| @@ -22,7 +22,7 @@ | ||||
| 認証情報は省略することもできますが、その場合非ログインでの利用ということになり、受信できる情報や可能な操作は限られます。例: | ||||
|  | ||||
| ``` | ||||
| %URL%/streaming | ||||
| %WS_URL%/streaming | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|   | ||||
| @@ -17,7 +17,8 @@ export default function(text: string, index: number) { | ||||
| 	const quote = match[1] | ||||
| 		.split('\n') | ||||
| 		.map(line => line.replace(/^>+/g, '').trim()) | ||||
| 		.join('\n'); | ||||
| 		.join('\n') | ||||
| 		.trim(); | ||||
|  | ||||
| 	return { | ||||
| 		type: 'quote', | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
|  | ||||
| const AccessToken = db.get<IAccessToken>('accessTokens'); | ||||
| AccessToken.createIndex('token'); | ||||
| @@ -15,30 +14,3 @@ export type IAccessToken = { | ||||
| 	token: string; | ||||
| 	hash: string; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * AccessTokenを物理削除します | ||||
|  */ | ||||
| export async function deleteAccessToken(accessToken: string | mongo.ObjectID | IAccessToken) { | ||||
| 	let a: IAccessToken; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(accessToken)) { | ||||
| 		a = await AccessToken.findOne({ | ||||
| 			_id: accessToken | ||||
| 		}); | ||||
| 	} else if (typeof accessToken === 'string') { | ||||
| 		a = await AccessToken.findOne({ | ||||
| 			_id: new mongo.ObjectID(accessToken) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		a = accessToken as IAccessToken; | ||||
| 	} | ||||
|  | ||||
| 	if (a == null) return; | ||||
|  | ||||
| 	// このAccessTokenを削除 | ||||
| 	await AccessToken.remove({ | ||||
| 		_id: a._id | ||||
| 	}); | ||||
| } | ||||
|   | ||||
							
								
								
									
										54
									
								
								src/models/blocking.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/models/blocking.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
| const deepcopy = require('deepcopy'); | ||||
| import { pack as packUser, IUser } from './user'; | ||||
|  | ||||
| const Blocking = db.get<IBlocking>('blocking'); | ||||
| Blocking.createIndex('blockerId'); | ||||
| Blocking.createIndex('blockeeId'); | ||||
| Blocking.createIndex(['blockerId', 'blockeeId'], { unique: true }); | ||||
| export default Blocking; | ||||
|  | ||||
| export type IBlocking = { | ||||
| 	_id: mongo.ObjectID; | ||||
| 	createdAt: Date; | ||||
| 	blockeeId: mongo.ObjectID; | ||||
| 	blockerId: mongo.ObjectID; | ||||
| }; | ||||
|  | ||||
| export const packMany = ( | ||||
| 	blockings: (string | mongo.ObjectID | IBlocking)[], | ||||
| 	me?: string | mongo.ObjectID | IUser | ||||
| ) => { | ||||
| 	return Promise.all(blockings.map(x => pack(x, me))); | ||||
| }; | ||||
|  | ||||
| export const pack = ( | ||||
| 	blocking: any, | ||||
| 	me?: any | ||||
| ) => new Promise<any>(async (resolve, reject) => { | ||||
| 	let _blocking: any; | ||||
|  | ||||
| 	// Populate the blocking if 'blocking' is ID | ||||
| 	if (isObjectId(blocking)) { | ||||
| 		_blocking = await Blocking.findOne({ | ||||
| 			_id: blocking | ||||
| 		}); | ||||
| 	} else if (typeof blocking === 'string') { | ||||
| 		_blocking = await Blocking.findOne({ | ||||
| 			_id: new mongo.ObjectID(blocking) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		_blocking = deepcopy(blocking); | ||||
| 	} | ||||
|  | ||||
| 	// Rename _id to id | ||||
| 	_blocking.id = _blocking._id; | ||||
| 	delete _blocking._id; | ||||
|  | ||||
| 	// Populate blockee | ||||
| 	_blocking.blockee = await packUser(_blocking.blockeeId, me); | ||||
|  | ||||
| 	resolve(_blocking); | ||||
| }); | ||||
| @@ -1,6 +1,5 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import monkDb, { nativeDbConn } from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
|  | ||||
| const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files'); | ||||
| DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true }); | ||||
| @@ -28,35 +27,3 @@ export type IDriveFileThumbnail = { | ||||
| 	contentType: string; | ||||
| 	metadata: IMetadata; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * DriveFileThumbnailを物理削除します | ||||
|  */ | ||||
| export async function deleteDriveFileThumbnail(driveFile: string | mongo.ObjectID | IDriveFileThumbnail) { | ||||
| 	let d: IDriveFileThumbnail; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(driveFile)) { | ||||
| 		d = await DriveFileThumbnail.findOne({ | ||||
| 			_id: driveFile | ||||
| 		}); | ||||
| 	} else if (typeof driveFile === 'string') { | ||||
| 		d = await DriveFileThumbnail.findOne({ | ||||
| 			_id: new mongo.ObjectID(driveFile) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		d = driveFile as IDriveFileThumbnail; | ||||
| 	} | ||||
|  | ||||
| 	if (d == null) return; | ||||
|  | ||||
| 	// このDriveFileThumbnailのチャンクをすべて削除 | ||||
| 	await DriveFileThumbnailChunk.remove({ | ||||
| 		files_id: d._id | ||||
| 	}); | ||||
|  | ||||
| 	// このDriveFileThumbnailを削除 | ||||
| 	await DriveFileThumbnail.remove({ | ||||
| 		_id: d._id | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -4,10 +4,6 @@ import { pack as packFolder } from './drive-folder'; | ||||
| import config from '../config'; | ||||
| import monkDb, { nativeDbConn } from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
| import Note, { deleteNote } from './note'; | ||||
| import MessagingMessage, { deleteMessagingMessage } from './messaging-message'; | ||||
| import User from './user'; | ||||
| import DriveFileThumbnail, { deleteDriveFileThumbnail } from './drive-file-thumbnail'; | ||||
|  | ||||
| const DriveFile = monkDb.get<IDriveFile>('driveFiles.files'); | ||||
| DriveFile.createIndex('md5'); | ||||
| @@ -77,71 +73,13 @@ export function validateFileName(name: string): boolean { | ||||
| 	); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * DriveFileを物理削除します | ||||
|  */ | ||||
| export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriveFile) { | ||||
| 	let d: IDriveFile; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(driveFile)) { | ||||
| 		d = await DriveFile.findOne({ | ||||
| 			_id: driveFile | ||||
| 		}); | ||||
| 	} else if (typeof driveFile === 'string') { | ||||
| 		d = await DriveFile.findOne({ | ||||
| 			_id: new mongo.ObjectID(driveFile) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		d = driveFile as IDriveFile; | ||||
| 	} | ||||
|  | ||||
| 	if (d == null) return; | ||||
|  | ||||
| 	// このDriveFileを添付しているNoteをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Note.find({ fileIds: d._id }) | ||||
| 	).map(x => deleteNote(x))); | ||||
|  | ||||
| 	// このDriveFileを添付しているMessagingMessageをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await MessagingMessage.find({ fileId: d._id }) | ||||
| 	).map(x => deleteMessagingMessage(x))); | ||||
|  | ||||
| 	// このDriveFileがアバターやバナーに使われていたらそれらのプロパティをnullにする | ||||
| 	const u = await User.findOne({ _id: d.metadata.userId }); | ||||
| 	if (u) { | ||||
| 		if (u.avatarId && u.avatarId.equals(d._id)) { | ||||
| 			await User.update({ _id: u._id }, { $set: { avatarId: null } }); | ||||
| 		} | ||||
| 		if (u.bannerId && u.bannerId.equals(d._id)) { | ||||
| 			await User.update({ _id: u._id }, { $set: { bannerId: null } }); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// このDriveFileのDriveFileThumbnailをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await DriveFileThumbnail.find({ 'metadata.originalId': d._id }) | ||||
| 	).map(x => deleteDriveFileThumbnail(x))); | ||||
|  | ||||
| 	// このDriveFileのチャンクをすべて削除 | ||||
| 	await DriveFileChunk.remove({ | ||||
| 		files_id: d._id | ||||
| 	}); | ||||
|  | ||||
| 	// このDriveFileを削除 | ||||
| 	await DriveFile.remove({ | ||||
| 		_id: d._id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export const packMany = async ( | ||||
| export const packMany = ( | ||||
| 	files: any[], | ||||
| 	options?: { | ||||
| 		detail: boolean | ||||
| 	} | ||||
| ) => { | ||||
| 	return (await Promise.all(files.map(f => pack(f, options)))).filter(x => x != null); | ||||
| 	return Promise.all(files.map(f => pack(f, options))); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -23,51 +23,6 @@ export function isValidFolderName(name: string): boolean { | ||||
| 	); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * DriveFolderを物理削除します | ||||
|  */ | ||||
| export async function deleteDriveFolder(driveFolder: string | mongo.ObjectID | IDriveFolder) { | ||||
| 	let d: IDriveFolder; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(driveFolder)) { | ||||
| 		d = await DriveFolder.findOne({ | ||||
| 			_id: driveFolder | ||||
| 		}); | ||||
| 	} else if (typeof driveFolder === 'string') { | ||||
| 		d = await DriveFolder.findOne({ | ||||
| 			_id: new mongo.ObjectID(driveFolder) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		d = driveFolder as IDriveFolder; | ||||
| 	} | ||||
|  | ||||
| 	if (d == null) return; | ||||
|  | ||||
| 	// このDriveFolderに格納されているDriveFileがあればすべてルートに移動 | ||||
| 	await DriveFile.update({ | ||||
| 		'metadata.folderId': d._id | ||||
| 	}, { | ||||
| 		$set: { | ||||
| 			'metadata.folderId': null | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	// このDriveFolderに格納されているDriveFolderがあればすべてルートに移動 | ||||
| 	await DriveFolder.update({ | ||||
| 		parentId: d._id | ||||
| 	}, { | ||||
| 		$set: { | ||||
| 			parentId: null | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	// このDriveFolderを削除 | ||||
| 	await DriveFolder.remove({ | ||||
| 		_id: d._id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Pack a drive folder for API response | ||||
|  */ | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import isObjectId from '../misc/is-objectid'; | ||||
| import { pack as packNote } from './note'; | ||||
|  | ||||
| const Favorite = db.get<IFavorite>('favorites'); | ||||
| Favorite.createIndex('userId'); | ||||
| Favorite.createIndex(['userId', 'noteId'], { unique: true }); | ||||
| export default Favorite; | ||||
|  | ||||
| @@ -15,38 +16,11 @@ export type IFavorite = { | ||||
| 	noteId: mongo.ObjectID; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Favoriteを物理削除します | ||||
|  */ | ||||
| export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavorite) { | ||||
| 	let f: IFavorite; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(favorite)) { | ||||
| 		f = await Favorite.findOne({ | ||||
| 			_id: favorite | ||||
| 		}); | ||||
| 	} else if (typeof favorite === 'string') { | ||||
| 		f = await Favorite.findOne({ | ||||
| 			_id: new mongo.ObjectID(favorite) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		f = favorite as IFavorite; | ||||
| 	} | ||||
|  | ||||
| 	if (f == null) return; | ||||
|  | ||||
| 	// このFavoriteを削除 | ||||
| 	await Favorite.remove({ | ||||
| 		_id: f._id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export const packMany = async ( | ||||
| export const packMany = ( | ||||
| 	favorites: any[], | ||||
| 	me: any | ||||
| ) => { | ||||
| 	return (await Promise.all(favorites.map(f => pack(f, me)))).filter(x => x != null); | ||||
| 	return Promise.all(favorites.map(f => pack(f, me))); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -5,6 +5,8 @@ import isObjectId from '../misc/is-objectid'; | ||||
| import { pack as packUser } from './user'; | ||||
|  | ||||
| const FollowRequest = db.get<IFollowRequest>('followRequests'); | ||||
| FollowRequest.createIndex('followerId'); | ||||
| FollowRequest.createIndex('followeeId'); | ||||
| FollowRequest.createIndex(['followerId', 'followeeId'], { unique: true }); | ||||
| export default FollowRequest; | ||||
|  | ||||
| @@ -28,33 +30,6 @@ export type IFollowRequest = { | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * FollowRequestを物理削除します | ||||
|  */ | ||||
| export async function deleteFollowRequest(followRequest: string | mongo.ObjectID | IFollowRequest) { | ||||
| 	let f: IFollowRequest; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(followRequest)) { | ||||
| 		f = await FollowRequest.findOne({ | ||||
| 			_id: followRequest | ||||
| 		}); | ||||
| 	} else if (typeof followRequest === 'string') { | ||||
| 		f = await FollowRequest.findOne({ | ||||
| 			_id: new mongo.ObjectID(followRequest) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		f = followRequest as IFollowRequest; | ||||
| 	} | ||||
|  | ||||
| 	if (f == null) return; | ||||
|  | ||||
| 	// このFollowingを削除 | ||||
| 	await FollowRequest.remove({ | ||||
| 		_id: f._id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Pack a request for API response | ||||
|  */ | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
|  | ||||
| const Following = db.get<IFollowing>('following'); | ||||
| Following.createIndex('followerId'); | ||||
| Following.createIndex('followeeId'); | ||||
| Following.createIndex(['followerId', 'followeeId'], { unique: true }); | ||||
| export default Following; | ||||
|  | ||||
| @@ -25,30 +26,3 @@ export type IFollowing = { | ||||
| 		sharedInbox?: string; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Followingを物理削除します | ||||
|  */ | ||||
| export async function deleteFollowing(following: string | mongo.ObjectID | IFollowing) { | ||||
| 	let f: IFollowing; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(following)) { | ||||
| 		f = await Following.findOne({ | ||||
| 			_id: following | ||||
| 		}); | ||||
| 	} else if (typeof following === 'string') { | ||||
| 		f = await Following.findOne({ | ||||
| 			_id: new mongo.ObjectID(following) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		f = following as IFollowing; | ||||
| 	} | ||||
|  | ||||
| 	if (f == null) return; | ||||
|  | ||||
| 	// このFollowingを削除 | ||||
| 	await Following.remove({ | ||||
| 		_id: f._id | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
|  | ||||
| const MessagingHistory = db.get<IMessagingHistory>('messagingHistories'); | ||||
| export default MessagingHistory; | ||||
| @@ -12,30 +11,3 @@ export type IMessagingHistory = { | ||||
| 	partnerId: mongo.ObjectID; | ||||
| 	messageId: mongo.ObjectID; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * MessagingHistoryを物理削除します | ||||
|  */ | ||||
| export async function deleteMessagingHistory(messagingHistory: string | mongo.ObjectID | IMessagingHistory) { | ||||
| 	let m: IMessagingHistory; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(messagingHistory)) { | ||||
| 		m = await MessagingHistory.findOne({ | ||||
| 			_id: messagingHistory | ||||
| 		}); | ||||
| 	} else if (typeof messagingHistory === 'string') { | ||||
| 		m = await MessagingHistory.findOne({ | ||||
| 			_id: new mongo.ObjectID(messagingHistory) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		m = messagingHistory as IMessagingHistory; | ||||
| 	} | ||||
|  | ||||
| 	if (m == null) return; | ||||
|  | ||||
| 	// このMessagingHistoryを削除 | ||||
| 	await MessagingHistory.remove({ | ||||
| 		_id: m._id | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import { pack as packUser } from './user'; | ||||
| import { pack as packFile } from './drive-file'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
| import MessagingHistory, { deleteMessagingHistory } from './messaging-history'; | ||||
| import { length } from 'stringz'; | ||||
|  | ||||
| const MessagingMessage = db.get<IMessagingMessage>('messagingMessages'); | ||||
| @@ -24,38 +23,6 @@ export function isValidText(text: string): boolean { | ||||
| 	return length(text.trim()) <= 1000 && text.trim() != ''; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * MessagingMessageを物理削除します | ||||
|  */ | ||||
| export async function deleteMessagingMessage(messagingMessage: string | mongo.ObjectID | IMessagingMessage) { | ||||
| 	let m: IMessagingMessage; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(messagingMessage)) { | ||||
| 		m = await MessagingMessage.findOne({ | ||||
| 			_id: messagingMessage | ||||
| 		}); | ||||
| 	} else if (typeof messagingMessage === 'string') { | ||||
| 		m = await MessagingMessage.findOne({ | ||||
| 			_id: new mongo.ObjectID(messagingMessage) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		m = messagingMessage as IMessagingMessage; | ||||
| 	} | ||||
|  | ||||
| 	if (m == null) return; | ||||
|  | ||||
| 	// このMessagingMessageを指すMessagingHistoryをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await MessagingHistory.find({ messageId: m._id }) | ||||
| 	).map(x => deleteMessagingHistory(x))); | ||||
|  | ||||
| 	// このMessagingMessageを削除 | ||||
| 	await MessagingMessage.remove({ | ||||
| 		_id: m._id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Pack a messaging message for API response | ||||
|  */ | ||||
|   | ||||
| @@ -1,8 +1,12 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
| const deepcopy = require('deepcopy'); | ||||
| import { pack as packUser, IUser } from './user'; | ||||
|  | ||||
| const Mute = db.get<IMute>('mute'); | ||||
| Mute.createIndex('muterId'); | ||||
| Mute.createIndex('muteeId'); | ||||
| Mute.createIndex(['muterId', 'muteeId'], { unique: true }); | ||||
| export default Mute; | ||||
|  | ||||
| @@ -13,29 +17,38 @@ export interface IMute { | ||||
| 	muteeId: mongo.ObjectID; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Muteを物理削除します | ||||
|  */ | ||||
| export async function deleteMute(mute: string | mongo.ObjectID | IMute) { | ||||
| 	let m: IMute; | ||||
| export const packMany = ( | ||||
| 	mutes: (string | mongo.ObjectID | IMute)[], | ||||
| 	me?: string | mongo.ObjectID | IUser | ||||
| ) => { | ||||
| 	return Promise.all(mutes.map(x => pack(x, me))); | ||||
| }; | ||||
|  | ||||
| 	// Populate | ||||
| export const pack = ( | ||||
| 	mute: any, | ||||
| 	me?: any | ||||
| ) => new Promise<any>(async (resolve, reject) => { | ||||
| 	let _mute: any; | ||||
|  | ||||
| 	// Populate the mute if 'mute' is ID | ||||
| 	if (isObjectId(mute)) { | ||||
| 		m = await Mute.findOne({ | ||||
| 		_mute = await Mute.findOne({ | ||||
| 			_id: mute | ||||
| 		}); | ||||
| 	} else if (typeof mute === 'string') { | ||||
| 		m = await Mute.findOne({ | ||||
| 		_mute = await Mute.findOne({ | ||||
| 			_id: new mongo.ObjectID(mute) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		m = mute as IMute; | ||||
| 		_mute = deepcopy(mute); | ||||
| 	} | ||||
|  | ||||
| 	if (m == null) return; | ||||
| 	// Rename _id to id | ||||
| 	_mute.id = _mute._id; | ||||
| 	delete _mute._id; | ||||
|  | ||||
| 	// このMuteを削除 | ||||
| 	await Mute.remove({ | ||||
| 		_id: m._id | ||||
| 	}); | ||||
| } | ||||
| 	// Populate mutee | ||||
| 	_mute.mutee = await packUser(_mute.muteeId, me); | ||||
|  | ||||
| 	resolve(_mute); | ||||
| }); | ||||
|   | ||||
| @@ -7,6 +7,8 @@ import Reaction from './note-reaction'; | ||||
| import { pack as packUser } from './user'; | ||||
|  | ||||
| const NoteReaction = db.get<INoteReaction>('noteReactions'); | ||||
| NoteReaction.createIndex('noteId'); | ||||
| NoteReaction.createIndex('userId'); | ||||
| NoteReaction.createIndex(['userId', 'noteId'], { unique: true }); | ||||
| export default NoteReaction; | ||||
|  | ||||
| @@ -31,33 +33,6 @@ export const validateReaction = $.str.or([ | ||||
| 	'pudding' | ||||
| ]); | ||||
|  | ||||
| /** | ||||
|  * NoteReactionを物理削除します | ||||
|  */ | ||||
| export async function deleteNoteReaction(noteReaction: string | mongo.ObjectID | INoteReaction) { | ||||
| 	let n: INoteReaction; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(noteReaction)) { | ||||
| 		n = await NoteReaction.findOne({ | ||||
| 			_id: noteReaction | ||||
| 		}); | ||||
| 	} else if (typeof noteReaction === 'string') { | ||||
| 		n = await NoteReaction.findOne({ | ||||
| 			_id: new mongo.ObjectID(noteReaction) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		n = noteReaction as INoteReaction; | ||||
| 	} | ||||
|  | ||||
| 	if (n == null) return; | ||||
|  | ||||
| 	// このNoteReactionを削除 | ||||
| 	await NoteReaction.remove({ | ||||
| 		_id: n._id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Pack a reaction for API response | ||||
|  */ | ||||
|   | ||||
| @@ -2,6 +2,8 @@ import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
|  | ||||
| const NoteUnread = db.get<INoteUnread>('noteUnreads'); | ||||
| NoteUnread.createIndex('userId'); | ||||
| NoteUnread.createIndex('noteId'); | ||||
| NoteUnread.createIndex(['userId', 'noteId'], { unique: true }); | ||||
| export default NoteUnread; | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
|  | ||||
| const NoteWatching = db.get<INoteWatching>('noteWatching'); | ||||
| NoteWatching.createIndex('userId'); | ||||
| NoteWatching.createIndex('noteId'); | ||||
| NoteWatching.createIndex(['userId', 'noteId'], { unique: true }); | ||||
| export default NoteWatching; | ||||
|  | ||||
| @@ -12,30 +13,3 @@ export interface INoteWatching { | ||||
| 	userId: mongo.ObjectID; | ||||
| 	noteId: mongo.ObjectID; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * NoteWatchingを物理削除します | ||||
|  */ | ||||
| export async function deleteNoteWatching(noteWatching: string | mongo.ObjectID | INoteWatching) { | ||||
| 	let n: INoteWatching; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(noteWatching)) { | ||||
| 		n = await NoteWatching.findOne({ | ||||
| 			_id: noteWatching | ||||
| 		}); | ||||
| 	} else if (typeof noteWatching === 'string') { | ||||
| 		n = await NoteWatching.findOne({ | ||||
| 			_id: new mongo.ObjectID(noteWatching) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		n = noteWatching as INoteWatching; | ||||
| 	} | ||||
|  | ||||
| 	if (n == null) return; | ||||
|  | ||||
| 	// このNoteWatchingを削除 | ||||
| 	await NoteWatching.remove({ | ||||
| 		_id: n._id | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -6,13 +6,10 @@ import isObjectId from '../misc/is-objectid'; | ||||
| import { length } from 'stringz'; | ||||
| import { IUser, pack as packUser } from './user'; | ||||
| import { pack as packApp } from './app'; | ||||
| import PollVote, { deletePollVote } from './poll-vote'; | ||||
| import Reaction, { deleteNoteReaction } from './note-reaction'; | ||||
| import PollVote from './poll-vote'; | ||||
| import Reaction from './note-reaction'; | ||||
| import { packMany as packFileMany, IDriveFile } from './drive-file'; | ||||
| import NoteWatching, { deleteNoteWatching } from './note-watching'; | ||||
| import NoteReaction from './note-reaction'; | ||||
| import Favorite, { deleteFavorite } from './favorite'; | ||||
| import Notification, { deleteNotification } from './notification'; | ||||
| import Favorite from './favorite'; | ||||
| import Following from './following'; | ||||
| import config from '../config'; | ||||
|  | ||||
| @@ -108,72 +105,6 @@ export type INote = { | ||||
| 	_files?: IDriveFile[]; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Noteを物理削除します | ||||
|  */ | ||||
| export async function deleteNote(note: string | mongo.ObjectID | INote) { | ||||
| 	let n: INote; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(note)) { | ||||
| 		n = await Note.findOne({ | ||||
| 			_id: note | ||||
| 		}); | ||||
| 	} else if (typeof note === 'string') { | ||||
| 		n = await Note.findOne({ | ||||
| 			_id: new mongo.ObjectID(note) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		n = note as INote; | ||||
| 	} | ||||
|  | ||||
| 	console.log(n == null ? `Note: delete skipped ${note}` : `Note: deleting ${n._id}`); | ||||
|  | ||||
| 	if (n == null) return; | ||||
|  | ||||
| 	// このNoteへの返信をすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Note.find({ replyId: n._id }) | ||||
| 	).map(x => deleteNote(x))); | ||||
|  | ||||
| 	// このNoteのRenoteをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Note.find({ renoteId: n._id }) | ||||
| 	).map(x => deleteNote(x))); | ||||
|  | ||||
| 	// この投稿に対するNoteWatchingをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await NoteWatching.find({ noteId: n._id }) | ||||
| 	).map(x => deleteNoteWatching(x))); | ||||
|  | ||||
| 	// この投稿に対するNoteReactionをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await NoteReaction.find({ noteId: n._id }) | ||||
| 	).map(x => deleteNoteReaction(x))); | ||||
|  | ||||
| 	// この投稿に対するPollVoteをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await PollVote.find({ noteId: n._id }) | ||||
| 	).map(x => deletePollVote(x))); | ||||
|  | ||||
| 	// この投稿に対するFavoriteをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Favorite.find({ noteId: n._id }) | ||||
| 	).map(x => deleteFavorite(x))); | ||||
|  | ||||
| 	// この投稿に対するNotificationをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Notification.find({ noteId: n._id }) | ||||
| 	).map(x => deleteNotification(x))); | ||||
|  | ||||
| 	// このNoteを削除 | ||||
| 	await Note.remove({ | ||||
| 		_id: n._id | ||||
| 	}); | ||||
|  | ||||
| 	console.log(`Note: deleted ${n._id}`); | ||||
| } | ||||
|  | ||||
| export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => { | ||||
| 	let hide = false; | ||||
|  | ||||
| @@ -233,7 +164,7 @@ export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => { | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export const packMany = async ( | ||||
| export const packMany = ( | ||||
| 	notes: (string | mongo.ObjectID | INote)[], | ||||
| 	me?: string | mongo.ObjectID | IUser, | ||||
| 	options?: { | ||||
| @@ -241,7 +172,7 @@ export const packMany = async ( | ||||
| 		skipHide?: boolean; | ||||
| 	} | ||||
| ) => { | ||||
| 	return (await Promise.all(notes.map(n => pack(n, me, options)))).filter(x => x != null); | ||||
| 	return Promise.all(notes.map(n => pack(n, me, options))); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -51,37 +51,10 @@ export interface INotification { | ||||
| 	isRead: Boolean; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Notificationを物理削除します | ||||
|  */ | ||||
| export async function deleteNotification(notification: string | mongo.ObjectID | INotification) { | ||||
| 	let n: INotification; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(notification)) { | ||||
| 		n = await Notification.findOne({ | ||||
| 			_id: notification | ||||
| 		}); | ||||
| 	} else if (typeof notification === 'string') { | ||||
| 		n = await Notification.findOne({ | ||||
| 			_id: new mongo.ObjectID(notification) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		n = notification as INotification; | ||||
| 	} | ||||
|  | ||||
| 	if (n == null) return; | ||||
|  | ||||
| 	// このNotificationを削除 | ||||
| 	await Notification.remove({ | ||||
| 		_id: n._id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export const packMany = async ( | ||||
| export const packMany = ( | ||||
| 	notifications: any[] | ||||
| ) => { | ||||
| 	return (await Promise.all(notifications.map(n => pack(n)))).filter(x => x != null); | ||||
| 	return Promise.all(notifications.map(n => pack(n))); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
|  | ||||
| const PollVote = db.get<IPollVote>('pollVotes'); | ||||
| export default PollVote; | ||||
| @@ -12,30 +11,3 @@ export interface IPollVote { | ||||
| 	noteId: mongo.ObjectID; | ||||
| 	choice: number; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * PollVoteを物理削除します | ||||
|  */ | ||||
| export async function deletePollVote(pollVote: string | mongo.ObjectID | IPollVote) { | ||||
| 	let p: IPollVote; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(pollVote)) { | ||||
| 		p = await PollVote.findOne({ | ||||
| 			_id: pollVote | ||||
| 		}); | ||||
| 	} else if (typeof pollVote === 'string') { | ||||
| 		p = await PollVote.findOne({ | ||||
| 			_id: new mongo.ObjectID(pollVote) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		p = pollVote as IPollVote; | ||||
| 	} | ||||
|  | ||||
| 	if (p == null) return; | ||||
|  | ||||
| 	// このPollVoteを削除 | ||||
| 	await PollVote.remove({ | ||||
| 		_id: p._id | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
|  | ||||
| const SwSubscription = db.get<ISwSubscription>('swSubscriptions'); | ||||
| export default SwSubscription; | ||||
| @@ -12,30 +11,3 @@ export interface ISwSubscription { | ||||
| 	auth: string; | ||||
| 	publickey: string; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * SwSubscriptionを物理削除します | ||||
|  */ | ||||
| export async function deleteSwSubscription(swSubscription: string | mongo.ObjectID | ISwSubscription) { | ||||
| 	let s: ISwSubscription; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(swSubscription)) { | ||||
| 		s = await SwSubscription.findOne({ | ||||
| 			_id: swSubscription | ||||
| 		}); | ||||
| 	} else if (typeof swSubscription === 'string') { | ||||
| 		s = await SwSubscription.findOne({ | ||||
| 			_id: new mongo.ObjectID(swSubscription) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		s = swSubscription as ISwSubscription; | ||||
| 	} | ||||
|  | ||||
| 	if (s == null) return; | ||||
|  | ||||
| 	// このSwSubscriptionを削除 | ||||
| 	await SwSubscription.remove({ | ||||
| 		_id: s._id | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -14,33 +14,6 @@ export interface IUserList { | ||||
| 	userIds: mongo.ObjectID[]; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * UserListを物理削除します | ||||
|  */ | ||||
| export async function deleteUserList(userList: string | mongo.ObjectID | IUserList) { | ||||
| 	let u: IUserList; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(userList)) { | ||||
| 		u = await UserList.findOne({ | ||||
| 			_id: userList | ||||
| 		}); | ||||
| 	} else if (typeof userList === 'string') { | ||||
| 		u = await UserList.findOne({ | ||||
| 			_id: new mongo.ObjectID(userList) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		u = userList as IUserList; | ||||
| 	} | ||||
|  | ||||
| 	if (u == null) return; | ||||
|  | ||||
| 	// このUserListを削除 | ||||
| 	await UserList.remove({ | ||||
| 		_id: u._id | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export const pack = ( | ||||
| 	userList: string | mongo.ObjectID | IUserList | ||||
| ) => new Promise<any>(async (resolve, reject) => { | ||||
|   | ||||
| @@ -1,27 +1,15 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| const deepcopy = require('deepcopy'); | ||||
| const sequential = require('promise-sequential'); | ||||
| import rap from '@prezzemolo/rap'; | ||||
| import db from '../db/mongodb'; | ||||
| import isObjectId from '../misc/is-objectid'; | ||||
| import Note, { packMany as packNoteMany, deleteNote } from './note'; | ||||
| import Following, { deleteFollowing } from './following'; | ||||
| import Mute, { deleteMute } from './mute'; | ||||
| import { packMany as packNoteMany } from './note'; | ||||
| import Following from './following'; | ||||
| import Blocking from './blocking'; | ||||
| import Mute from './mute'; | ||||
| import { getFriendIds } from '../server/api/common/get-friends'; | ||||
| import config from '../config'; | ||||
| import AccessToken, { deleteAccessToken } from './access-token'; | ||||
| import NoteWatching, { deleteNoteWatching } from './note-watching'; | ||||
| import Favorite, { deleteFavorite } from './favorite'; | ||||
| import NoteReaction, { deleteNoteReaction } from './note-reaction'; | ||||
| import MessagingMessage, { deleteMessagingMessage } from './messaging-message'; | ||||
| import MessagingHistory, { deleteMessagingHistory } from './messaging-history'; | ||||
| import DriveFile, { deleteDriveFile } from './drive-file'; | ||||
| import DriveFolder, { deleteDriveFolder } from './drive-folder'; | ||||
| import PollVote, { deletePollVote } from './poll-vote'; | ||||
| import SwSubscription, { deleteSwSubscription } from './sw-subscription'; | ||||
| import Notification, { deleteNotification } from './notification'; | ||||
| import UserList, { deleteUserList } from './user-list'; | ||||
| import FollowRequest, { deleteFollowRequest } from './follow-request'; | ||||
| import FollowRequest from './follow-request'; | ||||
|  | ||||
| const User = db.get<IUser>('users'); | ||||
|  | ||||
| @@ -167,151 +155,6 @@ export function isValidBirthday(birthday: string): boolean { | ||||
| } | ||||
| //#endregion | ||||
|  | ||||
| /** | ||||
|  * Userを物理削除します | ||||
|  */ | ||||
| export async function deleteUser(user: string | mongo.ObjectID | IUser) { | ||||
| 	let u: IUser; | ||||
|  | ||||
| 	// Populate | ||||
| 	if (isObjectId(user)) { | ||||
| 		u = await User.findOne({ | ||||
| 			_id: user | ||||
| 		}); | ||||
| 	} else if (typeof user === 'string') { | ||||
| 		u = await User.findOne({ | ||||
| 			_id: new mongo.ObjectID(user) | ||||
| 		}); | ||||
| 	} else { | ||||
| 		u = user as IUser; | ||||
| 	} | ||||
|  | ||||
| 	console.log(u == null ? `User: delete skipped ${user}` : `User: deleting ${u._id}`); | ||||
|  | ||||
| 	if (u == null) return; | ||||
|  | ||||
| 	// このユーザーのAccessTokenをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await AccessToken.find({ userId: u._id }) | ||||
| 	).map(x => deleteAccessToken(x))); | ||||
|  | ||||
| 	// このユーザーのNoteをすべて削除 | ||||
| 	await sequential(( | ||||
| 		await Note.find({ userId: u._id }) | ||||
| 	).map(x => () => deleteNote(x))); | ||||
|  | ||||
| 	// このユーザーのNoteReactionをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await NoteReaction.find({ userId: u._id }) | ||||
| 	).map(x => deleteNoteReaction(x))); | ||||
|  | ||||
| 	// このユーザーのNoteWatchingをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await NoteWatching.find({ userId: u._id }) | ||||
| 	).map(x => deleteNoteWatching(x))); | ||||
|  | ||||
| 	// このユーザーのPollVoteをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await PollVote.find({ userId: u._id }) | ||||
| 	).map(x => deletePollVote(x))); | ||||
|  | ||||
| 	// このユーザーのFavoriteをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Favorite.find({ userId: u._id }) | ||||
| 	).map(x => deleteFavorite(x))); | ||||
|  | ||||
| 	// このユーザーのMessageをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await MessagingMessage.find({ userId: u._id }) | ||||
| 	).map(x => deleteMessagingMessage(x))); | ||||
|  | ||||
| 	// このユーザーへのMessageをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await MessagingMessage.find({ recipientId: u._id }) | ||||
| 	).map(x => deleteMessagingMessage(x))); | ||||
|  | ||||
| 	// このユーザーの関わるMessagingHistoryをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await MessagingHistory.find({ $or: [{ partnerId: u._id }, { userId: u._id }] }) | ||||
| 	).map(x => deleteMessagingHistory(x))); | ||||
|  | ||||
| 	// このユーザーのDriveFileをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await DriveFile.find({ 'metadata.userId': u._id }) | ||||
| 	).map(x => deleteDriveFile(x))); | ||||
|  | ||||
| 	// このユーザーのDriveFolderをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await DriveFolder.find({ userId: u._id }) | ||||
| 	).map(x => deleteDriveFolder(x))); | ||||
|  | ||||
| 	// このユーザーのMuteをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Mute.find({ muterId: u._id }) | ||||
| 	).map(x => deleteMute(x))); | ||||
|  | ||||
| 	// このユーザーへのMuteをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Mute.find({ muteeId: u._id }) | ||||
| 	).map(x => deleteMute(x))); | ||||
|  | ||||
| 	// このユーザーのFollowingをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Following.find({ followerId: u._id }) | ||||
| 	).map(x => deleteFollowing(x))); | ||||
|  | ||||
| 	// このユーザーへのFollowingをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Following.find({ followeeId: u._id }) | ||||
| 	).map(x => deleteFollowing(x))); | ||||
|  | ||||
| 	// このユーザーのFollowRequestをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await FollowRequest.find({ followerId: u._id }) | ||||
| 	).map(x => deleteFollowRequest(x))); | ||||
|  | ||||
| 	// このユーザーへのFollowRequestをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await FollowRequest.find({ followeeId: u._id }) | ||||
| 	).map(x => deleteFollowRequest(x))); | ||||
|  | ||||
| 	// このユーザーのSwSubscriptionをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await SwSubscription.find({ userId: u._id }) | ||||
| 	).map(x => deleteSwSubscription(x))); | ||||
|  | ||||
| 	// このユーザーのNotificationをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Notification.find({ notifieeId: u._id }) | ||||
| 	).map(x => deleteNotification(x))); | ||||
|  | ||||
| 	// このユーザーが原因となったNotificationをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await Notification.find({ notifierId: u._id }) | ||||
| 	).map(x => deleteNotification(x))); | ||||
|  | ||||
| 	// このユーザーのUserListをすべて削除 | ||||
| 	await Promise.all(( | ||||
| 		await UserList.find({ userId: u._id }) | ||||
| 	).map(x => deleteUserList(x))); | ||||
|  | ||||
| 	// このユーザーが入っているすべてのUserListからこのユーザーを削除 | ||||
| 	await Promise.all(( | ||||
| 		await UserList.find({ userIds: u._id }) | ||||
| 	).map(x => | ||||
| 		UserList.update({ _id: x._id }, { | ||||
| 			$pull: { userIds: u._id } | ||||
| 		}) | ||||
| 	)); | ||||
|  | ||||
| 	// このユーザーを削除 | ||||
| 	await User.remove({ | ||||
| 		_id: u._id | ||||
| 	}); | ||||
|  | ||||
| 	console.log(`User: deleted ${u._id}`); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Pack a user for API response | ||||
|  * | ||||
| @@ -426,8 +269,8 @@ export const pack = ( | ||||
| 		delete _user.hasUnreadNotification; | ||||
| 	} | ||||
|  | ||||
| 	if (meId && !meId.equals(_user.id)) { | ||||
| 		const [following1, following2, followReq1, followReq2, mute] = await Promise.all([ | ||||
| 	if (meId && !meId.equals(_user.id) && opts.detail) { | ||||
| 		const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ | ||||
| 			Following.findOne({ | ||||
| 				followerId: meId, | ||||
| 				followeeId: _user.id | ||||
| @@ -444,6 +287,14 @@ export const pack = ( | ||||
| 				followerId: _user.id, | ||||
| 				followeeId: meId | ||||
| 			}), | ||||
| 			Blocking.findOne({ | ||||
| 				blockerId: meId, | ||||
| 				blockeeId: _user.id | ||||
| 			}), | ||||
| 			Blocking.findOne({ | ||||
| 				blockerId: _user.id, | ||||
| 				blockeeId: meId | ||||
| 			}), | ||||
| 			Mute.findOne({ | ||||
| 				muterId: meId, | ||||
| 				muteeId: _user.id | ||||
| @@ -460,6 +311,12 @@ export const pack = ( | ||||
| 		// Whether the user is followed | ||||
| 		_user.isFollowed = following2 !== null; | ||||
|  | ||||
| 		// Whether the user is blocking | ||||
| 		_user.isBlocking = toBlocking !== null; | ||||
|  | ||||
| 		// Whether the user is blocked | ||||
| 		_user.isBlocked = fromBlocked !== null; | ||||
|  | ||||
| 		// Whether the user is muted | ||||
| 		_user.isMuted = mute !== null; | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										34
									
								
								src/remote/activitypub/kernel/block/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/remote/activitypub/kernel/block/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import User, { IRemoteUser } from '../../../../models/user'; | ||||
| import config from '../../../../config'; | ||||
| import * as debug from 'debug'; | ||||
| import { IBlock } from '../../type'; | ||||
| import block from '../../../../services/blocking/create'; | ||||
|  | ||||
| const log = debug('misskey:activitypub'); | ||||
|  | ||||
| export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { | ||||
| 	const id = typeof activity.object == 'string' ? activity.object : activity.object.id; | ||||
|  | ||||
| 	const uri = activity.id || activity; | ||||
|  | ||||
| 	log(`Block: ${uri}`); | ||||
|  | ||||
| 	if (!id.startsWith(config.url + '/')) { | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	const blockee = await User.findOne({ | ||||
| 		_id: new mongo.ObjectID(id.split('/').pop()) | ||||
| 	}); | ||||
|  | ||||
| 	if (blockee === null) { | ||||
| 		throw new Error('blockee not found'); | ||||
| 	} | ||||
|  | ||||
| 	if (blockee.host != null) { | ||||
| 		throw new Error('ブロックしようとしているユーザーはローカルユーザーではありません'); | ||||
| 	} | ||||
|  | ||||
| 	block(actor, blockee); | ||||
| }; | ||||
| @@ -10,6 +10,7 @@ import accept from './accept'; | ||||
| import reject from './reject'; | ||||
| import add from './add'; | ||||
| import remove from './remove'; | ||||
| import block from './block'; | ||||
|  | ||||
| const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { | ||||
| 	switch (activity.type) { | ||||
| @@ -53,6 +54,10 @@ const self = async (actor: IRemoteUser, activity: Object): Promise<void> => { | ||||
| 		await undo(actor, activity); | ||||
| 		break; | ||||
|  | ||||
| 	case 'Block': | ||||
| 		await block(actor, activity); | ||||
| 		break; | ||||
|  | ||||
| 	case 'Collection': | ||||
| 	case 'OrderedCollection': | ||||
| 		// TODO | ||||
|   | ||||
							
								
								
									
										34
									
								
								src/remote/activitypub/kernel/undo/block.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/remote/activitypub/kernel/undo/block.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| import User, { IRemoteUser } from '../../../../models/user'; | ||||
| import config from '../../../../config'; | ||||
| import * as debug from 'debug'; | ||||
| import { IBlock } from '../../type'; | ||||
| import unblock from '../../../../services/blocking/delete'; | ||||
|  | ||||
| const log = debug('misskey:activitypub'); | ||||
|  | ||||
| export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => { | ||||
| 	const id = typeof activity.object == 'string' ? activity.object : activity.object.id; | ||||
|  | ||||
| 	const uri = activity.id || activity; | ||||
|  | ||||
| 	log(`UnBlock: ${uri}`); | ||||
|  | ||||
| 	if (!id.startsWith(config.url + '/')) { | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	const blockee = await User.findOne({ | ||||
| 		_id: new mongo.ObjectID(id.split('/').pop()) | ||||
| 	}); | ||||
|  | ||||
| 	if (blockee === null) { | ||||
| 		throw new Error('blockee not found'); | ||||
| 	} | ||||
|  | ||||
| 	if (blockee.host != null) { | ||||
| 		throw new Error('ブロック解除しようとしているユーザーはローカルユーザーではありません'); | ||||
| 	} | ||||
|  | ||||
| 	unblock(actor, blockee); | ||||
| }; | ||||
| @@ -1,8 +1,9 @@ | ||||
| import * as debug from 'debug'; | ||||
|  | ||||
| import { IRemoteUser } from '../../../../models/user'; | ||||
| import { IUndo, IFollow } from '../../type'; | ||||
| import { IUndo, IFollow, IBlock } from '../../type'; | ||||
| import unfollow from './follow'; | ||||
| import unblock from './block'; | ||||
| import Resolver from '../../resolver'; | ||||
|  | ||||
| const log = debug('misskey:activitypub'); | ||||
| @@ -31,6 +32,9 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => { | ||||
| 		case 'Follow': | ||||
| 			unfollow(actor, object as IFollow); | ||||
| 			break; | ||||
| 		case 'Block': | ||||
| 			unblock(actor, object as IBlock); | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	return null; | ||||
|   | ||||
							
								
								
									
										8
									
								
								src/remote/activitypub/renderer/block.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/remote/activitypub/renderer/block.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| import config from '../../../config'; | ||||
| import { ILocalUser, IRemoteUser } from "../../../models/user"; | ||||
|  | ||||
| export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({ | ||||
| 	type: 'Block', | ||||
| 	actor: `${config.url}/users/${blocker._id}`, | ||||
| 	object: blockee.uri | ||||
| }); | ||||
| @@ -108,6 +108,10 @@ export interface IAnnounce extends IActivity { | ||||
| 	type: 'Announce'; | ||||
| } | ||||
|  | ||||
| export interface IBlock extends IActivity { | ||||
| 	type: 'Block'; | ||||
| } | ||||
|  | ||||
| export type Object = | ||||
| 	ICollection | | ||||
| 	IOrderedCollection | | ||||
| @@ -120,4 +124,5 @@ export type Object = | ||||
| 	IAdd | | ||||
| 	IRemove | | ||||
| 	ILike | | ||||
| 	IAnnounce; | ||||
| 	IAnnounce | | ||||
| 	IBlock; | ||||
|   | ||||
| @@ -1,36 +1,83 @@ | ||||
| import { toUnicode, toASCII } from 'punycode'; | ||||
| import User, { IUser } from '../models/user'; | ||||
| import User, { IUser, IRemoteUser } from '../models/user'; | ||||
| import webFinger from './webfinger'; | ||||
| import config from '../config'; | ||||
| import { createPerson } from './activitypub/models/person'; | ||||
| import { createPerson, updatePerson } from './activitypub/models/person'; | ||||
| import { URL } from 'url'; | ||||
| import * as debug from 'debug'; | ||||
|  | ||||
| export default async (username: string, _host: string, option?: any): Promise<IUser> => { | ||||
| const log = debug('misskey:remote:resolve-user'); | ||||
|  | ||||
| export default async (username: string, _host: string, option?: any, resync?: boolean): Promise<IUser> => { | ||||
| 	const usernameLower = username.toLowerCase(); | ||||
|  | ||||
| 	if (_host == null) { | ||||
| 		return await User.findOne({ usernameLower }); | ||||
| 		log(`return local user: ${usernameLower}`); | ||||
| 		return await User.findOne({ usernameLower, host: null }); | ||||
| 	} | ||||
|  | ||||
| 	const hostAscii = toASCII(_host).toLowerCase(); | ||||
| 	const host = toUnicode(hostAscii); | ||||
|  | ||||
| 	if (config.host == host) { | ||||
| 		log(`return local user: ${usernameLower}`); | ||||
| 		return await User.findOne({ usernameLower, host: null }); | ||||
| 	} | ||||
|  | ||||
| 	let user = await User.findOne({ usernameLower, host }, option); | ||||
| 	const user = await User.findOne({ usernameLower, host }, option); | ||||
|  | ||||
| 	const acctLower = `${usernameLower}@${hostAscii}`; | ||||
|  | ||||
| 	if (user === null) { | ||||
| 		const acctLower = `${usernameLower}@${hostAscii}`; | ||||
| 		const self = await resolveSelf(acctLower); | ||||
|  | ||||
| 		const finger = await webFinger(acctLower); | ||||
| 		const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); | ||||
| 		if (!self) { | ||||
| 			throw new Error('self link not found'); | ||||
| 		} | ||||
|  | ||||
| 		user = await createPerson(self.href); | ||||
| 		log(`return new remote user: ${acctLower}`); | ||||
| 		return await createPerson(self.href); | ||||
| 	} | ||||
|  | ||||
| 	if (resync) { | ||||
| 		log(`try resync: ${acctLower}`); | ||||
| 		const self = await resolveSelf(acctLower); | ||||
|  | ||||
| 		if ((user as IRemoteUser).uri !== self.href) { | ||||
| 			// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. | ||||
| 			log(`uri missmatch: ${acctLower}`); | ||||
| 			console.log(`recovery missmatch uri for (username=${username}, host=${host}) from ${(user as IRemoteUser).uri} to ${self.href}`); | ||||
|  | ||||
| 			// validate uri | ||||
| 			const uri = new URL(self.href); | ||||
| 			if (uri.hostname !== hostAscii) { | ||||
| 				throw new Error(`Invalied uri`); | ||||
| 			} | ||||
|  | ||||
| 			await User.update({ | ||||
| 				usernameLower, | ||||
| 				host: host | ||||
| 			 }, { | ||||
| 				$set: { | ||||
| 					uri: self.href | ||||
| 				} | ||||
| 			}); | ||||
| 		} else { | ||||
| 			log(`uri is fine: ${acctLower}`); | ||||
| 		} | ||||
|  | ||||
| 		await updatePerson(self.href); | ||||
|  | ||||
| 		log(`return resynced remote user: ${acctLower}`); | ||||
| 		return await User.findOne({ uri: self.href }); | ||||
| } | ||||
|  | ||||
| 	log(`return existing remote user: ${acctLower}`); | ||||
| 	return user; | ||||
| }; | ||||
|  | ||||
| async function resolveSelf(acctLower: string) { | ||||
| 	log(`WebFinger for ${acctLower}`); | ||||
| 	const finger = await webFinger(acctLower); | ||||
| 	const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); | ||||
| 	if (!self) { | ||||
| 		throw new Error('self link not found'); | ||||
| 	} | ||||
| 	return self; | ||||
| } | ||||
|   | ||||
| @@ -62,7 +62,15 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any) | ||||
| 			console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); | ||||
| 		} | ||||
| 	} catch (e) { | ||||
| 		rej(e); | ||||
| 		if (e.name == 'INVALID_PARAM') { | ||||
| 			rej({ | ||||
| 				code: e.name, | ||||
| 				param: e.param, | ||||
| 				reason: e.message | ||||
| 			}); | ||||
| 		} else { | ||||
| 			rej(e); | ||||
| 		} | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -41,7 +41,7 @@ async function fetchAny(uri: string) { | ||||
| 	// URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ | ||||
| 	if (uri.startsWith(config.url + '/')) { | ||||
| 		const id = new mongo.ObjectID(uri.split('/').pop()); | ||||
| 		const [ user, note ] = await Promise.all([ | ||||
| 		const [user, note] = await Promise.all([ | ||||
| 			User.findOne({ _id: id }), | ||||
| 			Note.findOne({ _id: id }) | ||||
| 		]); | ||||
| @@ -52,7 +52,7 @@ async function fetchAny(uri: string) { | ||||
|  | ||||
| 	// URI(AP Object id)としてDB検索 | ||||
| 	{ | ||||
| 		const [ user, note ] = await Promise.all([ | ||||
| 		const [user, note] = await Promise.all([ | ||||
| 			User.findOne({ uri: uri }), | ||||
| 			Note.findOne({ uri: uri }) | ||||
| 		]); | ||||
| @@ -68,7 +68,7 @@ async function fetchAny(uri: string) { | ||||
| 	// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する | ||||
| 	// これはDBに存在する可能性があるため再度DB検索 | ||||
| 	if (uri !== object.id) { | ||||
| 		const [ user, note ] = await Promise.all([ | ||||
| 		const [user, note] = await Promise.all([ | ||||
| 			User.findOne({ uri: object.id }), | ||||
| 			Note.findOne({ uri: object.id }) | ||||
| 		]); | ||||
|   | ||||
							
								
								
									
										77
									
								
								src/server/api/endpoints/blocking/create.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/server/api/endpoints/blocking/create.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; | ||||
| const ms = require('ms'); | ||||
| import User, { pack, ILocalUser } from '../../../../models/user'; | ||||
| import Blocking from '../../../../models/blocking'; | ||||
| import create from '../../../../services/blocking/create'; | ||||
| import getParams from '../../get-params'; | ||||
|  | ||||
| export const meta = { | ||||
| 	stability: 'stable', | ||||
|  | ||||
| 	desc: { | ||||
| 		'ja-JP': '指定したユーザーをブロックします。', | ||||
| 		'en-US': 'Block a user.' | ||||
| 	}, | ||||
|  | ||||
| 	limit: { | ||||
| 		duration: ms('1hour'), | ||||
| 		max: 100 | ||||
| 	}, | ||||
|  | ||||
| 	requireCredential: true, | ||||
|  | ||||
| 	kind: 'following-write', | ||||
|  | ||||
| 	params: { | ||||
| 		userId: $.type(ID).note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': '対象のユーザーのID', | ||||
| 				'en-US': 'Target user ID' | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const blocker = user; | ||||
|  | ||||
| 	// 自分自身 | ||||
| 	if (user._id.equals(ps.userId)) { | ||||
| 		return rej('blockee is yourself'); | ||||
| 	} | ||||
|  | ||||
| 	// Get blockee | ||||
| 	const blockee = await User.findOne({ | ||||
| 		_id: ps.userId | ||||
| 	}, { | ||||
| 		fields: { | ||||
| 			data: false, | ||||
| 			profile: false | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	if (blockee === null) { | ||||
| 		return rej('user not found'); | ||||
| 	} | ||||
|  | ||||
| 	// Check if already blocking | ||||
| 	const exist = await Blocking.findOne({ | ||||
| 		blockerId: blocker._id, | ||||
| 		blockeeId: blockee._id | ||||
| 	}); | ||||
|  | ||||
| 	if (exist !== null) { | ||||
| 		return rej('already blocking'); | ||||
| 	} | ||||
|  | ||||
| 	// Create blocking | ||||
| 	await create(blocker, blockee); | ||||
|  | ||||
| 	// Send response | ||||
| 	res(await pack(blockee._id, user, { | ||||
| 		detail: true | ||||
| 	})); | ||||
| }); | ||||
							
								
								
									
										77
									
								
								src/server/api/endpoints/blocking/delete.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/server/api/endpoints/blocking/delete.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; | ||||
| const ms = require('ms'); | ||||
| import User, { pack, ILocalUser } from '../../../../models/user'; | ||||
| import Blocking from '../../../../models/blocking'; | ||||
| import deleteBlocking from '../../../../services/blocking/delete'; | ||||
| import getParams from '../../get-params'; | ||||
|  | ||||
| export const meta = { | ||||
| 	stability: 'stable', | ||||
|  | ||||
| 	desc: { | ||||
| 		'ja-JP': '指定したユーザーのブロックを解除します。', | ||||
| 		'en-US': 'Unblock a user.' | ||||
| 	}, | ||||
|  | ||||
| 	limit: { | ||||
| 		duration: ms('1hour'), | ||||
| 		max: 100 | ||||
| 	}, | ||||
|  | ||||
| 	requireCredential: true, | ||||
|  | ||||
| 	kind: 'following-write', | ||||
|  | ||||
| 	params: { | ||||
| 		userId: $.type(ID).note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': '対象のユーザーのID', | ||||
| 				'en-US': 'Target user ID' | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const blocker = user; | ||||
|  | ||||
| 	// Check if the blockee is yourself | ||||
| 	if (user._id.equals(ps.userId)) { | ||||
| 		return rej('blockee is yourself'); | ||||
| 	} | ||||
|  | ||||
| 	// Get blockee | ||||
| 	const blockee = await User.findOne({ | ||||
| 		_id: ps.userId | ||||
| 	}, { | ||||
| 		fields: { | ||||
| 			data: false, | ||||
| 			'profile': false | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	if (blockee === null) { | ||||
| 		return rej('user not found'); | ||||
| 	} | ||||
|  | ||||
| 	// Check not blocking | ||||
| 	const exist = await Blocking.findOne({ | ||||
| 		blockerId: blocker._id, | ||||
| 		blockeeId: blockee._id | ||||
| 	}); | ||||
|  | ||||
| 	if (exist === null) { | ||||
| 		return rej('already not blocking'); | ||||
| 	} | ||||
|  | ||||
| 	// Delete blocking | ||||
| 	await deleteBlocking(blocker, blockee); | ||||
|  | ||||
| 	// Send response | ||||
| 	res(await pack(blockee._id, user, { | ||||
| 		detail: true | ||||
| 	})); | ||||
| }); | ||||
							
								
								
									
										64
									
								
								src/server/api/endpoints/blocking/list.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/server/api/endpoints/blocking/list.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; | ||||
| import Blocking, { packMany } from '../../../../models/blocking'; | ||||
| import { ILocalUser } from '../../../../models/user'; | ||||
| import getParams from '../../get-params'; | ||||
|  | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| 		'ja-JP': 'ブロックしているユーザー一覧を取得します。', | ||||
| 		'en-US': 'Get blocking users.' | ||||
| 	}, | ||||
|  | ||||
| 	requireCredential: true, | ||||
|  | ||||
| 	kind: 'following-read', | ||||
|  | ||||
| 	params: { | ||||
| 		limit: $.num.optional.range(1, 100).note({ | ||||
| 			default: 30 | ||||
| 		}), | ||||
|  | ||||
| 		sinceId: $.type(ID).optional.note({ | ||||
| 		}), | ||||
|  | ||||
| 		untilId: $.type(ID).optional.note({ | ||||
| 		}), | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	// Check if both of sinceId and untilId is specified | ||||
| 	if (ps.sinceId && ps.untilId) { | ||||
| 		return rej('cannot set sinceId and untilId'); | ||||
| 	} | ||||
|  | ||||
| 	const query = { | ||||
| 		blockerId: me._id | ||||
| 	} as any; | ||||
|  | ||||
| 	const sort = { | ||||
| 		_id: -1 | ||||
| 	}; | ||||
|  | ||||
| 	if (ps.sinceId) { | ||||
| 		sort._id = 1; | ||||
| 		query._id = { | ||||
| 			$gt: ps.sinceId | ||||
| 		}; | ||||
| 	} else if (ps.untilId) { | ||||
| 		query._id = { | ||||
| 			$lt: ps.untilId | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	const blockings = await Blocking | ||||
| 		.find(query, { | ||||
| 			limit: ps.limit, | ||||
| 			sort: sort | ||||
| 		}); | ||||
|  | ||||
| 	res(await packMany(blockings, me)); | ||||
| }); | ||||
| @@ -25,7 +25,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await driveChart.getChart(ps.span as any, ps.limit); | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await federationChart.getChart(ps.span as any, ps.limit); | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag); | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await networkChart.getChart(ps.span as any, ps.limit); | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await notesChart.getChart(ps.span as any, ps.limit); | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId); | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId); | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId); | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId); | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ export const meta = { | ||||
|  | ||||
| export default (params: any) => new Promise(async (res, rej) => { | ||||
| 	const [ps, psErr] = getParams(meta, params); | ||||
| 	if (psErr) throw psErr; | ||||
| 	if (psErr) return rej(psErr); | ||||
|  | ||||
| 	const stats = await usersChart.getChart(ps.span as any, ps.limit); | ||||
|  | ||||
|   | ||||
| @@ -44,7 +44,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = | ||||
|  | ||||
| 	const [childFoldersCount, childFilesCount] = await Promise.all([ | ||||
| 		DriveFolder.count({ parentId: folder._id }), | ||||
| 		DriveFile.count({ folderId: folder._id }) | ||||
| 		DriveFile.count({ 'metadata.folderId': folder._id }) | ||||
| 	]); | ||||
|  | ||||
| 	if (childFoldersCount !== 0 || childFilesCount !== 0) { | ||||
|   | ||||
| @@ -68,7 +68,11 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = | ||||
| 	} | ||||
|  | ||||
| 	// Create following | ||||
| 	await create(follower, followee); | ||||
| 	try { | ||||
| 		await create(follower, followee); | ||||
| 	} catch (e) { | ||||
| 		return rej(e && e.message ? e.message : e); | ||||
| 	} | ||||
|  | ||||
| 	// Send response | ||||
| 	res(await pack(followee._id, user)); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user