From df38c2f485937d72c495d3195804830b09aa3e09 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Wed, 4 Apr 2018 20:29:26 +0900 Subject: [PATCH 01/15] Extract http request from post delivery job --- src/post/distribute.ts | 96 +++++++++++++++++++++++++-- src/processor/http/deliver-post.ts | 100 ++++------------------------- 2 files changed, 104 insertions(+), 92 deletions(-) diff --git a/src/post/distribute.ts b/src/post/distribute.ts index 49c6eb22d..ad699d6b8 100644 --- a/src/post/distribute.ts +++ b/src/post/distribute.ts @@ -1,8 +1,11 @@ +import Channel from '../models/channel'; +import ChannelWatching from '../models/channel-watching'; +import Following from '../models/following'; import Mute from '../models/mute'; import Post, { pack } from '../models/post'; import Watching from '../models/post-watching'; -import User from '../models/user'; -import stream from '../publishers/stream'; +import User, { isLocalUser } from '../models/user'; +import stream, { publishChannelStream } from '../publishers/stream'; import notify from '../publishers/notify'; import pushSw from '../publishers/push-sw'; import queue from '../queue'; @@ -21,10 +24,6 @@ export default async (user, mentions, post) => { latestPost: post._id } }), - new Promise((resolve, reject) => queue.create('http', { - type: 'deliverPost', - id: post._id, - }).save(error => error ? reject(error) : resolve())), ] as Array<Promise<any>>; function addMention(promisedMentionee, reason) { @@ -50,6 +49,91 @@ export default async (user, mentions, post) => { })); } + // タイムラインへの投稿 + if (!post.channelId) { + promises.push( + // Publish event to myself's stream + promisedPostObj.then(postObj => { + stream(post.userId, 'post', postObj); + }), + + Promise.all([ + User.findOne({ _id: post.userId }), + + // Fetch all followers + Following.aggregate([{ + $lookup: { + from: 'users', + localField: 'followerId', + foreignField: '_id', + as: 'follower' + } + }, { + $match: { + followeeId: post.userId + } + }], { + _id: false + }) + ]).then(([user, followers]) => Promise.all(followers.map(following => { + if (isLocalUser(following.follower)) { + // Publish event to followers stream + return promisedPostObj.then(postObj => { + stream(following.followerId, 'post', postObj); + }); + } + + return new Promise((resolve, reject) => { + queue.create('http', { + type: 'deliverPost', + fromId: user._id, + toId: following.followerId, + postId: post._id + }).save(error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + }))) + ); + } + + // チャンネルへの投稿 + if (post.channelId) { + promises.push( + // Increment channel index(posts count) + Channel.update({ _id: post.channelId }, { + $inc: { + index: 1 + } + }), + + // Publish event to channel + promisedPostObj.then(postObj => { + publishChannelStream(post.channelId, 'post', postObj); + }), + + Promise.all([ + promisedPostObj, + + // Get channel watchers + ChannelWatching.find({ + channelId: post.channelId, + // 削除されたドキュメントは除く + deletedAt: { $exists: false } + }) + ]).then(([postObj, watches]) => { + // チャンネルの視聴者(のタイムライン)に配信 + watches.forEach(w => { + stream(w.userId, 'post', postObj); + }); + }) + ); + } + // If has in reply to post if (post.replyId) { promises.push( diff --git a/src/processor/http/deliver-post.ts b/src/processor/http/deliver-post.ts index c00ab912c..48ad4f95a 100644 --- a/src/processor/http/deliver-post.ts +++ b/src/processor/http/deliver-post.ts @@ -1,93 +1,21 @@ -import Channel from '../../models/channel'; -import Following from '../../models/following'; -import ChannelWatching from '../../models/channel-watching'; -import Post, { pack } from '../../models/post'; -import User, { isLocalUser } from '../../models/user'; -import stream, { publishChannelStream } from '../../publishers/stream'; +import Post from '../../models/post'; +import User, { IRemoteUser } from '../../models/user'; import context from '../../remote/activitypub/renderer/context'; import renderCreate from '../../remote/activitypub/renderer/create'; import renderNote from '../../remote/activitypub/renderer/note'; import request from '../../remote/request'; -export default ({ data }) => Post.findOne({ _id: data.id }).then(post => { - const promisedPostObj = pack(post); - const promises = []; +export default async ({ data }) => { + const promisedTo = User.findOne({ _id: data.toId }) as Promise<IRemoteUser>; + const [from, post] = await Promise.all([ + User.findOne({ _id: data.fromId }), + Post.findOne({ _id: data.postId }) + ]); + const note = await renderNote(from, post); + const to = await promisedTo; + const create = renderCreate(note); - // タイムラインへの投稿 - if (!post.channelId) { - promises.push( - // Publish event to myself's stream - promisedPostObj.then(postObj => { - stream(post.userId, 'post', postObj); - }), + create['@context'] = context; - Promise.all([ - User.findOne({ _id: post.userId }), - - // Fetch all followers - Following.aggregate([{ - $lookup: { - from: 'users', - localField: 'followerId', - foreignField: '_id', - as: 'follower' - } - }, { - $match: { - followeeId: post.userId - } - }], { - _id: false - }) - ]).then(([user, followers]) => Promise.all(followers.map(following => { - if (isLocalUser(following.follower)) { - // Publish event to followers stream - return promisedPostObj.then(postObj => { - stream(following.followerId, 'post', postObj); - }); - } - - return renderNote(user, post).then(note => { - const create = renderCreate(note); - create['@context'] = context; - return request(user, following.follower[0].account.inbox, create); - }); - }))) - ); - } - - // チャンネルへの投稿 - if (post.channelId) { - promises.push( - // Increment channel index(posts count) - Channel.update({ _id: post.channelId }, { - $inc: { - index: 1 - } - }), - - // Publish event to channel - promisedPostObj.then(postObj => { - publishChannelStream(post.channelId, 'post', postObj); - }), - - Promise.all([ - promisedPostObj, - - // Get channel watchers - ChannelWatching.find({ - channelId: post.channelId, - // 削除されたドキュメントは除く - deletedAt: { $exists: false } - }) - ]).then(([postObj, watches]) => { - // チャンネルの視聴者(のタイムライン)に配信 - watches.forEach(w => { - stream(w.userId, 'post', postObj); - }); - }) - ); - } - - return Promise.all(promises); -}); + return request(from, to.account.inbox, create); +}; From 1b6bae72c2aa19141133bbaf6939a4a5dded03b1 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Wed, 4 Apr 2018 21:56:04 +0900 Subject: [PATCH 02/15] Make HTTP request first in follow processor --- src/processor/http/follow.ts | 118 +++++++++++++++++------------------ 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/src/processor/http/follow.ts b/src/processor/http/follow.ts index 8bf890efb..ed36fa18d 100644 --- a/src/processor/http/follow.ts +++ b/src/processor/http/follow.ts @@ -1,4 +1,4 @@ -import User, { isLocalUser, pack as packUser } from '../../models/user'; +import User, { isLocalUser, isRemoteUser, pack as packUser } from '../../models/user'; import Following from '../../models/following'; import FollowingLog from '../../models/following-log'; import FollowedLog from '../../models/followed-log'; @@ -7,63 +7,63 @@ import notify from '../../publishers/notify'; import context from '../../remote/activitypub/renderer/context'; import render from '../../remote/activitypub/renderer/follow'; import request from '../../remote/request'; +import Logger from '../../utils/logger'; -export default ({ data }) => Following.findOne({ _id: data.following }).then(({ followerId, followeeId }) => { - const promisedFollower = User.findOne({ _id: followerId }); - const promisedFollowee = User.findOne({ _id: followeeId }); - - return Promise.all([ - // Increment following count - User.update(followerId, { - $inc: { - followingCount: 1 - } - }), - - promisedFollower.then(({ followingCount }) => FollowingLog.insert({ - createdAt: data.following.createdAt, - userId: followerId, - count: followingCount + 1 - })), - - // Increment followers count - User.update({ _id: followeeId }, { - $inc: { - followersCount: 1 - } - }), - - promisedFollowee.then(({ followersCount }) => FollowedLog.insert({ - createdAt: data.following.createdAt, - userId: followerId, - count: followersCount + 1 - })), - - // Notify - promisedFollowee.then(followee => followee.host === null ? - notify(followeeId, followerId, 'follow') : null), - - // Publish follow event - Promise.all([promisedFollower, promisedFollowee]).then(([follower, followee]) => { - let followerEvent; - let followeeEvent; - - if (isLocalUser(follower)) { - followerEvent = packUser(followee, follower) - .then(packed => event(follower._id, 'follow', packed)); - } - - if (isLocalUser(followee)) { - followeeEvent = packUser(follower, followee) - .then(packed => event(followee._id, 'followed', packed)); - } else if (isLocalUser(follower)) { - const rendered = render(follower, followee); - rendered['@context'] = context; - - followeeEvent = request(follower, followee.account.inbox, rendered); - } - - return Promise.all([followerEvent, followeeEvent]); - }) +export default async ({ data }) => { + const { followerId, followeeId } = await Following.findOne({ _id: data.following }); + const [follower, followee] = await Promise.all([ + User.findOne({ _id: followerId }), + User.findOne({ _id: followeeId }) ]); -}); + + if (isLocalUser(follower) && isRemoteUser(followee)) { + const rendered = render(follower, followee); + rendered['@context'] = context; + + await request(follower, followee.account.inbox, rendered); + } + + try { + await Promise.all([ + // Increment following count + User.update(followerId, { + $inc: { + followingCount: 1 + } + }), + + FollowingLog.insert({ + createdAt: data.following.createdAt, + userId: followerId, + count: follower.followingCount + 1 + }), + + // Increment followers count + User.update({ _id: followeeId }, { + $inc: { + followersCount: 1 + } + }), + + FollowedLog.insert({ + createdAt: data.following.createdAt, + userId: followerId, + count: followee.followersCount + 1 + }), + + // Publish follow event + isLocalUser(follower) && packUser(followee, follower) + .then(packed => event(follower._id, 'follow', packed)), + + isLocalUser(followee) && Promise.all([ + packUser(follower, followee) + .then(packed => event(followee._id, 'followed', packed)), + + // Notify + isLocalUser(followee) && notify(followeeId, followerId, 'follow') + ]) + ]); + } catch (error) { + Logger.error(error.toString()); + } +}; From 86b1345c17482e188be2138f0247a4b204f2abc1 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Wed, 4 Apr 2018 22:05:12 +0900 Subject: [PATCH 03/15] Make HTTP request first in unfollow job --- src/processor/http/unfollow.ts | 81 ++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/processor/http/unfollow.ts b/src/processor/http/unfollow.ts index d3d5f2246..fbfd7b342 100644 --- a/src/processor/http/unfollow.ts +++ b/src/processor/http/unfollow.ts @@ -1,56 +1,63 @@ import FollowedLog from '../../models/followed-log'; import Following from '../../models/following'; import FollowingLog from '../../models/following-log'; -import User, { isRemoteUser, pack as packUser } from '../../models/user'; +import User, { isLocalUser, isRemoteUser, pack as packUser } from '../../models/user'; import stream from '../../publishers/stream'; import renderFollow from '../../remote/activitypub/renderer/follow'; import renderUndo from '../../remote/activitypub/renderer/undo'; import context from '../../remote/activitypub/renderer/context'; import request from '../../remote/request'; +import Logger from '../../utils/logger'; export default async ({ data }) => { - // Delete following - const following = await Following.findOneAndDelete({ _id: data.id }); + const following = await Following.findOne({ _id: data.id }); if (following === null) { return; } - const promisedFollower = User.findOne({ _id: following.followerId }); - const promisedFollowee = User.findOne({ _id: following.followeeId }); + const [follower, followee] = await Promise.all([ + User.findOne({ _id: following.followerId }), + User.findOne({ _id: following.followeeId }) + ]); - await Promise.all([ - // Decrement following count - User.update({ _id: following.followerId }, { $inc: { followingCount: -1 } }), - promisedFollower.then(({ followingCount }) => FollowingLog.insert({ - createdAt: new Date(), - userId: following.followerId, - count: followingCount - 1 - })), + if (isLocalUser(follower) && isRemoteUser(followee)) { + const undo = renderUndo(renderFollow(follower, followee)); + undo['@context'] = context; - // Decrement followers count - User.update({ _id: following.followeeId }, { $inc: { followersCount: -1 } }), - promisedFollowee.then(({ followersCount }) => FollowedLog.insert({ - createdAt: new Date(), - userId: following.followeeId, - count: followersCount - 1 - })), + await request(follower, followee.account.inbox, undo); + } + + try { + await Promise.all([ + // Delete following + Following.findOneAndDelete({ _id: data.id }), + + // Decrement following count + User.update({ _id: follower._id }, { $inc: { followingCount: -1 } }), + FollowingLog.insert({ + createdAt: new Date(), + userId: follower._id, + count: follower.followingCount - 1 + }), + + // Decrement followers count + User.update({ _id: followee._id }, { $inc: { followersCount: -1 } }), + FollowedLog.insert({ + createdAt: new Date(), + userId: followee._id, + count: followee.followersCount - 1 + }) + ]); + + if (isLocalUser(follower)) { + return; + } + + const promisedPackedUser = packUser(followee, follower); // Publish follow event - Promise.all([promisedFollower, promisedFollowee]).then(async ([follower, followee]) => { - if (isRemoteUser(follower)) { - return; - } - - const promisedPackedUser = packUser(followee, follower); - - if (isRemoteUser(followee)) { - const undo = renderUndo(renderFollow(follower, followee)); - undo['@context'] = context; - - await request(follower, followee.account.inbox, undo); - } - - stream(follower._id, 'unfollow', promisedPackedUser); - }) - ]); + stream(follower._id, 'unfollow', promisedPackedUser); + } catch (error) { + Logger.error(error.toString()); + } }; From d7c13b975f55c85b695b72a3ded3d5de97227414 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Wed, 4 Apr 2018 22:45:55 +0900 Subject: [PATCH 04/15] Retry HTTP requests --- src/following/distribute.ts | 42 +++++++++++++++++++ src/index.ts | 2 +- src/post/distribute.ts | 4 +- src/processor/http/perform-activitypub.ts | 7 ---- src/processor/index.ts | 18 -------- src/queue.ts | 10 ----- src/queue/index.ts | 38 +++++++++++++++++ .../processors}/db/delete-post-dependents.ts | 12 +++--- .../processors}/db/index.ts | 0 .../processors}/http/deliver-post.ts | 12 +++--- .../processors}/http/follow.ts | 20 ++++----- .../processors}/http/index.ts | 0 .../processors/http/perform-activitypub.ts | 7 ++++ .../processors}/http/process-inbox.ts | 10 ++--- .../processors}/http/report-github-failure.ts | 4 +- .../processors}/http/unfollow.ts | 20 ++++----- src/remote/activitypub/act/follow.ts | 4 +- src/remote/activitypub/act/undo/unfollow.ts | 4 +- src/remote/activitypub/delete/post.ts | 4 +- src/remote/activitypub/resolve-person.ts | 4 +- src/server/activitypub/inbox.ts | 4 +- src/server/api/endpoints/following/create.ts | 4 +- src/server/api/endpoints/following/delete.ts | 4 +- src/server/api/service/github.ts | 4 +- 24 files changed, 145 insertions(+), 93 deletions(-) create mode 100644 src/following/distribute.ts delete mode 100644 src/processor/http/perform-activitypub.ts delete mode 100644 src/processor/index.ts delete mode 100644 src/queue.ts create mode 100644 src/queue/index.ts rename src/{processor => queue/processors}/db/delete-post-dependents.ts (59%) rename src/{processor => queue/processors}/db/index.ts (100%) rename src/{processor => queue/processors}/http/deliver-post.ts (55%) rename src/{processor => queue/processors}/http/follow.ts (74%) rename src/{processor => queue/processors}/http/index.ts (100%) create mode 100644 src/queue/processors/http/perform-activitypub.ts rename src/{processor => queue/processors}/http/process-inbox.ts (76%) rename src/{processor => queue/processors}/http/report-github-failure.ts (85%) rename src/{processor => queue/processors}/http/unfollow.ts (71%) diff --git a/src/following/distribute.ts b/src/following/distribute.ts new file mode 100644 index 000000000..10ff98881 --- /dev/null +++ b/src/following/distribute.ts @@ -0,0 +1,42 @@ +import User, { pack as packUser } from '../models/user'; +import FollowingLog from '../models/following-log'; +import FollowedLog from '../models/followed-log'; +import event from '../publishers/stream'; +import notify from '../publishers/notify'; + +export default async (follower, followee) => Promise.all([ + // Increment following count + User.update(follower._id, { + $inc: { + followingCount: 1 + } + }), + + FollowingLog.insert({ + createdAt: new Date(), + userId: followee._id, + count: follower.followingCount + 1 + }), + + // Increment followers count + User.update({ _id: followee._id }, { + $inc: { + followersCount: 1 + } + }), + + FollowedLog.insert({ + createdAt: new Date(), + userId: follower._id, + count: followee.followersCount + 1 + }), + + followee.host === null && Promise.all([ + // Notify + notify(followee.id, follower.id, 'follow'), + + // Publish follow event + packUser(follower, followee) + .then(packed => event(followee._id, 'followed', packed)) + ]) +]); diff --git a/src/index.ts b/src/index.ts index 29c4f3431..21fb2f553 100644 --- a/src/index.ts +++ b/src/index.ts @@ -99,7 +99,7 @@ async function workerMain(opt) { if (!opt['only-server']) { // start processor - require('./processor').default(); + require('./queue').process(); } // Send a 'ready' message to parent process diff --git a/src/post/distribute.ts b/src/post/distribute.ts index ad699d6b8..f748a620c 100644 --- a/src/post/distribute.ts +++ b/src/post/distribute.ts @@ -8,7 +8,7 @@ import User, { isLocalUser } from '../models/user'; import stream, { publishChannelStream } from '../publishers/stream'; import notify from '../publishers/notify'; import pushSw from '../publishers/push-sw'; -import queue from '../queue'; +import { createHttp } from '../queue'; import watch from './watch'; export default async (user, mentions, post) => { @@ -84,7 +84,7 @@ export default async (user, mentions, post) => { } return new Promise((resolve, reject) => { - queue.create('http', { + createHttp({ type: 'deliverPost', fromId: user._id, toId: following.followerId, diff --git a/src/processor/http/perform-activitypub.ts b/src/processor/http/perform-activitypub.ts deleted file mode 100644 index 963e532fe..000000000 --- a/src/processor/http/perform-activitypub.ts +++ /dev/null @@ -1,7 +0,0 @@ -import User from '../../models/user'; -import act from '../../remote/activitypub/act'; -import Resolver from '../../remote/activitypub/resolver'; - -export default ({ data }) => User.findOne({ _id: data.actor }) - .then(actor => act(new Resolver(), actor, data.outbox)) - .then(Promise.all); diff --git a/src/processor/index.ts b/src/processor/index.ts deleted file mode 100644 index 172048dda..000000000 --- a/src/processor/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import queue from '../queue'; -import db from './db'; -import http from './http'; - -export default () => { - queue.process('db', db); - - /* - 256 is the default concurrency limit of Mozilla Firefox and Google - Chromium. - - a8af215e691f3a2205a3758d2d96e9d328e100ff - chromium/src.git - Git at Google - https://chromium.googlesource.com/chromium/src.git/+/a8af215e691f3a2205a3758d2d96e9d328e100ff - Network.http.max-connections - MozillaZine Knowledge Base - http://kb.mozillazine.org/Network.http.max-connections - */ - queue.process('http', 256, http); -}; diff --git a/src/queue.ts b/src/queue.ts deleted file mode 100644 index 08ea13c2a..000000000 --- a/src/queue.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createQueue } from 'kue'; -import config from './config'; - -export default createQueue({ - redis: { - port: config.redis.port, - host: config.redis.host, - auth: config.redis.pass - } -}); diff --git a/src/queue/index.ts b/src/queue/index.ts new file mode 100644 index 000000000..f90754a56 --- /dev/null +++ b/src/queue/index.ts @@ -0,0 +1,38 @@ +import { createQueue } from 'kue'; +import config from '../config'; +import db from './processors/db'; +import http from './processors/http'; + +const queue = createQueue({ + redis: { + port: config.redis.port, + host: config.redis.host, + auth: config.redis.pass + } +}); + +export function createHttp(data) { + return queue + .create('http', data) + .attempts(16) + .backoff({ delay: 16384, type: 'exponential' }); +} + +export function createDb(data) { + return queue.create('db', data); +} + +export function process() { + queue.process('db', db); + + /* + 256 is the default concurrency limit of Mozilla Firefox and Google + Chromium. + + a8af215e691f3a2205a3758d2d96e9d328e100ff - chromium/src.git - Git at Google + https://chromium.googlesource.com/chromium/src.git/+/a8af215e691f3a2205a3758d2d96e9d328e100ff + Network.http.max-connections - MozillaZine Knowledge Base + http://kb.mozillazine.org/Network.http.max-connections + */ + queue.process('http', 256, http); +} diff --git a/src/processor/db/delete-post-dependents.ts b/src/queue/processors/db/delete-post-dependents.ts similarity index 59% rename from src/processor/db/delete-post-dependents.ts rename to src/queue/processors/db/delete-post-dependents.ts index 879c41ec9..6de21eb05 100644 --- a/src/processor/db/delete-post-dependents.ts +++ b/src/queue/processors/db/delete-post-dependents.ts @@ -1,9 +1,9 @@ -import Favorite from '../../models/favorite'; -import Notification from '../../models/notification'; -import PollVote from '../../models/poll-vote'; -import PostReaction from '../../models/post-reaction'; -import PostWatching from '../../models/post-watching'; -import Post from '../../models/post'; +import Favorite from '../../../models/favorite'; +import Notification from '../../../models/notification'; +import PollVote from '../../../models/poll-vote'; +import PostReaction from '../../../models/post-reaction'; +import PostWatching from '../../../models/post-watching'; +import Post from '../../../models/post'; export default async ({ data }) => Promise.all([ Favorite.remove({ postId: data._id }), diff --git a/src/processor/db/index.ts b/src/queue/processors/db/index.ts similarity index 100% rename from src/processor/db/index.ts rename to src/queue/processors/db/index.ts diff --git a/src/processor/http/deliver-post.ts b/src/queue/processors/http/deliver-post.ts similarity index 55% rename from src/processor/http/deliver-post.ts rename to src/queue/processors/http/deliver-post.ts index 48ad4f95a..e743fc5f6 100644 --- a/src/processor/http/deliver-post.ts +++ b/src/queue/processors/http/deliver-post.ts @@ -1,9 +1,9 @@ -import Post from '../../models/post'; -import User, { IRemoteUser } from '../../models/user'; -import context from '../../remote/activitypub/renderer/context'; -import renderCreate from '../../remote/activitypub/renderer/create'; -import renderNote from '../../remote/activitypub/renderer/note'; -import request from '../../remote/request'; +import Post from '../../../models/post'; +import User, { IRemoteUser } from '../../../models/user'; +import context from '../../../remote/activitypub/renderer/context'; +import renderCreate from '../../../remote/activitypub/renderer/create'; +import renderNote from '../../../remote/activitypub/renderer/note'; +import request from '../../../remote/request'; export default async ({ data }) => { const promisedTo = User.findOne({ _id: data.toId }) as Promise<IRemoteUser>; diff --git a/src/processor/http/follow.ts b/src/queue/processors/http/follow.ts similarity index 74% rename from src/processor/http/follow.ts rename to src/queue/processors/http/follow.ts index ed36fa18d..4cb72828e 100644 --- a/src/processor/http/follow.ts +++ b/src/queue/processors/http/follow.ts @@ -1,13 +1,13 @@ -import User, { isLocalUser, isRemoteUser, pack as packUser } from '../../models/user'; -import Following from '../../models/following'; -import FollowingLog from '../../models/following-log'; -import FollowedLog from '../../models/followed-log'; -import event from '../../publishers/stream'; -import notify from '../../publishers/notify'; -import context from '../../remote/activitypub/renderer/context'; -import render from '../../remote/activitypub/renderer/follow'; -import request from '../../remote/request'; -import Logger from '../../utils/logger'; +import User, { isLocalUser, isRemoteUser, pack as packUser } from '../../../models/user'; +import Following from '../../../models/following'; +import FollowingLog from '../../../models/following-log'; +import FollowedLog from '../../../models/followed-log'; +import event from '../../../publishers/stream'; +import notify from '../../../publishers/notify'; +import context from '../../../remote/activitypub/renderer/context'; +import render from '../../../remote/activitypub/renderer/follow'; +import request from '../../../remote/request'; +import Logger from '../../../utils/logger'; export default async ({ data }) => { const { followerId, followeeId } = await Following.findOne({ _id: data.following }); diff --git a/src/processor/http/index.ts b/src/queue/processors/http/index.ts similarity index 100% rename from src/processor/http/index.ts rename to src/queue/processors/http/index.ts diff --git a/src/queue/processors/http/perform-activitypub.ts b/src/queue/processors/http/perform-activitypub.ts new file mode 100644 index 000000000..7b84400d5 --- /dev/null +++ b/src/queue/processors/http/perform-activitypub.ts @@ -0,0 +1,7 @@ +import User from '../../../models/user'; +import act from '../../../remote/activitypub/act'; +import Resolver from '../../../remote/activitypub/resolver'; + +export default ({ data }) => User.findOne({ _id: data.actor }) + .then(actor => act(new Resolver(), actor, data.outbox)) + .then(Promise.all); diff --git a/src/processor/http/process-inbox.ts b/src/queue/processors/http/process-inbox.ts similarity index 76% rename from src/processor/http/process-inbox.ts rename to src/queue/processors/http/process-inbox.ts index f102f8d6b..de1dbd2f9 100644 --- a/src/processor/http/process-inbox.ts +++ b/src/queue/processors/http/process-inbox.ts @@ -1,9 +1,9 @@ import { verifySignature } from 'http-signature'; -import parseAcct from '../../acct/parse'; -import User, { IRemoteUser } from '../../models/user'; -import act from '../../remote/activitypub/act'; -import resolvePerson from '../../remote/activitypub/resolve-person'; -import Resolver from '../../remote/activitypub/resolver'; +import parseAcct from '../../../acct/parse'; +import User, { IRemoteUser } from '../../../models/user'; +import act from '../../../remote/activitypub/act'; +import resolvePerson from '../../../remote/activitypub/resolve-person'; +import Resolver from '../../../remote/activitypub/resolver'; export default async ({ data }): Promise<void> => { const keyIdLower = data.signature.keyId.toLowerCase(); diff --git a/src/processor/http/report-github-failure.ts b/src/queue/processors/http/report-github-failure.ts similarity index 85% rename from src/processor/http/report-github-failure.ts rename to src/queue/processors/http/report-github-failure.ts index 4f6f5ccee..21683ba3c 100644 --- a/src/processor/http/report-github-failure.ts +++ b/src/queue/processors/http/report-github-failure.ts @@ -1,6 +1,6 @@ import * as request from 'request-promise-native'; -import User from '../../models/user'; -const createPost = require('../../server/api/endpoints/posts/create'); +import User from '../../../models/user'; +const createPost = require('../../../server/api/endpoints/posts/create'); export default async ({ data }) => { const asyncBot = User.findOne({ _id: data.userId }); diff --git a/src/processor/http/unfollow.ts b/src/queue/processors/http/unfollow.ts similarity index 71% rename from src/processor/http/unfollow.ts rename to src/queue/processors/http/unfollow.ts index fbfd7b342..801a3612a 100644 --- a/src/processor/http/unfollow.ts +++ b/src/queue/processors/http/unfollow.ts @@ -1,13 +1,13 @@ -import FollowedLog from '../../models/followed-log'; -import Following from '../../models/following'; -import FollowingLog from '../../models/following-log'; -import User, { isLocalUser, isRemoteUser, pack as packUser } from '../../models/user'; -import stream from '../../publishers/stream'; -import renderFollow from '../../remote/activitypub/renderer/follow'; -import renderUndo from '../../remote/activitypub/renderer/undo'; -import context from '../../remote/activitypub/renderer/context'; -import request from '../../remote/request'; -import Logger from '../../utils/logger'; +import FollowedLog from '../../../models/followed-log'; +import Following from '../../../models/following'; +import FollowingLog from '../../../models/following-log'; +import User, { isLocalUser, isRemoteUser, pack as packUser } from '../../../models/user'; +import stream from '../../../publishers/stream'; +import renderFollow from '../../../remote/activitypub/renderer/follow'; +import renderUndo from '../../../remote/activitypub/renderer/undo'; +import context from '../../../remote/activitypub/renderer/context'; +import request from '../../../remote/request'; +import Logger from '../../../utils/logger'; export default async ({ data }) => { const following = await Following.findOne({ _id: data.id }); diff --git a/src/remote/activitypub/act/follow.ts b/src/remote/activitypub/act/follow.ts index 23fa41df8..222a257e1 100644 --- a/src/remote/activitypub/act/follow.ts +++ b/src/remote/activitypub/act/follow.ts @@ -3,7 +3,7 @@ import parseAcct from '../../../acct/parse'; import Following, { IFollowing } from '../../../models/following'; import User from '../../../models/user'; import config from '../../../config'; -import queue from '../../../queue'; +import { createHttp } from '../../../queue'; import context from '../renderer/context'; import renderAccept from '../renderer/accept'; import request from '../../request'; @@ -44,7 +44,7 @@ export default async (resolver: Resolver, actor, activity, distribute) => { followerId: actor._id, followeeId: followee._id }).then(following => new Promise((resolve, reject) => { - queue.create('http', { + createHttp({ type: 'follow', following: following._id }).save(error => { diff --git a/src/remote/activitypub/act/undo/unfollow.ts b/src/remote/activitypub/act/undo/unfollow.ts index c17e06e8a..4f15d9a3e 100644 --- a/src/remote/activitypub/act/undo/unfollow.ts +++ b/src/remote/activitypub/act/undo/unfollow.ts @@ -1,7 +1,7 @@ -import queue from '../../../../queue'; +import { createHttp } from '../../../../queue'; export default ({ $id }) => new Promise((resolve, reject) => { - queue.create('http', { type: 'unfollow', id: $id }).save(error => { + createHttp({ type: 'unfollow', id: $id }).save(error => { if (error) { reject(error); } else { diff --git a/src/remote/activitypub/delete/post.ts b/src/remote/activitypub/delete/post.ts index f6c816647..59ae8c2b9 100644 --- a/src/remote/activitypub/delete/post.ts +++ b/src/remote/activitypub/delete/post.ts @@ -1,10 +1,10 @@ import Post from '../../../models/post'; -import queue from '../../../queue'; +import { createDb } from '../../../queue'; export default async ({ $id }) => { const promisedDeletion = Post.findOneAndDelete({ _id: $id }); - await new Promise((resolve, reject) => queue.create('db', { + await new Promise((resolve, reject) => createDb({ type: 'deletePostDependents', id: $id }).delay(65536).save(error => error ? reject(error) : resolve())); diff --git a/src/remote/activitypub/resolve-person.ts b/src/remote/activitypub/resolve-person.ts index 59be65908..2cf3ad32d 100644 --- a/src/remote/activitypub/resolve-person.ts +++ b/src/remote/activitypub/resolve-person.ts @@ -1,7 +1,7 @@ import { JSDOM } from 'jsdom'; import { toUnicode } from 'punycode'; import User, { validateUsername, isValidName, isValidDescription } from '../../models/user'; -import queue from '../../queue'; +import { createHttp } from '../../queue'; import webFinger from '../webfinger'; import create from './create'; import Resolver from './resolver'; @@ -69,7 +69,7 @@ export default async (value, verifier?: string) => { }, }); - queue.create('http', { + createHttp({ type: 'performActivityPub', actor: user._id, outbox diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index 5de843385..0907823b2 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -1,7 +1,7 @@ import * as bodyParser from 'body-parser'; import * as express from 'express'; import { parseRequest } from 'http-signature'; -import queue from '../../queue'; +import { createHttp } from '../../queue'; const app = express(); @@ -22,7 +22,7 @@ app.post('/@:user/inbox', bodyParser.json({ return res.sendStatus(401); } - queue.create('http', { + createHttp({ type: 'processInbox', inbox: req.body, signature, diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts index e56859521..9ccbe2017 100644 --- a/src/server/api/endpoints/following/create.ts +++ b/src/server/api/endpoints/following/create.ts @@ -4,7 +4,7 @@ import $ from 'cafy'; import User from '../../../../models/user'; import Following from '../../../../models/following'; -import queue from '../../../../queue'; +import { createHttp } from '../../../../queue'; /** * Follow a user @@ -56,7 +56,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { followeeId: followee._id }); - queue.create('http', { type: 'follow', following: _id }).save(); + createHttp({ type: 'follow', following: _id }).save(); // Send response res(); diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts index bf21bf0cb..0684b8750 100644 --- a/src/server/api/endpoints/following/delete.ts +++ b/src/server/api/endpoints/following/delete.ts @@ -4,7 +4,7 @@ import $ from 'cafy'; import User from '../../../../models/user'; import Following from '../../../../models/following'; -import queue from '../../../../queue'; +import { createHttp } from '../../../../queue'; /** * Unfollow a user @@ -49,7 +49,7 @@ module.exports = (params, user) => new Promise(async (res, rej) => { return rej('already not following'); } - queue.create('http', { + createHttp({ type: 'unfollow', id: exist._id }).save(error => { diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index 4fd59c2a9..5fc4a92f5 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -3,7 +3,7 @@ import * as express from 'express'; //const crypto = require('crypto'); import User from '../../../models/user'; import config from '../../../config'; -import queue from '../../../queue'; +import { createHttp } from '../../../queue'; module.exports = async (app: express.Application) => { if (config.github_bot == null) return; @@ -42,7 +42,7 @@ module.exports = async (app: express.Application) => { const commit = event.commit; const parent = commit.parents[0]; - queue.create('http', { + createHttp({ type: 'gitHubFailureReport', userId: bot._id, parentUrl: parent.url, From e330ac1934516807757afe2d2760fa21b27006e6 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Thu, 5 Apr 2018 01:04:44 +0900 Subject: [PATCH 05/15] Let unhandled rejection handler handle rejections in jobs --- .../processors/db/delete-post-dependents.ts | 4 +- src/queue/processors/db/index.ts | 2 +- src/queue/processors/http/deliver-post.ts | 28 ++++--- src/queue/processors/http/follow.ts | 79 +++++++++---------- src/queue/processors/http/index.ts | 2 +- .../processors/http/perform-activitypub.ts | 5 +- src/queue/processors/http/process-inbox.ts | 55 +++++++------ .../processors/http/report-github-failure.ts | 39 +++++---- src/queue/processors/http/unfollow.ts | 31 +++++--- 9 files changed, 134 insertions(+), 111 deletions(-) diff --git a/src/queue/processors/db/delete-post-dependents.ts b/src/queue/processors/db/delete-post-dependents.ts index 6de21eb05..fb6617e95 100644 --- a/src/queue/processors/db/delete-post-dependents.ts +++ b/src/queue/processors/db/delete-post-dependents.ts @@ -5,7 +5,7 @@ import PostReaction from '../../../models/post-reaction'; import PostWatching from '../../../models/post-watching'; import Post from '../../../models/post'; -export default async ({ data }) => Promise.all([ +export default ({ data }, done) => Promise.all([ Favorite.remove({ postId: data._id }), Notification.remove({ postId: data._id }), PollVote.remove({ postId: data._id }), @@ -19,4 +19,4 @@ export default async ({ data }) => Promise.all([ }), Post.remove({ repostId: data._id }) ])) -]); +]).then(() => done(), done); diff --git a/src/queue/processors/db/index.ts b/src/queue/processors/db/index.ts index 75838c099..468ec442a 100644 --- a/src/queue/processors/db/index.ts +++ b/src/queue/processors/db/index.ts @@ -4,4 +4,4 @@ const handlers = { deletePostDependents }; -export default (job, done) => handlers[job.data.type](job).then(() => done(), done); +export default (job, done) => handlers[job.data.type](job, done); diff --git a/src/queue/processors/http/deliver-post.ts b/src/queue/processors/http/deliver-post.ts index e743fc5f6..8107c8bf7 100644 --- a/src/queue/processors/http/deliver-post.ts +++ b/src/queue/processors/http/deliver-post.ts @@ -5,17 +5,23 @@ import renderCreate from '../../../remote/activitypub/renderer/create'; import renderNote from '../../../remote/activitypub/renderer/note'; import request from '../../../remote/request'; -export default async ({ data }) => { - const promisedTo = User.findOne({ _id: data.toId }) as Promise<IRemoteUser>; - const [from, post] = await Promise.all([ - User.findOne({ _id: data.fromId }), - Post.findOne({ _id: data.postId }) - ]); - const note = await renderNote(from, post); - const to = await promisedTo; - const create = renderCreate(note); +export default async ({ data }, done) => { + try { + const promisedTo = User.findOne({ _id: data.toId }) as Promise<IRemoteUser>; + const [from, post] = await Promise.all([ + User.findOne({ _id: data.fromId }), + Post.findOne({ _id: data.postId }) + ]); + const note = await renderNote(from, post); + const to = await promisedTo; + const create = renderCreate(note); - create['@context'] = context; + create['@context'] = context; - return request(from, to.account.inbox, create); + await request(from, to.account.inbox, create); + } catch (error) { + done(error); + } + + done(); }; diff --git a/src/queue/processors/http/follow.ts b/src/queue/processors/http/follow.ts index 4cb72828e..ba1cc3118 100644 --- a/src/queue/processors/http/follow.ts +++ b/src/queue/processors/http/follow.ts @@ -7,10 +7,8 @@ import notify from '../../../publishers/notify'; import context from '../../../remote/activitypub/renderer/context'; import render from '../../../remote/activitypub/renderer/follow'; import request from '../../../remote/request'; -import Logger from '../../../utils/logger'; -export default async ({ data }) => { - const { followerId, followeeId } = await Following.findOne({ _id: data.following }); +export default ({ data }, done) => Following.findOne({ _id: data.following }).then(async ({ followerId, followeeId }) => { const [follower, followee] = await Promise.all([ User.findOne({ _id: followerId }), User.findOne({ _id: followeeId }) @@ -23,47 +21,46 @@ export default async ({ data }) => { await request(follower, followee.account.inbox, rendered); } - try { - await Promise.all([ - // Increment following count - User.update(followerId, { - $inc: { - followingCount: 1 - } - }), + return [follower, followee]; +}).then(([follower, followee]) => Promise.all([ + // Increment following count + User.update(follower._id, { + $inc: { + followingCount: 1 + } + }), - FollowingLog.insert({ - createdAt: data.following.createdAt, - userId: followerId, - count: follower.followingCount + 1 - }), + FollowingLog.insert({ + createdAt: data.following.createdAt, + userId: follower._id, + count: follower.followingCount + 1 + }), - // Increment followers count - User.update({ _id: followeeId }, { - $inc: { - followersCount: 1 - } - }), + // Increment followers count + User.update({ _id: followee._id }, { + $inc: { + followersCount: 1 + } + }), - FollowedLog.insert({ - createdAt: data.following.createdAt, - userId: followerId, - count: followee.followersCount + 1 - }), + FollowedLog.insert({ + createdAt: data.following.createdAt, + userId: follower._id, + count: followee.followersCount + 1 + }), - // Publish follow event - isLocalUser(follower) && packUser(followee, follower) - .then(packed => event(follower._id, 'follow', packed)), + // Publish follow event + isLocalUser(follower) && packUser(followee, follower) + .then(packed => event(follower._id, 'follow', packed)), - isLocalUser(followee) && Promise.all([ - packUser(follower, followee) - .then(packed => event(followee._id, 'followed', packed)), + isLocalUser(followee) && Promise.all([ + packUser(follower, followee) + .then(packed => event(followee._id, 'followed', packed)), - // Notify - isLocalUser(followee) && notify(followeeId, followerId, 'follow') - ]) - ]); - } catch (error) { - Logger.error(error.toString()); - } -}; + // Notify + isLocalUser(followee) && notify(followee._id, follower._id, 'follow') + ]) +]).then(() => done(), error => { + done(); + throw error; +}), done); diff --git a/src/queue/processors/http/index.ts b/src/queue/processors/http/index.ts index 8f9aa717c..0ea79305c 100644 --- a/src/queue/processors/http/index.ts +++ b/src/queue/processors/http/index.ts @@ -14,4 +14,4 @@ const handlers = { unfollow }; -export default (job, done) => handlers[job.data.type](job).then(() => done(), done); +export default (job, done) => handlers[job.data.type](job, done); diff --git a/src/queue/processors/http/perform-activitypub.ts b/src/queue/processors/http/perform-activitypub.ts index 7b84400d5..ae70c0f0b 100644 --- a/src/queue/processors/http/perform-activitypub.ts +++ b/src/queue/processors/http/perform-activitypub.ts @@ -2,6 +2,7 @@ import User from '../../../models/user'; import act from '../../../remote/activitypub/act'; import Resolver from '../../../remote/activitypub/resolver'; -export default ({ data }) => User.findOne({ _id: data.actor }) +export default ({ data }, done) => User.findOne({ _id: data.actor }) .then(actor => act(new Resolver(), actor, data.outbox)) - .then(Promise.all); + .then(Promise.all) + .then(() => done(), done); diff --git a/src/queue/processors/http/process-inbox.ts b/src/queue/processors/http/process-inbox.ts index de1dbd2f9..88fbb9737 100644 --- a/src/queue/processors/http/process-inbox.ts +++ b/src/queue/processors/http/process-inbox.ts @@ -5,35 +5,40 @@ import act from '../../../remote/activitypub/act'; import resolvePerson from '../../../remote/activitypub/resolve-person'; import Resolver from '../../../remote/activitypub/resolver'; -export default async ({ data }): Promise<void> => { - const keyIdLower = data.signature.keyId.toLowerCase(); - let user; +export default async ({ data }, done) => { + try { + const keyIdLower = data.signature.keyId.toLowerCase(); + let user; - if (keyIdLower.startsWith('acct:')) { - const { username, host } = parseAcct(keyIdLower.slice('acct:'.length)); - if (host === null) { - throw 'request was made by local user'; + if (keyIdLower.startsWith('acct:')) { + const { username, host } = parseAcct(keyIdLower.slice('acct:'.length)); + if (host === null) { + done(); + return; + } + + user = await User.findOne({ usernameLower: username, hostLower: host }) as IRemoteUser; + } else { + user = await User.findOne({ + host: { $ne: null }, + 'account.publicKey.id': data.signature.keyId + }) as IRemoteUser; + + if (user === null) { + user = await resolvePerson(data.signature.keyId); + } } - user = await User.findOne({ usernameLower: username, hostLower: host }) as IRemoteUser; - } else { - user = await User.findOne({ - host: { $ne: null }, - 'account.publicKey.id': data.signature.keyId - }) as IRemoteUser; - - if (user === null) { - user = await resolvePerson(data.signature.keyId); + if (user === null || !verifySignature(data.signature, user.account.publicKey.publicKeyPem)) { + done(); + return; } + + await Promise.all(await act(new Resolver(), user, data.inbox, true)); + } catch (error) { + done(error); + return; } - if (user === null) { - throw 'failed to resolve user'; - } - - if (!verifySignature(data.signature, user.account.publicKey.publicKeyPem)) { - throw 'signature verification failed'; - } - - await Promise.all(await act(new Resolver(), user, data.inbox, true)); + done(); }; diff --git a/src/queue/processors/http/report-github-failure.ts b/src/queue/processors/http/report-github-failure.ts index 21683ba3c..af9659bda 100644 --- a/src/queue/processors/http/report-github-failure.ts +++ b/src/queue/processors/http/report-github-failure.ts @@ -2,23 +2,30 @@ import * as request from 'request-promise-native'; import User from '../../../models/user'; const createPost = require('../../../server/api/endpoints/posts/create'); -export default async ({ data }) => { - const asyncBot = User.findOne({ _id: data.userId }); +export default async ({ data }, done) => { + try { + const asyncBot = User.findOne({ _id: data.userId }); - // Fetch parent status - const parentStatuses = await request({ - url: `${data.parentUrl}/statuses`, - headers: { - 'User-Agent': 'misskey' - }, - json: true - }); + // Fetch parent status + const parentStatuses = await request({ + url: `${data.parentUrl}/statuses`, + headers: { + 'User-Agent': 'misskey' + }, + json: true + }); - const parentState = parentStatuses[0].state; - const stillFailed = parentState == 'failure' || parentState == 'error'; - const text = stillFailed ? - `**⚠️BUILD STILL FAILED⚠️**: ?[${data.message}](${data.htmlUrl})` : - `**🚨BUILD FAILED🚨**: →→→?[${data.message}](${data.htmlUrl})←←←`; + const parentState = parentStatuses[0].state; + const stillFailed = parentState == 'failure' || parentState == 'error'; + const text = stillFailed ? + `**⚠️BUILD STILL FAILED⚠️**: ?[${data.message}](${data.htmlUrl})` : + `**🚨BUILD FAILED🚨**: →→→?[${data.message}](${data.htmlUrl})←←←`; - createPost({ text }, await asyncBot); + createPost({ text }, await asyncBot); + } catch (error) { + done(error); + return; + } + + done(); }; diff --git a/src/queue/processors/http/unfollow.ts b/src/queue/processors/http/unfollow.ts index 801a3612a..dc50e946c 100644 --- a/src/queue/processors/http/unfollow.ts +++ b/src/queue/processors/http/unfollow.ts @@ -7,24 +7,31 @@ import renderFollow from '../../../remote/activitypub/renderer/follow'; import renderUndo from '../../../remote/activitypub/renderer/undo'; import context from '../../../remote/activitypub/renderer/context'; import request from '../../../remote/request'; -import Logger from '../../../utils/logger'; -export default async ({ data }) => { +export default async ({ data }, done) => { const following = await Following.findOne({ _id: data.id }); if (following === null) { + done(); return; } - const [follower, followee] = await Promise.all([ - User.findOne({ _id: following.followerId }), - User.findOne({ _id: following.followeeId }) - ]); + let follower, followee; - if (isLocalUser(follower) && isRemoteUser(followee)) { - const undo = renderUndo(renderFollow(follower, followee)); - undo['@context'] = context; + try { + [follower, followee] = await Promise.all([ + User.findOne({ _id: following.followerId }), + User.findOne({ _id: following.followeeId }) + ]); - await request(follower, followee.account.inbox, undo); + if (isLocalUser(follower) && isRemoteUser(followee)) { + const undo = renderUndo(renderFollow(follower, followee)); + undo['@context'] = context; + + await request(follower, followee.account.inbox, undo); + } + } catch (error) { + done(error); + return; } try { @@ -57,7 +64,7 @@ export default async ({ data }) => { // Publish follow event stream(follower._id, 'unfollow', promisedPackedUser); - } catch (error) { - Logger.error(error.toString()); + } finally { + done(); } }; From a5715ecc1b73d3e3a950c392fa3a466dee810248 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Thu, 5 Apr 2018 01:07:55 +0900 Subject: [PATCH 06/15] Do not declare two variables in a statement --- src/queue/processors/http/unfollow.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/queue/processors/http/unfollow.ts b/src/queue/processors/http/unfollow.ts index dc50e946c..d62eb280d 100644 --- a/src/queue/processors/http/unfollow.ts +++ b/src/queue/processors/http/unfollow.ts @@ -15,7 +15,8 @@ export default async ({ data }, done) => { return; } - let follower, followee; + let follower; + let followee; try { [follower, followee] = await Promise.all([ From 168b0730b46fd283b900b553dd2eede2aa4c7dac Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Thu, 5 Apr 2018 01:24:39 +0900 Subject: [PATCH 07/15] Implement Mention object --- src/post/create.ts | 14 ++------------ src/queue/processors/http/process-inbox.ts | 2 +- src/remote/activitypub/create.ts | 12 +++++++++++- src/remote/activitypub/resolve-person.ts | 5 ++--- src/remote/resolve-user.ts | 3 ++- src/server/api/endpoints/posts/create.ts | 10 +++++++--- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/post/create.ts b/src/post/create.ts index ecea37382..4ad1503e0 100644 --- a/src/post/create.ts +++ b/src/post/create.ts @@ -1,8 +1,6 @@ -import parseAcct from '../acct/parse'; import Post from '../models/post'; -import User from '../models/user'; -export default async (post, reply, repost, atMentions) => { +export default async (post, reply, repost, mentions) => { post.mentions = []; function addMention(mentionee) { @@ -36,15 +34,7 @@ export default async (post, reply, repost, atMentions) => { post._repost = null; } - await Promise.all(atMentions.map(async mention => { - // Fetch mentioned user - // SELECT _id - const { _id } = await User - .findOne(parseAcct(mention), { _id: true }); - - // Add mention - addMention(_id); - })); + await Promise.all(mentions.map(({ _id }) => addMention(_id))); return Post.insert(post); }; diff --git a/src/queue/processors/http/process-inbox.ts b/src/queue/processors/http/process-inbox.ts index 88fbb9737..7eeaa19f8 100644 --- a/src/queue/processors/http/process-inbox.ts +++ b/src/queue/processors/http/process-inbox.ts @@ -25,7 +25,7 @@ export default async ({ data }, done) => { }) as IRemoteUser; if (user === null) { - user = await resolvePerson(data.signature.keyId); + user = await resolvePerson(new Resolver(), data.signature.keyId); } } diff --git a/src/remote/activitypub/create.ts b/src/remote/activitypub/create.ts index 97c72860f..710d56fd3 100644 --- a/src/remote/activitypub/create.ts +++ b/src/remote/activitypub/create.ts @@ -7,6 +7,7 @@ import { IRemoteUser } from '../../models/user'; import uploadFromUrl from '../../drive/upload-from-url'; import createPost from '../../post/create'; import distributePost from '../../post/distribute'; +import resolvePerson from './resolve-person'; import Resolver from './resolver'; const createDOMPurify = require('dompurify'); @@ -53,6 +54,15 @@ class Creator { .map(({ object }) => object.$id); const { window } = new JSDOM(note.content); + const mentions = []; + + for (const { href, type } of note.tags) { + switch (type) { + case 'Mention': + mentions.push(resolvePerson(resolver, href)); + break; + } + } const inserted = await createPost({ channelId: undefined, @@ -69,7 +79,7 @@ class Creator { viaMobile: false, geo: undefined, uri: note.id - }, null, null, []); + }, null, null, await Promise.all(mentions)); const promises = []; diff --git a/src/remote/activitypub/resolve-person.ts b/src/remote/activitypub/resolve-person.ts index 2cf3ad32d..7ed01e322 100644 --- a/src/remote/activitypub/resolve-person.ts +++ b/src/remote/activitypub/resolve-person.ts @@ -4,14 +4,13 @@ import User, { validateUsername, isValidName, isValidDescription } from '../../m import { createHttp } from '../../queue'; import webFinger from '../webfinger'; import create from './create'; -import Resolver from './resolver'; async function isCollection(collection) { return ['Collection', 'OrderedCollection'].includes(collection.type); } -export default async (value, verifier?: string) => { - const { resolver, object } = await new Resolver().resolveOne(value); +export default async (parentResolver, value, verifier?: string) => { + const { resolver, object } = parentResolver.resolveOne(value); if ( object === null || diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index 48219e8cb..097ed6673 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -1,6 +1,7 @@ import { toUnicode, toASCII } from 'punycode'; import User from '../models/user'; import resolvePerson from './activitypub/resolve-person'; +import Resolver from './activitypub/resolver'; import webFinger from './webfinger'; export default async (username, host, option) => { @@ -19,7 +20,7 @@ export default async (username, host, option) => { throw new Error(); } - user = await resolvePerson(self.href, acctLower); + user = await resolvePerson(new Resolver(), self.href, acctLower); } return user; diff --git a/src/server/api/endpoints/posts/create.ts b/src/server/api/endpoints/posts/create.ts index 03af7ee76..47897626f 100644 --- a/src/server/api/endpoints/posts/create.ts +++ b/src/server/api/endpoints/posts/create.ts @@ -3,12 +3,13 @@ */ import $ from 'cafy'; import deepEqual = require('deep-equal'); +import parseAcct from '../../../../acct/parse'; import renderAcct from '../../../../acct/render'; import config from '../../../../config'; import html from '../../../../text/html'; import parse from '../../../../text/parse'; import Post, { IPost, isValidText, isValidCw } from '../../../../models/post'; -import { ILocalUser } from '../../../../models/user'; +import User, { ILocalUser } from '../../../../models/user'; import Channel, { IChannel } from '../../../../models/channel'; import DriveFile from '../../../../models/drive-file'; import create from '../../../../post/create'; @@ -267,7 +268,10 @@ module.exports = (params, user: ILocalUser, app) => new Promise(async (res, rej) .filter(t => t.type == 'mention') .map(renderAcct) // Drop dupulicates - .filter((v, i, s) => s.indexOf(v) == i); + .filter((v, i, s) => s.indexOf(v) == i) + // Fetch mentioned user + // SELECT _id + .map(mention => User.findOne(parseAcct(mention), { _id: true })); } // 投稿を作成 @@ -286,7 +290,7 @@ module.exports = (params, user: ILocalUser, app) => new Promise(async (res, rej) viaMobile: viaMobile, visibility, geo - }, reply, repost, atMentions); + }, reply, repost, await Promise.all(atMentions)); const postObj = await distribute(user, post.mentions, post); From f44a7389e2b67e524c953843fcb4e007349c76c2 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Thu, 5 Apr 2018 01:29:56 +0900 Subject: [PATCH 08/15] Implement Hashtag object --- src/remote/activitypub/create.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/remote/activitypub/create.ts b/src/remote/activitypub/create.ts index 710d56fd3..31e9dba86 100644 --- a/src/remote/activitypub/create.ts +++ b/src/remote/activitypub/create.ts @@ -55,9 +55,16 @@ class Creator { const { window } = new JSDOM(note.content); const mentions = []; + const tags = []; - for (const { href, type } of note.tags) { + for (const { href, name, type } of note.tags) { switch (type) { + case 'Hashtag': + if (name.startsWith('#')) { + tags.push(name.slice(1)); + } + break; + case 'Mention': mentions.push(resolvePerson(resolver, href)); break; @@ -78,7 +85,8 @@ class Creator { appId: null, viaMobile: false, geo: undefined, - uri: note.id + uri: note.id, + tags }, null, null, await Promise.all(mentions)); const promises = []; From ced6b9a76f71e95a376dc4cb37b6b5617a0321d9 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Thu, 5 Apr 2018 01:57:18 +0900 Subject: [PATCH 09/15] Handle inReplyTo property --- src/remote/activitypub/create.ts | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/remote/activitypub/create.ts b/src/remote/activitypub/create.ts index 31e9dba86..3bc0c66f3 100644 --- a/src/remote/activitypub/create.ts +++ b/src/remote/activitypub/create.ts @@ -48,11 +48,6 @@ class Creator { throw new Error(); } - const mediaIds = 'attachment' in note && - (await Promise.all(await this.create(resolver, note.attachment))) - .filter(media => media !== null && media.object.$ref === 'driveFiles.files') - .map(({ object }) => object.$id); - const { window } = new JSDOM(note.content); const mentions = []; const tags = []; @@ -71,13 +66,27 @@ class Creator { } } + const [mediaIds, reply] = await Promise.all([ + 'attachment' in note && this.create(resolver, note.attachment) + .then(collection => Promise.all(collection)) + .then(collection => collection + .filter(media => media !== null && media.object.$ref === 'driveFiles.files') + .map(({ object }: IResult) => object.$id)), + + 'inReplyTo' in note && this.create(resolver, note.inReplyTo) + .then(collection => Promise.all(collection.map(promise => promise.then(result => { + if (result !== null && result.object.$ref === 'posts') { + throw result.object; + } + }, () => { })))) + .then(() => null, ({ $id }) => Post.findOne({ _id: $id })) + ]); + const inserted = await createPost({ channelId: undefined, index: undefined, createdAt: new Date(note.published), mediaIds, - replyId: undefined, - repostId: undefined, poll: undefined, text: window.document.body.textContent, textHtml: note.content && createDOMPurify(window).sanitize(note.content), @@ -87,7 +96,7 @@ class Creator { geo: undefined, uri: note.id, tags - }, null, null, await Promise.all(mentions)); + }, reply, null, await Promise.all(mentions)); const promises = []; From 068c0b4cceaa9461cc87a6d44abebdfc429d1861 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Thu, 5 Apr 2018 21:16:14 +0900 Subject: [PATCH 10/15] Add a missing await expression --- src/remote/activitypub/resolve-person.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote/activitypub/resolve-person.ts b/src/remote/activitypub/resolve-person.ts index 7ed01e322..a7c0020dd 100644 --- a/src/remote/activitypub/resolve-person.ts +++ b/src/remote/activitypub/resolve-person.ts @@ -10,7 +10,7 @@ async function isCollection(collection) { } export default async (parentResolver, value, verifier?: string) => { - const { resolver, object } = parentResolver.resolveOne(value); + const { resolver, object } = await parentResolver.resolveOne(value); if ( object === null || From f0e8e6392b5ef99488ea0bbecbf9029e30ef0cfa Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Fri, 6 Apr 2018 01:36:34 +0900 Subject: [PATCH 11/15] Allow name property of user to be null --- src/client/app/ch/tags/channel.tag | 10 ++++++-- .../common/scripts/compose-notification.ts | 13 ++++++----- .../common/views/components/autocomplete.vue | 4 +++- .../app/common/views/components/messaging.vue | 6 +++-- .../views/components/welcome-timeline.vue | 4 +++- .../views/components/followers-window.vue | 11 +++++++-- .../views/components/following-window.vue | 11 +++++++-- .../views/components/friends-maker.vue | 4 +++- .../components/messaging-room-window.vue | 6 ++++- .../views/components/notifications.vue | 16 +++++++------ .../views/components/post-detail.sub.vue | 6 ++++- .../desktop/views/components/post-detail.vue | 11 +++++++-- .../desktop/views/components/post-preview.vue | 6 ++++- .../views/components/posts.post.sub.vue | 6 ++++- .../desktop/views/components/posts.post.vue | 6 ++++- .../views/components/settings.mute.vue | 6 +++-- .../views/components/settings.profile.vue | 4 ++-- .../desktop/views/components/ui.header.vue | 9 +++++++- .../views/components/users-list.item.vue | 6 ++++- .../desktop/views/pages/messaging-room.vue | 3 ++- .../pages/user/user.followers-you-know.vue | 6 +++-- .../desktop/views/pages/user/user.header.vue | 6 ++++- .../app/desktop/views/pages/user/user.vue | 3 ++- .../views/widgets/channel.channel.post.vue | 6 ++++- .../app/desktop/views/widgets/profile.vue | 9 +++++++- .../app/desktop/views/widgets/users.vue | 4 +++- .../views/components/notification-preview.vue | 23 +++++++++++++------ .../mobile/views/components/notification.vue | 15 ++++++++---- .../app/mobile/views/components/post-card.vue | 6 ++++- .../views/components/post-detail.sub.vue | 6 ++++- .../mobile/views/components/post-detail.vue | 11 +++++++-- .../mobile/views/components/post-preview.vue | 6 ++++- .../app/mobile/views/components/post.sub.vue | 6 ++++- .../app/mobile/views/components/post.vue | 11 +++++++-- .../app/mobile/views/components/ui.header.vue | 8 ++++++- .../app/mobile/views/components/ui.nav.vue | 8 ++++++- .../app/mobile/views/components/user-card.vue | 6 ++++- .../mobile/views/components/user-preview.vue | 6 ++++- .../app/mobile/views/pages/followers.vue | 10 ++++++-- .../app/mobile/views/pages/following.vue | 10 ++++++-- .../app/mobile/views/pages/messaging-room.vue | 10 ++++++-- .../mobile/views/pages/profile-setting.vue | 4 ++-- .../app/mobile/views/pages/settings.vue | 8 ++++++- src/client/app/mobile/views/pages/user.vue | 11 +++++---- .../pages/user/home.followers-you-know.vue | 8 ++++++- .../app/mobile/views/widgets/profile.vue | 10 +++++++- src/models/user.ts | 6 ++--- src/othello/ai/back.ts | 17 +++++++------- src/renderers/get-notification-summary.ts | 15 ++++++------ src/renderers/get-user-summary.ts | 3 ++- src/server/api/bot/core.ts | 7 +++--- src/server/api/bot/interfaces/line.ts | 5 ++-- 52 files changed, 311 insertions(+), 107 deletions(-) diff --git a/src/client/app/ch/tags/channel.tag b/src/client/app/ch/tags/channel.tag index 1ebc3cceb..4856728de 100644 --- a/src/client/app/ch/tags/channel.tag +++ b/src/client/app/ch/tags/channel.tag @@ -165,7 +165,7 @@ <mk-channel-post> <header> <a class="index" @click="reply">{ post.index }:</a> - <a class="name" href={ _URL_ + '/@' + acct }><b>{ post.user.name }</b></a> + <a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(post.user) }</b></a> <mk-time time={ post.createdAt }/> <mk-time time={ post.createdAt } mode="detail"/> <span>ID:<i>{ acct }</i></span> @@ -230,10 +230,12 @@ </style> <script lang="typescript"> import getAcct from '../../../../acct/render'; + import getUserName from '../../../../renderers/get-user-name'; this.post = this.opts.post; this.form = this.opts.form; this.acct = getAcct(this.post.user); + this.name = getUserName(this.post.user); this.reply = () => { this.form.update({ @@ -244,7 +246,7 @@ </mk-channel-post> <mk-channel-form> - <p v-if="reply"><b>>>{ reply.index }</b> ({ reply.user.name }): <a @click="clearReply">[x]</a></p> + <p v-if="reply"><b>>>{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p> <textarea ref="text" disabled={ wait } oninput={ update } onkeydown={ onkeydown } onpaste={ onpaste } placeholder="%i18n:ch.tags.mk-channel-form.textarea%"></textarea> <div class="actions"> <button @click="selectFile">%fa:upload%%i18n:ch.tags.mk-channel-form.upload%</button> @@ -286,6 +288,8 @@ </style> <script lang="typescript"> + import getUserName from '../../../../renderers/get-user-name'; + this.mixin('api'); this.channel = this.opts.channel; @@ -373,6 +377,8 @@ } }); }; + + this.getUserName = getUserName; </script> </mk-channel-form> diff --git a/src/client/app/common/scripts/compose-notification.ts b/src/client/app/common/scripts/compose-notification.ts index ebc15952f..e99d50296 100644 --- a/src/client/app/common/scripts/compose-notification.ts +++ b/src/client/app/common/scripts/compose-notification.ts @@ -1,5 +1,6 @@ import getPostSummary from '../../../../renderers/get-post-summary'; import getReactionEmoji from '../../../../renderers/get-reaction-emoji'; +import getUserName from '../../../../renderers/get-user-name'; type Notification = { title: string; @@ -21,35 +22,35 @@ export default function(type, data): Notification { case 'mention': return { - title: `${data.user.name}さんから:`, + title: `${getUserName(data.user)}さんから:`, body: getPostSummary(data), icon: data.user.avatarUrl + '?thumbnail&size=64' }; case 'reply': return { - title: `${data.user.name}さんから返信:`, + title: `${getUserName(data.user)}さんから返信:`, body: getPostSummary(data), icon: data.user.avatarUrl + '?thumbnail&size=64' }; case 'quote': return { - title: `${data.user.name}さんが引用:`, + title: `${getUserName(data.user)}さんが引用:`, body: getPostSummary(data), icon: data.user.avatarUrl + '?thumbnail&size=64' }; case 'reaction': return { - title: `${data.user.name}: ${getReactionEmoji(data.reaction)}:`, + title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`, body: getPostSummary(data.post), icon: data.user.avatarUrl + '?thumbnail&size=64' }; case 'unread_messaging_message': return { - title: `${data.user.name}さんからメッセージ:`, + title: `${getUserName(data.user)}さんからメッセージ:`, body: data.text, // TODO: getMessagingMessageSummary(data), icon: data.user.avatarUrl + '?thumbnail&size=64' }; @@ -57,7 +58,7 @@ export default function(type, data): Notification { case 'othello_invited': return { title: '対局への招待があります', - body: `${data.parent.name}さんから`, + body: `${getUserName(data.parent)}さんから`, icon: data.parent.avatarUrl + '?thumbnail&size=64' }; diff --git a/src/client/app/common/views/components/autocomplete.vue b/src/client/app/common/views/components/autocomplete.vue index 38eaf8650..8837fde6b 100644 --- a/src/client/app/common/views/components/autocomplete.vue +++ b/src/client/app/common/views/components/autocomplete.vue @@ -3,7 +3,7 @@ <ol class="users" ref="suggests" v-if="users.length > 0"> <li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1"> <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/> - <span class="name">{{ user.name }}</span> + <span class="name">{{ getUserName(user) }}</span> <span class="username">@{{ getAcct(user) }}</span> </li> </ol> @@ -22,6 +22,7 @@ import Vue from 'vue'; import * as emojilib from 'emojilib'; import contains from '../../../common/scripts/contains'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; const lib = Object.entries(emojilib.lib).filter((x: any) => { return x[1].category != 'flags'; @@ -107,6 +108,7 @@ export default Vue.extend({ }, methods: { getAcct, + getUserName, exec() { this.select = -1; if (this.$refs.suggests) { diff --git a/src/client/app/common/views/components/messaging.vue b/src/client/app/common/views/components/messaging.vue index 4ab3e46e8..9b1449daa 100644 --- a/src/client/app/common/views/components/messaging.vue +++ b/src/client/app/common/views/components/messaging.vue @@ -14,7 +14,7 @@ tabindex="-1" > <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/> - <span class="name">{{ user.name }}</span> + <span class="name">{{ getUserName(user) }}</span> <span class="username">@{{ getAcct(user) }}</span> </li> </ol> @@ -33,7 +33,7 @@ <div> <img class="avatar" :src="`${isMe(message) ? message.recipient.avatarUrl : message.user.avatarUrl}?thumbnail&size=64`" alt=""/> <header> - <span class="name">{{ isMe(message) ? message.recipient.name : message.user.name }}</span> + <span class="name">{{ getUserName(isMe(message) ? message.recipient : message.user) }}</span> <span class="username">@{{ getAcct(isMe(message) ? message.recipient : message.user) }}</span> <mk-time :time="message.createdAt"/> </header> @@ -52,6 +52,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: { @@ -94,6 +95,7 @@ export default Vue.extend({ }, methods: { getAcct, + getUserName, isMe(message) { return message.userId == (this as any).os.i.id; }, diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue index ef0c7beaa..bfb4b2bbe 100644 --- a/src/client/app/common/views/components/welcome-timeline.vue +++ b/src/client/app/common/views/components/welcome-timeline.vue @@ -6,7 +6,7 @@ </router-link> <div class="body"> <header> - <router-link class="name" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">{{ post.user.name }}</router-link> + <router-link class="name" :to="`/@${getAcct(post.user)}`" v-user-preview="post.user.id">{{ getUserName(post.user) }}</router-link> <span class="username">@{{ getAcct(post.user) }}</span> <div class="info"> <router-link class="created-at" :to="`/@${getAcct(post.user)}/${post.id}`"> @@ -25,6 +25,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -38,6 +39,7 @@ export default Vue.extend({ }, methods: { getAcct, + getUserName, fetch(cb?) { this.fetching = true; (this as any).api('posts', { diff --git a/src/client/app/desktop/views/components/followers-window.vue b/src/client/app/desktop/views/components/followers-window.vue index 623971fa3..d37ca745a 100644 --- a/src/client/app/desktop/views/components/followers-window.vue +++ b/src/client/app/desktop/views/components/followers-window.vue @@ -1,7 +1,7 @@ <template> <mk-window width="400px" height="550px" @closed="$destroy"> <span slot="header" :class="$style.header"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user.name }}のフォロワー + <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ name }}のフォロワー </span> <mk-followers :user="user"/> </mk-window> @@ -9,8 +9,15 @@ <script lang="ts"> import Vue from 'vue'; +import getUserName from '../../../../../renderers/get-user-name'; + export default Vue.extend({ - props: ['user'] + props: ['user'], + computed { + name() { + return getUserName(this.user); + } + } }); </script> diff --git a/src/client/app/desktop/views/components/following-window.vue b/src/client/app/desktop/views/components/following-window.vue index 612847b38..cbd8ec5f9 100644 --- a/src/client/app/desktop/views/components/following-window.vue +++ b/src/client/app/desktop/views/components/following-window.vue @@ -1,7 +1,7 @@ <template> <mk-window width="400px" height="550px" @closed="$destroy"> <span slot="header" :class="$style.header"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user.name }}のフォロー + <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ name }}のフォロー </span> <mk-following :user="user"/> </mk-window> @@ -9,8 +9,15 @@ <script lang="ts"> import Vue from 'vue'; +import getUserName from '../../../../../renderers/get-user-name'; + export default Vue.extend({ - props: ['user'] + props: ['user'], + computed: { + name() { + return getUserName(this.user); + } + } }); </script> diff --git a/src/client/app/desktop/views/components/friends-maker.vue b/src/client/app/desktop/views/components/friends-maker.vue index 351e9e1c5..acc4542d9 100644 --- a/src/client/app/desktop/views/components/friends-maker.vue +++ b/src/client/app/desktop/views/components/friends-maker.vue @@ -7,7 +7,7 @@ <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="user.id"/> </router-link> <div class="body"> - <router-link class="name" :to="`/@${getAcct(user)}`" v-user-preview="user.id">{{ user.name }}</router-link> + <router-link class="name" :to="`/@${getAcct(user)}`" v-user-preview="user.id">{{ getUserName(user) }}</router-link> <p class="username">@{{ getAcct(user) }}</p> </div> <mk-follow-button :user="user"/> @@ -23,6 +23,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -38,6 +39,7 @@ export default Vue.extend({ }, methods: { getAcct, + getUserName, fetch() { this.fetching = true; this.users = []; diff --git a/src/client/app/desktop/views/components/messaging-room-window.vue b/src/client/app/desktop/views/components/messaging-room-window.vue index f29f9b74e..7f8c35c2f 100644 --- a/src/client/app/desktop/views/components/messaging-room-window.vue +++ b/src/client/app/desktop/views/components/messaging-room-window.vue @@ -1,6 +1,6 @@ <template> <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy"> - <span slot="header" :class="$style.header">%fa:comments%メッセージ: {{ user.name }}</span> + <span slot="header" :class="$style.header">%fa:comments%メッセージ: {{ name }}</span> <mk-messaging-room :user="user" :class="$style.content"/> </mk-window> </template> @@ -9,10 +9,14 @@ import Vue from 'vue'; import { url } from '../../../config'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['user'], computed: { + name(): string { + return getUserName(this.user); + }, popout(): string { return `${url}/i/messaging/${getAcct(this.user)}`; } diff --git a/src/client/app/desktop/views/components/notifications.vue b/src/client/app/desktop/views/components/notifications.vue index c5ab284df..d8b8eab21 100644 --- a/src/client/app/desktop/views/components/notifications.vue +++ b/src/client/app/desktop/views/components/notifications.vue @@ -11,7 +11,7 @@ <div class="text"> <p> <mk-reaction-icon :reaction="notification.reaction"/> - <router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ notification.user.name }}</router-link> + <router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link> </p> <router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> %fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% @@ -24,7 +24,7 @@ </router-link> <div class="text"> <p>%fa:retweet% - <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ notification.post.user.name }}</router-link> + <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> </p> <router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> %fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right% @@ -37,7 +37,7 @@ </router-link> <div class="text"> <p>%fa:quote-left% - <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ notification.post.user.name }}</router-link> + <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> </p> <router-link class="post-preview" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</router-link> </div> @@ -48,7 +48,7 @@ </router-link> <div class="text"> <p>%fa:user-plus% - <router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ notification.user.name }}</router-link> + <router-link :to="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</router-link> </p> </div> </template> @@ -58,7 +58,7 @@ </router-link> <div class="text"> <p>%fa:reply% - <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ notification.post.user.name }}</router-link> + <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> </p> <router-link class="post-preview" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</router-link> </div> @@ -69,7 +69,7 @@ </router-link> <div class="text"> <p>%fa:at% - <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ notification.post.user.name }}</router-link> + <router-link :to="`/@${getAcct(notification.post.user)}`" v-user-preview="notification.post.userId">{{ getUserName(notification.post.user) }}</router-link> </p> <a class="post-preview" :href="`/@${getAcct(notification.post.user)}/${notification.post.id}`">{{ getPostSummary(notification.post) }}</a> </div> @@ -79,7 +79,7 @@ <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> </router-link> <div class="text"> - <p>%fa:chart-pie%<a :href="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ notification.user.name }}</a></p> + <p>%fa:chart-pie%<a :href="`/@${getAcct(notification.user)}`" v-user-preview="notification.user.id">{{ getUserName(notification.user) }}</a></p> <router-link class="post-ref" :to="`/@${getAcct(notification.post.user)}/${notification.post.id}`"> %fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% </router-link> @@ -104,6 +104,7 @@ import Vue from 'vue'; import getAcct from '../../../../../acct/render'; import getPostSummary from '../../../../../renderers/get-post-summary'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -154,6 +155,7 @@ export default Vue.extend({ }, methods: { getAcct, + getUserName, fetchMoreNotifications() { this.fetchingMoreNotifications = true; diff --git a/src/client/app/desktop/views/components/post-detail.sub.vue b/src/client/app/desktop/views/components/post-detail.sub.vue index 59bc9ce0c..496003eb8 100644 --- a/src/client/app/desktop/views/components/post-detail.sub.vue +++ b/src/client/app/desktop/views/components/post-detail.sub.vue @@ -6,7 +6,7 @@ <div class="main"> <header> <div class="left"> - <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ post.user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ getUserName(post.user) }}</router-link> <span class="username">@{{ acct }}</span> </div> <div class="right"> @@ -29,6 +29,7 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['post'], @@ -36,6 +37,9 @@ export default Vue.extend({ acct() { return getAcct(this.post.user); }, + name() { + return getUserName(this.post.user); + }, title(): string { return dateStringify(this.post.createdAt); } diff --git a/src/client/app/desktop/views/components/post-detail.vue b/src/client/app/desktop/views/components/post-detail.vue index 8000ce2e6..1a3c0d1b6 100644 --- a/src/client/app/desktop/views/components/post-detail.vue +++ b/src/client/app/desktop/views/components/post-detail.vue @@ -22,7 +22,7 @@ <img class="avatar" :src="`${post.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> </router-link> %fa:retweet% - <router-link class="name" :href="`/@${acct}`">{{ post.user.name }}</router-link> + <router-link class="name" :href="`/@${acct}`">{{ getUserName(post.user) }}</router-link> がRepost </p> </div> @@ -31,7 +31,7 @@ <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> </router-link> <header> - <router-link class="name" :to="`/@${pAcct}`" v-user-preview="p.user.id">{{ p.user.name }}</router-link> + <router-link class="name" :to="`/@${pAcct}`" v-user-preview="p.user.id">{{ getUserName(p.user) }}</router-link> <span class="username">@{{ pAcct }}</span> <router-link class="time" :to="`/@${pAcct}/${p.id}`"> <mk-time :time="p.createdAt"/> @@ -79,6 +79,7 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; import parse from '../../../../../text/parse'; import MkPostFormWindow from './post-form-window.vue'; @@ -133,9 +134,15 @@ export default Vue.extend({ acct(): string { return getAcct(this.post.user); }, + name(): string { + return getUserName(this.post.user); + }, pAcct(): string { return getAcct(this.p.user); }, + pName(): string { + return getUserName(this.p.user); + }, urls(): string[] { if (this.p.text) { const ast = parse(this.p.text); diff --git a/src/client/app/desktop/views/components/post-preview.vue b/src/client/app/desktop/views/components/post-preview.vue index 7129f67b3..99d9442d9 100644 --- a/src/client/app/desktop/views/components/post-preview.vue +++ b/src/client/app/desktop/views/components/post-preview.vue @@ -5,7 +5,7 @@ </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ post.user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ name }}</router-link> <span class="username">@{{ acct }}</span> <router-link class="time" :to="`/@${acct}/${post.id}`"> <mk-time :time="post.createdAt"/> @@ -22,6 +22,7 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['post'], @@ -29,6 +30,9 @@ export default Vue.extend({ acct() { return getAcct(this.post.user); }, + name() { + return getUserName(this.post.user); + }, title(): string { return dateStringify(this.post.createdAt); } diff --git a/src/client/app/desktop/views/components/posts.post.sub.vue b/src/client/app/desktop/views/components/posts.post.sub.vue index dffecb89c..a9cd0a927 100644 --- a/src/client/app/desktop/views/components/posts.post.sub.vue +++ b/src/client/app/desktop/views/components/posts.post.sub.vue @@ -5,7 +5,7 @@ </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ post.user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`" v-user-preview="post.userId">{{ name }}</router-link> <span class="username">@{{ acct }}</span> <router-link class="created-at" :to="`/@${acct}/${post.id}`"> <mk-time :time="post.createdAt"/> @@ -22,6 +22,7 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['post'], @@ -29,6 +30,9 @@ export default Vue.extend({ acct() { return getAcct(this.post.user); }, + name(): string { + return getUserName(this.post.user); + }, title(): string { return dateStringify(this.post.createdAt); } diff --git a/src/client/app/desktop/views/components/posts.post.vue b/src/client/app/desktop/views/components/posts.post.vue index 9a13dd687..17fe33042 100644 --- a/src/client/app/desktop/views/components/posts.post.vue +++ b/src/client/app/desktop/views/components/posts.post.vue @@ -10,7 +10,7 @@ </router-link> %fa:retweet% <span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span> - <a class="name" :href="`/@${acct}`" v-user-preview="post.userId">{{ post.user.name }}</a> + <a class="name" :href="`/@${acct}`" v-user-preview="post.userId">{{ getUserName(post.user) }}</a> <span>{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span> </p> <mk-time :time="post.createdAt"/> @@ -86,6 +86,7 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; import parse from '../../../../../text/parse'; import MkPostFormWindow from './post-form-window.vue'; @@ -124,6 +125,9 @@ export default Vue.extend({ acct(): string { return getAcct(this.p.user); }, + name(): string { + return getUserName(this.p.user); + }, isRepost(): boolean { return (this.post.repost && this.post.text == null && diff --git a/src/client/app/desktop/views/components/settings.mute.vue b/src/client/app/desktop/views/components/settings.mute.vue index c87f973fa..6bdc76653 100644 --- a/src/client/app/desktop/views/components/settings.mute.vue +++ b/src/client/app/desktop/views/components/settings.mute.vue @@ -5,7 +5,7 @@ </div> <div class="users" v-if="users.length != 0"> <div v-for="user in users" :key="user.id"> - <p><b>{{ user.name }}</b> @{{ getAcct(user) }}</p> + <p><b>{{ getUserName(user) }}</b> @{{ getAcct(user) }}</p> </div> </div> </div> @@ -14,6 +14,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -23,7 +24,8 @@ export default Vue.extend({ }; }, methods: { - getAcct + getAcct, + getUserName }, mounted() { (this as any).api('mute/list').then(x => { diff --git a/src/client/app/desktop/views/components/settings.profile.vue b/src/client/app/desktop/views/components/settings.profile.vue index ba86286f8..28be48e0a 100644 --- a/src/client/app/desktop/views/components/settings.profile.vue +++ b/src/client/app/desktop/views/components/settings.profile.vue @@ -42,7 +42,7 @@ export default Vue.extend({ }; }, created() { - this.name = (this as any).os.i.name; + this.name = (this as any).os.i.name || ''; this.location = (this as any).os.i.account.profile.location; this.description = (this as any).os.i.description; this.birthday = (this as any).os.i.account.profile.birthday; @@ -53,7 +53,7 @@ export default Vue.extend({ }, save() { (this as any).api('i/update', { - name: this.name, + name: this.name || null, location: this.location || null, description: this.description || null, birthday: this.birthday || null diff --git a/src/client/app/desktop/views/components/ui.header.vue b/src/client/app/desktop/views/components/ui.header.vue index 448d04d26..7d93847fa 100644 --- a/src/client/app/desktop/views/components/ui.header.vue +++ b/src/client/app/desktop/views/components/ui.header.vue @@ -4,7 +4,7 @@ <div class="main" ref="main"> <div class="backdrop"></div> <div class="main"> - <p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ os.i.name }}</b>さん</p> + <p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ name }}</b>さん</p> <div class="container" ref="mainContainer"> <div class="left"> <x-nav/> @@ -33,7 +33,14 @@ import XNotifications from './ui.header.notifications.vue'; import XPost from './ui.header.post.vue'; import XClock from './ui.header.clock.vue'; +import getUserName from '../../../../../renderers/get-user-name'; + export default Vue.extend({ + computed: { + name() { + return getUserName(this.os.i); + } + }, components: { XNav, XSearch, diff --git a/src/client/app/desktop/views/components/users-list.item.vue b/src/client/app/desktop/views/components/users-list.item.vue index 2d7d4dc72..c7a132ecf 100644 --- a/src/client/app/desktop/views/components/users-list.item.vue +++ b/src/client/app/desktop/views/components/users-list.item.vue @@ -5,7 +5,7 @@ </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`" v-user-preview="user.id">{{ user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`" v-user-preview="user.id">{{ name }}</router-link> <span class="username">@{{ acct }}</span> </header> <div class="body"> @@ -20,12 +20,16 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['user'], computed: { acct() { return getAcct(this.user); + }, + name() { + return getUserName(this.user); } } }); diff --git a/src/client/app/desktop/views/pages/messaging-room.vue b/src/client/app/desktop/views/pages/messaging-room.vue index 1e61f3ce1..1cc8d8a77 100644 --- a/src/client/app/desktop/views/pages/messaging-room.vue +++ b/src/client/app/desktop/views/pages/messaging-room.vue @@ -8,6 +8,7 @@ import Vue from 'vue'; import Progress from '../../../common/scripts/loading'; import parseAcct from '../../../../../acct/parse'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -34,7 +35,7 @@ export default Vue.extend({ this.user = user; this.fetching = false; - document.title = 'メッセージ: ' + this.user.name; + document.title = 'メッセージ: ' + getUserName(this.user); Progress.done(); }); diff --git a/src/client/app/desktop/views/pages/user/user.followers-you-know.vue b/src/client/app/desktop/views/pages/user/user.followers-you-know.vue index 7497acd0e..16625b689 100644 --- a/src/client/app/desktop/views/pages/user/user.followers-you-know.vue +++ b/src/client/app/desktop/views/pages/user/user.followers-you-know.vue @@ -4,7 +4,7 @@ <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:desktop.tags.mk-user.followers-you-know.loading%<mk-ellipsis/></p> <div v-if="!fetching && users.length > 0"> <router-link v-for="user in users" :to="`/@${getAcct(user)}`" :key="user.id"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user.name" v-user-preview="user.id"/> + <img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="getUserName(user)" v-user-preview="user.id"/> </router-link> </div> <p class="empty" v-if="!fetching && users.length == 0">%i18n:desktop.tags.mk-user.followers-you-know.no-users%</p> @@ -14,6 +14,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../../acct/render'; +import getUserName from '../../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['user'], @@ -24,7 +25,8 @@ export default Vue.extend({ }; }, method() { - getAcct + getAcct, + getUserName }, mounted() { (this as any).api('users/followers', { diff --git a/src/client/app/desktop/views/pages/user/user.header.vue b/src/client/app/desktop/views/pages/user/user.header.vue index d30f423d5..5c6746d5d 100644 --- a/src/client/app/desktop/views/pages/user/user.header.vue +++ b/src/client/app/desktop/views/pages/user/user.header.vue @@ -7,7 +7,7 @@ <div class="container"> <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=150`" alt="avatar"/> <div class="title"> - <p class="name">{{ user.name }}</p> + <p class="name">{{ name }}</p> <p class="username">@{{ acct }}</p> <p class="location" v-if="user.host === null && user.account.profile.location">%fa:map-marker%{{ user.account.profile.location }}</p> </div> @@ -23,12 +23,16 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../../acct/render'; +import getUserName from '../../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['user'], computed: { acct() { return getAcct(this.user); + }, + name() { + return getUserName(this.user); } }, mounted() { diff --git a/src/client/app/desktop/views/pages/user/user.vue b/src/client/app/desktop/views/pages/user/user.vue index 02ddc2421..d07b462b5 100644 --- a/src/client/app/desktop/views/pages/user/user.vue +++ b/src/client/app/desktop/views/pages/user/user.vue @@ -10,6 +10,7 @@ <script lang="ts"> import Vue from 'vue'; import parseAcct from '../../../../../../acct/parse'; +import getUserName from '../../../../../../renderers/get-user-name'; import Progress from '../../../../common/scripts/loading'; import XHeader from './user.header.vue'; import XHome from './user.home.vue'; @@ -44,7 +45,7 @@ export default Vue.extend({ this.user = user; this.fetching = false; Progress.done(); - document.title = user.name + ' | Misskey'; + document.title = getUserName(user) + ' | Misskey'; }); } } diff --git a/src/client/app/desktop/views/widgets/channel.channel.post.vue b/src/client/app/desktop/views/widgets/channel.channel.post.vue index e10e9c4f7..fa6d8c34a 100644 --- a/src/client/app/desktop/views/widgets/channel.channel.post.vue +++ b/src/client/app/desktop/views/widgets/channel.channel.post.vue @@ -2,7 +2,7 @@ <div class="post"> <header> <a class="index" @click="reply">{{ post.index }}:</a> - <router-link class="name" :to="`/@${acct}`" v-user-preview="post.user.id"><b>{{ post.user.name }}</b></router-link> + <router-link class="name" :to="`/@${acct}`" v-user-preview="post.user.id"><b>{{ name }}</b></router-link> <span>ID:<i>{{ acct }}</i></span> </header> <div> @@ -20,12 +20,16 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['post'], computed: { acct() { return getAcct(this.post.user); + }, + name() { + return getUserName(this.post.user); } }, methods: { diff --git a/src/client/app/desktop/views/widgets/profile.vue b/src/client/app/desktop/views/widgets/profile.vue index 83cd67b50..98e42222e 100644 --- a/src/client/app/desktop/views/widgets/profile.vue +++ b/src/client/app/desktop/views/widgets/profile.vue @@ -15,19 +15,26 @@ title="クリックでアバター編集" v-user-preview="os.i.id" /> - <router-link class="name" :to="`/@${os.i.username}`">{{ os.i.name }}</router-link> + <router-link class="name" :to="`/@${os.i.username}`">{{ name }}</router-link> <p class="username">@{{ os.i.username }}</p> </div> </template> <script lang="ts"> import define from '../../../common/define-widget'; +import getUserName from '../../../../../renderers/get-user-name'; + export default define({ name: 'profile', props: () => ({ design: 0 }) }).extend({ + computed: { + name() { + return getUserName(this.os.i); + } + }, methods: { func() { if (this.props.design == 2) { diff --git a/src/client/app/desktop/views/widgets/users.vue b/src/client/app/desktop/views/widgets/users.vue index 6f6a10157..a5dabb68f 100644 --- a/src/client/app/desktop/views/widgets/users.vue +++ b/src/client/app/desktop/views/widgets/users.vue @@ -11,7 +11,7 @@ <img class="avatar" :src="`${_user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="_user.id"/> </router-link> <div class="body"> - <router-link class="name" :to="`/@${getAcct(_user)}`" v-user-preview="_user.id">{{ _user.name }}</router-link> + <router-link class="name" :to="`/@${getAcct(_user)}`" v-user-preview="_user.id">{{ getUserName(_user) }}</router-link> <p class="username">@{{ getAcct(_user) }}</p> </div> <mk-follow-button :user="_user"/> @@ -24,6 +24,7 @@ <script lang="ts"> import define from '../../../common/define-widget'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; const limit = 3; @@ -45,6 +46,7 @@ export default define({ }, methods: { getAcct, + getUserName, func() { this.props.compact = !this.props.compact; }, diff --git a/src/client/app/mobile/views/components/notification-preview.vue b/src/client/app/mobile/views/components/notification-preview.vue index 77abd3c0e..0492c5d86 100644 --- a/src/client/app/mobile/views/components/notification-preview.vue +++ b/src/client/app/mobile/views/components/notification-preview.vue @@ -3,7 +3,7 @@ <template v-if="notification.type == 'reaction'"> <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p><mk-reaction-icon :reaction="notification.reaction"/>{{ notification.user.name }}</p> + <p><mk-reaction-icon :reaction="notification.reaction"/>{{ name }}</p> <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p> </div> </template> @@ -11,7 +11,7 @@ <template v-if="notification.type == 'repost'"> <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:retweet%{{ notification.post.user.name }}</p> + <p>%fa:retweet%{{ posterName }}</p> <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right%</p> </div> </template> @@ -19,7 +19,7 @@ <template v-if="notification.type == 'quote'"> <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:quote-left%{{ notification.post.user.name }}</p> + <p>%fa:quote-left%{{ posterName }}</p> <p class="post-preview">{{ getPostSummary(notification.post) }}</p> </div> </template> @@ -27,14 +27,14 @@ <template v-if="notification.type == 'follow'"> <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:user-plus%{{ notification.user.name }}</p> + <p>%fa:user-plus%{{ name }}</p> </div> </template> <template v-if="notification.type == 'reply'"> <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:reply%{{ notification.post.user.name }}</p> + <p>%fa:reply%{{ posterName }}</p> <p class="post-preview">{{ getPostSummary(notification.post) }}</p> </div> </template> @@ -42,7 +42,7 @@ <template v-if="notification.type == 'mention'"> <img class="avatar" :src="`${notification.post.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:at%{{ notification.post.user.name }}</p> + <p>%fa:at%{{ posterName }}</p> <p class="post-preview">{{ getPostSummary(notification.post) }}</p> </div> </template> @@ -50,7 +50,7 @@ <template v-if="notification.type == 'poll_vote'"> <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> <div class="text"> - <p>%fa:chart-pie%{{ notification.user.name }}</p> + <p>%fa:chart-pie%{{ name }}</p> <p class="post-ref">%fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right%</p> </div> </template> @@ -60,9 +60,18 @@ <script lang="ts"> import Vue from 'vue'; import getPostSummary from '../../../../../renderers/get-post-summary'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['notification'], + computed: { + name() { + return getUserName(this.notification.user); + }, + posterName() { + return getUserName(this.notification.post.user); + } + }, data() { return { getPostSummary diff --git a/src/client/app/mobile/views/components/notification.vue b/src/client/app/mobile/views/components/notification.vue index 189d7195f..62a0e6425 100644 --- a/src/client/app/mobile/views/components/notification.vue +++ b/src/client/app/mobile/views/components/notification.vue @@ -8,7 +8,7 @@ <div class="text"> <p> <mk-reaction-icon :reaction="notification.reaction"/> - <router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link> + <router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> </p> <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> %fa:quote-left%{{ getPostSummary(notification.post) }} @@ -25,7 +25,7 @@ <div class="text"> <p> %fa:retweet% - <router-link :to="`/@${acct}`">{{ notification.post.user.name }}</router-link> + <router-link :to="`/@${acct}`">{{ getUserName(notification.post.user) }}</router-link> </p> <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> %fa:quote-left%{{ getPostSummary(notification.post.repost) }}%fa:quote-right% @@ -45,7 +45,7 @@ <div class="text"> <p> %fa:user-plus% - <router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link> + <router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> </p> </div> </div> @@ -66,7 +66,7 @@ <div class="text"> <p> %fa:chart-pie% - <router-link :to="`/@${acct}`">{{ notification.user.name }}</router-link> + <router-link :to="`/@${acct}`">{{ getUserName(notification.user) }}</router-link> </p> <router-link class="post-ref" :to="`/@${acct}/${notification.post.id}`"> %fa:quote-left%{{ getPostSummary(notification.post) }}%fa:quote-right% @@ -80,12 +80,19 @@ import Vue from 'vue'; import getPostSummary from '../../../../../renderers/get-post-summary'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['notification'], computed: { acct() { return getAcct(this.notification.user); + }, + name() { + return getUserName(this.notification.user); + }, + posterName() { + return getUserName(this.notification.post.user); } }, data() { diff --git a/src/client/app/mobile/views/components/post-card.vue b/src/client/app/mobile/views/components/post-card.vue index 38d4522d2..68a42ef24 100644 --- a/src/client/app/mobile/views/components/post-card.vue +++ b/src/client/app/mobile/views/components/post-card.vue @@ -2,7 +2,7 @@ <div class="mk-post-card"> <a :href="`/@${acct}/${post.id}`"> <header> - <img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ post.user.name }}</h3> + <img :src="`${acct}?thumbnail&size=64`" alt="avatar"/><h3>{{ name }}</h3> </header> <div> {{ text }} @@ -16,6 +16,7 @@ import Vue from 'vue'; import summary from '../../../../../renderers/get-post-summary'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['post'], @@ -23,6 +24,9 @@ export default Vue.extend({ acct() { return getAcct(this.post.user); }, + name() { + return getUserName(this.post.user); + }, text(): string { return summary(this.post); } diff --git a/src/client/app/mobile/views/components/post-detail.sub.vue b/src/client/app/mobile/views/components/post-detail.sub.vue index 7ff9f1aab..98d6a14ca 100644 --- a/src/client/app/mobile/views/components/post-detail.sub.vue +++ b/src/client/app/mobile/views/components/post-detail.sub.vue @@ -5,7 +5,7 @@ </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link> <span class="username">@{{ acct }}</span> <router-link class="time" :to="`/@${acct}/${post.id}`"> <mk-time :time="post.createdAt"/> @@ -21,12 +21,16 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['post'], computed: { acct() { return getAcct(this.post.user); + }, + name() { + return getUserName(this.post.user); } } }); diff --git a/src/client/app/mobile/views/components/post-detail.vue b/src/client/app/mobile/views/components/post-detail.vue index ed394acd3..0226ce081 100644 --- a/src/client/app/mobile/views/components/post-detail.vue +++ b/src/client/app/mobile/views/components/post-detail.vue @@ -22,7 +22,7 @@ </router-link> %fa:retweet% <router-link class="name" :to="`/@${acct}`"> - {{ post.user.name }} + {{ name }} </router-link> がRepost </p> @@ -33,7 +33,7 @@ <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> </router-link> <div> - <router-link class="name" :to="`/@${pAcct}`">{{ p.user.name }}</router-link> + <router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link> <span class="username">@{{ pAcct }}</span> </div> </header> @@ -81,6 +81,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; import parse from '../../../../../text/parse'; import MkPostMenu from '../../../common/views/components/post-menu.vue'; @@ -114,9 +115,15 @@ export default Vue.extend({ acct(): string { return getAcct(this.post.user); }, + name(): string { + return getUserName(this.post.user); + }, pAcct(): string { return getAcct(this.p.user); }, + pName(): string { + return getUserName(this.p.user); + }, isRepost(): boolean { return (this.post.repost && this.post.text == null && diff --git a/src/client/app/mobile/views/components/post-preview.vue b/src/client/app/mobile/views/components/post-preview.vue index 81b0c22bf..96bd4d5c1 100644 --- a/src/client/app/mobile/views/components/post-preview.vue +++ b/src/client/app/mobile/views/components/post-preview.vue @@ -5,7 +5,7 @@ </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> <span class="username">@{{ acct }}</span> <router-link class="time" :to="`/@${acct}/${post.id}`"> <mk-time :time="post.createdAt"/> @@ -21,12 +21,16 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['post'], computed: { acct() { return getAcct(this.post.user); + }, + name() { + return getUserName(this.post.user); } } }); diff --git a/src/client/app/mobile/views/components/post.sub.vue b/src/client/app/mobile/views/components/post.sub.vue index 85ddb9188..909d5cb59 100644 --- a/src/client/app/mobile/views/components/post.sub.vue +++ b/src/client/app/mobile/views/components/post.sub.vue @@ -5,7 +5,7 @@ </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`">{{ getUserName(post.user) }}</router-link> <span class="username">@{{ acct }}</span> <router-link class="created-at" :to="`/@${acct}/${post.id}`"> <mk-time :time="post.createdAt"/> @@ -21,12 +21,16 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['post'], computed: { acct() { return getAcct(this.post.user); + }, + name() { + return getUserName(this.post.user); } } }); diff --git a/src/client/app/mobile/views/components/post.vue b/src/client/app/mobile/views/components/post.vue index 1454bc939..eee1e80fd 100644 --- a/src/client/app/mobile/views/components/post.vue +++ b/src/client/app/mobile/views/components/post.vue @@ -10,7 +10,7 @@ </router-link> %fa:retweet% <span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}</span> - <router-link class="name" :to="`/@${acct}`">{{ post.user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> <span>{{ '%i18n:mobile.tags.mk-timeline-post.reposted-by%'.substr('%i18n:mobile.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}</span> </p> <mk-time :time="post.createdAt"/> @@ -21,7 +21,7 @@ </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${pAcct}`">{{ p.user.name }}</router-link> + <router-link class="name" :to="`/@${pAcct}`">{{ pName }}</router-link> <span class="is-bot" v-if="p.user.host === null && p.user.account.isBot">bot</span> <span class="username">@{{ pAcct }}</span> <div class="info"> @@ -78,6 +78,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; import parse from '../../../../../text/parse'; import MkPostMenu from '../../../common/views/components/post-menu.vue'; @@ -102,9 +103,15 @@ export default Vue.extend({ acct(): string { return getAcct(this.post.user); }, + name(): string { + return getUserName(this.post.user); + }, pAcct(): string { return getAcct(this.p.user); }, + pName(): string { + return getUserName(this.p.user); + }, isRepost(): boolean { return (this.post.repost && this.post.text == null && diff --git a/src/client/app/mobile/views/components/ui.header.vue b/src/client/app/mobile/views/components/ui.header.vue index 2bf47a90a..fd4f31fd9 100644 --- a/src/client/app/mobile/views/components/ui.header.vue +++ b/src/client/app/mobile/views/components/ui.header.vue @@ -3,7 +3,7 @@ <mk-special-message/> <div class="main" ref="main"> <div class="backdrop"></div> - <p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ os.i.name }}</b>さん</p> + <p ref="welcomeback" v-if="os.isSignedIn">おかえりなさい、<b>{{ name }}</b>さん</p> <div class="content" ref="mainContainer"> <button class="nav" @click="$parent.isDrawerOpening = true">%fa:bars%</button> <template v-if="hasUnreadNotifications || hasUnreadMessagingMessages || hasGameInvitations">%fa:circle%</template> @@ -19,9 +19,15 @@ <script lang="ts"> import Vue from 'vue'; import * as anime from 'animejs'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['func'], + computed: { + name() { + return getUserName(this.os.i); + } + }, data() { return { hasUnreadNotifications: false, diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue index a923774a7..61dd8ca9d 100644 --- a/src/client/app/mobile/views/components/ui.nav.vue +++ b/src/client/app/mobile/views/components/ui.nav.vue @@ -11,7 +11,7 @@ <div class="body" v-if="isOpen"> <router-link class="me" v-if="os.isSignedIn" :to="`/@${os.i.username}`"> <img class="avatar" :src="`${os.i.avatarUrl}?thumbnail&size=128`" alt="avatar"/> - <p class="name">{{ os.i.name }}</p> + <p class="name">{{ name }}</p> </router-link> <div class="links"> <ul> @@ -40,9 +40,15 @@ <script lang="ts"> import Vue from 'vue'; import { docsUrl, chUrl, lang } from '../../../config'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['isOpen'], + computed: { + name() { + return getUserName(this.os.i); + } + }, data() { return { hasUnreadNotifications: false, diff --git a/src/client/app/mobile/views/components/user-card.vue b/src/client/app/mobile/views/components/user-card.vue index 46fa3b473..e8698a62f 100644 --- a/src/client/app/mobile/views/components/user-card.vue +++ b/src/client/app/mobile/views/components/user-card.vue @@ -5,7 +5,7 @@ <img :src="`${user.avatarUrl}?thumbnail&size=200`" alt="avatar"/> </a> </header> - <a class="name" :href="`/@${acct}`" target="_blank">{{ user.name }}</a> + <a class="name" :href="`/@${acct}`" target="_blank">{{ name }}</a> <p class="username">@{{ acct }}</p> <mk-follow-button :user="user"/> </div> @@ -14,12 +14,16 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['user'], computed: { acct() { return getAcct(this.user); + }, + name() { + return getUserName(this.user); } } }); diff --git a/src/client/app/mobile/views/components/user-preview.vue b/src/client/app/mobile/views/components/user-preview.vue index 00f87e554..72a6bcf8a 100644 --- a/src/client/app/mobile/views/components/user-preview.vue +++ b/src/client/app/mobile/views/components/user-preview.vue @@ -5,7 +5,7 @@ </router-link> <div class="main"> <header> - <router-link class="name" :to="`/@${acct}`">{{ user.name }}</router-link> + <router-link class="name" :to="`/@${acct}`">{{ name }}</router-link> <span class="username">@{{ acct }}</span> </header> <div class="body"> @@ -18,12 +18,16 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../acct/render'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['user'], computed: { acct() { return getAcct(this.user); + }, + name() { + return getUserName(this.user); } } }); diff --git a/src/client/app/mobile/views/pages/followers.vue b/src/client/app/mobile/views/pages/followers.vue index d2e6a9aea..f4225d556 100644 --- a/src/client/app/mobile/views/pages/followers.vue +++ b/src/client/app/mobile/views/pages/followers.vue @@ -2,7 +2,7 @@ <mk-ui> <template slot="header" v-if="!fetching"> <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""> - {{ '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', user.name) }} + {{ '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', name) }} </template> <mk-users-list v-if="!fetching" @@ -20,6 +20,7 @@ import Vue from 'vue'; import Progress from '../../../common/scripts/loading'; import parseAcct from '../../../../../acct/parse'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -28,6 +29,11 @@ export default Vue.extend({ user: null }; }, + computed: { + name() { + return getUserName(this.user); + } + }, watch: { $route: 'fetch' }, @@ -46,7 +52,7 @@ export default Vue.extend({ this.user = user; this.fetching = false; - document.title = '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', user.name) + ' | Misskey'; + document.title = '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', this.name) + ' | Misskey'; }); }, onLoaded() { diff --git a/src/client/app/mobile/views/pages/following.vue b/src/client/app/mobile/views/pages/following.vue index 3690536cf..cc2442e47 100644 --- a/src/client/app/mobile/views/pages/following.vue +++ b/src/client/app/mobile/views/pages/following.vue @@ -2,7 +2,7 @@ <mk-ui> <template slot="header" v-if="!fetching"> <img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""> - {{ '%i18n:mobile.tags.mk-user-following-page.following-of%'.replace('{}', user.name) }} + {{ '%i18n:mobile.tags.mk-user-following-page.following-of%'.replace('{}', name) }} </template> <mk-users-list v-if="!fetching" @@ -20,6 +20,7 @@ import Vue from 'vue'; import Progress from '../../../common/scripts/loading'; import parseAcct from '../../../../../acct/parse'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -28,6 +29,11 @@ export default Vue.extend({ user: null }; }, + computed: { + name() { + return getUserName(this.user); + } + }, watch: { $route: 'fetch' }, @@ -46,7 +52,7 @@ export default Vue.extend({ this.user = user; this.fetching = false; - document.title = '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', user.name) + ' | Misskey'; + document.title = '%i18n:mobile.tags.mk-user-followers-page.followers-of%'.replace('{}', this.name) + ' | Misskey'; }); }, onLoaded() { diff --git a/src/client/app/mobile/views/pages/messaging-room.vue b/src/client/app/mobile/views/pages/messaging-room.vue index 172666eea..ae6662dc0 100644 --- a/src/client/app/mobile/views/pages/messaging-room.vue +++ b/src/client/app/mobile/views/pages/messaging-room.vue @@ -1,7 +1,7 @@ <template> <mk-ui> <span slot="header"> - <template v-if="user">%fa:R comments%{{ user.name }}</template> + <template v-if="user">%fa:R comments%{{ name }}</template> <template v-else><mk-ellipsis/></template> </span> <mk-messaging-room v-if="!fetching" :user="user" :is-naked="true"/> @@ -11,6 +11,7 @@ <script lang="ts"> import Vue from 'vue'; import parseAcct from '../../../../../acct/parse'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -19,6 +20,11 @@ export default Vue.extend({ user: null }; }, + computed: { + name() { + return getUserName(this.user); + } + }, watch: { $route: 'fetch' }, @@ -33,7 +39,7 @@ export default Vue.extend({ this.user = user; this.fetching = false; - document.title = `%i18n:mobile.tags.mk-messaging-room-page.message%: ${user.name} | Misskey`; + document.title = `%i18n:mobile.tags.mk-messaging-room-page.message%: ${this.name} | Misskey`; }); } } diff --git a/src/client/app/mobile/views/pages/profile-setting.vue b/src/client/app/mobile/views/pages/profile-setting.vue index 15f9bc9b6..4a560c027 100644 --- a/src/client/app/mobile/views/pages/profile-setting.vue +++ b/src/client/app/mobile/views/pages/profile-setting.vue @@ -52,7 +52,7 @@ export default Vue.extend({ }; }, created() { - this.name = (this as any).os.i.name; + this.name = (this as any).os.i.name || ''; this.location = (this as any).os.i.account.profile.location; this.description = (this as any).os.i.description; this.birthday = (this as any).os.i.account.profile.birthday; @@ -94,7 +94,7 @@ export default Vue.extend({ this.saving = true; (this as any).api('i/update', { - name: this.name, + name: this.name || null, location: this.location || null, description: this.description || null, birthday: this.birthday || null diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue index a945a21c5..58a9d4e37 100644 --- a/src/client/app/mobile/views/pages/settings.vue +++ b/src/client/app/mobile/views/pages/settings.vue @@ -2,7 +2,7 @@ <mk-ui> <span slot="header">%fa:cog%%i18n:mobile.tags.mk-settings-page.settings%</span> <div :class="$style.content"> - <p v-html="'%i18n:mobile.tags.mk-settings.signed-in-as%'.replace('{}', '<b>' + os.i.name + '</b>')"></p> + <p v-html="'%i18n:mobile.tags.mk-settings.signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></p> <ul> <li><router-link to="./settings/profile">%fa:user%%i18n:mobile.tags.mk-settings-page.profile%%fa:angle-right%</router-link></li> <li><router-link to="./settings/authorized-apps">%fa:puzzle-piece%%i18n:mobile.tags.mk-settings-page.applications%%fa:angle-right%</router-link></li> @@ -20,6 +20,7 @@ <script lang="ts"> import Vue from 'vue'; import { version, codename } from '../../../config'; +import getUserName from '../../../../../renderers/get-user-name'; export default Vue.extend({ data() { @@ -28,6 +29,11 @@ export default Vue.extend({ codename }; }, + computed: { + name() { + return getUserName(this.os.i); + } + }, mounted() { document.title = 'Misskey | %i18n:mobile.tags.mk-settings-page.settings%'; document.documentElement.style.background = '#313a42'; diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue index be696484b..3b7b4d6c2 100644 --- a/src/client/app/mobile/views/pages/user.vue +++ b/src/client/app/mobile/views/pages/user.vue @@ -1,6 +1,6 @@ <template> <mk-ui> - <span slot="header" v-if="!fetching">%fa:user% {{ user.name }}</span> + <span slot="header" v-if="!fetching">%fa:user% {{ user }}</span> <main v-if="!fetching"> <header> <div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl}?thumbnail&size=1024)` : ''"></div> @@ -12,7 +12,7 @@ <mk-follow-button v-if="os.isSignedIn && os.i.id != user.id" :user="user"/> </div> <div class="title"> - <h1>{{ user.name }}</h1> + <h1>{{ user }}</h1> <span class="username">@{{ acct }}</span> <span class="followed" v-if="user.isFollowed">%i18n:mobile.tags.mk-user.follows-you%</span> </div> @@ -61,7 +61,7 @@ import Vue from 'vue'; import * as age from 's-age'; import getAcct from '../../../../../acct/render'; -import getAcct from '../../../../../acct/parse'; +import getUserName from '../../../../../renderers/get-user-name'; import Progress from '../../../common/scripts/loading'; import XHome from './user/home.vue'; @@ -82,6 +82,9 @@ export default Vue.extend({ }, age(): number { return age(this.user.account.profile.birthday); + }, + name() { + return getUserName(this.user); } }, watch: { @@ -102,7 +105,7 @@ export default Vue.extend({ this.fetching = false; Progress.done(); - document.title = user.name + ' | Misskey'; + document.title = this.name + ' | Misskey'; }); } } diff --git a/src/client/app/mobile/views/pages/user/home.followers-you-know.vue b/src/client/app/mobile/views/pages/user/home.followers-you-know.vue index ffdd9f178..1b128e2f2 100644 --- a/src/client/app/mobile/views/pages/user/home.followers-you-know.vue +++ b/src/client/app/mobile/views/pages/user/home.followers-you-know.vue @@ -3,7 +3,7 @@ <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-followers-you-know.loading%<mk-ellipsis/></p> <div v-if="!fetching && users.length > 0"> <a v-for="user in users" :key="user.id" :href="`/@${getAcct(user)}`"> - <img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="user.name"/> + <img :src="`${user.avatarUrl}?thumbnail&size=64`" :alt="getUserName(user)"/> </a> </div> <p class="empty" v-if="!fetching && users.length == 0">%i18n:mobile.tags.mk-user-overview-followers-you-know.no-users%</p> @@ -13,6 +13,7 @@ <script lang="ts"> import Vue from 'vue'; import getAcct from '../../../../../../acct/render'; +import getUserName from '../../../../../../renderers/get-user-name'; export default Vue.extend({ props: ['user'], @@ -22,6 +23,11 @@ export default Vue.extend({ users: [] }; }, + computed: { + name() { + return getUserName(this.user); + } + }, methods: { getAcct }, diff --git a/src/client/app/mobile/views/widgets/profile.vue b/src/client/app/mobile/views/widgets/profile.vue index f1d283e45..bd257a3ff 100644 --- a/src/client/app/mobile/views/widgets/profile.vue +++ b/src/client/app/mobile/views/widgets/profile.vue @@ -8,15 +8,23 @@ :src="`${os.i.avatarUrl}?thumbnail&size=96`" alt="avatar" /> - <router-link :class="$style.name" :to="`/@${os.i.username}`">{{ os.i.name }}</router-link> + <router-link :class="$style.name" :to="`/@${os.i.username}`">{{ name }}</router-link> </mk-widget-container> </div> </template> <script lang="ts"> import define from '../../../common/define-widget'; +import getUserName from '../../../../../renderers/get-user-name'; + export default define({ name: 'profile' +}).extend({ + computed: { + name() { + return getUserName(this.os.i); + } + } }); </script> diff --git a/src/models/user.ts b/src/models/user.ts index f817c33aa..92091c687 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -21,7 +21,7 @@ type IUserBase = { deletedAt: Date; followersCount: number; followingCount: number; - name: string; + name?: string; postsCount: number; driveCapacity: number; username: string; @@ -99,8 +99,8 @@ export function validatePassword(password: string): boolean { return typeof password == 'string' && password != ''; } -export function isValidName(name: string): boolean { - return typeof name == 'string' && name.length < 30 && name.trim() != ''; +export function isValidName(name?: string): boolean { + return name === null || (typeof name == 'string' && name.length < 30 && name.trim() != ''); } export function isValidDescription(description: string): boolean { diff --git a/src/othello/ai/back.ts b/src/othello/ai/back.ts index d6704b175..4d06ed956 100644 --- a/src/othello/ai/back.ts +++ b/src/othello/ai/back.ts @@ -9,6 +9,7 @@ import * as request from 'request-promise-native'; import Othello, { Color } from '../core'; import conf from '../../config'; +import getUserName from '../../renderers/get-user-name'; let game; let form; @@ -47,8 +48,8 @@ process.on('message', async msg => { const user = game.user1Id == id ? game.user2 : game.user1; const isSettai = form[0].value === 0; const text = isSettai - ? `?[${user.name}](${conf.url}/@${user.username})さんの接待を始めました!` - : `対局を?[${user.name}](${conf.url}/@${user.username})さんと始めました! (強さ${form[0].value})`; + ? `?[${getUserName(user)}](${conf.url}/@${user.username})さんの接待を始めました!` + : `対局を?[${getUserName(user)}](${conf.url}/@${user.username})さんと始めました! (強さ${form[0].value})`; const res = await request.post(`${conf.api_url}/posts/create`, { json: { i, @@ -72,15 +73,15 @@ process.on('message', async msg => { const isSettai = form[0].value === 0; const text = isSettai ? msg.body.winnerId === null - ? `?[${user.name}](${conf.url}/@${user.username})さんに接待で引き分けました...` + ? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で引き分けました...` : msg.body.winnerId == id - ? `?[${user.name}](${conf.url}/@${user.username})さんに接待で勝ってしまいました...` - : `?[${user.name}](${conf.url}/@${user.username})さんに接待で負けてあげました♪` + ? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で勝ってしまいました...` + : `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で負けてあげました♪` : msg.body.winnerId === null - ? `?[${user.name}](${conf.url}/@${user.username})さんと引き分けました~` + ? `?[${getUserName(user)}](${conf.url}/@${user.username})さんと引き分けました~` : msg.body.winnerId == id - ? `?[${user.name}](${conf.url}/@${user.username})さんに勝ちました♪` - : `?[${user.name}](${conf.url}/@${user.username})さんに負けました...`; + ? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに勝ちました♪` + : `?[${getUserName(user)}](${conf.url}/@${user.username})さんに負けました...`; await request.post(`${conf.api_url}/posts/create`, { json: { i, diff --git a/src/renderers/get-notification-summary.ts b/src/renderers/get-notification-summary.ts index 03db722c8..f5e38faf9 100644 --- a/src/renderers/get-notification-summary.ts +++ b/src/renderers/get-notification-summary.ts @@ -1,3 +1,4 @@ +import getUserName from '../renderers/get-user-name'; import getPostSummary from './get-post-summary'; import getReactionEmoji from './get-reaction-emoji'; @@ -8,19 +9,19 @@ import getReactionEmoji from './get-reaction-emoji'; export default function(notification: any): string { switch (notification.type) { case 'follow': - return `${notification.user.name}にフォローされました`; + return `${getUserName(notification.user)}にフォローされました`; case 'mention': - return `言及されました:\n${notification.user.name}「${getPostSummary(notification.post)}」`; + return `言及されました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`; case 'reply': - return `返信されました:\n${notification.user.name}「${getPostSummary(notification.post)}」`; + return `返信されました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`; case 'repost': - return `Repostされました:\n${notification.user.name}「${getPostSummary(notification.post)}」`; + return `Repostされました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`; case 'quote': - return `引用されました:\n${notification.user.name}「${getPostSummary(notification.post)}」`; + return `引用されました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`; case 'reaction': - return `リアクションされました:\n${notification.user.name} <${getReactionEmoji(notification.reaction)}>「${getPostSummary(notification.post)}」`; + return `リアクションされました:\n${getUserName(notification.user)} <${getReactionEmoji(notification.reaction)}>「${getPostSummary(notification.post)}」`; case 'poll_vote': - return `投票されました:\n${notification.user.name}「${getPostSummary(notification.post)}」`; + return `投票されました:\n${getUserName(notification.user)}「${getPostSummary(notification.post)}」`; default: return `<不明な通知タイプ: ${notification.type}>`; } diff --git a/src/renderers/get-user-summary.ts b/src/renderers/get-user-summary.ts index 208987113..d002933b6 100644 --- a/src/renderers/get-user-summary.ts +++ b/src/renderers/get-user-summary.ts @@ -1,12 +1,13 @@ import { IUser, isLocalUser } from '../models/user'; import getAcct from '../acct/render'; +import getUserName from './get-user-name'; /** * ユーザーを表す文字列を取得します。 * @param user ユーザー */ export default function(user: IUser): string { - let string = `${user.name} (@${getAcct(user)})\n` + + let string = `${getUserName(user)} (@${getAcct(user)})\n` + `${user.postsCount}投稿、${user.followingCount}フォロー、${user.followersCount}フォロワー\n`; if (isLocalUser(user)) { diff --git a/src/server/api/bot/core.ts b/src/server/api/bot/core.ts index 7e80f31e5..a44aa9d7b 100644 --- a/src/server/api/bot/core.ts +++ b/src/server/api/bot/core.ts @@ -4,6 +4,7 @@ import * as bcrypt from 'bcryptjs'; import User, { IUser, init as initUser, ILocalUser } from '../../../models/user'; import getPostSummary from '../../../renderers/get-post-summary'; +import getUserName from '../../../renderers/get-user-name'; import getUserSummary from '../../../renderers/get-user-summary'; import parseAcct from '../../../acct/parse'; import getNotificationSummary from '../../../renderers/get-notification-summary'; @@ -90,7 +91,7 @@ export default class BotCore extends EventEmitter { 'タイムラインや通知を見た後、「次」というとさらに遡ることができます。'; case 'me': - return this.user ? `${this.user.name}としてサインインしています。\n\n${getUserSummary(this.user)}` : 'サインインしていません'; + return this.user ? `${getUserName(this.user)}としてサインインしています。\n\n${getUserSummary(this.user)}` : 'サインインしていません'; case 'login': case 'signin': @@ -230,7 +231,7 @@ class SigninContext extends Context { if (same) { this.bot.signin(this.temporaryUser); this.bot.clearContext(); - return `${this.temporaryUser.name}さん、おかえりなさい!`; + return `${getUserName(this.temporaryUser)}さん、おかえりなさい!`; } else { return `パスワードが違います... もう一度教えてください:`; } @@ -305,7 +306,7 @@ class TlContext extends Context { this.emit('updated'); const text = tl - .map(post => `${post.user.name}\n「${getPostSummary(post)}」`) + .map(post => `${getUserName(post.user)}\n「${getPostSummary(post)}」`) .join('\n-----\n'); return text; diff --git a/src/server/api/bot/interfaces/line.ts b/src/server/api/bot/interfaces/line.ts index 7847cbdea..1191aaf39 100644 --- a/src/server/api/bot/interfaces/line.ts +++ b/src/server/api/bot/interfaces/line.ts @@ -10,6 +10,7 @@ import prominence = require('prominence'); import getAcct from '../../../../acct/render'; import parseAcct from '../../../../acct/parse'; import getPostSummary from '../../../../renderers/get-post-summary'; +import getUserName from '../../../../renderers/get-user-name'; const redis = prominence(_redis); @@ -131,7 +132,7 @@ class LineBot extends BotCore { template: { type: 'buttons', thumbnailImageUrl: `${user.avatarUrl}?thumbnail&size=1024`, - title: `${user.name} (@${acct})`, + title: `${getUserName(user)} (@${acct})`, text: user.description || '(no description)', actions: actions } @@ -146,7 +147,7 @@ class LineBot extends BotCore { limit: 5 }, this.user); - const text = `${tl[0].user.name}さんのタイムラインはこちらです:\n\n` + tl + const text = `${getUserName(tl[0].user)}さんのタイムラインはこちらです:\n\n` + tl .map(post => getPostSummary(post)) .join('\n-----\n'); From d99d628f50eb62ca9d5ac56470e8e6f0ae8c3d2c Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Fri, 6 Apr 2018 01:37:24 +0900 Subject: [PATCH 12/15] =?UTF-8?q?Do=20not=20save=20=E5=90=8D=E7=84=A1?= =?UTF-8?q?=E3=81=97=20as=20the=20name=20of=20a=20new=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/api/private/signup.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 4203ce526..c54d6f1a1 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -47,7 +47,6 @@ export default async (req: express.Request, res: express.Response) => { const username = req.body['username']; const password = req.body['password']; - const name = '名無し'; // Validate username if (!validateUsername(username)) { @@ -113,7 +112,7 @@ export default async (req: express.Request, res: express.Response) => { description: null, followersCount: 0, followingCount: 0, - name: name, + name: null, postsCount: 0, driveCapacity: 1024 * 1024 * 128, // 128MiB username: username, From 39b896a900f4c0c3bf48cab9e01bc2f7b1f05e47 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Fri, 6 Apr 2018 12:19:44 +0900 Subject: [PATCH 13/15] Add missing src/renderers/get-user-name.ts --- src/renderers/get-user-name.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/renderers/get-user-name.ts diff --git a/src/renderers/get-user-name.ts b/src/renderers/get-user-name.ts new file mode 100644 index 000000000..acd5e6626 --- /dev/null +++ b/src/renderers/get-user-name.ts @@ -0,0 +1,5 @@ +import { IUser } from '../models/user'; + +export default function(user: IUser): string { + return user.name || '名無し'; +} From 6752594578e64a26c0cd1c5a9fd2d83313134466 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Fri, 6 Apr 2018 12:20:11 +0900 Subject: [PATCH 14/15] Resolve local Person ID --- src/remote/activitypub/resolve-person.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/remote/activitypub/resolve-person.ts b/src/remote/activitypub/resolve-person.ts index a7c0020dd..84746169f 100644 --- a/src/remote/activitypub/resolve-person.ts +++ b/src/remote/activitypub/resolve-person.ts @@ -1,5 +1,7 @@ import { JSDOM } from 'jsdom'; import { toUnicode } from 'punycode'; +import parseAcct from '../../acct/parse'; +import config from '../../config'; import User, { validateUsername, isValidName, isValidDescription } from '../../models/user'; import { createHttp } from '../../queue'; import webFinger from '../webfinger'; @@ -10,10 +12,18 @@ async function isCollection(collection) { } export default async (parentResolver, value, verifier?: string) => { + const id = value.id || value; + const localPrefix = config.url + '/@'; + + if (id.startsWith(localPrefix)) { + return User.findOne(parseAcct(id.slice(localPrefix))); + } + const { resolver, object } = await parentResolver.resolveOne(value); if ( object === null || + object.id !== id || object.type !== 'Person' || typeof object.preferredUsername !== 'string' || !validateUsername(object.preferredUsername) || @@ -36,7 +46,7 @@ export default async (parentResolver, value, verifier?: string) => { resolved => isCollection(resolved.object) ? resolved.object : null, () => null ), - webFinger(object.id, verifier), + webFinger(id, verifier), ]); const host = toUnicode(finger.subject.replace(/^.*?@/, '')); @@ -64,7 +74,7 @@ export default async (parentResolver, value, verifier?: string) => { publicKeyPem: object.publicKey.publicKeyPem }, inbox: object.inbox, - uri: object.id, + uri: id, }, }); From 41acde0d8a5fd59e4b0250d650eade3019947f2e Mon Sep 17 00:00:00 2001 From: Akihiko Odaki <nekomanma@pixiv.co.jp> Date: Fri, 6 Apr 2018 12:53:39 +0900 Subject: [PATCH 15/15] Resolve local Object ID --- src/remote/activitypub/create.ts | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/remote/activitypub/create.ts b/src/remote/activitypub/create.ts index 3bc0c66f3..bbe595a45 100644 --- a/src/remote/activitypub/create.ts +++ b/src/remote/activitypub/create.ts @@ -1,8 +1,10 @@ import { JSDOM } from 'jsdom'; import { ObjectID } from 'mongodb'; +import parseAcct from '../../acct/parse'; import config from '../../config'; import DriveFile from '../../models/drive-file'; import Post from '../../models/post'; +import User from '../../models/user'; import { IRemoteUser } from '../../models/user'; import uploadFromUrl from '../../drive/upload-from-url'; import createPost from '../../post/create'; @@ -133,6 +135,41 @@ class Creator { return collection.object.map(async element => { const uri = element.id || element; + const localPrefix = config.url + '/@'; + + if (uri.startsWith(localPrefix)) { + const [acct, id] = uri.slice(localPrefix).split('/', 2); + const user = await User.aggregate([ + { + $match: parseAcct(acct) + }, + { + $lookup: { + from: 'posts', + localField: '_id', + foreignField: 'userId', + as: 'post' + } + }, + { + $match: { + post: { _id: id } + } + } + ]); + + if (user === null || user.posts.length <= 0) { + throw new Error(); + } + + return { + resolver: collection.resolver, + object: { + $ref: 'posts', + id + } + }; + } try { await Promise.all([