Compare commits
	
		
			142 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e41f74e77c | ||
|   | c21caad1c5 | ||
|   | 86fcd3a378 | ||
|   | 2b3687b3cb | ||
|   | 5d61c7c691 | ||
|   | 1bb266e7c7 | ||
|   | 1fca8d322c | ||
|   | 325cd03a59 | ||
|   | 2f7e6baa05 | ||
|   | d252e066fe | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | fe7bd9ab3c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 84e3f41305 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 3e8cccad0d | ||
|   | a2b94d67f7 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 6ab61e73b0 | ||
|   | 051c6973af | ||
|   | 806a49ec3d | ||
|   | 3829fe128a | ||
|   | 649177985d | ||
|   | c15148b23c | ||
|   | 261a3f5d91 | ||
|   | 256ba78ba5 | ||
|   | 04aff8866e | ||
|   | 1a51b98700 | ||
|   | f64100226d | ||
|   | b7805e48a6 | ||
|   | 0d9556620d | ||
|   | a51828a7a2 | ||
|   | 7e2009f408 | ||
|   | 008d950a39 | ||
|   | 22d5862afb | ||
|   | de569147a5 | ||
|   | a82c3db750 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 80706d10af | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 93f01ed4df | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a3a28e5557 | ||
|   | 8948a0d3a4 | ||
|   | d849ea9b41 | ||
|   | 0144575f3f | ||
|   | bdbe646ca7 | ||
|   | 1a1483a242 | ||
|   | 962346785b | ||
|   | a73da3cd70 | ||
|   | 9c27d0ae3f | ||
|   | 525d5218c1 | ||
|   | e23b13ec7f | ||
|   | 29b000e03c | ||
|   | 6a7b0df810 | ||
|   | 4142de9195 | ||
|   | 9195e1be00 | ||
|   | 75382d13fd | ||
|   | d444280a28 | ||
|   | 52fc0fe04a | ||
|   | 216bebadf1 | ||
|   | a5592931cb | ||
|   | a2228417ff | ||
|   | 3e1e292c3e | ||
|   | f2f039ae9e | ||
|   | 29dde1eda0 | ||
|   | 45d3792ce0 | ||
|   | 875d0aaebb | ||
|   | 26c9d8ff6f | ||
|   | 5e3372e932 | ||
|   | f7069dcd18 | ||
|   | 560bb65384 | ||
|   | 50cd6a036e | ||
|   | 441ab2b5f8 | ||
|   | ba5ed188a1 | ||
|   | 72e672f08d | ||
|   | 120474ec6a | ||
|   | eee57c47f5 | ||
|   | 4c160869b8 | ||
|   | 3720a7fbe0 | ||
|   | 7afa541a53 | ||
|   | 6f979c8275 | ||
|   | d399241e65 | ||
|   | e85dec030a | ||
|   | d0220764cc | ||
|   | 75c1df9531 | ||
|   | bca7156d6b | ||
|   | 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 | 
| @@ -9,6 +9,7 @@ mongodb: | ||||
|   db: test-misskey | ||||
|   user: admin | ||||
|   pass: '' | ||||
| # __REDIS__ | ||||
| redis: | ||||
|   host: localhost | ||||
|   port: 6379 | ||||
							
								
								
									
										137
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| 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 . | tee docker.log | ||||
|             tail -n 1 docker.log | read __Successfully __built tag | ||||
|       - when: | ||||
|           condition: <<parameters.with_deploy>> | ||||
|           steps: | ||||
|             - run: | ||||
|                 name: Deploy | ||||
|                 command: | | ||||
|                   if [ "$DOCKERHUB_USERNAME$DOCKERHUB_PASSWORD" ] | ||||
|                    then | ||||
|                     docker tag $tag misskey/misskey | ||||
|                     docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD | ||||
|                     docker push misskey/misskey | ||||
|                    else | ||||
|                     echo -e '\033[0;33mAborted deploying to Docker Hub\033[0;39m' | ||||
|                   fi | ||||
|  | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										20
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								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 \ | ||||
| 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"] | ||||
|   | ||||
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								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/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/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/3?token-time=2145916800&token-hash=qsdn0-e6yLaLI6hUX9JAkyTR6a5UdnSp7T1foniBvGQ%3D" alt="YUKIMOCHI"></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=5881381">Naoki Kosaka</a></td> | ||||
| <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> | ||||
| <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</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:** Wed, 31 Oct 2018 23:21: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). | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "通知" | ||||
|   apps: "アプリ" | ||||
|   mute: "ミュート" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "パスワード" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "Profil" | ||||
|   notification: "Mitteilungen" | ||||
|   apps: "In App öffnen" | ||||
|   mute: "Stummschalten" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "Sicherheit" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "Passwort" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "Passwort ändern" | ||||
|   enter-current-password: "Derzeitiges Passwort eingeben" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "Profile" | ||||
|   notification: "Notification" | ||||
|   apps: "Apps" | ||||
|   mute: "Mute" | ||||
|   mute-and-block: "Mute / Block" | ||||
|   blocking: "Blocking" | ||||
|   security: "Security" | ||||
|   signin: "Sign in history" | ||||
|   password: "Password" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "Max" | ||||
|   in-use: "In use" | ||||
|   stats: "Statistics" | ||||
| desktop/views/components/settings.mute.vue: | ||||
|   no-users: "No muted users" | ||||
| common/views/components/mute-and-block.vue: | ||||
|   mute-and-block: "Mute / Block" | ||||
|   mute: "Mute" | ||||
|   block: "Blocking" | ||||
|   no-muted-users: "No muted users" | ||||
|   no-blocked-users: "No blocked users" | ||||
| desktop/views/components/settings.password.vue: | ||||
|   reset: "Change password" | ||||
|   enter-current-password: "Enter the current password" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,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" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "Perfil" | ||||
|   notification: "Notificación" | ||||
|   apps: "Aplicaciones" | ||||
|   mute: "Silenciar" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "Seguridad" | ||||
|   signin: "Historial de inicios de sesión" | ||||
|   password: "Contraseña" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   in-use: "使用中" | ||||
|   stats: "統計" | ||||
| desktop/views/components/settings.mute.vue: | ||||
|   no-users: "No hay usuarios silenciados" | ||||
| 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: "Cambiar contraseña" | ||||
|   enter-current-password: "Ingresar contraseña actual" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -28,11 +28,11 @@ common: | ||||
|   BSoD: | ||||
|     fatal-error: ":( 致命的な問題が発生しました。" | ||||
|     update-browser-os: "お使いのブラウザ(またはOS)のバージョンを更新すると解決する可能性があります。" | ||||
|     error-code: "エラーコード" | ||||
|     browser-version: "ブラウザ バージョン" | ||||
|     client-version: "クライアント バージョン" | ||||
|     error-code: "Code d’erreur" | ||||
|     browser-version: "Version du navigateur" | ||||
|     client-version: "La version du client" | ||||
|     email-support: "問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。" | ||||
|     thanks: "Thank you for using Misskey." | ||||
|     thanks: "Merci d’avoir choisi d’utiliser Misskey." | ||||
|   got-it: "J'ai compris !" | ||||
|   customization-tips: | ||||
|     title: "Conseils de personnalisation" | ||||
| @@ -62,7 +62,7 @@ common: | ||||
|     years_ago: "Il y a {} an·s" | ||||
|   month-and-day: "{month} mois/{day} jour" | ||||
|   trash: "Corbeille" | ||||
|   drive: "ドライブ" | ||||
|   drive: "Drive" | ||||
|   weekday-short: | ||||
|     sunday: "D" | ||||
|     monday: "L" | ||||
| @@ -124,12 +124,12 @@ common: | ||||
|   reduce-motion: "Réduire les animations dans l’interface utilisateur" | ||||
|   this-setting-is-this-device-only: "Uniquement sur cet appareil" | ||||
|   do-not-use-in-production: 'Il s’agit d’une version de développement. Ne pas utiliser dans un environnement de production.' | ||||
|   is-remote-user: "このユーザー情報はコピーです。" | ||||
|   is-remote-user: "Ces informations utilisateur ont été copiées." | ||||
|   is-remote-post: "この投稿情報はコピーです。" | ||||
|   view-on-remote: "正確な情報を見る" | ||||
|   view-on-remote: "Consulter le profil complet" | ||||
|   error: | ||||
|     title: '問題が発生しました' | ||||
|     retry: 'やり直す' | ||||
|     title: 'Une erreur est survenue' | ||||
|     retry: 'Réessayer' | ||||
|   reversi: | ||||
|     drawn: "Partie nulle" | ||||
|     my-turn: "C’est votre tour" | ||||
| @@ -185,7 +185,7 @@ common: | ||||
|     rename: "Renommer" | ||||
|     stack-left: "Vers la gauche" | ||||
|     pop-right: "Vers la droite" | ||||
|   dev: "アプリの作成に失敗しました。再度お試しください。" | ||||
|   dev: "Échec lors de la création de l’application. Veuillez réessayer." | ||||
| auth/views/form.vue: | ||||
|   share-access: "Désirez-vous <b>autoriser</b> <i>{{ app.name }}</i> à avoir accès à votre compte ?" | ||||
|   permission-ask: "Cette application nécessite les autorisations suivantes :" | ||||
| @@ -542,24 +542,24 @@ desktop/views/components/charts.vue: | ||||
|   title: "Graphiques" | ||||
|   per-day: "par jour" | ||||
|   per-hour: "par heure" | ||||
|   federation: "フェデレーション" | ||||
|   federation: "Fédération" | ||||
|   notes: "Publications" | ||||
|   users: "Utilisateurs" | ||||
|   drive: "Drive" | ||||
|   network: "Réseau" | ||||
|   charts: | ||||
|     federation-instances: "インスタンスの増減" | ||||
|     federation-instances-total: "インスタンスの積算" | ||||
|     federation-instances-total: "Nombre total d’instances" | ||||
|     notes: "投稿の増減 (統合)" | ||||
|     local-notes: "投稿の増減 (ローカル)" | ||||
|     remote-notes: "投稿の増減 (リモート)" | ||||
|     notes-total: "Total des notes" | ||||
|     users: "Nombre d’utilisateurs·trices : augmentation/diminution" | ||||
|     users-total: "ユーザーの積算" | ||||
|     users-total: "Nombre total des utilisateurs·rices" | ||||
|     drive: "ドライブ使用量の増減" | ||||
|     drive-total: "ドライブ使用量の積算" | ||||
|     drive-total: "Utilisation totale du lecteur" | ||||
|     drive-files: "ドライブのファイル数の増減" | ||||
|     drive-files-total: "ドライブのファイル数の積算" | ||||
|     drive-files-total: "Nombre total de fichiers sur le lecteur" | ||||
|     network-requests: "Requêtes" | ||||
|     network-time: "Temps de réponse" | ||||
|     network-usage: "Traffic" | ||||
| @@ -679,10 +679,10 @@ desktop/views/components/note.vue: | ||||
|   reposted-by: "Partagé par {}" | ||||
|   reply: "Répondre" | ||||
|   renote: "Partager" | ||||
|   add-reaction: "リアクション" | ||||
|   detail: "詳細" | ||||
|   private: "この投稿は非公開です" | ||||
|   deleted: "この投稿は削除されました" | ||||
|   add-reaction: "Ajouter votre réaction" | ||||
|   detail: "Détails" | ||||
|   private: "Cette publication est privée" | ||||
|   deleted: "Cette publication a été supprimée" | ||||
| desktop/views/components/notes.vue: | ||||
|   error: "Échec du chargement." | ||||
|   retry: "Réessayer" | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "Profil" | ||||
|   notification: "Notification" | ||||
|   apps: "Applications" | ||||
|   mute: "Mettre en sourdine" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "Sécurité" | ||||
|   signin: "Historique de connexion" | ||||
|   password: "Mot de Passe" | ||||
| @@ -765,7 +766,7 @@ desktop/views/components/settings.vue: | ||||
|   deck-default: "デッキをデフォルトのUIにする" | ||||
|   display: "Affichage et design" | ||||
|   customize: "Personnaliser l'Accueil" | ||||
|   wallpaper: "壁紙" | ||||
|   wallpaper: "Arrière plan" | ||||
|   choose-wallpaper: "Sélectionner un fond d'écran" | ||||
|   delete-wallpaper: "Supprimer le fond d'écran" | ||||
|   dark-mode: "Mode nuit" | ||||
| @@ -777,14 +778,14 @@ desktop/views/components/settings.vue: | ||||
|   suggest-recent-hashtags: "Afficher les hashtags populaires dans le champs de saisie" | ||||
|   show-clock-on-header: "Afficher l'horloge à droite sur le coté supérieur" | ||||
|   show-reply-target: "Afficher les réponses" | ||||
|   timeline: "タイムライン" | ||||
|   timeline: "Chronologie" | ||||
|   show-my-renotes: "Afficher mes republications dans le fil" | ||||
|   show-renoted-my-notes: "Afficher mes republications dans les fils" | ||||
|   show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する" | ||||
|   show-maps: "Afficher la carte" | ||||
|   deck-column-align: "デッキのカラムの位置" | ||||
|   deck-column-align-center: "中央" | ||||
|   deck-column-align-left: "左" | ||||
|   deck-column-align-center: "Centrer" | ||||
|   deck-column-align-left: "À gauche" | ||||
|   sound: "Son" | ||||
|   enable-sounds: "Activer le son" | ||||
|   enable-sounds-desc: "Jouer un son lorsque vous recevez un message. Ce paramètre est sauvegardé dans le navigateur." | ||||
| @@ -825,7 +826,7 @@ desktop/views/components/settings.vue: | ||||
|   tools: "Outils" | ||||
|   task-manager: "Gestionnaire de tâches" | ||||
|   third-parties: "Services tiers" | ||||
|   navbar-position: "ナビゲーションバーの位置" | ||||
|   navbar-position: "Position de la barre de navigation" | ||||
|   navbar-position-top: "En haut" | ||||
|   navbar-position-left: "à gauche" | ||||
|   navbar-position-right: "à droite" | ||||
| @@ -850,25 +851,29 @@ desktop/views/components/settings.2fa.vue: | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   regeneration-of-token: "Si votre jeton est compromis, vous pouvez le régénérer." | ||||
|   regenerate-token: "Régénérer le jeton" | ||||
|   token: "Jeton :" | ||||
|   enter-password: "Entrez le mot de passe" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
|     title: 'Console API' | ||||
|     endpoint: 'Point de terminaison' | ||||
|     parameter: 'Paramètres' | ||||
|     send: 'Envoyer' | ||||
|     sending: 'Envoi en cours' | ||||
|     response: 'Résultat' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "Aucune application autorisée" | ||||
| common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   in-use: "使用中" | ||||
|   stats: "統計" | ||||
| desktop/views/components/settings.mute.vue: | ||||
|   no-users: "Aucun utilisateurs mis en sourdine" | ||||
|   in-use: "utilisé" | ||||
|   stats: "Statistiques" | ||||
| 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: "Changer votre mot de passe" | ||||
|   enter-current-password: "Entrez votre mot de passe actuel" | ||||
| @@ -945,8 +950,8 @@ desktop/views/pages/admin/admin.vue: | ||||
|   dashboard: "Tableau de bord" | ||||
|   users: "Utilisateur·rice·s" | ||||
|   update: "Mises à jour" | ||||
|   announcements: "お知らせ" | ||||
|   hashtags: "ハッシュタグ" | ||||
|   announcements: "Annonces" | ||||
|   hashtags: "Hashtags" | ||||
| desktop/views/pages/admin/admin.dashboard.vue: | ||||
|   dashboard: "Tableau de bord" | ||||
|   all-users: "Toutes les utilisateurrices" | ||||
| @@ -954,9 +959,9 @@ desktop/views/pages/admin/admin.dashboard.vue: | ||||
|   all-notes: "Toutes les publications" | ||||
|   original-notes: "Publications sur cette instance" | ||||
|   invite: "Invitation" | ||||
|   banner-url: "Banner URL" | ||||
|   disableRegistration: "Disable new user registration" | ||||
|   disableLocalTimeline: "Disable the local timeline" | ||||
|   banner-url: "URL de la bannière" | ||||
|   disableRegistration: "Désactiver l’enregistrement de nouveaux utilisateurs·rices" | ||||
|   disableLocalTimeline: "Désactiver le fil local" | ||||
| desktop/views/pages/admin/admin.suspend-user.vue: | ||||
|   suspend-user: "Suspendre un·e utilisateur·rice" | ||||
|   suspend: "Suspendre" | ||||
| @@ -974,22 +979,22 @@ desktop/views/pages/admin/admin.unverify-user.vue: | ||||
|   unverify: "Ôter la vérification du compte" | ||||
|   unverified: "Ce compte n'est pas vérifié" | ||||
| desktop/views/pages/admin/admin.announcements.vue: | ||||
|   announcements: "お知らせ" | ||||
|   announcements: "Annonces" | ||||
| desktop/views/pages/admin/admin.hashtags.vue: | ||||
|   hided-tags: "Hidden Tags" | ||||
|   hided-tags: "Tags cachés" | ||||
| desktop/views/pages/deck/deck.tl-column.vue: | ||||
|   is-media-only: "Les publications médias uniquement" | ||||
|   is-media-view: "Vue média" | ||||
|   edit: "Options" | ||||
| desktop/views/pages/deck/deck.user-column.vue: | ||||
|   posts: "投稿" | ||||
|   following: "フォロー" | ||||
|   followers: "フォロワー" | ||||
|   images: "画像" | ||||
|   activity: "アクティビティ" | ||||
|   timeline: "タイムライン" | ||||
|   pinned-notes: "ピン留めされた投稿" | ||||
|   push-to-a-list: "リストに追加" | ||||
|   posts: "Publications" | ||||
|   following: "Suit" | ||||
|   followers: "Abonné·e·s" | ||||
|   images: "Images" | ||||
|   activity: "Activité" | ||||
|   timeline: "Chronologie" | ||||
|   pinned-notes: "Publications épinglées" | ||||
|   push-to-a-list: "Ajouter à la liste" | ||||
| desktop/views/pages/stats/stats.vue: | ||||
|   all-users: "Toutes les utilisateurrices" | ||||
|   original-users: "Utilisateur·rice·s sur cette instance" | ||||
| @@ -1042,7 +1047,7 @@ desktop/views/pages/user/user.friends.vue: | ||||
|   no-users: "Pas d'utilisateurs" | ||||
| desktop/views/pages/user/user.vue: | ||||
|   is-suspended: "Ce compte a été suspendu." | ||||
|   last-used-at: "最終アクセス" | ||||
|   last-used-at: "Actif·ive pour la dernière fois" | ||||
| desktop/views/pages/user/user.photos.vue: | ||||
|   title: "Photos" | ||||
|   loading: "Chargement en cours" | ||||
| @@ -1055,6 +1060,9 @@ desktop/views/pages/user/user.profile.vue: | ||||
|   mute: "Mettre en sourdine" | ||||
|   muted: "Muting" | ||||
|   unmute: "Enlever la sourdine" | ||||
|   block: "Bloquer" | ||||
|   unblock: "Débloquer" | ||||
|   block-confirm: "Bloquer cet utilisateur ?" | ||||
|   push-to-a-list: "Ajouter à la liste" | ||||
|   list-pushed: "Vous avez ajouté {user} à la liste {list}." | ||||
| desktop/views/pages/user/user.header.vue: | ||||
| @@ -1062,10 +1070,10 @@ desktop/views/pages/user/user.header.vue: | ||||
|   following: "Suit" | ||||
|   followers: "Abonné·e·s" | ||||
|   is-bot: "Ce compte est un Bot" | ||||
|   years-old: "歳" | ||||
|   year: "年" | ||||
|   month: "月" | ||||
|   day: "日" | ||||
|   years-old: "ans d’âge" | ||||
|   year: "Année" | ||||
|   month: "Mois" | ||||
|   day: "Jour" | ||||
| desktop/views/pages/user/user.timeline.vue: | ||||
|   default: "Publications" | ||||
|   with-replies: "Publications et réponses" | ||||
| @@ -1124,8 +1132,8 @@ mobile/views/components/drive.file-detail.vue: | ||||
|   hash: "Hash (md5)" | ||||
|   exif: "EXIF" | ||||
|   nsfw: "CW" | ||||
|   mark-as-sensitive: "閲覧注意に設定" | ||||
|   unmark-as-sensitive: "閲覧注意を解除" | ||||
|   mark-as-sensitive: "Marquer comme sensible" | ||||
|   unmark-as-sensitive: "Ne pas marquer comme sensible" | ||||
| mobile/views/components/media-image.vue: | ||||
|   sensitive: "Le contenu est NSFW" | ||||
|   click-to-show: "Cliquer pour afficher" | ||||
| @@ -1285,8 +1293,8 @@ mobile/views/pages/settings.vue: | ||||
|   timeline: "Fil d'actualité" | ||||
|   show-reply-target: "Afficher les réponses" | ||||
|   show-my-renotes: "Afficher mes republications" | ||||
|   show-renoted-my-notes: "自分の投稿のRenoteを表示する" | ||||
|   show-local-renotes: "ローカルの投稿のRenoteを表示する" | ||||
|   show-renoted-my-notes: "Afficher mes publications partagées" | ||||
|   show-local-renotes: "Afficher les publications partagées localement" | ||||
|   post-style: "Style de la publication" | ||||
|   post-style-standard: "Standard" | ||||
|   post-style-smart: "Intelligent" | ||||
| @@ -1319,7 +1327,7 @@ mobile/views/pages/settings.vue: | ||||
|   signout: "Déconnexion" | ||||
|   sound: "Sons" | ||||
|   enable-sounds: "Activer les sons" | ||||
|   mark-as-read-all-unread-notes: "すべての投稿を既読にする" | ||||
|   mark-as-read-all-unread-notes: "Marquer toutes les publications comme lues" | ||||
| mobile/views/pages/user.vue: | ||||
|   follows-you: "Vous suit" | ||||
|   following: "Abonnements" | ||||
| @@ -1329,6 +1337,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "Fil d'actualité" | ||||
|   media: "Media" | ||||
|   is-suspended: "This account has been suspended." | ||||
|   mute: "Mettre en sourdine" | ||||
|   unmute: "Enlever la sourdine" | ||||
|   block: "Bloquer" | ||||
|   unblock: "Débloquer" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "Notes récentes" | ||||
|   images: "Images" | ||||
| @@ -1375,28 +1387,28 @@ docs: | ||||
| dev/views/index.vue: | ||||
|   manage-apps: "Gestion des applications" | ||||
| dev/views/apps.vue: | ||||
|   manage-apps: "アプリを管理" | ||||
|   create-app: "アプリ作成" | ||||
|   app-missing: "アプリなし" | ||||
|   manage-apps: "Gestion des applications" | ||||
|   create-app: "Créer une app" | ||||
|   app-missing: "Aucune application" | ||||
| 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クライアント。" | ||||
|   create-app: "Création d’une application" | ||||
|   app-name: "Nom de l’application" | ||||
|   app-name-desc: "Le nom de votre application" | ||||
|   app-name-ex: "p. ex. Misskey pour iOS" | ||||
|   app-overview: "Description courte de l’application" | ||||
|   app-desc: "Brève description introductive à votre application." | ||||
|   app-desc-ex: "p. ex) Misskey pour iOS" | ||||
|   callback-url: "コールバックURL (オプション)" | ||||
|   callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。" | ||||
|   authority: "権限" | ||||
|   authority: "Autorisations " | ||||
|   authority-desc: "ここで要求した機能だけがAPIからアクセスできます。" | ||||
|   authority-warning: "アプリ作成後も変更できますが、新たな権限を付与する場合、その時点で関連付けられているユーザーキーはすべて無効になります。" | ||||
|   account-read: "アカウントの情報を見る。" | ||||
|   account-write: "アカウントの情報を操作する。" | ||||
|   note-write: "投稿する。" | ||||
|   reaction-write: "リアクションしたりリアクションをキャンセルする。" | ||||
|   following-write: "フォローしたりフォロー解除する。" | ||||
|   drive-read: "ドライブを見る。" | ||||
|   drive-write: "ドライブを操作する。" | ||||
|   notification-read: "通知を見る。" | ||||
|   notification-write: "通知を操作する。" | ||||
|   account-read: "Afficher les informations du compte" | ||||
|   account-write: "Modifications des informations du compte" | ||||
|   note-write: "Publications." | ||||
|   reaction-write: "Ajout et suppression de réactions." | ||||
|   following-write: "S’abonner et se désabonner." | ||||
|   drive-read: "Lecture du Drive." | ||||
|   drive-write: "Téléversement/suppression des fichiers de votre Lecteur." | ||||
|   notification-read: "Lire vos notifications." | ||||
|   notification-write: "Gestion de vos notifications." | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "通知" | ||||
|   apps: "アプリ" | ||||
|   mute: "ミュート" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "パスワード" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,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: "パスワード" | ||||
| @@ -973,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: "パスワードを変更する" | ||||
| @@ -1203,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}に追加しました。" | ||||
|  | ||||
| @@ -1531,6 +1539,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
|  | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "通知" | ||||
|   apps: "アプリ" | ||||
|   mute: "ミュート" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "守護神セキュリティ" | ||||
|   signin: "こんな感じでサインインしたらしいで" | ||||
|   password: "パスワード" | ||||
| @@ -848,27 +849,31 @@ desktop/views/components/settings.2fa.vue: | ||||
|   failed: "なんか設定に失敗したで。トークンを間違えとらんか確認してや。" | ||||
|   info: "次のサインインからは、パスワードに加えてデバイスに出とるトークンを入力してな。" | ||||
| common/views/components/api-settings.vue: | ||||
|   intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。" | ||||
|   caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。" | ||||
|   regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。" | ||||
|   regenerate-token: "トークンを再生成" | ||||
|   intro: "API使うんやったらこのトークンを「i」っちゅうパラメータにくっつけてリクエストできるで。" | ||||
|   caution: "アカウント勝手にいじられるかも知れんから、このトークンは教えたらあかんし、アプリにも書いたらあかんで(これはフリちゃうで)" | ||||
|   regeneration-of-token: "トークン漏れてもうたんやったらもっかい生成できるで。" | ||||
|   regenerate-token: "トークンもっかい生成" | ||||
|   token: "Token:" | ||||
|   enter-password: "パスワードを入力してください" | ||||
|   enter-password: "パスワードを入れてや" | ||||
|   console: | ||||
|     title: 'APIコンソール' | ||||
|     endpoint: 'エンドポイント' | ||||
|     parameter: 'パラメータ' | ||||
|     send: '送信' | ||||
|     sending: '応答待ち' | ||||
|     response: '結果' | ||||
|     send: '送る' | ||||
|     sending: '応答待っとる' | ||||
|     response: 'こんなん返ってきたわ' | ||||
| desktop/views/components/settings.apps.vue: | ||||
|   no-apps: "連携しているアプリケーションはあらへんで" | ||||
| common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "パスワードを変更する" | ||||
|   enter-current-password: "今のパスワードを入れてや" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーはあかんわ。凍結されとる。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近儲かりまっか?" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "通知" | ||||
|   apps: "アプリ" | ||||
|   mute: "ミュート" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "パスワード" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "Profiel" | ||||
|   notification: "Melding" | ||||
|   apps: "Apps" | ||||
|   mute: "Dempen" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "Beveiliging" | ||||
|   signin: "Inloggeschiedenis" | ||||
|   password: "Wachtwoord" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   in-use: "使用中" | ||||
|   stats: "統計" | ||||
| desktop/views/components/settings.mute.vue: | ||||
|   no-users: "Geen gedempte gebruikers" | ||||
| 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: "Wachtwoord wijzigen" | ||||
|   enter-current-password: "Voer je huidige wachtwoord in" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,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" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "Notifikasjon" | ||||
|   apps: "Apper" | ||||
|   mute: "Demp" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "Passord" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,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" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "Profil" | ||||
|   notification: "Powiadomienia" | ||||
|   apps: "Aplikacje" | ||||
|   mute: "Wyciszanie" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "Bezpieczeństwo" | ||||
|   signin: "Historia logowań" | ||||
|   password: "Hasło" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   in-use: "使用中" | ||||
|   stats: "統計" | ||||
| desktop/views/components/settings.mute.vue: | ||||
|   no-users: "Brak wyciszonych użytkowników" | ||||
| 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: "Zmień hasło" | ||||
|   enter-current-password: "Wprowadź obecne hasło" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,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" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "通知" | ||||
|   apps: "アプリ" | ||||
|   mute: "ミュート" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "パスワード" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,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" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "通知" | ||||
|   apps: "アプリ" | ||||
|   mute: "ミュート" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "パスワード" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
| @@ -741,7 +741,8 @@ desktop/views/components/settings.vue: | ||||
|   profile: "プロフィール" | ||||
|   notification: "通知" | ||||
|   apps: "アプリ" | ||||
|   mute: "ミュート" | ||||
|   mute-and-block: "ミュート/ブロック" | ||||
|   blocking: "ブロック" | ||||
|   security: "セキュリティ" | ||||
|   signin: "サインイン履歴" | ||||
|   password: "パスワード" | ||||
| @@ -867,8 +868,12 @@ common/views/components/drive-settings.vue: | ||||
|   max: "容量" | ||||
|   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: "パスワードを変更する" | ||||
|   enter-current-password: "現在のパスワードを入力してください" | ||||
| @@ -1055,6 +1060,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: | ||||
| @@ -1329,6 +1337,10 @@ mobile/views/pages/user.vue: | ||||
|   timeline: "タイムライン" | ||||
|   media: "メディア" | ||||
|   is-suspended: "このユーザーは凍結されています。" | ||||
|   mute: "ミュート" | ||||
|   unmute: "ミュート解除" | ||||
|   block: "ブロック" | ||||
|   unblock: "ブロック解除" | ||||
| mobile/views/pages/user/home.vue: | ||||
|   recent-notes: "最近の投稿" | ||||
|   images: "画像" | ||||
|   | ||||
							
								
								
									
										75
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										75
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"version": "10.32.0", | ||||
| 	"version": "10.36.0", | ||||
| 	"lockfileVersion": 1, | ||||
| 	"requires": true, | ||||
| 	"dependencies": { | ||||
| @@ -691,9 +691,12 @@ | ||||
| 			"integrity": "sha512-XWwqRvaSsf4yq/4SYL5/7n5i2RWqf+jtIRlasj65cuZZDZ01wtkDfAIxrEpYcLvzT1HMqBmsnMEcZjK+OMeojQ==" | ||||
| 		}, | ||||
| 		"@types/speakeasy": { | ||||
| 			"version": "2.0.2", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/speakeasy/-/speakeasy-2.0.2.tgz", | ||||
| 			"integrity": "sha512-h8KW3wSd3/l4oKRGYPxExCaos5VmjcnwDG3RK25tfcoWQR9iLmM9UbwvF1Pd+UT5aY1Z3LdQGt4xU0u9Zk/C2Q==" | ||||
| 			"version": "2.0.3", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/speakeasy/-/speakeasy-2.0.3.tgz", | ||||
| 			"integrity": "sha512-lDc49Aec4WnQPRhW3n90ct14CH0Zyrj48RGMK1SSQP6BOf8HamWg+PG9uj1DVnaa6t+lhQU1j1lhGA7d9baxWw==", | ||||
| 			"requires": { | ||||
| 				"@types/node": "*" | ||||
| 			} | ||||
| 		}, | ||||
| 		"@types/superagent": { | ||||
| 			"version": "3.8.4", | ||||
| @@ -1259,9 +1262,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"apexcharts": { | ||||
| 			"version": "2.1.6", | ||||
| 			"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-2.1.6.tgz", | ||||
| 			"integrity": "sha512-kIb4Q07bWwTGuTWhyzhDAOz6nrltDgyP8VUUwqetxr0o11mNH6PA6YVnR/e9nyd9HU6q3bFZN8eVuSatnqdxAQ==", | ||||
| 			"version": "2.1.9", | ||||
| 			"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-2.1.9.tgz", | ||||
| 			"integrity": "sha512-Qs/jLUa03wqPR53yMk8QAwq+qrX/Odc3IIXH2WVVjdWyFXS1lYzGSDbVcVDnOKkxoLdAlzPI3icb2bMjskwfXQ==", | ||||
| 			"requires": { | ||||
| 				"babel-polyfill": "^6.26.0", | ||||
| 				"core-js": "^2.5.7", | ||||
| @@ -3332,12 +3335,12 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"data-urls": { | ||||
| 			"version": "1.0.1", | ||||
| 			"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.1.tgz", | ||||
| 			"integrity": "sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg==", | ||||
| 			"version": "1.1.0", | ||||
| 			"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", | ||||
| 			"integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", | ||||
| 			"requires": { | ||||
| 				"abab": "^2.0.0", | ||||
| 				"whatwg-mimetype": "^2.1.0", | ||||
| 				"whatwg-mimetype": "^2.2.0", | ||||
| 				"whatwg-url": "^7.0.0" | ||||
| 			} | ||||
| 		}, | ||||
| @@ -4755,9 +4758,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", | ||||
| @@ -5311,9 +5314,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"file-type": { | ||||
| 			"version": "10.1.0", | ||||
| 			"resolved": "https://registry.npmjs.org/file-type/-/file-type-10.1.0.tgz", | ||||
| 			"integrity": "sha512-fkjfXnqBRrdUFTS6opakWyMXb+uzDv8zOCqjSOWPbzMLnYnmnUEv/RNY9igkk4nc8TVL44Xd1OCC2fJXH3eb7Q==" | ||||
| 			"version": "10.2.0", | ||||
| 			"resolved": "https://registry.npmjs.org/file-type/-/file-type-10.2.0.tgz", | ||||
| 			"integrity": "sha512-eqX81S1PWdLDPW39yyB214TVVOsUQjSmPcyUjeVH6ksH+94Y2YA/ItiIwa53rJiSofJZLK6lGsuCE3rwt0vp4w==" | ||||
| 		}, | ||||
| 		"filename-regex": { | ||||
| 			"version": "2.0.1", | ||||
| @@ -8351,9 +8354,9 @@ | ||||
| 			"integrity": "sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==" | ||||
| 		}, | ||||
| 		"jsdom": { | ||||
| 			"version": "12.2.0", | ||||
| 			"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-12.2.0.tgz", | ||||
| 			"integrity": "sha512-QPOggIJ8fquWPLaYYMoh+zqUmdphDtu1ju0QGTitZT1Yd8I5qenPpXM1etzUegu3MjVp8XPzgZxdn8Yj7e40ig==", | ||||
| 			"version": "13.0.0", | ||||
| 			"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-13.0.0.tgz", | ||||
| 			"integrity": "sha512-Kmq4ASMNkgpY+YufE322EnIKoiz0UWY2DRkKlU7d5YrIW4xiVRhWFrZV1fr6w/ZNxQ50wGAH5gGRzydgnmkkvw==", | ||||
| 			"requires": { | ||||
| 				"abab": "^2.0.0", | ||||
| 				"acorn": "^6.0.2", | ||||
| @@ -8374,6 +8377,7 @@ | ||||
| 				"symbol-tree": "^3.2.2", | ||||
| 				"tough-cookie": "^2.4.3", | ||||
| 				"w3c-hr-time": "^1.0.1", | ||||
| 				"w3c-xmlserializer": "^1.0.0", | ||||
| 				"webidl-conversions": "^4.0.2", | ||||
| 				"whatwg-encoding": "^1.0.5", | ||||
| 				"whatwg-mimetype": "^2.2.0", | ||||
| @@ -15569,9 +15573,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"ts-loader": { | ||||
| 			"version": "5.2.2", | ||||
| 			"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.2.2.tgz", | ||||
| 			"integrity": "sha512-vM/TrEKXBqRYq5yLatsXyKFnYSpv53klmGtrILGlNqcMsxPVi8+e4yr1Agbu9oMZepx/4szDVn5QpFo83IQdQg==", | ||||
| 			"version": "5.3.0", | ||||
| 			"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.0.tgz", | ||||
| 			"integrity": "sha512-lGSNs7szRFj/rK9T1EQuayE3QNLg6izDUxt5jpmq0RG1rU2bapAt7E7uLckLCUPeO1jwxCiet2oRaWovc53UAg==", | ||||
| 			"requires": { | ||||
| 				"chalk": "^2.3.0", | ||||
| 				"enhanced-resolve": "^4.0.0", | ||||
| @@ -15683,16 +15687,17 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"typescript": { | ||||
| 			"version": "3.1.3", | ||||
| 			"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.3.tgz", | ||||
| 			"integrity": "sha512-+81MUSyX+BaSo+u2RbozuQk/UWx6hfG0a5gHu4ANEM4sU96XbuIyAB+rWBW1u70c6a5QuZfuYICn3s2UjuHUpA==" | ||||
| 			"version": "3.1.4", | ||||
| 			"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.4.tgz", | ||||
| 			"integrity": "sha512-JZHJtA6ZL15+Q3Dqkbh8iCUmvxD3iJ7ujXS+fVkKnwIVAdHc5BJTDNM0aTrnr2luKulFjU7W+SRhDZvi66Ru7Q==" | ||||
| 		}, | ||||
| 		"typescript-eslint-parser": { | ||||
| 			"version": "20.0.0", | ||||
| 			"resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-20.0.0.tgz", | ||||
| 			"integrity": "sha512-HZEoGA+LnS3etUlVAPX6I8sZ7872Yb0vPvQv6QDCIE44KD3bFmvPEQ4LbiD+qGkcxh6segjVK0v3rxpb2R6oSA==", | ||||
| 			"version": "20.1.1", | ||||
| 			"resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-20.1.1.tgz", | ||||
| 			"integrity": "sha512-IJhpqHK60Pz2J5pe8rJUQ10DcMcGwhQnvRFcPV79coEV3bpNfSiHkgpS+sf6zx2ANDWgBhmtZbK9ICOy+v3FKA==", | ||||
| 			"requires": { | ||||
| 				"eslint": "4.19.1", | ||||
| 				"eslint-visitor-keys": "^1.0.0", | ||||
| 				"typescript-estree": "2.1.0" | ||||
| 			}, | ||||
| 			"dependencies": { | ||||
| @@ -15825,7 +15830,7 @@ | ||||
| 				}, | ||||
| 				"fast-deep-equal": { | ||||
| 					"version": "1.1.0", | ||||
| 					"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", | ||||
| 					"resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", | ||||
| 					"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" | ||||
| 				}, | ||||
| 				"ignore": { | ||||
| @@ -16775,6 +16780,16 @@ | ||||
| 				"browser-process-hrtime": "^0.1.2" | ||||
| 			} | ||||
| 		}, | ||||
| 		"w3c-xmlserializer": { | ||||
| 			"version": "1.0.0", | ||||
| 			"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.0.0.tgz", | ||||
| 			"integrity": "sha512-0et1+9uXYiIRAecx1D5Z1nk60+vimniGdIKl4XjeqkWi6acoHNlXMv1VR5jV+jF4ooeO08oWbYxeAJOcon1oMA==", | ||||
| 			"requires": { | ||||
| 				"domexception": "^1.0.1", | ||||
| 				"webidl-conversions": "^4.0.2", | ||||
| 				"xml-name-validator": "^3.0.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"ware": { | ||||
| 			"version": "1.3.0", | ||||
| 			"resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", | ||||
|   | ||||
							
								
								
									
										27
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | ||||
| { | ||||
| 	"name": "misskey", | ||||
| 	"author": "syuilo <i@syuilo.com>", | ||||
| 	"version": "10.33.0", | ||||
| 	"clientVersion": "1.0.11172", | ||||
| 	"version": "10.37.0", | ||||
| 	"clientVersion": "1.0.11314", | ||||
| 	"codename": "nighthike", | ||||
| 	"main": "./built/index.js", | ||||
| 	"private": true, | ||||
| @@ -74,7 +74,7 @@ | ||||
| 		"@types/sharp": "0.21.0", | ||||
| 		"@types/showdown": "1.7.5", | ||||
| 		"@types/single-line-log": "1.1.0", | ||||
| 		"@types/speakeasy": "2.0.2", | ||||
| 		"@types/speakeasy": "2.0.3", | ||||
| 		"@types/systeminformation": "3.23.0", | ||||
| 		"@types/tinycolor2": "1.4.1", | ||||
| 		"@types/tmp": "0.0.33", | ||||
| @@ -84,7 +84,7 @@ | ||||
| 		"@types/websocket": "0.0.40", | ||||
| 		"@types/ws": "6.0.1", | ||||
| 		"animejs": "2.2.0", | ||||
| 		"apexcharts": "2.1.6", | ||||
| 		"apexcharts": "2.1.9", | ||||
| 		"autobind-decorator": "2.1.0", | ||||
| 		"autosize": "4.0.2", | ||||
| 		"autwh": "0.1.0", | ||||
| @@ -108,11 +108,11 @@ | ||||
| 		"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", | ||||
| 		"file-type": "10.1.0", | ||||
| 		"file-type": "10.2.0", | ||||
| 		"fuckadblock": "3.2.1", | ||||
| 		"gulp": "3.9.1", | ||||
| 		"gulp-cssnano": "2.1.3", | ||||
| @@ -135,7 +135,7 @@ | ||||
| 		"is-root": "2.0.0", | ||||
| 		"is-url": "1.2.4", | ||||
| 		"js-yaml": "3.12.0", | ||||
| 		"jsdom": "12.2.0", | ||||
| 		"jsdom": "13.0.0", | ||||
| 		"json5": "2.1.0", | ||||
| 		"json5-loader": "1.0.1", | ||||
| 		"koa": "2.6.1", | ||||
| @@ -201,11 +201,11 @@ | ||||
| 		"textarea-caret": "3.1.0", | ||||
| 		"tinycolor2": "1.4.1", | ||||
| 		"tmp": "0.0.33", | ||||
| 		"ts-loader": "5.2.2", | ||||
| 		"ts-loader": "5.3.0", | ||||
| 		"ts-node": "7.0.1", | ||||
| 		"tslint": "5.10.0", | ||||
| 		"typescript": "3.1.3", | ||||
| 		"typescript-eslint-parser": "20.0.0", | ||||
| 		"typescript": "3.1.4", | ||||
| 		"typescript-eslint-parser": "20.1.1", | ||||
| 		"uglify-es": "3.3.9", | ||||
| 		"url-loader": "1.1.2", | ||||
| 		"uuid": "3.3.2", | ||||
| @@ -234,12 +234,5 @@ | ||||
| 		"websocket": "1.0.28", | ||||
| 		"ws": "6.1.0", | ||||
| 		"xev": "2.0.1" | ||||
| 	}, | ||||
| 	"greenkeeper": { | ||||
| 		"ignore": [ | ||||
| 			"deepcopy", | ||||
| 			"cafy", | ||||
| 			"@types/gulp" | ||||
| 		] | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,8 @@ | ||||
| 	</ol> | ||||
| 	<ol class="emojis" ref="suggests" v-if="emojis.length > 0"> | ||||
| 		<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1"> | ||||
| 			<span class="emoji">{{ emoji.emoji }}</span> | ||||
| 			<span class="emoji" v-if="emoji.url"><img :src="emoji.url" :alt="emoji.emoji"/></span> | ||||
| 			<span class="emoji" v-else>{{ emoji.emoji }}</span> | ||||
| 			<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> | ||||
| 			<span class="alias" v-if="emoji.alias">({{ emoji.alias }})</span> | ||||
| 		</li> | ||||
| @@ -169,22 +170,45 @@ export default Vue.extend({ | ||||
| 				} | ||||
| 			} else if (this.type == 'emoji') { | ||||
| 				const matched = []; | ||||
| 				const max = 30; | ||||
|  | ||||
| 				const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis; | ||||
| 				customEmojis.some(x => { | ||||
| 					if (x.name.startsWith(this.q)) matched.push({ | ||||
| 						name: x.name, | ||||
| 						emoji: `:${x.name}:`, | ||||
| 						url: x.url | ||||
| 					}); | ||||
| 					return matched.length == max; | ||||
| 				}); | ||||
| 				customEmojis.some(x => { | ||||
| 					const alias = (x.aliases || []).find(a => a.startsWith(this.q)); | ||||
| 					if (alias) matched.push({ | ||||
| 						alias: x.name, | ||||
| 						name: alias, | ||||
| 						emoji: `:${x.name}:`, | ||||
| 						url: x.url | ||||
| 					}); | ||||
| 					return matched.length == max; | ||||
| 				}); | ||||
|  | ||||
| 				emjdb.some(x => { | ||||
| 					if (x.name.indexOf(this.q) == 0 && !x.alias && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | ||||
| 					return matched.length == 30; | ||||
| 					return matched.length == max; | ||||
| 				}); | ||||
| 				if (matched.length < 30) { | ||||
| 				if (matched.length < max) { | ||||
| 					emjdb.some(x => { | ||||
| 						if (x.name.indexOf(this.q) == 0 && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | ||||
| 						return matched.length == 30; | ||||
| 						return matched.length == max; | ||||
| 					}); | ||||
| 				} | ||||
| 				if (matched.length < 30) { | ||||
| 				if (matched.length < max) { | ||||
| 					emjdb.some(x => { | ||||
| 						if (x.name.indexOf(this.q) > -1 && !matched.some(y => y.emoji == x.emoji)) matched.push(x); | ||||
| 						return matched.length == 30; | ||||
| 						return matched.length == max; | ||||
| 					}); | ||||
| 				} | ||||
|  | ||||
| 				this.emojis = matched; | ||||
| 			} | ||||
| 		}, | ||||
| @@ -340,6 +364,10 @@ export default Vue.extend({ | ||||
| 			margin 0 4px 0 0 | ||||
| 			width 24px | ||||
|  | ||||
| 			> img | ||||
| 				width 24px | ||||
| 				vertical-align bottom | ||||
|  | ||||
| 		.name | ||||
| 			color var(--autocompleteItemText) | ||||
|  | ||||
|   | ||||
							
								
								
									
										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,7 @@ | ||||
| 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'; | ||||
| @@ -49,6 +51,8 @@ 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); | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import * as emojilib from 'emojilib'; | ||||
| import { length } from 'stringz'; | ||||
| import parse from '../../../../../mfm/parse'; | ||||
| import getAcct from '../../../../../misc/acct/render'; | ||||
| import { url } from '../../../config'; | ||||
| import MkUrl from './url.vue'; | ||||
| import MkGoogle from './google.vue'; | ||||
| import { concat } from '../../../../../prelude/array'; | ||||
| @@ -186,6 +185,21 @@ export default Vue.component('misskey-flavored-markdown', { | ||||
| 				} | ||||
|  | ||||
| 				case 'emoji': { | ||||
| 					//#region カスタム絵文字 | ||||
| 					const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis; | ||||
| 					const customEmoji = customEmojis.find(e => e.name == token.emoji || (e.aliases || []).includes(token.emoji)); | ||||
| 					if (customEmoji) { | ||||
| 						return [createElement('img', { | ||||
| 							attrs: { | ||||
| 								src: customEmoji.url, | ||||
| 								alt: token.emoji, | ||||
| 								title: token.emoji, | ||||
| 								style: 'height: 2.5em; vertical-align: middle;' | ||||
| 							} | ||||
| 						})]; | ||||
| 					} | ||||
| 					//#endregion | ||||
|  | ||||
| 					const emoji = emojilib.lib[token.emoji]; | ||||
| 					return [createElement('span', emoji ? emoji.char : token.content)]; | ||||
| 				} | ||||
|   | ||||
							
								
								
									
										52
									
								
								src/client/app/common/views/components/mute-and-block.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/client/app/common/views/components/mute-and-block.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| <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">%i18n:@no-muted-users%</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">%i18n:@no-blocked-users%</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 | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| <template> | ||||
| <div class="ymxyweixqwsxauxldgpvecjepnwxbylu" :class="{ warn }"> | ||||
| 	<i v-if="warn">%fa:exclamation-triangle%</i> | ||||
| 	<i v-else>%fa:info-circle%</i> | ||||
| 	<slot></slot> | ||||
| </div> | ||||
| </template> | ||||
| @@ -23,11 +24,20 @@ export default Vue.extend({ | ||||
| 	margin 16px 0 | ||||
| 	padding 16px | ||||
| 	font-size 90% | ||||
|  | ||||
| 	> i | ||||
| 		margin-right 4px | ||||
| 	background var(--infoBg) | ||||
| 	color var(--infoFg) | ||||
|  | ||||
| 	&.warn | ||||
| 		background var(--infoWarnBg) | ||||
| 		color var(--infoWarnFg) | ||||
|  | ||||
| 	&:first-child | ||||
| 		margin-top 0 | ||||
|  | ||||
| 	&:last-child | ||||
| 		margin-bottom 0 | ||||
|  | ||||
| 	> i | ||||
| 		margin-right 4px | ||||
|  | ||||
| </style> | ||||
|   | ||||
| @@ -222,13 +222,15 @@ class Autocomplete { | ||||
| 			const trimmedBefore = before.substring(0, before.lastIndexOf(':')); | ||||
| 			const after = source.substr(caret); | ||||
|  | ||||
| 			if (value.startsWith(':')) value = value + ' '; | ||||
|  | ||||
| 			// 挿入 | ||||
| 			this.text = trimmedBefore + value + after; | ||||
|  | ||||
| 			// キャレットを戻す | ||||
| 			this.vm.$nextTick(() => { | ||||
| 				this.textarea.focus(); | ||||
| 				const pos = trimmedBefore.length + 1; | ||||
| 				const pos = trimmedBefore.length + (value.startsWith(':') ? value.length : 1); | ||||
| 				this.textarea.setSelectionRange(pos, pos); | ||||
| 			}); | ||||
| 		} | ||||
|   | ||||
| @@ -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,8 +1,6 @@ | ||||
| <template> | ||||
| <div class="root"> | ||||
| 	<div class="none ui info" v-if="!fetching && apps.length == 0"> | ||||
| 		<p>%fa:info-circle%%i18n:@no-apps%</p> | ||||
| 	</div> | ||||
| 	<ui-info v-if="!fetching && apps.length == 0">%i18n:@no-apps%</ui-info> | ||||
| 	<div class="apps" v-if="apps.length != 0"> | ||||
| 		<div v-for="app in apps"> | ||||
| 			<p><b>{{ app.name }}</b></p> | ||||
|   | ||||
| @@ -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> | ||||
| @@ -289,7 +286,6 @@ | ||||
|  | ||||
| <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 XApps from './settings.apps.vue'; | ||||
| @@ -300,7 +296,6 @@ import checkForUpdate from '../../../common/scripts/check-for-update'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XMute, | ||||
| 		XPassword, | ||||
| 		X2fa, | ||||
| 		XApps, | ||||
|   | ||||
| @@ -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% | ||||
|   | ||||
| @@ -510,6 +510,14 @@ export default class MiOS extends EventEmitter { | ||||
| 		return promise; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Misskeyのメタ情報を取得します | ||||
| 	 */ | ||||
| 	@autobind | ||||
| 	public getMetaSync() { | ||||
| 		return this.meta ? this.meta.data : null; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Misskeyのメタ情報を取得します | ||||
| 	 * @param force キャッシュを無視するか否か | ||||
|   | ||||
| @@ -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> | ||||
| @@ -85,6 +85,8 @@ | ||||
|  | ||||
| 			<mk-drive-settings/> | ||||
|  | ||||
| 			<mk-mute-and-block/> | ||||
|  | ||||
| 			<ui-card> | ||||
| 				<div slot="title">%fa:volume-up% %i18n:@sound%</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,8 +110,62 @@ 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,8 @@ | ||||
| 		remoteInfoBg: '#42321c', | ||||
| 		remoteInfoFg: '#ffbd3e', | ||||
|  | ||||
| 		infoBg: '#253142', | ||||
| 		infoFg: '#fff', | ||||
| 		infoWarnBg: '#42321c', | ||||
| 		infoWarnFg: '#ffbd3e', | ||||
|  | ||||
|   | ||||
| @@ -131,6 +131,8 @@ | ||||
| 		remoteInfoBg: '#fff0db', | ||||
| 		remoteInfoFg: '#573c08', | ||||
|  | ||||
| 		infoBg: '#e5f5ff', | ||||
| 		infoFg: '#72818a', | ||||
| 		infoWarnBg: '#fff0db', | ||||
| 		infoWarnFg: '#573c08', | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|   | ||||
| @@ -9,9 +9,9 @@ export type TextElementHashtag = { | ||||
| }; | ||||
|  | ||||
| export default function(text: string, i: number) { | ||||
| 	if (!(/^\s#[^\s\.,!\?]+/.test(text) || (i == 0 && /^#[^\s\.,!\?]+/.test(text)))) return null; | ||||
| 	if (!(/^\s#[^\s\.,!\?#]+/.test(text) || (i == 0 && /^#[^\s\.,!\?#]+/.test(text)))) return null; | ||||
| 	const isHead = text.startsWith('#'); | ||||
| 	const hashtag = text.match(/^\s?#[^\s\.,!\?]+/)[0]; | ||||
| 	const hashtag = text.match(/^\s?#[^\s\.,!\?#]+/)[0]; | ||||
| 	const res: any[] = !isHead ? [{ | ||||
| 		type: 'text', | ||||
| 		content: text[0] | ||||
|   | ||||
| @@ -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', | ||||
|   | ||||
							
								
								
									
										20
									
								
								src/misc/get-drive-file-url.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/misc/get-drive-file-url.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import { IDriveFile } from '../models/drive-file'; | ||||
| import config from '../config'; | ||||
|  | ||||
| export default function(file: IDriveFile, thumbnail = false): string { | ||||
| 	if (file == null) return null; | ||||
|  | ||||
| 	if (file.metadata.withoutChunks) { | ||||
| 		if (thumbnail) { | ||||
| 			return file.metadata.thumbnailUrl || file.metadata.url; | ||||
| 		} else { | ||||
| 			return file.metadata.url; | ||||
| 		} | ||||
| 	} else { | ||||
| 		if (thumbnail) { | ||||
| 			return `${config.drive_url}/${file._id}?thumbnail`; | ||||
| 		} else { | ||||
| 			return `${config.drive_url}/${file._id}`; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -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 | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -22,27 +22,28 @@ export type IApp = { | ||||
|  | ||||
| /** | ||||
|  * Pack an app for API response | ||||
|  * | ||||
|  * @param {any} app | ||||
|  * @param {any} me? | ||||
|  * @param {any} options? | ||||
|  * @return {Promise<any>} | ||||
|  */ | ||||
| export const pack = ( | ||||
| 	app: any, | ||||
| 	me?: any, | ||||
| 	options?: { | ||||
| 		detail?: boolean, | ||||
| 		includeSecret?: boolean, | ||||
| 		includeProfileImageIds?: boolean | ||||
| 	} | ||||
| ) => new Promise<any>(async (resolve, reject) => { | ||||
| 	const opts = options || { | ||||
| 	const opts = Object.assign({ | ||||
| 		detail: false, | ||||
| 		includeSecret: false, | ||||
| 		includeProfileImageIds: false | ||||
| 	}; | ||||
| 	}, options); | ||||
|  | ||||
| 	let _app: any; | ||||
|  | ||||
| 	const fields = opts.detail ? {} : { | ||||
| 		name: true | ||||
| 	}; | ||||
|  | ||||
| 	// Populate the app if 'app' is ID | ||||
| 	if (isObjectId(app)) { | ||||
| 		_app = await App.findOne({ | ||||
| @@ -51,7 +52,7 @@ export const pack = ( | ||||
| 	} else if (typeof app === 'string') { | ||||
| 		_app = await App.findOne({ | ||||
| 			_id: new mongo.ObjectID(app) | ||||
| 		}); | ||||
| 		}, { fields }); | ||||
| 	} else { | ||||
| 		_app = deepcopy(app); | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										56
									
								
								src/models/blocking.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/models/blocking.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| 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, { | ||||
| 		detail: true | ||||
| 	}); | ||||
|  | ||||
| 	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 | ||||
| 	}); | ||||
| } | ||||
|   | ||||
| @@ -1,13 +1,9 @@ | ||||
| import * as mongo from 'mongodb'; | ||||
| const deepcopy = require('deepcopy'); | ||||
| 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'; | ||||
| import getDriveFileUrl from '../misc/get-drive-file-url'; | ||||
|  | ||||
| const DriveFile = monkDb.get<IDriveFile>('driveFiles.files'); | ||||
| DriveFile.createIndex('md5'); | ||||
| @@ -37,7 +33,14 @@ export type IMetadata = { | ||||
| 	thumbnailUrl?: string; | ||||
| 	src?: string; | ||||
| 	deletedAt?: Date; | ||||
|  | ||||
| 	/** | ||||
| 	 * このファイルの中身データがMongoDB内に保存されているのか否か | ||||
| 	 * オブジェクトストレージを利用している or リモートサーバーへの直リンクである | ||||
| 	 * な場合は false になります | ||||
| 	 */ | ||||
| 	withoutChunks?: boolean; | ||||
|  | ||||
| 	storage?: string; | ||||
| 	storageProps?: any; | ||||
| 	isSensitive?: boolean; | ||||
| @@ -77,71 +80,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))); | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -190,8 +135,8 @@ export const pack = ( | ||||
|  | ||||
| 	_target = Object.assign(_target, _file.metadata); | ||||
|  | ||||
| 	_target.url = _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; | ||||
| 	_target.thumbnailUrl = _file.metadata.thumbnailUrl ? _file.metadata.thumbnailUrl : _file.metadata.url ? _file.metadata.url : `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}?thumbnail`; | ||||
| 	_target.url = getDriveFileUrl(_file); | ||||
| 	_target.thumbnailUrl = getDriveFileUrl(_file, true); | ||||
| 	_target.isRemote = _file.metadata.isRemote; | ||||
|  | ||||
| 	if (_target.properties == null) _target.properties = {}; | ||||
| @@ -218,6 +163,7 @@ export const pack = ( | ||||
| 	delete _target.storage; | ||||
| 	delete _target.storageProps; | ||||
| 	delete _target.isRemote; | ||||
| 	delete _target._user; | ||||
|  | ||||
| 	resolve(_target); | ||||
| }); | ||||
|   | ||||
| @@ -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 | ||||
|  */ | ||||
|   | ||||
| @@ -15,4 +15,24 @@ export type IMeta = { | ||||
| 	disableLocalTimeline?: boolean; | ||||
| 	hidedTags?: string[]; | ||||
| 	bannerUrl?: string; | ||||
|  | ||||
| 	/** | ||||
| 	 * カスタム絵文字定義 | ||||
| 	 */ | ||||
| 	emojis?: { | ||||
| 		/** | ||||
| 		 * 絵文字名 (例: thinking_ai) | ||||
| 		 */ | ||||
| 		name: string; | ||||
|  | ||||
| 		/** | ||||
| 		 * エイリアス | ||||
| 		 */ | ||||
| 		aliases?: string[]; | ||||
|  | ||||
| 		/** | ||||
| 		 * 絵文字画像のURL | ||||
| 		 */ | ||||
| 		url: string; | ||||
| 	}[]; | ||||
| }; | ||||
|   | ||||
| @@ -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,40 @@ 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, { | ||||
| 		detail: true | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| 	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))); | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -328,11 +259,6 @@ export const pack = async ( | ||||
|  | ||||
| 	// When requested a detailed note data | ||||
| 	if (opts.detail) { | ||||
| 		//#region 重いので廃止 | ||||
| 		_note.prev = null; | ||||
| 		_note.next = null; | ||||
| 		//#endregion | ||||
|  | ||||
| 		if (_note.replyId) { | ||||
| 			// Populate reply to note | ||||
| 			_note.reply = pack(_note.replyId, meId, { | ||||
|   | ||||
| @@ -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,149 +155,48 @@ 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 } | ||||
| export async function getRelation(me: mongo.ObjectId, target: mongo.ObjectId) { | ||||
| 	const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ | ||||
| 		Following.findOne({ | ||||
| 			followerId: me, | ||||
| 			followeeId: target | ||||
| 		}), | ||||
| 		Following.findOne({ | ||||
| 			followerId: target, | ||||
| 			followeeId: me | ||||
| 		}), | ||||
| 		FollowRequest.findOne({ | ||||
| 			followerId: me, | ||||
| 			followeeId: target | ||||
| 		}), | ||||
| 		FollowRequest.findOne({ | ||||
| 			followerId: target, | ||||
| 			followeeId: me | ||||
| 		}), | ||||
| 		Blocking.findOne({ | ||||
| 			blockerId: me, | ||||
| 			blockeeId: target | ||||
| 		}), | ||||
| 		Blocking.findOne({ | ||||
| 			blockerId: target, | ||||
| 			blockeeId: me | ||||
| 		}), | ||||
| 		Mute.findOne({ | ||||
| 			muterId: me, | ||||
| 			muteeId: target | ||||
| 		}) | ||||
| 	)); | ||||
| 	]); | ||||
|  | ||||
| 	// このユーザーを削除 | ||||
| 	await User.remove({ | ||||
| 		_id: u._id | ||||
| 	}); | ||||
|  | ||||
| 	console.log(`User: deleted ${u._id}`); | ||||
| 	return { | ||||
| 		isFollowing: following1 !== null, | ||||
| 		isStalking: following1 && following1.stalk, | ||||
| 		hasPendingFollowRequestFromYou: followReq1 !== null, | ||||
| 		hasPendingFollowRequestToYou: followReq2 !== null, | ||||
| 		isFollowed: following2 !== null, | ||||
| 		isBlocking: toBlocking !== null, | ||||
| 		isBlocked: fromBlocked !== null, | ||||
| 		isMuted: mute !== null | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -336,13 +223,16 @@ export const pack = ( | ||||
|  | ||||
| 	let _user: any; | ||||
|  | ||||
| 	const fields = opts.detail ? { | ||||
| 	} : { | ||||
| 		settings: false, | ||||
| 		clientSettings: false, | ||||
| 		profile: false, | ||||
| 		keywords: false, | ||||
| 		domains: false | ||||
| 	const fields = opts.detail ? {} : { | ||||
| 		name: true, | ||||
| 		username: true, | ||||
| 		host: true, | ||||
| 		avatarColor: true, | ||||
| 		avatarUrl: true, | ||||
| 		isCat: true, | ||||
| 		isBot: true, | ||||
| 		isAdmin: true, | ||||
| 		isVerified: true | ||||
| 	}; | ||||
|  | ||||
| 	// Populate the user if 'user' is ID | ||||
| @@ -377,6 +267,8 @@ export const pack = ( | ||||
| 	_user.id = _user._id; | ||||
| 	delete _user._id; | ||||
|  | ||||
| 	delete _user.usernameLower; | ||||
|  | ||||
| 	if (_user.host == null) { | ||||
| 		// Remove private properties | ||||
| 		delete _user.keypair; | ||||
| @@ -384,7 +276,6 @@ export const pack = ( | ||||
| 		delete _user.token; | ||||
| 		delete _user.twoFactorTempSecret; | ||||
| 		delete _user.twoFactorSecret; | ||||
| 		delete _user.usernameLower; | ||||
| 		if (_user.twitter) { | ||||
| 			delete _user.twitter.accessToken; | ||||
| 			delete _user.twitter.accessTokenSecret; | ||||
| @@ -407,16 +298,6 @@ export const pack = ( | ||||
|  | ||||
| 	if (_user.avatarUrl == null) { | ||||
| 		_user.avatarUrl = `${config.drive_url}/default-avatar.jpg`; | ||||
|  | ||||
| 		// 互換性のため | ||||
| 		if (_user.avatarId) { | ||||
| 			_user.avatarUrl = `${config.drive_url}/${_user.avatarId}`; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 互換性のため | ||||
| 	if (_user.bannerId && _user.bannerUrl == null) { | ||||
| 		_user.bannerUrl = `${config.drive_url}/${_user.bannerId}`; | ||||
| 	} | ||||
|  | ||||
| 	if (!meId || !meId.equals(_user.id) || !opts.detail) { | ||||
| @@ -426,42 +307,17 @@ export const pack = ( | ||||
| 		delete _user.hasUnreadNotification; | ||||
| 	} | ||||
|  | ||||
| 	if (meId && !meId.equals(_user.id)) { | ||||
| 		const [following1, following2, followReq1, followReq2, mute] = await Promise.all([ | ||||
| 			Following.findOne({ | ||||
| 				followerId: meId, | ||||
| 				followeeId: _user.id | ||||
| 			}), | ||||
| 			Following.findOne({ | ||||
| 				followerId: _user.id, | ||||
| 				followeeId: meId | ||||
| 			}), | ||||
| 			FollowRequest.findOne({ | ||||
| 				followerId: meId, | ||||
| 				followeeId: _user.id | ||||
| 			}), | ||||
| 			FollowRequest.findOne({ | ||||
| 				followerId: _user.id, | ||||
| 				followeeId: meId | ||||
| 			}), | ||||
| 			Mute.findOne({ | ||||
| 				muterId: meId, | ||||
| 				muteeId: _user.id | ||||
| 			}) | ||||
| 		]); | ||||
| 	if (meId && !meId.equals(_user.id) && opts.detail) { | ||||
| 		const relation = await getRelation(meId, _user.id); | ||||
|  | ||||
| 		// Whether the user is following | ||||
| 		_user.isFollowing = following1 !== null; | ||||
| 		_user.isStalking = following1 && following1.stalk; | ||||
|  | ||||
| 		_user.hasPendingFollowRequestFromYou = followReq1 !== null; | ||||
| 		_user.hasPendingFollowRequestToYou = followReq2 !== null; | ||||
|  | ||||
| 		// Whether the user is followed | ||||
| 		_user.isFollowed = following2 !== null; | ||||
|  | ||||
| 		// Whether the user is muted | ||||
| 		_user.isMuted = mute !== null; | ||||
| 		_user.isFollowing = relation.isFollowing; | ||||
| 		_user.isFollowed = relation.isFollowed; | ||||
| 		_user.isStalking = relation.isStalking; | ||||
| 		_user.hasPendingFollowRequestFromYou = relation.hasPendingFollowRequestFromYou; | ||||
| 		_user.hasPendingFollowRequestToYou = relation.hasPendingFollowRequestToYou; | ||||
| 		_user.isBlocking = relation.isBlocking; | ||||
| 		_user.isBlocked = relation.isBlocked; | ||||
| 		_user.isMuted = relation.isMuted; | ||||
| 	} | ||||
|  | ||||
| 	if (opts.detail) { | ||||
|   | ||||
							
								
								
									
										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; | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { URL } from 'url'; | ||||
| import { resolveNote } from './note'; | ||||
| import registerInstance from '../../../services/register-instance'; | ||||
| import Instance from '../../../models/instance'; | ||||
| import getDriveFileUrl from '../../../misc/get-drive-file-url'; | ||||
|  | ||||
| const log = debug('misskey:activitypub'); | ||||
|  | ||||
| @@ -209,8 +210,8 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU | ||||
|  | ||||
| 	const avatarId = avatar ? avatar._id : null; | ||||
| 	const bannerId = banner ? banner._id : null; | ||||
| 	const avatarUrl = (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null; | ||||
| 	const bannerUrl = (banner && banner.metadata.url) ? banner.metadata.url : null; | ||||
| 	const avatarUrl = getDriveFileUrl(avatar, true); | ||||
| 	const bannerUrl = getDriveFileUrl(banner, false); | ||||
|  | ||||
| 	await User.update({ _id: user._id }, { | ||||
| 		$set: { | ||||
| @@ -303,8 +304,8 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje | ||||
| 			featured: person.featured, | ||||
| 			avatarId: avatar ? avatar._id : null, | ||||
| 			bannerId: banner ? banner._id : null, | ||||
| 			avatarUrl: (avatar && avatar.metadata.thumbnailUrl) ? avatar.metadata.thumbnailUrl : (avatar && avatar.metadata.url) ? avatar.metadata.url : null, | ||||
| 			bannerUrl: banner && banner.metadata.url ? banner.metadata.url : null, | ||||
| 			avatarUrl: getDriveFileUrl(avatar, true), | ||||
| 			bannerUrl: getDriveFileUrl(banner, false), | ||||
| 			description: htmlToMFM(person.summary), | ||||
| 			followersCount, | ||||
| 			followingCount, | ||||
|   | ||||
							
								
								
									
										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 | ||||
| }); | ||||
| @@ -1,8 +1,8 @@ | ||||
| import config from '../../../config'; | ||||
| import { IDriveFile } from '../../../models/drive-file'; | ||||
| import getDriveFileUrl from '../../../misc/get-drive-file-url'; | ||||
|  | ||||
| export default (file: IDriveFile) => ({ | ||||
| 	type: 'Document', | ||||
| 	mediaType: file.contentType, | ||||
| 	url: file.metadata.url || `${config.drive_url}/${file._id}` | ||||
| 	url: getDriveFileUrl(file) | ||||
| }); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import config from '../../../config'; | ||||
| import { IDriveFile } from '../../../models/drive-file'; | ||||
| import getDriveFileUrl from '../../../misc/get-drive-file-url'; | ||||
|  | ||||
| export default (file: IDriveFile) => ({ | ||||
| 	type: 'Image', | ||||
| 	url: file.metadata.url || `${config.drive_url}/${file._id}`, | ||||
| 	url: getDriveFileUrl(file), | ||||
| 	sensitive: file.metadata.isSensitive | ||||
| }); | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
| 	if (user === null) { | ||||
| 	const acctLower = `${usernameLower}@${hostAscii}`; | ||||
|  | ||||
| 	if (user === null) { | ||||
| 		const self = await resolveSelf(acctLower); | ||||
|  | ||||
| 		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'); | ||||
| 	} | ||||
|  | ||||
| 		user = await createPerson(self.href); | ||||
| 	} | ||||
|  | ||||
| 	return user; | ||||
| }; | ||||
| 	return self; | ||||
| } | ||||
|   | ||||
| @@ -17,6 +17,12 @@ export const meta = { | ||||
| 			} | ||||
| 		}), | ||||
|  | ||||
| 		emojis: $.arr($.obj()).optional.note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': 'カスタム絵文字定義' | ||||
| 			} | ||||
| 		}), | ||||
|  | ||||
| 		disableRegistration: $.bool.optional.nullable.note({ | ||||
| 			desc: { | ||||
| 				'ja-JP': '招待制か否か' | ||||
| @@ -53,6 +59,10 @@ export default (params: any) => new Promise(async (res, rej) => { | ||||
| 		set.broadcasts = ps.broadcasts; | ||||
| 	} | ||||
|  | ||||
| 	if (ps.emojis) { | ||||
| 		set.emojis = ps.emojis; | ||||
| 	} | ||||
|  | ||||
| 	if (typeof ps.disableRegistration === 'boolean') { | ||||
| 		set.disableRegistration = ps.disableRegistration; | ||||
| 	} | ||||
|   | ||||
| @@ -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 }) | ||||
| 		]); | ||||
|   | ||||
| @@ -44,6 +44,7 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res, | ||||
|  | ||||
| 	// Response | ||||
| 	res(await pack(app, null, { | ||||
| 		detail: true, | ||||
| 		includeSecret: true | ||||
| 	})); | ||||
| }); | ||||
|   | ||||
| @@ -21,6 +21,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( | ||||
|  | ||||
| 	// Send response | ||||
| 	res(await pack(ap, user, { | ||||
| 		detail: true, | ||||
| 		includeSecret: isSecure && ap.userId.equals(user._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)); | ||||
| }); | ||||
| @@ -16,7 +16,7 @@ export const meta = { | ||||
|  | ||||
| 	limit: { | ||||
| 		duration: ms('1hour'), | ||||
| 		max: 100 | ||||
| 		max: 120 | ||||
| 	}, | ||||
|  | ||||
| 	requireFile: true, | ||||
|   | ||||
| @@ -68,7 +68,11 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = | ||||
| 	} | ||||
|  | ||||
| 	// Create following | ||||
| 	try { | ||||
| 		await create(follower, followee); | ||||
| 	} catch (e) { | ||||
| 		return rej(e && e.message ? e.message : e); | ||||
| 	} | ||||
|  | ||||
| 	// Send response | ||||
| 	res(await pack(followee._id, user)); | ||||
|   | ||||
| @@ -34,6 +34,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = | ||||
| 		}); | ||||
|  | ||||
| 	// Serialize | ||||
| 	res(await Promise.all(tokens.map(async token => | ||||
| 		await pack(token.appId)))); | ||||
| 	res(await Promise.all(tokens.map(token => pack(token.appId, user, { | ||||
| 		detail: true | ||||
| 	})))); | ||||
| }); | ||||
|   | ||||
| @@ -4,9 +4,9 @@ import { publishMainStream } from '../../../../stream'; | ||||
| import DriveFile from '../../../../models/drive-file'; | ||||
| import acceptAllFollowRequests from '../../../../services/following/requests/accept-all'; | ||||
| import { IApp } from '../../../../models/app'; | ||||
| import config from '../../../../config'; | ||||
| import { publishToFollowers } from '../../../../services/i/update'; | ||||
| import getParams from '../../get-params'; | ||||
| import getDriveFileUrl from '../../../../misc/get-drive-file-url'; | ||||
|  | ||||
| export const meta = { | ||||
| 	desc: { | ||||
| @@ -129,7 +129,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a | ||||
| 		if (avatar == null) return rej('avatar not found'); | ||||
| 		if (!avatar.contentType.startsWith('image/')) return rej('avatar not an image'); | ||||
|  | ||||
| 		updates.avatarUrl = avatar.metadata.thumbnailUrl || avatar.metadata.url || `${config.drive_url}/${avatar._id}`; | ||||
| 		updates.avatarUrl = getDriveFileUrl(avatar, true); | ||||
|  | ||||
| 		if (avatar.metadata.properties.avgColor) { | ||||
| 			updates.avatarColor = avatar.metadata.properties.avgColor; | ||||
| @@ -144,7 +144,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a | ||||
| 		if (banner == null) return rej('banner not found'); | ||||
| 		if (!banner.contentType.startsWith('image/')) return rej('banner not an image'); | ||||
|  | ||||
| 		updates.bannerUrl = banner.metadata.url || `${config.drive_url}/${banner._id}`; | ||||
| 		updates.bannerUrl = getDriveFileUrl(banner, false); | ||||
|  | ||||
| 		if (banner.metadata.properties.avgColor) { | ||||
| 			updates.bannerColor = banner.metadata.properties.avgColor; | ||||
| @@ -162,7 +162,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a | ||||
|  | ||||
| 			if (wallpaper == null) return rej('wallpaper not found'); | ||||
|  | ||||
| 			updates.wallpaperUrl = wallpaper.metadata.url || `${config.drive_url}/${wallpaper._id}`; | ||||
| 			updates.wallpaperUrl = getDriveFileUrl(wallpaper); | ||||
|  | ||||
| 			if (wallpaper.metadata.properties.avgColor) { | ||||
| 				updates.wallpaperColor = wallpaper.metadata.properties.avgColor; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user