parent
61461b7f59
commit
68571d8f57
10 changed files with 92 additions and 9 deletions
|
@ -722,6 +722,12 @@ notSpecifiedMentionWarning: "宛先に含まれていないメンションがあ
|
||||||
info: "情報"
|
info: "情報"
|
||||||
userInfo: "ユーザー情報"
|
userInfo: "ユーザー情報"
|
||||||
unknown: "不明"
|
unknown: "不明"
|
||||||
|
onlineStatus: "オンライン状態"
|
||||||
|
hideOnlineStatus: "オンライン状態を隠す"
|
||||||
|
hideOnlineStatusDescription: "オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。"
|
||||||
|
online: "オンライン"
|
||||||
|
active: "アクティブ"
|
||||||
|
offline: "オフライン"
|
||||||
|
|
||||||
_email:
|
_email:
|
||||||
_follow:
|
_follow:
|
||||||
|
|
16
migration/1618637372000-user-last-active-date.ts
Normal file
16
migration/1618637372000-user-last-active-date.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class userLastActiveDate1618637372000 implements MigrationInterface {
|
||||||
|
name = 'userLastActiveDate1618637372000'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" ADD "lastActiveDate" TIMESTAMP WITH TIME ZONE DEFAULT NULL`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_seoignmeoprigmkpodgrjmkpormg" ON "user" ("lastActiveDate") `);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_seoignmeoprigmkpodgrjmkpormg"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "lastActiveDate"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
migration/1618639857000-user-hide-online-status.ts
Normal file
14
migration/1618639857000-user-hide-online-status.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
|
export class userHideOnlineStatus1618639857000 implements MigrationInterface {
|
||||||
|
name = 'userHideOnlineStatus1618639857000'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" ADD "hideOnlineStatus" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "hideOnlineStatus"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,10 @@
|
||||||
<FormSwitch v-model:value="autoAcceptFollowed" :disabled="!isLocked" @update:value="save()">{{ $ts.autoAcceptFollowed }}</FormSwitch>
|
<FormSwitch v-model:value="autoAcceptFollowed" :disabled="!isLocked" @update:value="save()">{{ $ts.autoAcceptFollowed }}</FormSwitch>
|
||||||
<template #caption>{{ $ts.lockedAccountInfo }}</template>
|
<template #caption>{{ $ts.lockedAccountInfo }}</template>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
<FormSwitch v-model:value="hideOnlineStatus" @update:value="save()">
|
||||||
|
{{ $ts.hideOnlineStatus }}
|
||||||
|
<template #desc>{{ $ts.hideOnlineStatusDescription }}</template>
|
||||||
|
</FormSwitch>
|
||||||
<FormSwitch v-model:value="noCrawle" @update:value="save()">
|
<FormSwitch v-model:value="noCrawle" @update:value="save()">
|
||||||
{{ $ts.noCrawle }}
|
{{ $ts.noCrawle }}
|
||||||
<template #desc>{{ $ts.noCrawleDescription }}</template>
|
<template #desc>{{ $ts.noCrawleDescription }}</template>
|
||||||
|
@ -58,6 +62,7 @@ export default defineComponent({
|
||||||
autoAcceptFollowed: false,
|
autoAcceptFollowed: false,
|
||||||
noCrawle: false,
|
noCrawle: false,
|
||||||
isExplorable: false,
|
isExplorable: false,
|
||||||
|
hideOnlineStatus: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -72,6 +77,7 @@ export default defineComponent({
|
||||||
this.autoAcceptFollowed = this.$i.autoAcceptFollowed;
|
this.autoAcceptFollowed = this.$i.autoAcceptFollowed;
|
||||||
this.noCrawle = this.$i.noCrawle;
|
this.noCrawle = this.$i.noCrawle;
|
||||||
this.isExplorable = this.$i.isExplorable;
|
this.isExplorable = this.$i.isExplorable;
|
||||||
|
this.hideOnlineStatus = this.$i.hideOnlineStatus;
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -85,6 +91,7 @@ export default defineComponent({
|
||||||
autoAcceptFollowed: !!this.autoAcceptFollowed,
|
autoAcceptFollowed: !!this.autoAcceptFollowed,
|
||||||
noCrawle: !!this.noCrawle,
|
noCrawle: !!this.noCrawle,
|
||||||
isExplorable: !!this.isExplorable,
|
isExplorable: !!this.isExplorable,
|
||||||
|
hideOnlineStatus: !!this.hideOnlineStatus,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
src/const.ts
Normal file
2
src/const.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
||||||
|
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
|
|
@ -26,6 +26,17 @@ export class User {
|
||||||
})
|
})
|
||||||
public lastFetchedAt: Date | null;
|
public lastFetchedAt: Date | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('timestamp with time zone', {
|
||||||
|
nullable: true
|
||||||
|
})
|
||||||
|
public lastActiveDate: Date | null;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public hideOnlineStatus: boolean;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128,
|
length: 128,
|
||||||
comment: 'The username of the User.'
|
comment: 'The username of the User.'
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { SchemaType } from '@/misc/schema';
|
||||||
import { awaitAll } from '../../prelude/await-all';
|
import { awaitAll } from '../../prelude/await-all';
|
||||||
import { populateEmojis } from '@/misc/populate-emojis';
|
import { populateEmojis } from '@/misc/populate-emojis';
|
||||||
import { getAntennas } from '@/misc/antenna-cache';
|
import { getAntennas } from '@/misc/antenna-cache';
|
||||||
|
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const';
|
||||||
|
|
||||||
export type PackedUser = SchemaType<typeof packedUserSchema>;
|
export type PackedUser = SchemaType<typeof packedUserSchema>;
|
||||||
|
|
||||||
|
@ -145,6 +146,17 @@ export class UserRepository extends Repository<User> {
|
||||||
return count > 0;
|
return count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getOnlineStatus(user: User): string {
|
||||||
|
if (user.hideOnlineStatus == null) return 'unknown';
|
||||||
|
if (user.lastActiveDate == null) return 'unknown';
|
||||||
|
const elapsed = Date.now() - user.lastActiveDate.getTime();
|
||||||
|
return (
|
||||||
|
elapsed < USER_ONLINE_THRESHOLD ? 'online' :
|
||||||
|
elapsed < USER_ACTIVE_THRESHOLD ? 'active' :
|
||||||
|
'offline'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async pack(
|
public async pack(
|
||||||
src: User['id'] | User,
|
src: User['id'] | User,
|
||||||
me?: { id: User['id'] } | null | undefined,
|
me?: { id: User['id'] } | null | undefined,
|
||||||
|
@ -192,6 +204,7 @@ export class UserRepository extends Repository<User> {
|
||||||
themeColor: instance.themeColor,
|
themeColor: instance.themeColor,
|
||||||
} : undefined) : undefined,
|
} : undefined) : undefined,
|
||||||
emojis: populateEmojis(user.emojis, user.host),
|
emojis: populateEmojis(user.emojis, user.host),
|
||||||
|
onlineStatus: this.getOnlineStatus(user),
|
||||||
|
|
||||||
...(opts.detail ? {
|
...(opts.detail ? {
|
||||||
url: profile!.url,
|
url: profile!.url,
|
||||||
|
@ -239,6 +252,7 @@ export class UserRepository extends Repository<User> {
|
||||||
autoAcceptFollowed: profile!.autoAcceptFollowed,
|
autoAcceptFollowed: profile!.autoAcceptFollowed,
|
||||||
noCrawle: profile!.noCrawle,
|
noCrawle: profile!.noCrawle,
|
||||||
isExplorable: user.isExplorable,
|
isExplorable: user.isExplorable,
|
||||||
|
hideOnlineStatus: user.hideOnlineStatus,
|
||||||
hasUnreadSpecifiedNotes: NoteUnreads.count({
|
hasUnreadSpecifiedNotes: NoteUnreads.count({
|
||||||
where: { userId: user.id, isSpecified: true },
|
where: { userId: user.id, isSpecified: true },
|
||||||
take: 1
|
take: 1
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { USER_ONLINE_THRESHOLD } from '@/const';
|
||||||
|
import { Users } from '@/models';
|
||||||
|
import { MoreThan } from 'typeorm';
|
||||||
import define from '../define';
|
import define from '../define';
|
||||||
import { redisClient } from '../../../db/redis';
|
|
||||||
import config from '@/config';
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
|
@ -11,12 +12,12 @@ export const meta = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, (ps, user) => {
|
export default define(meta, async () => {
|
||||||
return new Promise((res, rej) => {
|
const count = await Users.count({
|
||||||
redisClient.pubsub('numsub', config.host, (_, x) => {
|
lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD))
|
||||||
res({
|
|
||||||
count: x[1]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
count
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,6 +96,10 @@ export const meta = {
|
||||||
validator: $.optional.bool,
|
validator: $.optional.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hideOnlineStatus: {
|
||||||
|
validator: $.optional.bool,
|
||||||
|
},
|
||||||
|
|
||||||
carefulBot: {
|
carefulBot: {
|
||||||
validator: $.optional.bool,
|
validator: $.optional.bool,
|
||||||
desc: {
|
desc: {
|
||||||
|
@ -228,6 +232,7 @@ export default define(meta, async (ps, _user, token) => {
|
||||||
if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][];
|
if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][];
|
||||||
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
||||||
if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable;
|
if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable;
|
||||||
|
if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
|
||||||
if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
|
if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
|
||||||
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||||
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ParsedUrlQuery } from 'querystring';
|
||||||
import authenticate from './authenticate';
|
import authenticate from './authenticate';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { subsdcriber as redisClient } from '../../db/redis';
|
import { subsdcriber as redisClient } from '../../db/redis';
|
||||||
|
import { Users } from '@/models';
|
||||||
|
|
||||||
module.exports = (server: http.Server) => {
|
module.exports = (server: http.Server) => {
|
||||||
// Init websocket server
|
// Init websocket server
|
||||||
|
@ -45,5 +46,11 @@ module.exports = (server: http.Server) => {
|
||||||
connection.send('pong');
|
connection.send('pong');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
Users.update(user.id, {
|
||||||
|
lastActiveDate: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue