From 0177023ead75594c603e41f9c1f355e7f4b18369 Mon Sep 17 00:00:00 2001 From: mei23 Date: Sun, 2 Sep 2018 02:57:34 +0900 Subject: Use Tombstone for Delete --- src/services/note/delete.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/services/note/delete.ts') diff --git a/src/services/note/delete.ts b/src/services/note/delete.ts index d0e2b12b41..dea306feec 100644 --- a/src/services/note/delete.ts +++ b/src/services/note/delete.ts @@ -5,8 +5,9 @@ import renderDelete from '../../remote/activitypub/renderer/delete'; import pack from '../../remote/activitypub/renderer'; import { deliver } from '../../queue'; import Following from '../../models/following'; -import renderNote from '../../remote/activitypub/renderer/note'; +import renderTombstone from '../../remote/activitypub/renderer/tombstone'; import { updateNoteStats } from '../update-chart'; +import config from '../../config'; /** * 投稿を削除します。 @@ -32,7 +33,7 @@ export default async function(user: IUser, note: INote) { //#region ローカルの投稿なら削除アクティビティを配送 if (isLocalUser(user)) { - const content = pack(renderDelete(await renderNote(note), user)); + const content = pack(renderDelete(renderTombstone(`${config.url}/notes/${note._id}`), user)); const followings = await Following.find({ followeeId: user._id, -- cgit v1.3.1-freya From a1b82e97230eab994c06fc7c902e71539664d3d7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 5 Sep 2018 19:32:46 +0900 Subject: #2620 --- package.json | 2 +- .../app/common/scripts/parse-search-query.ts | 53 ---------------------- .../common/views/components/welcome-timeline.vue | 2 +- .../app/desktop/views/components/note-detail.vue | 6 +-- .../app/desktop/views/components/notes.note.vue | 6 +-- src/client/app/desktop/views/components/notes.vue | 2 +- .../desktop/views/components/post-form-window.vue | 10 ++-- .../app/desktop/views/components/post-form.vue | 16 +++---- .../desktop/views/components/sub-note-content.vue | 6 +-- .../app/desktop/views/pages/deck/deck.list-tl.vue | 6 +-- .../app/desktop/views/pages/deck/deck.note.vue | 16 +++---- .../app/desktop/views/pages/deck/deck.notes.vue | 2 +- .../app/desktop/views/pages/deck/deck.tl.vue | 6 +-- .../app/desktop/views/pages/user/user.photos.vue | 6 +-- .../app/desktop/views/pages/user/user.timeline.vue | 4 +- src/client/app/desktop/views/widgets/trends.vue | 2 +- .../app/mobile/views/components/note-detail.vue | 8 ++-- src/client/app/mobile/views/components/note.vue | 8 ++-- src/client/app/mobile/views/components/notes.vue | 2 +- .../app/mobile/views/components/post-form.vue | 8 ++-- .../mobile/views/components/sub-note-content.vue | 6 +-- .../app/mobile/views/components/user-timeline.vue | 4 +- .../app/mobile/views/pages/user/home.photos.vue | 2 +- src/docs/api/entities/note.yaml | 12 ++--- src/misc/get-note-summary.ts | 6 +-- src/misc/is-quote.ts | 2 +- src/models/drive-file.ts | 2 +- src/models/note.ts | 26 +++++++++-- src/remote/activitypub/models/note.ts | 6 +-- src/remote/activitypub/renderer/note.ts | 4 +- src/server/activitypub/outbox.ts | 2 +- src/server/api/endpoints/notes.ts | 10 ++-- src/server/api/endpoints/notes/create.ts | 23 ++++++---- src/server/api/endpoints/notes/global-timeline.ts | 10 ++-- src/server/api/endpoints/notes/hybrid-timeline.ts | 12 ++--- src/server/api/endpoints/notes/local-timeline.ts | 10 ++-- src/server/api/endpoints/notes/search_by_tag.ts | 6 +-- src/server/api/endpoints/notes/timeline.ts | 12 ++--- src/server/api/endpoints/notes/trend.ts | 2 +- .../api/endpoints/notes/user-list-timeline.ts | 12 ++--- src/server/api/endpoints/users/notes.ts | 10 ++-- src/services/note/create.ts | 11 +++-- src/services/note/delete.ts | 2 +- 43 files changed, 167 insertions(+), 196 deletions(-) delete mode 100644 src/client/app/common/scripts/parse-search-query.ts (limited to 'src/services/note/delete.ts') diff --git a/package.json b/package.json index 70124a0ae4..775c997e55 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "8.26.0", + "version": "9.0.0", "clientVersion": "1.0.9358", "codename": "nighthike", "main": "./built/index.js", diff --git a/src/client/app/common/scripts/parse-search-query.ts b/src/client/app/common/scripts/parse-search-query.ts deleted file mode 100644 index 5f6ae3320a..0000000000 --- a/src/client/app/common/scripts/parse-search-query.ts +++ /dev/null @@ -1,53 +0,0 @@ -export default function(qs: string) { - const q = { - text: '' - }; - - qs.split(' ').forEach(x => { - if (/^([a-z_]+?):(.+?)$/.test(x)) { - const [key, value] = x.split(':'); - switch (key) { - case 'user': - q['includeUserUsernames'] = value.split(','); - break; - case 'exclude_user': - q['excludeUserUsernames'] = value.split(','); - break; - case 'follow': - q['following'] = value == 'null' ? null : value == 'true'; - break; - case 'reply': - q['reply'] = value == 'null' ? null : value == 'true'; - break; - case 'renote': - q['renote'] = value == 'null' ? null : value == 'true'; - break; - case 'media': - q['media'] = value == 'null' ? null : value == 'true'; - break; - case 'poll': - q['poll'] = value == 'null' ? null : value == 'true'; - break; - case 'until': - case 'since': - // YYYY-MM-DD - if (/^[0-9]+\-[0-9]+\-[0-9]+$/) { - const [yyyy, mm, dd] = value.split('-'); - q[`${key}_date`] = (new Date(parseInt(yyyy, 10), parseInt(mm, 10) - 1, parseInt(dd, 10))).getTime(); - } - break; - default: - q[key] = value; - break; - } - } else { - q.text += x + ' '; - } - }); - - if (q.text) { - q.text = q.text.trim(); - } - - return q; -} diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue index d4e7902c7b..b427721d37 100644 --- a/src/client/app/common/views/components/welcome-timeline.vue +++ b/src/client/app/common/views/components/welcome-timeline.vue @@ -63,7 +63,7 @@ export default Vue.extend({ local: true, reply: false, renote: false, - media: false, + file: false, poll: false }).then(notes => { this.notes = notes; diff --git a/src/client/app/desktop/views/components/note-detail.vue b/src/client/app/desktop/views/components/note-detail.vue index 1ba4a9a447..a61a004a85 100644 --- a/src/client/app/desktop/views/components/note-detail.vue +++ b/src/client/app/desktop/views/components/note-detail.vue @@ -42,8 +42,8 @@ %i18n:@deleted% -
- +
+
@@ -114,7 +114,7 @@ export default Vue.extend({ isRenote(): boolean { return (this.note.renote && this.note.text == null && - this.note.mediaIds.length == 0 && + this.note.fileIds.length == 0 && this.note.poll == null); }, p(): any { diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index 7592ae3905..1d6b2048ba 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -28,8 +28,8 @@ RP:
-
- +
+
%fa:map-marker-alt% 位置情報 @@ -110,7 +110,7 @@ export default Vue.extend({ isRenote(): boolean { return (this.note.renote && this.note.text == null && - this.note.mediaIds.length == 0 && + this.note.fileIds.length == 0 && this.note.poll == null); }, diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue index a1c1207a7b..f19ecf8f9a 100644 --- a/src/client/app/desktop/views/components/notes.vue +++ b/src/client/app/desktop/views/components/notes.vue @@ -122,7 +122,7 @@ export default Vue.extend({ prepend(note, silent = false) { //#region 弾く const isMyNote = note.userId == this.$store.state.i.id; - const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null; + const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null; if (this.$store.state.settings.showMyRenotes === false) { if (isMyNote && isPureRenote) { diff --git a/src/client/app/desktop/views/components/post-form-window.vue b/src/client/app/desktop/views/components/post-form-window.vue index 51a416e281..a88c96d1bf 100644 --- a/src/client/app/desktop/views/components/post-form-window.vue +++ b/src/client/app/desktop/views/components/post-form-window.vue @@ -4,7 +4,7 @@ %fa:map-marker-alt% %i18n:@note% %i18n:@reply% - {{ '%i18n:@attaches%'.replace('{}', media.length) }} + {{ '%i18n:@attaches%'.replace('{}', files.length) }} {{ '%i18n:@uploading-media%'.replace('{}', uploadings.length) }} @@ -14,7 +14,7 @@ :reply="reply" @posted="onPosted" @change-uploadings="onChangeUploadings" - @change-attached-media="onChangeMedia" + @change-attached-files="onChangeFiles" @geo-attached="onGeoAttached" @geo-dettached="onGeoDettached"/>
@@ -29,7 +29,7 @@ export default Vue.extend({ data() { return { uploadings: [], - media: [], + files: [], geo: null }; }, @@ -42,8 +42,8 @@ export default Vue.extend({ onChangeUploadings(files) { this.uploadings = files; }, - onChangeMedia(media) { - this.media = media; + onChangeFiles(files) { + this.files = files; }, onGeoAttached(geo) { this.geo = geo; diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 2ca5484610..f6f52c8f1f 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -20,7 +20,7 @@ @keydown="onKeydown" @paste="onPaste" :placeholder="placeholder" v-autocomplete="'text'" > -
+
@@ -188,7 +188,7 @@ export default Vue.extend({ (this.$refs.poll as any).set(draft.data.poll); }); } - this.$emit('change-attached-media', this.files); + this.$emit('change-attached-files', this.files); } } @@ -225,12 +225,12 @@ export default Vue.extend({ attachMedia(driveFile) { this.files.push(driveFile); - this.$emit('change-attached-media', this.files); + this.$emit('change-attached-files', this.files); }, detachMedia(id) { this.files = this.files.filter(x => x.id != id); - this.$emit('change-attached-media', this.files); + this.$emit('change-attached-files', this.files); }, onChangeFile() { @@ -249,7 +249,7 @@ export default Vue.extend({ this.text = ''; this.files = []; this.poll = false; - this.$emit('change-attached-media', this.files); + this.$emit('change-attached-files', this.files); }, onKeydown(e) { @@ -297,7 +297,7 @@ export default Vue.extend({ if (driveFile != null && driveFile != '') { const file = JSON.parse(driveFile); this.files.push(file); - this.$emit('change-attached-media', this.files); + this.$emit('change-attached-files', this.files); e.preventDefault(); } //#endregion @@ -354,7 +354,7 @@ export default Vue.extend({ (this as any).api('notes/create', { text: this.text == '' ? undefined : this.text, - mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, + fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, replyId: this.reply ? this.reply.id : undefined, renoteId: this.renote ? this.renote.id : undefined, poll: this.poll ? (this.$refs.poll as any).get() : undefined, @@ -514,7 +514,7 @@ root(isDark) margin-right 8px white-space nowrap - > .medias + > .files margin 0 padding 0 background isDark ? #181b23 : lighten($theme-color, 98%) diff --git a/src/client/app/desktop/views/components/sub-note-content.vue b/src/client/app/desktop/views/components/sub-note-content.vue index cb0374b910..6889dc231e 100644 --- a/src/client/app/desktop/views/components/sub-note-content.vue +++ b/src/client/app/desktop/views/components/sub-note-content.vue @@ -7,9 +7,9 @@ RP: ...
-
- ({{ '%i18n:@media-count%'.replace('{}', note.media.length) }}) - +
+ ({{ '%i18n:@media-count%'.replace('{}', note.files.length) }}) +
%i18n:@poll% diff --git a/src/client/app/desktop/views/pages/deck/deck.list-tl.vue b/src/client/app/desktop/views/pages/deck/deck.list-tl.vue index 70048f99e3..e82e76e4d0 100644 --- a/src/client/app/desktop/views/pages/deck/deck.list-tl.vue +++ b/src/client/app/desktop/views/pages/deck/deck.list-tl.vue @@ -68,7 +68,7 @@ export default Vue.extend({ (this as any).api('notes/user-list-timeline', { listId: this.list.id, limit: fetchLimit + 1, - mediaOnly: this.mediaOnly, + withFiles: this.mediaOnly, includeMyRenotes: this.$store.state.settings.showMyRenotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeLocalRenotes: this.$store.state.settings.showLocalRenotes @@ -90,7 +90,7 @@ export default Vue.extend({ listId: this.list.id, limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, - mediaOnly: this.mediaOnly, + withFiles: this.mediaOnly, includeMyRenotes: this.$store.state.settings.showMyRenotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeLocalRenotes: this.$store.state.settings.showLocalRenotes @@ -109,7 +109,7 @@ export default Vue.extend({ return promise; }, onNote(note) { - if (this.mediaOnly && note.media.length == 0) return; + if (this.mediaOnly && note.files.length == 0) return; // Prepend a note (this.$refs.timeline as any).prepend(note); diff --git a/src/client/app/desktop/views/pages/deck/deck.note.vue b/src/client/app/desktop/views/pages/deck/deck.note.vue index 2615c0d090..b42df1f347 100644 --- a/src/client/app/desktop/views/pages/deck/deck.note.vue +++ b/src/client/app/desktop/views/pages/deck/deck.note.vue @@ -28,8 +28,8 @@ RP:
-
- +
+
%fa:map-marker-alt% %i18n:@location% @@ -54,11 +54,11 @@
-
- +
+
-
- +
+
@@ -100,7 +100,7 @@ export default Vue.extend({ isRenote(): boolean { return (this.note.renote && this.note.text == null && - this.note.mediaIds.length == 0 && + this.note.fileIds.length == 0 && this.note.poll == null); }, @@ -371,7 +371,7 @@ root(isDark) .mk-url-preview margin-top 8px - > .media + > .files > img display block max-width 100% diff --git a/src/client/app/desktop/views/pages/deck/deck.notes.vue b/src/client/app/desktop/views/pages/deck/deck.notes.vue index f7fca5de92..2e7e30f12a 100644 --- a/src/client/app/desktop/views/pages/deck/deck.notes.vue +++ b/src/client/app/desktop/views/pages/deck/deck.notes.vue @@ -127,7 +127,7 @@ export default Vue.extend({ prepend(note, silent = false) { //#region 弾く const isMyNote = note.userId == this.$store.state.i.id; - const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null; + const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null; if (this.$store.state.settings.showMyRenotes === false) { if (isMyNote && isPureRenote) { diff --git a/src/client/app/desktop/views/pages/deck/deck.tl.vue b/src/client/app/desktop/views/pages/deck/deck.tl.vue index a9e4d489c3..120ceb7fc2 100644 --- a/src/client/app/desktop/views/pages/deck/deck.tl.vue +++ b/src/client/app/desktop/views/pages/deck/deck.tl.vue @@ -96,7 +96,7 @@ export default Vue.extend({ (this.$refs.timeline as any).init(() => new Promise((res, rej) => { (this as any).api(this.endpoint, { limit: fetchLimit + 1, - mediaOnly: this.mediaOnly, + withFiles: this.mediaOnly, includeMyRenotes: this.$store.state.settings.showMyRenotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeLocalRenotes: this.$store.state.settings.showLocalRenotes @@ -117,7 +117,7 @@ export default Vue.extend({ const promise = (this as any).api(this.endpoint, { limit: fetchLimit + 1, - mediaOnly: this.mediaOnly, + withFiles: this.mediaOnly, untilId: (this.$refs.timeline as any).tail().id, includeMyRenotes: this.$store.state.settings.showMyRenotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, @@ -138,7 +138,7 @@ export default Vue.extend({ }, onNote(note) { - if (this.mediaOnly && note.media.length == 0) return; + if (this.mediaOnly && note.files.length == 0) return; // Prepend a note (this.$refs.timeline as any).prepend(note); diff --git a/src/client/app/desktop/views/pages/user/user.photos.vue b/src/client/app/desktop/views/pages/user/user.photos.vue index 64c537f1ed..c5cd9e24fe 100644 --- a/src/client/app/desktop/views/pages/user/user.photos.vue +++ b/src/client/app/desktop/views/pages/user/user.photos.vue @@ -24,12 +24,12 @@ export default Vue.extend({ mounted() { (this as any).api('users/notes', { userId: this.user.id, - withMedia: true, + withFiles: true, limit: 9 }).then(notes => { notes.forEach(note => { - note.media.forEach(media => { - if (this.images.length < 9) this.images.push(media); + note.files.forEach(file => { + if (this.images.length < 9) this.images.push(file); }); }); this.fetching = false; diff --git a/src/client/app/desktop/views/pages/user/user.timeline.vue b/src/client/app/desktop/views/pages/user/user.timeline.vue index 67987fcb94..54221380a7 100644 --- a/src/client/app/desktop/views/pages/user/user.timeline.vue +++ b/src/client/app/desktop/views/pages/user/user.timeline.vue @@ -66,7 +66,7 @@ export default Vue.extend({ limit: fetchLimit + 1, untilDate: this.date ? this.date.getTime() : undefined, includeReplies: this.mode == 'with-replies', - withMedia: this.mode == 'with-media' + withFiles: this.mode == 'with-media' }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -86,7 +86,7 @@ export default Vue.extend({ userId: this.user.id, limit: fetchLimit + 1, includeReplies: this.mode == 'with-replies', - withMedia: this.mode == 'with-media', + withFiles: this.mode == 'with-media', untilId: (this.$refs.timeline as any).tail().id }); diff --git a/src/client/app/desktop/views/widgets/trends.vue b/src/client/app/desktop/views/widgets/trends.vue index c33bf2f2f2..aeaab63ac4 100644 --- a/src/client/app/desktop/views/widgets/trends.vue +++ b/src/client/app/desktop/views/widgets/trends.vue @@ -49,7 +49,7 @@ export default define({ offset: this.offset, renote: false, reply: false, - media: false, + file: false, poll: false }).then(notes => { const note = notes ? notes[0] : null; diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue index 786e57bb22..10ff3fcc09 100644 --- a/src/client/app/mobile/views/components/note-detail.vue +++ b/src/client/app/mobile/views/components/note-detail.vue @@ -40,8 +40,8 @@ (%i18n:@deleted%)
-
- +
+
@@ -113,7 +113,7 @@ export default Vue.extend({ isRenote(): boolean { return (this.note.renote && this.note.text == null && - this.note.mediaIds.length == 0 && + this.note.fileIds.length == 0 && this.note.poll == null); }, @@ -369,7 +369,7 @@ root(isDark) > .mk-url-preview margin-top 8px - > .media + > .files > img display block max-width 100% diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue index 258433cb3f..9bd4a83ecb 100644 --- a/src/client/app/mobile/views/components/note.vue +++ b/src/client/app/mobile/views/components/note.vue @@ -28,8 +28,8 @@ RP:
-
- +
+
@@ -90,7 +90,7 @@ export default Vue.extend({ isRenote(): boolean { return (this.note.renote && this.note.text == null && - this.note.mediaIds.length == 0 && + this.note.fileIds.length == 0 && this.note.poll == null); }, @@ -414,7 +414,7 @@ root(isDark) .mk-url-preview margin-top 8px - > .media + > .files > img display block max-width 100% diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue index 714e521c0f..ce2670dc52 100644 --- a/src/client/app/mobile/views/components/notes.vue +++ b/src/client/app/mobile/views/components/notes.vue @@ -125,7 +125,7 @@ export default Vue.extend({ prepend(note, silent = false) { //#region 弾く const isMyNote = note.userId == this.$store.state.i.id; - const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null; + const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null; if (this.$store.state.settings.showMyRenotes === false) { if (isMyNote && isPureRenote) { diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue index 8b1f7b08c8..644e27cce8 100644 --- a/src/client/app/mobile/views/components/post-form.vue +++ b/src/client/app/mobile/views/components/post-form.vue @@ -200,12 +200,12 @@ export default Vue.extend({ attachMedia(driveFile) { this.files.push(driveFile); - this.$emit('change-attached-media', this.files); + this.$emit('change-attached-files', this.files); }, detachMedia(file) { this.files = this.files.filter(x => x.id != file.id); - this.$emit('change-attached-media', this.files); + this.$emit('change-attached-files', this.files); }, onChangeFile() { @@ -269,7 +269,7 @@ export default Vue.extend({ this.text = ''; this.files = []; this.poll = false; - this.$emit('change-attached-media'); + this.$emit('change-attached-files'); }, post() { @@ -277,7 +277,7 @@ export default Vue.extend({ const viaMobile = this.$store.state.settings.disableViaMobile !== true; (this as any).api('notes/create', { text: this.text == '' ? undefined : this.text, - mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, + fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, replyId: this.reply ? this.reply.id : undefined, renoteId: this.renote ? this.renote.id : undefined, poll: this.poll ? (this.$refs.poll as any).get() : undefined, diff --git a/src/client/app/mobile/views/components/sub-note-content.vue b/src/client/app/mobile/views/components/sub-note-content.vue index a4ce49786e..4d0aa25f34 100644 --- a/src/client/app/mobile/views/components/sub-note-content.vue +++ b/src/client/app/mobile/views/components/sub-note-content.vue @@ -7,9 +7,9 @@ RP: ...
-
- ({{ '%i18n:@media-count%'.replace('{}', note.media.length) }}) - +
+ ({{ '%i18n:@media-count%'.replace('{}', note.files.length) }}) +
%i18n:@poll% diff --git a/src/client/app/mobile/views/components/user-timeline.vue b/src/client/app/mobile/views/components/user-timeline.vue index 6be675c0a7..7cd23d6655 100644 --- a/src/client/app/mobile/views/components/user-timeline.vue +++ b/src/client/app/mobile/views/components/user-timeline.vue @@ -41,7 +41,7 @@ export default Vue.extend({ (this.$refs.timeline as any).init(() => new Promise((res, rej) => { (this as any).api('users/notes', { userId: this.user.id, - withMedia: this.withMedia, + withFiles: this.withMedia, limit: fetchLimit + 1 }).then(notes => { if (notes.length == fetchLimit + 1) { @@ -62,7 +62,7 @@ export default Vue.extend({ const promise = (this as any).api('users/notes', { userId: this.user.id, - withMedia: this.withMedia, + withFiles: this.withMedia, limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id }); diff --git a/src/client/app/mobile/views/pages/user/home.photos.vue b/src/client/app/mobile/views/pages/user/home.photos.vue index 73ff1d5173..e9025ec816 100644 --- a/src/client/app/mobile/views/pages/user/home.photos.vue +++ b/src/client/app/mobile/views/pages/user/home.photos.vue @@ -26,7 +26,7 @@ export default Vue.extend({ mounted() { (this as any).api('users/notes', { userId: this.user.id, - withMedia: true, + withFiles: true, limit: 6 }).then(notes => { notes.forEach(note => { diff --git a/src/docs/api/entities/note.yaml b/src/docs/api/entities/note.yaml index cae9a53f82..6654be2b02 100644 --- a/src/docs/api/entities/note.yaml +++ b/src/docs/api/entities/note.yaml @@ -33,19 +33,19 @@ props: ja-JP: "投稿の本文" en-US: "The text of this note" - mediaIds: + fileIds: type: "id(DriveFile)[]" optional: true desc: - ja-JP: "添付されているメディアのID (なければレスポンスでは空配列)" - en-US: "The IDs of the attached media (empty array for response if no media is attached)" + ja-JP: "添付されているファイルのID (なければレスポンスでは空配列)" + en-US: "The IDs of the attached files (empty array for response if no files is attached)" - media: + files: type: "entity(DriveFile)[]" optional: true desc: - ja-JP: "添付されているメディア" - en-US: "The attached media" + ja-JP: "添付されているファイル" + en-US: "The attached files" userId: type: "id(User)" diff --git a/src/misc/get-note-summary.ts b/src/misc/get-note-summary.ts index ec7c74cf9f..3c6f2dd3d6 100644 --- a/src/misc/get-note-summary.ts +++ b/src/misc/get-note-summary.ts @@ -16,9 +16,9 @@ const summarize = (note: any): string => { // 本文 summary += note.text ? note.text : ''; - // メディアが添付されているとき - if (note.media.length != 0) { - summary += ` (${note.media.length}つのメディア)`; + // ファイルが添付されているとき + if (note.files.length != 0) { + summary += ` (${note.files.length}つのファイル)`; } // 投票が添付されているとき diff --git a/src/misc/is-quote.ts b/src/misc/is-quote.ts index 420f03a489..a99b8f6434 100644 --- a/src/misc/is-quote.ts +++ b/src/misc/is-quote.ts @@ -1,5 +1,5 @@ import { INote } from '../models/note'; export default function(note: INote): boolean { - return note.renoteId != null && (note.text != null || note.poll != null || (note.mediaIds != null && note.mediaIds.length > 0)); + return note.renoteId != null && (note.text != null || note.poll != null || (note.fileIds != null && note.fileIds.length > 0)); } diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts index 698ef092a6..215b49b305 100644 --- a/src/models/drive-file.ts +++ b/src/models/drive-file.ts @@ -92,7 +92,7 @@ export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriv // このDriveFileを添付しているNoteをすべて削除 await Promise.all(( - await Note.find({ mediaIds: d._id }) + await Note.find({ fileIds: d._id }) ).map(x => deleteNote(x))); // このDriveFileを添付しているMessagingMessageをすべて削除 diff --git a/src/models/note.ts b/src/models/note.ts index 9d2e23d901..98d37caf22 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -6,7 +6,7 @@ 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 { pack as packFile } from './drive-file'; +import { pack as packFile, IDriveFile } from './drive-file'; import NoteWatching, { deleteNoteWatching } from './note-watching'; import NoteReaction from './note-reaction'; import Favorite, { deleteFavorite } from './favorite'; @@ -17,9 +17,20 @@ const Note = db.get('notes'); Note.createIndex('uri', { sparse: true, unique: true }); Note.createIndex('userId'); Note.createIndex('tagsLower'); +Note.createIndex('_files.contentType'); Note.createIndex({ createdAt: -1 }); + +// 後方互換性のため +Note.update({}, { + $rename: { + mediaIds: 'fileIds' + } +}, { + multi: true +}); + export default Note; export function isValidText(text: string): boolean { @@ -34,7 +45,7 @@ export type INote = { _id: mongo.ObjectID; createdAt: Date; deletedAt: Date; - mediaIds: mongo.ObjectID[]; + fileIds: mongo.ObjectID[]; replyId: mongo.ObjectID; renoteId: mongo.ObjectID; poll: { @@ -92,6 +103,7 @@ export type INote = { inbox?: string; }; _replyIds?: mongo.ObjectID[]; + _files?: IDriveFile[]; }; /** @@ -271,11 +283,15 @@ export const pack = async ( _note.app = packApp(_note.appId); } - // Populate media - _note.media = hide ? [] : Promise.all(_note.mediaIds.map((fileId: mongo.ObjectID) => + // Populate files + _note.files = hide ? [] : Promise.all(_note.fileIds.map((fileId: mongo.ObjectID) => packFile(fileId) )); + // 後方互換性のため + _note.mediaIds = _note.fileIds; + _note.media = _note.files; + // When requested a detailed note data if (opts.detail) { //#region 重いので廃止 @@ -344,7 +360,7 @@ export const pack = async ( } if (hide) { - _note.mediaIds = []; + _note.fileIds = []; _note.text = null; _note.poll = null; _note.cw = null; diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index 1dfeebfdf7..97188b44a6 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -78,11 +78,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false } //#endergion - // 添付メディア + // 添付ファイル // TODO: attachmentは必ずしもImageではない // TODO: attachmentは必ずしも配列ではない // Noteがsensitiveなら添付もsensitiveにする - const media = note.attachment + const files = note.attachment .map(attach => attach.sensitive = note.sensitive) ? await Promise.all(note.attachment.map(x => resolveImage(actor, x))) : []; @@ -100,7 +100,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false return await post(actor, { createdAt: new Date(note.published), - media, + files: files, reply, renote: undefined, cw: note.summary, diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index 1d169d3088..6b30324ae7 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -8,8 +8,8 @@ import User from '../../../models/user'; import toHtml from '../misc/get-note-html'; export default async function renderNote(note: INote, dive = true): Promise { - const promisedFiles: Promise = note.mediaIds - ? DriveFile.find({ _id: { $in: note.mediaIds } }) + const promisedFiles: Promise = note.fileIds + ? DriveFile.find({ _id: { $in: note.fileIds } }) : Promise.resolve([]); let inReplyTo; diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts index 37df190880..cc7e55b5df 100644 --- a/src/server/activitypub/outbox.ts +++ b/src/server/activitypub/outbox.ts @@ -58,7 +58,7 @@ export default async (ctx: Router.IRouterContext) => { $or: [{ text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }] }] } as any; diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 029bc1a95e..4d15e9483f 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -20,9 +20,9 @@ export default (params: any) => new Promise(async (res, rej) => { const [renote, renoteErr] = $.bool.optional.get(params.renote); if (renoteErr) return rej('invalid renote param'); - // Get 'media' parameter - const [media, mediaErr] = $.bool.optional.get(params.media); - if (mediaErr) return rej('invalid media param'); + // Get 'files' parameter + const [files, filesErr] = $.bool.optional.get(params.files); + if (filesErr) return rej('invalid files param'); // Get 'poll' parameter const [poll, pollErr] = $.bool.optional.get(params.poll); @@ -79,8 +79,8 @@ export default (params: any) => new Promise(async (res, rej) => { query.renoteId = renote ? { $exists: true, $ne: null } : null; } - if (media != undefined) { - query.mediaIds = media ? { $exists: true, $ne: null } : []; + if (files != undefined) { + query.fileIds = files ? { $exists: true, $ne: null } : []; } if (poll != undefined) { diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 04f5f7562e..47b53c943b 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -71,9 +71,15 @@ export const meta = { ref: 'geo' }), + fileIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({ + desc: { + 'ja-JP': '添付するファイル' + } + }), + mediaIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({ desc: { - 'ja-JP': '添付するメディア' + 'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)' } }), @@ -124,15 +130,16 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( } let files: IDriveFile[] = []; - if (ps.mediaIds !== undefined) { + const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; + if (fileIds != null) { // Fetch files // forEach だと途中でエラーなどがあっても return できないので // 敢えて for を使っています。 - for (const mediaId of ps.mediaIds) { + for (const fileId of fileIds) { // Fetch file // SELECT _id const entity = await DriveFile.findOne({ - _id: mediaId, + _id: fileId, 'metadata.userId': user._id }); @@ -155,7 +162,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( if (renote == null) { return rej('renoteee is not found'); - } else if (renote.renoteId && !renote.text && !renote.mediaIds) { + } else if (renote.renoteId && !renote.text && !renote.fileIds) { return rej('cannot renote to renote'); } } @@ -176,7 +183,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( } // 返信対象が引用でないRenoteだったらエラー - if (reply.renoteId && !reply.text && !reply.mediaIds) { + if (reply.renoteId && !reply.text && !reply.fileIds) { return rej('cannot reply to renote'); } } @@ -191,13 +198,13 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async ( // テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー if ((ps.text === undefined || ps.text === null) && files === null && renote === null && ps.poll === undefined) { - return rej('text, mediaIds, renoteId or poll is required'); + return rej('text, fileIds, renoteId or poll is required'); } // 投稿を作成 const note = await create(user, { createdAt: new Date(), - media: files, + files: files, poll: ps.poll, text: ps.text, reply, diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 8f7233e308..554245a0f4 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -33,9 +33,9 @@ export default async (params: any, user: ILocalUser) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); - if (mediaOnlyErr) throw 'invalid mediaOnly param'; + // Get 'withFiles' parameter + const [withFiles, withFilesErr] = $.bool.optional.get(params.withFiles); + if (withFilesErr) throw 'invalid withFiles param'; // ミュートしているユーザーを取得 const mutedUserIds = user ? (await Mute.find({ @@ -68,8 +68,8 @@ export default async (params: any, user: ILocalUser) => { }; } - if (mediaOnly) { - query.mediaIds = { $exists: true, $ne: [] }; + if (withFiles) { + query.fileIds = { $exists: true, $ne: [] }; } if (sinceId) { diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 2dbb1190c1..1060792683 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -66,7 +66,7 @@ export const meta = { } }), - mediaOnly: $.bool.optional.note({ + withFiles: $.bool.optional.note({ desc: { 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' } @@ -164,7 +164,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -180,7 +180,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -196,16 +196,16 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + if (ps.withFiles) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index bbcc6303ca..018e636ab5 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -33,9 +33,9 @@ export default async (params: any, user: ILocalUser) => { throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; } - // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); - if (mediaOnlyErr) throw 'invalid mediaOnly param'; + // Get 'withFiles' parameter + const [withFiles, withFilesErr] = $.bool.optional.get(params.withFiles); + if (withFilesErr) throw 'invalid withFiles param'; // ミュートしているユーザーを取得 const mutedUserIds = user ? (await Mute.find({ @@ -69,8 +69,8 @@ export default async (params: any, user: ILocalUser) => { }; } - if (mediaOnly) { - query.mediaIds = { $exists: true, $ne: [] }; + if (withFiles) { + query.fileIds = { $exists: true, $ne: [] }; } if (sinceId) { diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index e092275fe8..317a0726d3 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -247,7 +247,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => if (media != null) { if (media) { push({ - mediaIds: { + fileIds: { $exists: true, $ne: null } @@ -255,11 +255,11 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } else { push({ $or: [{ - mediaIds: { + fileIds: { $exists: false } }, { - mediaIds: null + fileIds: null }] }); } diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 099bf2010b..145f648c56 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -67,7 +67,7 @@ export const meta = { } }), - mediaOnly: $.bool.optional.note({ + withFiles: $.bool.optional.note({ desc: { 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' } @@ -154,7 +154,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -170,7 +170,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -186,16 +186,16 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + if (ps.withFiles) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } diff --git a/src/server/api/endpoints/notes/trend.ts b/src/server/api/endpoints/notes/trend.ts index 7a0a098f28..9f55ed3243 100644 --- a/src/server/api/endpoints/notes/trend.ts +++ b/src/server/api/endpoints/notes/trend.ts @@ -52,7 +52,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } if (media != undefined) { - query.mediaIds = media ? { $exists: true, $ne: null } : null; + query.fileIds = media ? { $exists: true, $ne: null } : null; } if (poll != undefined) { diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index a7b43014ed..e00a7de371 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -73,7 +73,7 @@ export const meta = { } }), - mediaOnly: $.bool.optional.note({ + withFiles: $.bool.optional.note({ desc: { 'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します' } @@ -160,7 +160,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -176,7 +176,7 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] @@ -192,16 +192,16 @@ export default async (params: any, user: ILocalUser) => { }, { text: { $ne: null } }, { - mediaIds: { $ne: [] } + fileIds: { $ne: [] } }, { poll: { $ne: null } }] }); } - if (ps.mediaOnly) { + if (ps.withFiles) { query.$and.push({ - mediaIds: { $exists: true, $ne: [] } + fileIds: { $exists: true, $ne: [] } }); } diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index ff7855bde0..d894e52dba 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -27,9 +27,9 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => const [includeReplies = true, includeRepliesErr] = $.bool.optional.get(params.includeReplies); if (includeRepliesErr) return rej('invalid includeReplies param'); - // Get 'withMedia' parameter - const [withMedia = false, withMediaErr] = $.bool.optional.get(params.withMedia); - if (withMediaErr) return rej('invalid withMedia param'); + // Get 'withFiles' parameter + const [withFiles = false, withFilesErr] = $.bool.optional.get(params.withFiles); + if (withFilesErr) return rej('invalid withFiles param'); // Get 'limit' parameter const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); @@ -104,8 +104,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => query.replyId = null; } - if (withMedia) { - query.mediaIds = { + if (withFiles) { + query.fileIds = { $exists: true, $ne: [] }; diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 63e3557828..11e3755863 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -84,7 +84,7 @@ type Option = { text?: string; reply?: INote; renote?: INote; - media?: IDriveFile[]; + files?: IDriveFile[]; geo?: any; poll?: any; viaMobile?: boolean; @@ -135,7 +135,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< const mentionedUsers = await extractMentionedUsers(tokens); - const note = await insertNote(user, data, tokens, tags, mentionedUsers); + const note = await insertNote(user, data, tags, mentionedUsers); res(note); @@ -309,10 +309,10 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren publishToUserLists(note, noteObj); } -async function insertNote(user: IUser, data: Option, tokens: ReturnType, tags: string[], mentionedUsers: IUser[]) { +async function insertNote(user: IUser, data: Option, tags: string[], mentionedUsers: IUser[]) { const insert: any = { createdAt: data.createdAt, - mediaIds: data.media ? data.media.map(file => file._id) : [], + fileIds: data.files ? data.files.map(file => file._id) : [], replyId: data.reply ? data.reply._id : null, renoteId: data.renote ? data.renote._id : null, text: data.text, @@ -347,7 +347,8 @@ async function insertNote(user: IUser, data: Option, tokens: ReturnType Date: Mon, 10 Sep 2018 14:48:19 +0900 Subject: Fix #2677 --- src/services/note/delete.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/services/note/delete.ts') diff --git a/src/services/note/delete.ts b/src/services/note/delete.ts index 32e736b7a5..b164d59781 100644 --- a/src/services/note/delete.ts +++ b/src/services/note/delete.ts @@ -25,7 +25,8 @@ export default async function(user: IUser, note: INote) { tags: [], fileIds: [], poll: null, - geo: null + geo: null, + cw: null } }); -- cgit v1.3.1-freya From d0570d7fe3a3bf3c6b0312dece74bacc04c3534a Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Oct 2018 11:06:17 +0900 Subject: V10 (#2826) * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update CHANGELOG.md * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update CHANGELOG.md * Update CHANGELOG.md * wip * Update CHANGELOG.md * wip * wip * wip * wip --- CHANGELOG.md | 82 +++++ package.json | 3 +- .../app/common/scripts/compose-notification.ts | 6 +- src/client/app/common/scripts/note-subscriber.ts | 105 +++++++ src/client/app/common/scripts/stream.ts | 318 ++++++++++++++++++++ src/client/app/common/scripts/streaming/drive.ts | 34 --- .../streaming/games/reversi/reversi-game.ts | 13 - .../scripts/streaming/games/reversi/reversi.ts | 31 -- .../common/scripts/streaming/global-timeline.ts | 34 --- src/client/app/common/scripts/streaming/hashtag.ts | 13 - src/client/app/common/scripts/streaming/home.ts | 126 -------- .../common/scripts/streaming/hybrid-timeline.ts | 34 --- .../app/common/scripts/streaming/local-timeline.ts | 34 --- .../common/scripts/streaming/messaging-index.ts | 34 --- .../app/common/scripts/streaming/messaging.ts | 20 -- .../app/common/scripts/streaming/notes-stats.ts | 30 -- .../app/common/scripts/streaming/server-stats.ts | 30 -- .../app/common/scripts/streaming/stream-manager.ts | 109 ------- src/client/app/common/scripts/streaming/stream.ts | 137 --------- .../app/common/scripts/streaming/user-list.ts | 17 -- .../components/games/reversi/reversi.gameroom.vue | 8 +- .../components/games/reversi/reversi.index.vue | 9 +- .../views/components/games/reversi/reversi.vue | 8 +- .../app/common/views/components/messaging-room.vue | 8 +- .../app/common/views/components/messaging.vue | 10 +- src/client/app/common/views/components/signin.vue | 2 +- src/client/app/common/views/components/signup.vue | 4 +- .../common/views/components/stream-indicator.vue | 2 +- .../common/views/components/welcome-timeline.vue | 9 +- .../app/common/views/widgets/photo-stream.vue | 11 +- .../app/common/views/widgets/posts-monitor.vue | 8 +- src/client/app/common/views/widgets/server.vue | 8 +- src/client/app/config.ts | 2 +- src/client/app/desktop/script.ts | 84 +++--- src/client/app/desktop/views/components/drive.vue | 13 +- .../app/desktop/views/components/follow-button.vue | 11 +- src/client/app/desktop/views/components/home.vue | 6 +- .../app/desktop/views/components/note-detail.vue | 3 + .../app/desktop/views/components/notes.note.vue | 86 +----- .../app/desktop/views/components/notifications.vue | 14 +- .../desktop/views/components/settings.signins.vue | 12 +- .../app/desktop/views/components/timeline.core.vue | 61 +--- .../app/desktop/views/components/ui.header.nav.vue | 12 +- .../views/components/user-list-timeline.vue | 1 - .../desktop/views/pages/admin/admin.dashboard.vue | 8 +- .../app/desktop/views/pages/deck/deck.direct.vue | 10 +- .../desktop/views/pages/deck/deck.hashtag-tl.vue | 3 +- .../app/desktop/views/pages/deck/deck.list-tl.vue | 1 - .../app/desktop/views/pages/deck/deck.mentions.vue | 10 +- .../app/desktop/views/pages/deck/deck.note.vue | 68 +---- .../views/pages/deck/deck.notifications.vue | 11 +- .../app/desktop/views/pages/deck/deck.tl.vue | 21 +- src/client/app/mios.ts | 135 ++------- src/client/app/mobile/views/components/drive.vue | 13 +- .../app/mobile/views/components/follow-button.vue | 10 +- .../app/mobile/views/components/note-detail.vue | 3 + src/client/app/mobile/views/components/note.vue | 86 +----- .../app/mobile/views/components/notifications.vue | 17 +- .../app/mobile/views/components/ui.header.vue | 17 +- src/client/app/mobile/views/components/ui.nav.vue | 10 +- src/client/app/mobile/views/components/ui.vue | 17 +- .../mobile/views/components/user-list-timeline.vue | 1 - .../app/mobile/views/pages/home.timeline.vue | 60 +--- src/client/app/tsconfig.json | 3 +- src/docs/stream.ja-JP.md | 8 +- src/notify.ts | 6 +- src/server/api/call.ts | 4 + src/server/api/common/read-messaging-message.ts | 4 +- src/server/api/common/read-notification.ts | 4 +- src/server/api/endpoints/games/reversi/match.ts | 6 +- src/server/api/endpoints/i/regenerate_token.ts | 4 +- src/server/api/endpoints/i/update.ts | 4 +- .../api/endpoints/i/update_client_setting.ts | 4 +- src/server/api/endpoints/i/update_home.ts | 4 +- src/server/api/endpoints/i/update_mobile_home.ts | 4 +- src/server/api/endpoints/i/update_widget.ts | 4 +- .../api/endpoints/messaging/messages/create.ts | 10 +- src/server/api/endpoints/notes/polls/vote.ts | 5 +- .../endpoints/notifications/mark_all_as_read.ts | 4 +- src/server/api/private/signin.ts | 4 +- src/server/api/service/twitter.ts | 6 +- src/server/api/stream/channel.ts | 39 +++ src/server/api/stream/channels/drive.ts | 12 + .../api/stream/channels/games/reversi-game.ts | 309 +++++++++++++++++++ src/server/api/stream/channels/games/reversi.ts | 30 ++ src/server/api/stream/channels/global-timeline.ts | 39 +++ src/server/api/stream/channels/hashtag.ts | 33 ++ src/server/api/stream/channels/home-timeline.ts | 39 +++ src/server/api/stream/channels/hybrid-timeline.ts | 41 +++ src/server/api/stream/channels/index.ts | 31 ++ src/server/api/stream/channels/local-timeline.ts | 39 +++ src/server/api/stream/channels/main.ts | 25 ++ src/server/api/stream/channels/messaging-index.ts | 12 + src/server/api/stream/channels/messaging.ts | 26 ++ src/server/api/stream/channels/notes-stats.ts | 34 +++ src/server/api/stream/channels/server-stats.ts | 37 +++ src/server/api/stream/channels/user-list.ts | 14 + src/server/api/stream/drive.ts | 9 - src/server/api/stream/games/reversi-game.ts | 332 --------------------- src/server/api/stream/games/reversi.ts | 28 -- src/server/api/stream/global-timeline.ts | 27 -- src/server/api/stream/hashtag.ts | 40 --- src/server/api/stream/home.ts | 110 ------- src/server/api/stream/hybrid-timeline.ts | 38 --- src/server/api/stream/index.ts | 213 +++++++++++++ src/server/api/stream/local-timeline.ts | 35 --- src/server/api/stream/messaging-index.ts | 9 - src/server/api/stream/messaging.ts | 25 -- src/server/api/stream/notes-stats.ts | 35 --- src/server/api/stream/server-stats.ts | 38 --- src/server/api/stream/user-list.ts | 13 - src/server/api/streaming.ts | 73 +---- src/services/drive/add-file.ts | 6 +- src/services/following/create.ts | 6 +- src/services/following/delete.ts | 4 +- src/services/following/requests/accept.ts | 6 +- src/services/following/requests/cancel.ts | 4 +- src/services/following/requests/create.ts | 6 +- src/services/following/requests/reject.ts | 4 +- src/services/note/create.ts | 14 +- src/services/note/delete.ts | 2 +- src/services/note/reaction/create.ts | 4 +- src/services/note/read.ts | 6 +- src/services/note/unread.ts | 6 +- src/stream.ts | 36 ++- tsconfig.json | 3 +- 126 files changed, 1811 insertions(+), 2272 deletions(-) create mode 100644 src/client/app/common/scripts/note-subscriber.ts create mode 100644 src/client/app/common/scripts/stream.ts delete mode 100644 src/client/app/common/scripts/streaming/drive.ts delete mode 100644 src/client/app/common/scripts/streaming/games/reversi/reversi-game.ts delete mode 100644 src/client/app/common/scripts/streaming/games/reversi/reversi.ts delete mode 100644 src/client/app/common/scripts/streaming/global-timeline.ts delete mode 100644 src/client/app/common/scripts/streaming/hashtag.ts delete mode 100644 src/client/app/common/scripts/streaming/home.ts delete mode 100644 src/client/app/common/scripts/streaming/hybrid-timeline.ts delete mode 100644 src/client/app/common/scripts/streaming/local-timeline.ts delete mode 100644 src/client/app/common/scripts/streaming/messaging-index.ts delete mode 100644 src/client/app/common/scripts/streaming/messaging.ts delete mode 100644 src/client/app/common/scripts/streaming/notes-stats.ts delete mode 100644 src/client/app/common/scripts/streaming/server-stats.ts delete mode 100644 src/client/app/common/scripts/streaming/stream-manager.ts delete mode 100644 src/client/app/common/scripts/streaming/stream.ts delete mode 100644 src/client/app/common/scripts/streaming/user-list.ts create mode 100644 src/server/api/stream/channel.ts create mode 100644 src/server/api/stream/channels/drive.ts create mode 100644 src/server/api/stream/channels/games/reversi-game.ts create mode 100644 src/server/api/stream/channels/games/reversi.ts create mode 100644 src/server/api/stream/channels/global-timeline.ts create mode 100644 src/server/api/stream/channels/hashtag.ts create mode 100644 src/server/api/stream/channels/home-timeline.ts create mode 100644 src/server/api/stream/channels/hybrid-timeline.ts create mode 100644 src/server/api/stream/channels/index.ts create mode 100644 src/server/api/stream/channels/local-timeline.ts create mode 100644 src/server/api/stream/channels/main.ts create mode 100644 src/server/api/stream/channels/messaging-index.ts create mode 100644 src/server/api/stream/channels/messaging.ts create mode 100644 src/server/api/stream/channels/notes-stats.ts create mode 100644 src/server/api/stream/channels/server-stats.ts create mode 100644 src/server/api/stream/channels/user-list.ts delete mode 100644 src/server/api/stream/drive.ts delete mode 100644 src/server/api/stream/games/reversi-game.ts delete mode 100644 src/server/api/stream/games/reversi.ts delete mode 100644 src/server/api/stream/global-timeline.ts delete mode 100644 src/server/api/stream/hashtag.ts delete mode 100644 src/server/api/stream/home.ts delete mode 100644 src/server/api/stream/hybrid-timeline.ts create mode 100644 src/server/api/stream/index.ts delete mode 100644 src/server/api/stream/local-timeline.ts delete mode 100644 src/server/api/stream/messaging-index.ts delete mode 100644 src/server/api/stream/messaging.ts delete mode 100644 src/server/api/stream/notes-stats.ts delete mode 100644 src/server/api/stream/server-stats.ts delete mode 100644 src/server/api/stream/user-list.ts (limited to 'src/services/note/delete.ts') diff --git a/CHANGELOG.md b/CHANGELOG.md index c492e43839..b26010b146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,88 @@ ChangeLog This document describes breaking changes only. +10.0.0 +------ + +ストリーミングAPIに破壊的変更があります。運営者がすべきことはありません。 + +変更は以下の通りです + +* ストリーミングでやり取りする際の snake_case が全て camelCase に +* リバーシのストリームエンドポイント名が reversi → gamesReversi、reversiGame → gamesReversiGame に +* ストリーミングの個々のエンドポイントが廃止され、一旦元となるストリームに接続してから、個々のチャンネル(今までのエンドポイント)に接続します。詳細は後述します。 +* ストリームから流れてくる、キャプチャした投稿の更新イベントに投稿自体のデータは含まれず、代わりにアクションが設定されるようになります。詳細は後述します。 +* ストリームに接続する際に追加で指定していたパラメータ(トークン除く)が、URLにクエリとして含むのではなくチャンネル接続時にパラメータ指定するように + +### 個々のエンドポイントが廃止されることによる新しいストリーミングAPIの利用方法 +具体的には、まず https://example.misskey/streaming にwebsocket接続します。 +次に、例えば「messaging」ストリーム(チャンネルと呼びます)に接続したいときは、ストリームに次のようなデータを送信します: +``` javascript +{ + type: 'connect', + body: { + channel: 'messaging', + id: 'foobar', + params: { + otherparty: 'xxxxxxxxxxxx' + } + } +} +``` +ここで、`id`にはそのチャンネルとやり取りするための任意のIDを設定します。 +IDはチャンネルごとではなく「チャンネルの接続ごと」です。なぜなら、同じチャンネルに異なるパラメータで複数接続するケースもあるからです。 +`params`はチャンネルに接続する際のパラメータです。チャンネルによって接続時に必要とされるパラメータは異なります。パラメータ不要のチャンネルに接続する際は、このプロパティは省略可能です。 + +チャンネルにメッセージを送信するには、次のようなデータを送信します: +``` javascript +{ + type: 'channel', + body: { + id: 'foobar', + type: 'something', + body: { + some: 'thing' + } + } +} +``` +ここで、`id`にはチャンネルに接続するときに指定したIDを設定します。 + +逆に、チャンネルからメッセージが流れてくると、次のようなデータが受信されます: +``` javascript +{ + type: 'channel', + body: { + id: 'foobar', + type: 'something', + body: { + some: 'thing' + } + } +} +``` +ここで、`id`にはチャンネルに接続するときに指定したIDが設定されています。 + +### 投稿のキャプチャに関する変更 +投稿の更新イベントに投稿情報は含まれなくなりました。代わりに、その投稿が「リアクションされた」「アンケートに投票された」「削除された」といったアクション情報が設定されます。 + +具体的には次のようなデータが受信されます: +``` javascript +{ + type: 'noteUpdated', + body: { + id: 'xxxxxxxxxxx', + type: 'reacted', + body: { + reaction: 'hmm' + } + } +} +``` + +* reacted ... 投稿にリアクションされた。`reaction`プロパティにリアクションコードが含まれます。 +* pollVoted ... アンケートに投票された。`choice`プロパティに選択肢ID、`userId`に投票者IDが含まれます。 + 9.0.0 ----- diff --git a/package.json b/package.json index 27bf5c0f13..dc76ad0f81 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@types/websocket": "0.0.40", "@types/ws": "6.0.1", "animejs": "2.2.0", + "autobind-decorator": "2.1.0", "autosize": "4.0.2", "autwh": "0.1.0", "bcryptjs": "2.4.3", @@ -225,8 +226,8 @@ "vuex-persistedstate": "2.5.4", "web-push": "3.3.3", "webfinger.js": "2.6.6", - "webpack-cli": "3.1.2", "webpack": "4.20.2", + "webpack-cli": "3.1.2", "websocket": "1.0.28", "ws": "6.0.0", "xev": "2.0.1" diff --git a/src/client/app/common/scripts/compose-notification.ts b/src/client/app/common/scripts/compose-notification.ts index f42af94370..65087cc98e 100644 --- a/src/client/app/common/scripts/compose-notification.ts +++ b/src/client/app/common/scripts/compose-notification.ts @@ -13,21 +13,21 @@ type Notification = { export default function(type, data): Notification { switch (type) { - case 'drive_file_created': + case 'driveFileCreated': return { title: '%i18n:common.notification.file-uploaded%', body: data.name, icon: data.url }; - case 'unread_messaging_message': + case 'unreadMessagingMessage': return { title: '%i18n:common.notification.message-from%'.split("{}")[0] + `${getUserName(data.user)}` + '%i18n:common.notification.message-from%'.split("{}")[1] , body: data.text, // TODO: getMessagingMessageSummary(data), icon: data.user.avatarUrl }; - case 'reversi_invited': + case 'reversiInvited': return { title: '%i18n:common.notification.reversi-invited%', body: '%i18n:common.notification.reversi-invited-by%'.split("{}")[0] + `${getUserName(data.parent)}` + '%i18n:common.notification.reversi-invited-by%'.split("{}")[1], diff --git a/src/client/app/common/scripts/note-subscriber.ts b/src/client/app/common/scripts/note-subscriber.ts new file mode 100644 index 0000000000..5fc82942d5 --- /dev/null +++ b/src/client/app/common/scripts/note-subscriber.ts @@ -0,0 +1,105 @@ +import Vue from 'vue'; + +export default prop => ({ + data() { + return { + connection: null + }; + }, + + computed: { + $_ns_note_(): any { + return this[prop]; + }, + + $_ns_isRenote(): boolean { + return (this.$_ns_note_.renote && + this.$_ns_note_.text == null && + this.$_ns_note_.fileIds.length == 0 && + this.$_ns_note_.poll == null); + }, + + $_ns_target(): any { + return this._ns_isRenote ? this.$_ns_note_.renote : this.$_ns_note_; + }, + }, + + created() { + if (this.$store.getters.isSignedIn) { + this.connection = (this as any).os.stream; + } + }, + + mounted() { + this.capture(true); + + if (this.$store.getters.isSignedIn) { + this.connection.on('_connected_', this.onStreamConnected); + } + }, + + beforeDestroy() { + this.decapture(true); + + if (this.$store.getters.isSignedIn) { + this.connection.off('_connected_', this.onStreamConnected); + } + }, + + methods: { + capture(withHandler = false) { + if (this.$store.getters.isSignedIn) { + const data = { + id: this.$_ns_target.id + } as any; + + if ( + (this.$_ns_target.visibleUserIds || []).includes(this.$store.state.i.id) || + (this.$_ns_target.mentions || []).includes(this.$store.state.i.id) + ) { + data.read = true; + } + + this.connection.send('sn', data); + if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); + } + }, + + decapture(withHandler = false) { + if (this.$store.getters.isSignedIn) { + this.connection.send('un', { + id: this.$_ns_target.id + }); + if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); + } + }, + + onStreamConnected() { + this.capture(); + }, + + onStreamNoteUpdated(data) { + const { type, id, body } = data; + + if (id !== this.$_ns_target.id) return; + + switch (type) { + case 'reacted': { + const reaction = body.reaction; + if (this.$_ns_target.reactionCounts == null) Vue.set(this.$_ns_target, 'reactionCounts', {}); + this.$_ns_target.reactionCounts[reaction] = (this.$_ns_target.reactionCounts[reaction] || 0) + 1; + break; + } + + case 'pollVoted': { + if (body.userId == this.$store.state.i.id) return; + const choice = body.choice; + this.$_ns_target.poll.choices.find(c => c.id === choice).votes++; + break; + } + } + + this.$emit(`update:${prop}`, this.$_ns_note_); + }, + } +}); diff --git a/src/client/app/common/scripts/stream.ts b/src/client/app/common/scripts/stream.ts new file mode 100644 index 0000000000..7dc130937b --- /dev/null +++ b/src/client/app/common/scripts/stream.ts @@ -0,0 +1,318 @@ +import autobind from 'autobind-decorator'; +import { EventEmitter } from 'eventemitter3'; +import * as ReconnectingWebsocket from 'reconnecting-websocket'; +import { wsUrl } from '../../config'; +import MiOS from '../../mios'; + +/** + * Misskey stream connection + */ +export default class Stream extends EventEmitter { + private stream: ReconnectingWebsocket; + private state: string; + private buffer: any[]; + private sharedConnections: SharedConnection[] = []; + private nonSharedConnections: NonSharedConnection[] = []; + + constructor(os: MiOS) { + super(); + + this.state = 'initializing'; + this.buffer = []; + + const user = os.store.state.i; + + this.stream = new ReconnectingWebsocket(wsUrl + (user ? `?i=${user.token}` : '')); + this.stream.addEventListener('open', this.onOpen); + this.stream.addEventListener('close', this.onClose); + this.stream.addEventListener('message', this.onMessage); + + if (user) { + const main = this.useSharedConnection('main'); + + // 自分の情報が更新されたとき + main.on('meUpdated', i => { + os.store.dispatch('mergeMe', i); + }); + + main.on('readAllNotifications', () => { + os.store.dispatch('mergeMe', { + hasUnreadNotification: false + }); + }); + + main.on('unreadNotification', () => { + os.store.dispatch('mergeMe', { + hasUnreadNotification: true + }); + }); + + main.on('readAllMessagingMessages', () => { + os.store.dispatch('mergeMe', { + hasUnreadMessagingMessage: false + }); + }); + + main.on('unreadMessagingMessage', () => { + os.store.dispatch('mergeMe', { + hasUnreadMessagingMessage: true + }); + }); + + main.on('unreadMention', () => { + os.store.dispatch('mergeMe', { + hasUnreadMentions: true + }); + }); + + main.on('readAllUnreadMentions', () => { + os.store.dispatch('mergeMe', { + hasUnreadMentions: false + }); + }); + + main.on('unreadSpecifiedNote', () => { + os.store.dispatch('mergeMe', { + hasUnreadSpecifiedNotes: true + }); + }); + + main.on('readAllUnreadSpecifiedNotes', () => { + os.store.dispatch('mergeMe', { + hasUnreadSpecifiedNotes: false + }); + }); + + main.on('clientSettingUpdated', x => { + os.store.commit('settings/set', { + key: x.key, + value: x.value + }); + }); + + main.on('homeUpdated', x => { + os.store.commit('settings/setHome', x); + }); + + main.on('mobileHomeUpdated', x => { + os.store.commit('settings/setMobileHome', x); + }); + + main.on('widgetUpdated', x => { + os.store.commit('settings/setWidget', { + id: x.id, + data: x.data + }); + }); + + // トークンが再生成されたとき + // このままではMisskeyが利用できないので強制的にサインアウトさせる + main.on('myTokenRegenerated', () => { + alert('%i18n:common.my-token-regenerated%'); + os.signout(); + }); + } + } + + public useSharedConnection = (channel: string): SharedConnection => { + const existConnection = this.sharedConnections.find(c => c.channel === channel); + + if (existConnection) { + existConnection.use(); + return existConnection; + } else { + const connection = new SharedConnection(this, channel); + connection.use(); + this.sharedConnections.push(connection); + return connection; + } + } + + @autobind + public removeSharedConnection(connection: SharedConnection) { + this.sharedConnections = this.sharedConnections.filter(c => c.id !== connection.id); + } + + public connectToChannel = (channel: string, params?: any): NonSharedConnection => { + const connection = new NonSharedConnection(this, channel, params); + this.nonSharedConnections.push(connection); + return connection; + } + + @autobind + public disconnectToChannel(connection: NonSharedConnection) { + this.nonSharedConnections = this.nonSharedConnections.filter(c => c.id !== connection.id); + } + + /** + * Callback of when open connection + */ + @autobind + private onOpen() { + const isReconnect = this.state == 'reconnecting'; + + this.state = 'connected'; + this.emit('_connected_'); + + // バッファーを処理 + const _buffer = [].concat(this.buffer); // Shallow copy + this.buffer = []; // Clear buffer + _buffer.forEach(data => { + this.send(data); // Resend each buffered messages + }); + + // チャンネル再接続 + if (isReconnect) { + this.sharedConnections.forEach(c => { + c.connect(); + }); + this.nonSharedConnections.forEach(c => { + c.connect(); + }); + } + } + + /** + * Callback of when close connection + */ + @autobind + private onClose() { + this.state = 'reconnecting'; + this.emit('_disconnected_'); + } + + /** + * Callback of when received a message from connection + */ + @autobind + private onMessage(message) { + const { type, body } = JSON.parse(message.data); + + if (type == 'channel') { + const id = body.id; + const connection = this.sharedConnections.find(c => c.id === id) || this.nonSharedConnections.find(c => c.id === id); + connection.emit(body.type, body.body); + } else { + this.emit(type, body); + } + } + + /** + * Send a message to connection + */ + @autobind + public send(typeOrPayload, payload?) { + const data = payload === undefined ? typeOrPayload : { + type: typeOrPayload, + body: payload + }; + + // まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する + if (this.state != 'connected') { + this.buffer.push(data); + return; + } + + this.stream.send(JSON.stringify(data)); + } + + /** + * Close this connection + */ + @autobind + public close() { + this.stream.removeEventListener('open', this.onOpen); + this.stream.removeEventListener('message', this.onMessage); + } +} + +abstract class Connection extends EventEmitter { + public channel: string; + public id: string; + protected params: any; + protected stream: Stream; + + constructor(stream: Stream, channel: string, params?: any) { + super(); + + this.stream = stream; + this.channel = channel; + this.params = params; + this.id = Math.random().toString(); + this.connect(); + } + + @autobind + public connect() { + this.stream.send('connect', { + channel: this.channel, + id: this.id, + params: this.params + }); + } + + @autobind + public send(typeOrPayload, payload?) { + const data = payload === undefined ? typeOrPayload : { + type: typeOrPayload, + body: payload + }; + + this.stream.send('channel', { + id: this.id, + body: data + }); + } + + public abstract dispose: () => void; +} + +class SharedConnection extends Connection { + private users = 0; + private disposeTimerId: any; + + constructor(stream: Stream, channel: string) { + super(stream, channel); + } + + @autobind + public use() { + this.users++; + + // タイマー解除 + if (this.disposeTimerId) { + clearTimeout(this.disposeTimerId); + this.disposeTimerId = null; + } + } + + @autobind + public dispose() { + this.users--; + + // そのコネクションの利用者が誰もいなくなったら + if (this.users === 0) { + // また直ぐに再利用される可能性があるので、一定時間待ち、 + // 新たな利用者が現れなければコネクションを切断する + this.disposeTimerId = setTimeout(() => { + this.disposeTimerId = null; + this.removeAllListeners(); + this.stream.send('disconnect', { id: this.id }); + this.stream.removeSharedConnection(this); + }, 3000); + } + } +} + +class NonSharedConnection extends Connection { + constructor(stream: Stream, channel: string, params?: any) { + super(stream, channel, params); + } + + @autobind + public dispose() { + this.removeAllListeners(); + this.stream.send('disconnect', { id: this.id }); + this.stream.disconnectToChannel(this); + } +} diff --git a/src/client/app/common/scripts/streaming/drive.ts b/src/client/app/common/scripts/streaming/drive.ts deleted file mode 100644 index 50fff05737..0000000000 --- a/src/client/app/common/scripts/streaming/drive.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Stream from './stream'; -import StreamManager from './stream-manager'; -import MiOS from '../../../mios'; - -/** - * Drive stream connection - */ -export class DriveStream extends Stream { - constructor(os: MiOS, me) { - super(os, 'drive', { - i: me.token - }); - } -} - -export class DriveStreamManager extends StreamManager { - private me; - private os: MiOS; - - constructor(os: MiOS, me) { - super(); - - this.me = me; - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new DriveStream(this.os, this.me); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/games/reversi/reversi-game.ts b/src/client/app/common/scripts/streaming/games/reversi/reversi-game.ts deleted file mode 100644 index adfa75ff3b..0000000000 --- a/src/client/app/common/scripts/streaming/games/reversi/reversi-game.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Stream from '../../stream'; -import MiOS from '../../../../../mios'; - -export class ReversiGameStream extends Stream { - constructor(os: MiOS, me, game) { - super(os, 'games/reversi-game', me ? { - i: me.token, - game: game.id - } : { - game: game.id - }); - } -} diff --git a/src/client/app/common/scripts/streaming/games/reversi/reversi.ts b/src/client/app/common/scripts/streaming/games/reversi/reversi.ts deleted file mode 100644 index 1f4fd8c63e..0000000000 --- a/src/client/app/common/scripts/streaming/games/reversi/reversi.ts +++ /dev/null @@ -1,31 +0,0 @@ -import StreamManager from '../../stream-manager'; -import Stream from '../../stream'; -import MiOS from '../../../../../mios'; - -export class ReversiStream extends Stream { - constructor(os: MiOS, me) { - super(os, 'games/reversi', { - i: me.token - }); - } -} - -export class ReversiStreamManager extends StreamManager { - private me; - private os: MiOS; - - constructor(os: MiOS, me) { - super(); - - this.me = me; - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new ReversiStream(this.os, this.me); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/global-timeline.ts b/src/client/app/common/scripts/streaming/global-timeline.ts deleted file mode 100644 index a639f1595c..0000000000 --- a/src/client/app/common/scripts/streaming/global-timeline.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Stream from './stream'; -import StreamManager from './stream-manager'; -import MiOS from '../../../mios'; - -/** - * Global timeline stream connection - */ -export class GlobalTimelineStream extends Stream { - constructor(os: MiOS, me) { - super(os, 'global-timeline', { - i: me.token - }); - } -} - -export class GlobalTimelineStreamManager extends StreamManager { - private me; - private os: MiOS; - - constructor(os: MiOS, me) { - super(); - - this.me = me; - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new GlobalTimelineStream(this.os, this.me); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/hashtag.ts b/src/client/app/common/scripts/streaming/hashtag.ts deleted file mode 100644 index 276b8f8d3d..0000000000 --- a/src/client/app/common/scripts/streaming/hashtag.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Stream from './stream'; -import MiOS from '../../../mios'; - -export class HashtagStream extends Stream { - constructor(os: MiOS, me, q) { - super(os, 'hashtag', me ? { - i: me.token, - q: JSON.stringify(q) - } : { - q: JSON.stringify(q) - }); - } -} diff --git a/src/client/app/common/scripts/streaming/home.ts b/src/client/app/common/scripts/streaming/home.ts deleted file mode 100644 index 26729507fb..0000000000 --- a/src/client/app/common/scripts/streaming/home.ts +++ /dev/null @@ -1,126 +0,0 @@ -import Stream from './stream'; -import StreamManager from './stream-manager'; -import MiOS from '../../../mios'; - -/** - * Home stream connection - */ -export class HomeStream extends Stream { - constructor(os: MiOS, me) { - super(os, '', { - i: me.token - }); - - // 最終利用日時を更新するため定期的にaliveメッセージを送信 - setInterval(() => { - this.send({ type: 'alive' }); - me.lastUsedAt = new Date(); - }, 1000 * 60); - - // 自分の情報が更新されたとき - this.on('meUpdated', i => { - if (os.debug) { - console.log('I updated:', i); - } - - os.store.dispatch('mergeMe', i); - }); - - this.on('read_all_notifications', () => { - os.store.dispatch('mergeMe', { - hasUnreadNotification: false - }); - }); - - this.on('unread_notification', () => { - os.store.dispatch('mergeMe', { - hasUnreadNotification: true - }); - }); - - this.on('read_all_messaging_messages', () => { - os.store.dispatch('mergeMe', { - hasUnreadMessagingMessage: false - }); - }); - - this.on('unread_messaging_message', () => { - os.store.dispatch('mergeMe', { - hasUnreadMessagingMessage: true - }); - }); - - this.on('unreadMention', () => { - os.store.dispatch('mergeMe', { - hasUnreadMentions: true - }); - }); - - this.on('readAllUnreadMentions', () => { - os.store.dispatch('mergeMe', { - hasUnreadMentions: false - }); - }); - - this.on('unreadSpecifiedNote', () => { - os.store.dispatch('mergeMe', { - hasUnreadSpecifiedNotes: true - }); - }); - - this.on('readAllUnreadSpecifiedNotes', () => { - os.store.dispatch('mergeMe', { - hasUnreadSpecifiedNotes: false - }); - }); - - this.on('clientSettingUpdated', x => { - os.store.commit('settings/set', { - key: x.key, - value: x.value - }); - }); - - this.on('home_updated', x => { - os.store.commit('settings/setHome', x); - }); - - this.on('mobile_home_updated', x => { - os.store.commit('settings/setMobileHome', x); - }); - - this.on('widgetUpdated', x => { - os.store.commit('settings/setWidget', { - id: x.id, - data: x.data - }); - }); - - // トークンが再生成されたとき - // このままではMisskeyが利用できないので強制的にサインアウトさせる - this.on('my_token_regenerated', () => { - alert('%i18n:common.my-token-regenerated%'); - os.signout(); - }); - } -} - -export class HomeStreamManager extends StreamManager { - private me; - private os: MiOS; - - constructor(os: MiOS, me) { - super(); - - this.me = me; - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new HomeStream(this.os, this.me); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/hybrid-timeline.ts b/src/client/app/common/scripts/streaming/hybrid-timeline.ts deleted file mode 100644 index cd290797c4..0000000000 --- a/src/client/app/common/scripts/streaming/hybrid-timeline.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Stream from './stream'; -import StreamManager from './stream-manager'; -import MiOS from '../../../mios'; - -/** - * Hybrid timeline stream connection - */ -export class HybridTimelineStream extends Stream { - constructor(os: MiOS, me) { - super(os, 'hybrid-timeline', { - i: me.token - }); - } -} - -export class HybridTimelineStreamManager extends StreamManager { - private me; - private os: MiOS; - - constructor(os: MiOS, me) { - super(); - - this.me = me; - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new HybridTimelineStream(this.os, this.me); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/local-timeline.ts b/src/client/app/common/scripts/streaming/local-timeline.ts deleted file mode 100644 index 41c36aa14c..0000000000 --- a/src/client/app/common/scripts/streaming/local-timeline.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Stream from './stream'; -import StreamManager from './stream-manager'; -import MiOS from '../../../mios'; - -/** - * Local timeline stream connection - */ -export class LocalTimelineStream extends Stream { - constructor(os: MiOS, me) { - super(os, 'local-timeline', me ? { - i: me.token - } : {}); - } -} - -export class LocalTimelineStreamManager extends StreamManager { - private me; - private os: MiOS; - - constructor(os: MiOS, me) { - super(); - - this.me = me; - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new LocalTimelineStream(this.os, this.me); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/messaging-index.ts b/src/client/app/common/scripts/streaming/messaging-index.ts deleted file mode 100644 index addcccb952..0000000000 --- a/src/client/app/common/scripts/streaming/messaging-index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Stream from './stream'; -import StreamManager from './stream-manager'; -import MiOS from '../../../mios'; - -/** - * Messaging index stream connection - */ -export class MessagingIndexStream extends Stream { - constructor(os: MiOS, me) { - super(os, 'messaging-index', { - i: me.token - }); - } -} - -export class MessagingIndexStreamManager extends StreamManager { - private me; - private os: MiOS; - - constructor(os: MiOS, me) { - super(); - - this.me = me; - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new MessagingIndexStream(this.os, this.me); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/messaging.ts b/src/client/app/common/scripts/streaming/messaging.ts deleted file mode 100644 index a59377d867..0000000000 --- a/src/client/app/common/scripts/streaming/messaging.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Stream from './stream'; -import MiOS from '../../../mios'; - -/** - * Messaging stream connection - */ -export class MessagingStream extends Stream { - constructor(os: MiOS, me, otherparty) { - super(os, 'messaging', { - i: me.token, - otherparty - }); - - (this as any).on('_connected_', () => { - this.send({ - i: me.token - }); - }); - } -} diff --git a/src/client/app/common/scripts/streaming/notes-stats.ts b/src/client/app/common/scripts/streaming/notes-stats.ts deleted file mode 100644 index 9e3e78a709..0000000000 --- a/src/client/app/common/scripts/streaming/notes-stats.ts +++ /dev/null @@ -1,30 +0,0 @@ -import Stream from './stream'; -import StreamManager from './stream-manager'; -import MiOS from '../../../mios'; - -/** - * Notes stats stream connection - */ -export class NotesStatsStream extends Stream { - constructor(os: MiOS) { - super(os, 'notes-stats'); - } -} - -export class NotesStatsStreamManager extends StreamManager { - private os: MiOS; - - constructor(os: MiOS) { - super(); - - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new NotesStatsStream(this.os); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/server-stats.ts b/src/client/app/common/scripts/streaming/server-stats.ts deleted file mode 100644 index 9983dfcaf0..0000000000 --- a/src/client/app/common/scripts/streaming/server-stats.ts +++ /dev/null @@ -1,30 +0,0 @@ -import Stream from './stream'; -import StreamManager from './stream-manager'; -import MiOS from '../../../mios'; - -/** - * Server stats stream connection - */ -export class ServerStatsStream extends Stream { - constructor(os: MiOS) { - super(os, 'server-stats'); - } -} - -export class ServerStatsStreamManager extends StreamManager { - private os: MiOS; - - constructor(os: MiOS) { - super(); - - this.os = os; - } - - public getConnection() { - if (this.connection == null) { - this.connection = new ServerStatsStream(this.os); - } - - return this.connection; - } -} diff --git a/src/client/app/common/scripts/streaming/stream-manager.ts b/src/client/app/common/scripts/streaming/stream-manager.ts deleted file mode 100644 index 8dd06f67d3..0000000000 --- a/src/client/app/common/scripts/streaming/stream-manager.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { EventEmitter } from 'eventemitter3'; -import * as uuid from 'uuid'; -import Connection from './stream'; -import { erase } from '../../../../../prelude/array'; - -/** - * ストリーム接続を管理するクラス - * 複数の場所から同じストリームを利用する際、接続をまとめたりする - */ -export default abstract class StreamManager extends EventEmitter { - private _connection: T = null; - - private disposeTimerId: any; - - /** - * コネクションを必要としているユーザー - */ - private users = []; - - protected set connection(connection: T) { - this._connection = connection; - - if (this._connection == null) { - this.emit('disconnected'); - } else { - this.emit('connected', this._connection); - - this._connection.on('_connected_', () => { - this.emit('_connected_'); - }); - - this._connection.on('_disconnected_', () => { - this.emit('_disconnected_'); - }); - - this._connection.user = 'Managed'; - } - } - - protected get connection() { - return this._connection; - } - - /** - * コネクションを持っているか否か - */ - public get hasConnection() { - return this._connection != null; - } - - public get state(): string { - if (!this.hasConnection) return 'no-connection'; - return this._connection.state; - } - - /** - * コネクションを要求します - */ - public abstract getConnection(): T; - - /** - * 現在接続しているコネクションを取得します - */ - public borrow() { - return this._connection; - } - - /** - * コネクションを要求するためのユーザーIDを発行します - */ - public use() { - // タイマー解除 - if (this.disposeTimerId) { - clearTimeout(this.disposeTimerId); - this.disposeTimerId = null; - } - - // ユーザーID生成 - const userId = uuid(); - - this.users.push(userId); - - this._connection.user = `Managed (${ this.users.length })`; - - return userId; - } - - /** - * コネクションを利用し終わってもう必要ないことを通知します - * @param userId use で発行したユーザーID - */ - public dispose(userId) { - this.users = erase(userId, this.users); - - this._connection.user = `Managed (${ this.users.length })`; - - // 誰もコネクションの利用者がいなくなったら - if (this.users.length == 0) { - // また直ぐに再利用される可能性があるので、一定時間待ち、 - // 新たな利用者が現れなければコネクションを切断する - this.disposeTimerId = setTimeout(() => { - this.disposeTimerId = null; - - this.connection.close(); - this.connection = null; - }, 3000); - } - } -} diff --git a/src/client/app/common/scripts/streaming/stream.ts b/src/client/app/common/scripts/streaming/stream.ts deleted file mode 100644 index 4ab78f1190..0000000000 --- a/src/client/app/common/scripts/streaming/stream.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { EventEmitter } from 'eventemitter3'; -import * as uuid from 'uuid'; -import * as ReconnectingWebsocket from 'reconnecting-websocket'; -import { wsUrl } from '../../../config'; -import MiOS from '../../../mios'; - -/** - * Misskey stream connection - */ -export default class Connection extends EventEmitter { - public state: string; - private buffer: any[]; - public socket: ReconnectingWebsocket; - public name: string; - public connectedAt: Date; - public user: string = null; - public in: number = 0; - public out: number = 0; - public inout: Array<{ - type: 'in' | 'out', - at: Date, - data: string - }> = []; - public id: string; - public isSuspended = false; - private os: MiOS; - - constructor(os: MiOS, endpoint, params?) { - super(); - - //#region BIND - this.onOpen = this.onOpen.bind(this); - this.onClose = this.onClose.bind(this); - this.onMessage = this.onMessage.bind(this); - this.send = this.send.bind(this); - this.close = this.close.bind(this); - //#endregion - - this.id = uuid(); - this.os = os; - this.name = endpoint; - this.state = 'initializing'; - this.buffer = []; - - const query = params - ? Object.keys(params) - .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`) - .join('&') - : null; - - this.socket = new ReconnectingWebsocket(`${wsUrl}/${endpoint}${query ? `?${query}` : ''}`); - this.socket.addEventListener('open', this.onOpen); - this.socket.addEventListener('close', this.onClose); - this.socket.addEventListener('message', this.onMessage); - - // Register this connection for debugging - this.os.registerStreamConnection(this); - } - - /** - * Callback of when open connection - */ - private onOpen() { - this.state = 'connected'; - this.emit('_connected_'); - - this.connectedAt = new Date(); - - // バッファーを処理 - const _buffer = [].concat(this.buffer); // Shallow copy - this.buffer = []; // Clear buffer - _buffer.forEach(data => { - this.send(data); // Resend each buffered messages - - if (this.os.debug) { - this.out++; - this.inout.push({ type: 'out', at: new Date(), data }); - } - }); - } - - /** - * Callback of when close connection - */ - private onClose() { - this.state = 'reconnecting'; - this.emit('_disconnected_'); - } - - /** - * Callback of when received a message from connection - */ - private onMessage(message) { - if (this.isSuspended) return; - - if (this.os.debug) { - this.in++; - this.inout.push({ type: 'in', at: new Date(), data: message.data }); - } - - try { - const msg = JSON.parse(message.data); - if (msg.type) this.emit(msg.type, msg.body); - } catch (e) { - // noop - } - } - - /** - * Send a message to connection - */ - public send(data) { - if (this.isSuspended) return; - - // まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する - if (this.state != 'connected') { - this.buffer.push(data); - return; - } - - if (this.os.debug) { - this.out++; - this.inout.push({ type: 'out', at: new Date(), data }); - } - - this.socket.send(JSON.stringify(data)); - } - - /** - * Close this connection - */ - public close() { - this.os.unregisterStreamConnection(this); - this.socket.removeEventListener('open', this.onOpen); - this.socket.removeEventListener('message', this.onMessage); - } -} diff --git a/src/client/app/common/scripts/streaming/user-list.ts b/src/client/app/common/scripts/streaming/user-list.ts deleted file mode 100644 index 30a52b98dd..0000000000 --- a/src/client/app/common/scripts/streaming/user-list.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Stream from './stream'; -import MiOS from '../../mios'; - -export class UserListStream extends Stream { - constructor(os: MiOS, me, listId) { - super(os, 'user-list', { - i: me.token, - listId - }); - - (this as any).on('_connected_', () => { - this.send({ - i: me.token - }); - }); - } -} diff --git a/src/client/app/common/views/components/games/reversi/reversi.gameroom.vue b/src/client/app/common/views/components/games/reversi/reversi.gameroom.vue index 1539c88de0..0a18e0b19a 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.gameroom.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.gameroom.vue @@ -9,7 +9,6 @@ import Vue from 'vue'; import XGame from './reversi.game.vue'; import XRoom from './reversi.room.vue'; -import { ReversiGameStream } from '../../../../scripts/streaming/games/reversi/reversi-game'; export default Vue.extend({ components: { @@ -34,12 +33,13 @@ export default Vue.extend({ }, created() { this.g = this.game; - this.connection = new ReversiGameStream((this as any).os, this.$store.state.i, this.game); + this.connection = (this as any).os.stream.connectToChannel('gamesReversiGame', { + gameId: this.game.id + }); this.connection.on('started', this.onStarted); }, beforeDestroy() { - this.connection.off('started', this.onStarted); - this.connection.close(); + this.connection.dispose(); }, methods: { onStarted(game) { diff --git a/src/client/app/common/views/components/games/reversi/reversi.index.vue b/src/client/app/common/views/components/games/reversi/reversi.index.vue index 3725aa6cb4..a040162802 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.index.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.index.vue @@ -59,15 +59,13 @@ export default Vue.extend({ myGames: [], matching: null, invitations: [], - connection: null, - connectionId: null + connection: null }; }, mounted() { if (this.$store.getters.isSignedIn) { - this.connection = (this as any).os.streams.reversiStream.getConnection(); - this.connectionId = (this as any).os.streams.reversiStream.use(); + this.connection = (this as any).os.stream.useSharedConnection('gamesReversi'); this.connection.on('invited', this.onInvited); @@ -90,8 +88,7 @@ export default Vue.extend({ beforeDestroy() { if (this.connection) { - this.connection.off('invited', this.onInvited); - (this as any).os.streams.reversiStream.dispose(this.connectionId); + this.connection.dispose(); } }, diff --git a/src/client/app/common/views/components/games/reversi/reversi.vue b/src/client/app/common/views/components/games/reversi/reversi.vue index 6eb9511ce9..f2156bc41b 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.vue @@ -47,7 +47,6 @@ export default Vue.extend({ game: null, matching: null, connection: null, - connectionId: null, pingClock: null }; }, @@ -66,8 +65,7 @@ export default Vue.extend({ this.fetch(); if (this.$store.getters.isSignedIn) { - this.connection = (this as any).os.streams.reversiStream.getConnection(); - this.connectionId = (this as any).os.streams.reversiStream.use(); + this.connection = (this as any).os.stream.useSharedConnection('gamesReversi'); this.connection.on('matched', this.onMatched); @@ -84,9 +82,7 @@ export default Vue.extend({ beforeDestroy() { if (this.connection) { - this.connection.off('matched', this.onMatched); - (this as any).os.streams.reversiStream.dispose(this.connectionId); - + this.connection.dispose(); clearInterval(this.pingClock); } }, diff --git a/src/client/app/common/views/components/messaging-room.vue b/src/client/app/common/views/components/messaging-room.vue index 98661bc39d..c2cd79e116 100644 --- a/src/client/app/common/views/components/messaging-room.vue +++ b/src/client/app/common/views/components/messaging-room.vue @@ -30,7 +30,6 @@