diff --git a/locales/ja.yml b/locales/ja.yml index 9c3daf450f..112b4b194c 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -710,6 +710,7 @@ desktop/views/components/settings.vue: show-reply-target: "リプライ先を表示する" show-my-renotes: "自分の行ったRenoteをタイムラインに表示する" show-renoted-my-notes: "Renoteされた自分の投稿をタイムラインに表示する" + show-local-renotes: "Renoteされたローカルの投稿の投稿をタイムラインに表示する" show-maps: "マップの自動展開" show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。" @@ -1294,6 +1295,7 @@ mobile/views/pages/settings.vue: show-reply-target: "リプライ先を表示する" show-my-renotes: "自分の行ったRenoteを表示する" show-renoted-my-notes: "Renoteされた自分の投稿を表示する" + show-local-renotes: "Renoteされたローカルの投稿の投稿をタイムラインに表示する" post-style: "投稿の表示スタイル" post-style-standard: "標準" post-style-smart: "スマート" diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue index 02167ef85c..efb9db70fa 100644 --- a/src/client/app/desktop/views/components/notes.vue +++ b/src/client/app/desktop/views/components/notes.vue @@ -135,6 +135,12 @@ export default Vue.extend({ return; } } + + if (this.$store.state.settings.showLocalRenotes === false) { + if (isPureRenote && (note.renote.user.host == null)) { + return; + } + } //#endregion // 投稿が自分のものではないかつ、タブが非表示またはスクロール位置が最上部ではないならタイトルで通知 diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue index 84ea768a5c..fa21410d39 100644 --- a/src/client/app/desktop/views/components/settings.vue +++ b/src/client/app/desktop/views/components/settings.vue @@ -51,6 +51,7 @@ + %i18n:@show-maps-desc% @@ -353,6 +354,12 @@ export default Vue.extend({ value: v }); }, + onChangeShowLocalRenotes(v) { + this.$store.dispatch('settings/set', { + key: 'showLocalRenotes', + value: v + }); + }, onChangeShowMaps(v) { this.$store.dispatch('settings/set', { key: 'showMaps', diff --git a/src/client/app/desktop/views/components/timeline.core.vue b/src/client/app/desktop/views/components/timeline.core.vue index 15e188be67..25fd5d36ac 100644 --- a/src/client/app/desktop/views/components/timeline.core.vue +++ b/src/client/app/desktop/views/components/timeline.core.vue @@ -100,7 +100,8 @@ export default Vue.extend({ limit: fetchLimit + 1, untilDate: this.date ? this.date.getTime() : undefined, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -122,7 +123,8 @@ export default Vue.extend({ limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }); promise.then(notes => { diff --git a/src/client/app/desktop/views/components/user-list-timeline.vue b/src/client/app/desktop/views/components/user-list-timeline.vue index 03ac81a4a1..0a6f758763 100644 --- a/src/client/app/desktop/views/components/user-list-timeline.vue +++ b/src/client/app/desktop/views/components/user-list-timeline.vue @@ -47,7 +47,8 @@ export default Vue.extend({ listId: this.list.id, limit: fetchLimit + 1, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -67,7 +68,8 @@ export default Vue.extend({ limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }); promise.then(notes => { 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 d2f46bd8be..70048f99e3 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 @@ -70,7 +70,8 @@ export default Vue.extend({ limit: fetchLimit + 1, mediaOnly: this.mediaOnly, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -91,7 +92,8 @@ export default Vue.extend({ untilId: (this.$refs.timeline as any).tail().id, mediaOnly: this.mediaOnly, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }); promise.then(notes => { 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 3578e17287..f7fca5de92 100644 --- a/src/client/app/desktop/views/pages/deck/deck.notes.vue +++ b/src/client/app/desktop/views/pages/deck/deck.notes.vue @@ -140,6 +140,12 @@ export default Vue.extend({ return; } } + + if (this.$store.state.settings.showLocalRenotes === false) { + if (isPureRenote && (note.renote.user.host == null)) { + return; + } + } //#endregion if (this.isScrollTop()) { 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 d402ee6a8b..a9e4d489c3 100644 --- a/src/client/app/desktop/views/pages/deck/deck.tl.vue +++ b/src/client/app/desktop/views/pages/deck/deck.tl.vue @@ -98,7 +98,8 @@ export default Vue.extend({ limit: fetchLimit + 1, mediaOnly: this.mediaOnly, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -119,7 +120,8 @@ export default Vue.extend({ mediaOnly: this.mediaOnly, untilId: (this.$refs.timeline as any).tail().id, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }); promise.then(notes => { diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue index aed372d9a2..cce81d1b78 100644 --- a/src/client/app/mobile/views/components/notes.vue +++ b/src/client/app/mobile/views/components/notes.vue @@ -139,6 +139,12 @@ export default Vue.extend({ return; } } + + if (this.$store.state.settings.showLocalRenotes === false) { + if (isPureRenote && (note.renote.user.host == null)) { + return; + } + } //#endregion // 投稿が自分のものではないかつ、タブが非表示またはスクロール位置が最上部ではないならタイトルで通知 diff --git a/src/client/app/mobile/views/components/user-list-timeline.vue b/src/client/app/mobile/views/components/user-list-timeline.vue index 2c1564b7ed..9b3f11f5c2 100644 --- a/src/client/app/mobile/views/components/user-list-timeline.vue +++ b/src/client/app/mobile/views/components/user-list-timeline.vue @@ -59,7 +59,8 @@ export default Vue.extend({ listId: this.list.id, limit: fetchLimit + 1, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -82,7 +83,8 @@ export default Vue.extend({ limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }); promise.then(notes => { diff --git a/src/client/app/mobile/views/pages/home.timeline.vue b/src/client/app/mobile/views/pages/home.timeline.vue index 93d1364e09..416b006cd8 100644 --- a/src/client/app/mobile/views/pages/home.timeline.vue +++ b/src/client/app/mobile/views/pages/home.timeline.vue @@ -95,7 +95,8 @@ export default Vue.extend({ limit: fetchLimit + 1, untilDate: this.date ? this.date.getTime() : undefined, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -117,7 +118,8 @@ export default Vue.extend({ limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes }); promise.then(notes => { diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue index f74b734b6e..7636a0370a 100644 --- a/src/client/app/mobile/views/pages/settings.vue +++ b/src/client/app/mobile/views/pages/settings.vue @@ -21,6 +21,7 @@ %i18n:@show-reply-target% %i18n:@show-my-renotes% %i18n:@show-renoted-my-notes% + %i18n:@show-local-renotes%
@@ -221,6 +222,13 @@ export default Vue.extend({ }); }, + onChangeShowLocalRenotes(v) { + this.$store.dispatch('settings/set', { + key: 'showLocalRenotes', + value: v + }); + }, + checkForUpdate() { this.checkingForUpdate = true; checkForUpdate((this as any).os, true, true).then(newer => { diff --git a/src/client/app/store.ts b/src/client/app/store.ts index dfb24bb5f4..487b12df8b 100644 --- a/src/client/app/store.ts +++ b/src/client/app/store.ts @@ -16,6 +16,7 @@ const defaultSettings = { showReplyTarget: true, showMyRenotes: true, showRenotedMyNotes: true, + showLocalRenotes: true, loadRemoteMedia: true, disableViaMobile: false, memo: null, diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 036f84b54b..3fce4fb9af 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -59,6 +59,13 @@ export const meta = { } }), + includeLocalRenotes: $.bool.optional.note({ + default: true, + desc: { + ja: 'Renoteされたローカルの投稿を含めるかどうか' + } + }), + mediaOnly: $.bool.optional.note({ desc: { ja: 'true にすると、メディアが添付された投稿だけ取得します' @@ -180,6 +187,22 @@ export default async (params: any, user: ILocalUser) => { }); } + if (ps.includeLocalRenotes === false) { + query.$and.push({ + $or: [{ + '_renote.user.host': { $ne: null } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + mediaIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + if (ps.mediaOnly) { query.$and.push({ mediaIds: { $exists: true, $ne: [] } diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index c1b8644e4d..3e3fa8c4aa 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -60,6 +60,13 @@ export const meta = { } }), + includeLocalRenotes: $.bool.optional.note({ + default: true, + desc: { + ja: 'Renoteされたローカルの投稿を含めるかどうか' + } + }), + mediaOnly: $.bool.optional.note({ desc: { ja: 'true にすると、メディアが添付された投稿だけ取得します' @@ -170,6 +177,22 @@ export default async (params: any, user: ILocalUser) => { }); } + if (ps.includeLocalRenotes === false) { + query.$and.push({ + $or: [{ + '_renote.user.host': { $ne: null } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + mediaIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + if (ps.mediaOnly) { query.$and.push({ mediaIds: { $exists: true, $ne: [] } diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 5837a9a301..dcef548666 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -4,6 +4,7 @@ import Mute from '../../../../models/mute'; import { pack } from '../../../../models/note'; import UserList from '../../../../models/user-list'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -11,56 +12,84 @@ export const meta = { en: 'Get timeline of a user list.' }, - requireCredential: true + requireCredential: true, + + params: { + listId: $.type(ID).note({ + desc: { + ja: 'リストのID' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 10, + desc: { + ja: '最大数' + } + }), + + sinceId: $.type(ID).optional.note({ + desc: { + ja: '指定すると、この投稿を基点としてより新しい投稿を取得します' + } + }), + + untilId: $.type(ID).optional.note({ + desc: { + ja: '指定すると、この投稿を基点としてより古い投稿を取得します' + } + }), + + sinceDate: $.num.optional.note({ + desc: { + ja: '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }), + + untilDate: $.num.optional.note({ + desc: { + ja: '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。' + } + }), + + includeMyRenotes: $.bool.optional.note({ + default: true, + desc: { + ja: '自分の行ったRenoteを含めるかどうか' + } + }), + + includeRenotedMyNotes: $.bool.optional.note({ + default: true, + desc: { + ja: 'Renoteされた自分の投稿を含めるかどうか' + } + }), + + includeLocalRenotes: $.bool.optional.note({ + default: true, + desc: { + ja: 'Renoteされたローカルの投稿を含めるかどうか' + } + }), + + mediaOnly: $.bool.optional.note({ + desc: { + ja: 'true にすると、メディアが添付された投稿だけ取得します' + } + }), + } }; export default async (params: any, user: ILocalUser) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) throw 'invalid limit param'; - - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); - if (sinceIdErr) throw 'invalid sinceId param'; - - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); - if (untilIdErr) throw 'invalid untilId param'; - - // Get 'sinceDate' parameter - const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate); - if (sinceDateErr) throw 'invalid sinceDate param'; - - // Get 'untilDate' parameter - const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate); - if (untilDateErr) throw 'invalid untilDate param'; - - // Check if only one of sinceId, untilId, sinceDate, untilDate specified - if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) { - throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; - } - - // Get 'includeMyRenotes' parameter - const [includeMyRenotes = true, includeMyRenotesErr] = $.bool.optional.get(params.includeMyRenotes); - if (includeMyRenotesErr) throw 'invalid includeMyRenotes param'; - - // Get 'includeRenotedMyNotes' parameter - const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional.get(params.includeRenotedMyNotes); - if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; - - // Get 'mediaOnly' parameter - const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly); - if (mediaOnlyErr) throw 'invalid mediaOnly param'; - - // Get 'listId' parameter - const [listId, listIdErr] = $.type(ID).get(params.listId); - if (listIdErr) throw 'invalid listId param'; + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; const [list, mutedUserIds] = await Promise.all([ // リストを取得 // Fetch the list UserList.findOne({ - _id: listId, + _id: ps.listId, userId: user._id }), @@ -122,7 +151,7 @@ export default async (params: any, user: ILocalUser) => { // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws - if (includeMyRenotes === false) { + if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ userId: { $ne: user._id } @@ -138,7 +167,7 @@ export default async (params: any, user: ILocalUser) => { }); } - if (includeRenotedMyNotes === false) { + if (ps.includeRenotedMyNotes === false) { query.$and.push({ $or: [{ '_renote.userId': { $ne: user._id } @@ -154,29 +183,45 @@ export default async (params: any, user: ILocalUser) => { }); } - if (mediaOnly) { + if (ps.includeLocalRenotes === false) { + query.$and.push({ + $or: [{ + '_renote.user.host': { $ne: null } + }, { + renoteId: null + }, { + text: { $ne: null } + }, { + mediaIds: { $ne: [] } + }, { + poll: { $ne: null } + }] + }); + } + + if (ps.mediaOnly) { query.$and.push({ mediaIds: { $exists: true, $ne: [] } }); } - if (sinceId) { + if (ps.sinceId) { sort._id = 1; query._id = { - $gt: sinceId + $gt: ps.sinceId }; - } else if (untilId) { + } else if (ps.untilId) { query._id = { - $lt: untilId + $lt: ps.untilId }; - } else if (sinceDate) { + } else if (ps.sinceDate) { sort._id = 1; query.createdAt = { - $gt: new Date(sinceDate) + $gt: new Date(ps.sinceDate) }; - } else if (untilDate) { + } else if (ps.untilDate) { query.createdAt = { - $lt: new Date(untilDate) + $lt: new Date(ps.untilDate) }; } //#endregion @@ -184,7 +229,7 @@ export default async (params: any, user: ILocalUser) => { // Issue query const timeline = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort });