parent
61461b7f59
commit
68571d8f57
|
@ -722,6 +722,12 @@ notSpecifiedMentionWarning: "宛先に含まれていないメンションがあ
|
|||
info: "情報"
|
||||
userInfo: "ユーザー情報"
|
||||
unknown: "不明"
|
||||
onlineStatus: "オンライン状態"
|
||||
hideOnlineStatus: "オンライン状態を隠す"
|
||||
hideOnlineStatusDescription: "オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。"
|
||||
online: "オンライン"
|
||||
active: "アクティブ"
|
||||
offline: "オフライン"
|
||||
|
||||
_email:
|
||||
_follow:
|
||||
|
|
|
@ -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"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
<template #caption>{{ $ts.lockedAccountInfo }}</template>
|
||||
</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()">
|
||||
{{ $ts.noCrawle }}
|
||||
<template #desc>{{ $ts.noCrawleDescription }}</template>
|
||||
|
@ -58,6 +62,7 @@ export default defineComponent({
|
|||
autoAcceptFollowed: false,
|
||||
noCrawle: false,
|
||||
isExplorable: false,
|
||||
hideOnlineStatus: false,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -72,6 +77,7 @@ export default defineComponent({
|
|||
this.autoAcceptFollowed = this.$i.autoAcceptFollowed;
|
||||
this.noCrawle = this.$i.noCrawle;
|
||||
this.isExplorable = this.$i.isExplorable;
|
||||
this.hideOnlineStatus = this.$i.hideOnlineStatus;
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
@ -85,6 +91,7 @@ export default defineComponent({
|
|||
autoAcceptFollowed: !!this.autoAcceptFollowed,
|
||||
noCrawle: !!this.noCrawle,
|
||||
isExplorable: !!this.isExplorable,
|
||||
hideOnlineStatus: !!this.hideOnlineStatus,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true
|
||||
})
|
||||
public lastActiveDate: Date | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public hideOnlineStatus: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
comment: 'The username of the User.'
|
||||
|
|
|
@ -7,6 +7,7 @@ import { SchemaType } from '@/misc/schema';
|
|||
import { awaitAll } from '../../prelude/await-all';
|
||||
import { populateEmojis } from '@/misc/populate-emojis';
|
||||
import { getAntennas } from '@/misc/antenna-cache';
|
||||
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const';
|
||||
|
||||
export type PackedUser = SchemaType<typeof packedUserSchema>;
|
||||
|
||||
|
@ -145,6 +146,17 @@ export class UserRepository extends Repository<User> {
|
|||
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(
|
||||
src: User['id'] | User,
|
||||
me?: { id: User['id'] } | null | undefined,
|
||||
|
@ -192,6 +204,7 @@ export class UserRepository extends Repository<User> {
|
|||
themeColor: instance.themeColor,
|
||||
} : undefined) : undefined,
|
||||
emojis: populateEmojis(user.emojis, user.host),
|
||||
onlineStatus: this.getOnlineStatus(user),
|
||||
|
||||
...(opts.detail ? {
|
||||
url: profile!.url,
|
||||
|
@ -239,6 +252,7 @@ export class UserRepository extends Repository<User> {
|
|||
autoAcceptFollowed: profile!.autoAcceptFollowed,
|
||||
noCrawle: profile!.noCrawle,
|
||||
isExplorable: user.isExplorable,
|
||||
hideOnlineStatus: user.hideOnlineStatus,
|
||||
hasUnreadSpecifiedNotes: NoteUnreads.count({
|
||||
where: { userId: user.id, isSpecified: true },
|
||||
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 { redisClient } from '../../../db/redis';
|
||||
import config from '@/config';
|
||||
|
||||
export const meta = {
|
||||
tags: ['meta'],
|
||||
|
@ -11,12 +12,12 @@ export const meta = {
|
|||
}
|
||||
};
|
||||
|
||||
export default define(meta, (ps, user) => {
|
||||
return new Promise((res, rej) => {
|
||||
redisClient.pubsub('numsub', config.host, (_, x) => {
|
||||
res({
|
||||
count: x[1]
|
||||
});
|
||||
});
|
||||
export default define(meta, async () => {
|
||||
const count = await Users.count({
|
||||
lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD))
|
||||
});
|
||||
|
||||
return {
|
||||
count
|
||||
};
|
||||
});
|
||||
|
|
|
@ -96,6 +96,10 @@ export const meta = {
|
|||
validator: $.optional.bool,
|
||||
},
|
||||
|
||||
hideOnlineStatus: {
|
||||
validator: $.optional.bool,
|
||||
},
|
||||
|
||||
carefulBot: {
|
||||
validator: $.optional.bool,
|
||||
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 (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
|
||||
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.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ParsedUrlQuery } from 'querystring';
|
|||
import authenticate from './authenticate';
|
||||
import { EventEmitter } from 'events';
|
||||
import { subsdcriber as redisClient } from '../../db/redis';
|
||||
import { Users } from '@/models';
|
||||
|
||||
module.exports = (server: http.Server) => {
|
||||
// Init websocket server
|
||||
|
@ -45,5 +46,11 @@ module.exports = (server: http.Server) => {
|
|||
connection.send('pong');
|
||||
}
|
||||
});
|
||||
|
||||
if (user) {
|
||||
Users.update(user.id, {
|
||||
lastActiveDate: new Date(),
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue