From 405184b1f85b02ce0e45337056e17a81ed33e83e Mon Sep 17 00:00:00 2001 From: Terncode Date: Mon, 11 May 2026 21:38:11 +0200 Subject: [PATCH] Replace moment with date-fns --- package-lock.json | 48 +++++-------------- package.json | 2 +- src/ts/common/adminUtils.ts | 31 +++++++----- .../shared/admin-chat-log/admin-chat-log.ts | 8 ++-- src/ts/components/admin/shared/from-now.ts | 10 ++-- src/ts/components/shared/chat-log/chat-log.ts | 15 ++++-- src/ts/server/accountUtils.ts | 4 +- src/ts/server/adminServerActions.ts | 10 +++- src/ts/server/api/account.ts | 4 +- src/ts/server/api/admin.ts | 4 +- src/ts/server/move.ts | 4 +- src/ts/server/requestUtils.ts | 6 ++- src/ts/server/stats.ts | 4 +- 13 files changed, 73 insertions(+), 77 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65a0b43..b13be81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,6 +77,7 @@ "connect-mongo": "6.0.0", "cookie-parser": "1.4.4", "core-js": "3.2.1", + "date-fns": "^4.1.0", "del": "8.0.1", "delta-e": "0.0.8", "errorhandler": "1.5.1", @@ -116,7 +117,6 @@ "markdown-tree": "0.0.0", "merge2": "1.2.4", "mocha": "6.2.0", - "moment": "2.24.0", "mongoose": "9.3.2", "morgan": "1.9.1", "ngx-bootstrap": "18.0.1", @@ -5495,9 +5495,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5511,9 +5508,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5527,9 +5521,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5543,9 +5534,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5559,9 +5547,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5575,9 +5560,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5591,9 +5573,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5607,9 +5586,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5623,9 +5599,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -10685,6 +10658,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -20800,15 +20783,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/mongodb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.1.tgz", diff --git a/package.json b/package.json index 69f8566..af1c346 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ "connect-mongo": "6.0.0", "cookie-parser": "1.4.4", "core-js": "3.2.1", + "date-fns": "^4.1.0", "del": "8.0.1", "delta-e": "0.0.8", "errorhandler": "1.5.1", @@ -157,7 +158,6 @@ "markdown-tree": "0.0.0", "merge2": "1.2.4", "mocha": "6.2.0", - "moment": "2.24.0", "mongoose": "9.3.2", "morgan": "1.9.1", "ngx-bootstrap": "18.0.1", diff --git a/src/ts/common/adminUtils.ts b/src/ts/common/adminUtils.ts index 50968e6..795ee85 100644 --- a/src/ts/common/adminUtils.ts +++ b/src/ts/common/adminUtils.ts @@ -1,5 +1,5 @@ -import * as moment from 'moment'; -import { escapeRegExp, startsWith, range, uniq, compact } from 'lodash'; +import { differenceInYears, format, formatDistanceToNow, subDays } from 'date-fns'; +import { escapeRegExp, startsWith, uniq, compact } from 'lodash'; import { fromNow, toInt, hasFlag, compareDates, removeItem, includes } from './utils'; import { DAY } from './constants'; import { @@ -45,7 +45,7 @@ export function highlightWords(text?: string) { } export function getAge(birthdate: Date) { - return moment().diff(birthdate, 'years'); + return differenceInYears(new Date(), birthdate); } // chat & events @@ -55,17 +55,19 @@ export interface ChatDate { label: string; } -export function createChatDate(date: moment.Moment): ChatDate { +export function createChatDate(date: Date): ChatDate { return { value: date.toISOString(), - label: date.format('MMMM Do YYYY'), + label: format(date, 'MMMM do yyyy') }; } export function createDateRange(startDate: string | Date, days: number): ChatDate[] { - return range(days, 0) - .map(d => moment(startDate).subtract(d, 'days')) - .map(createChatDate); + const start = new Date(startDate); + + return Array.from({ length: days }, (_, i) => + createChatDate(subDays(start, days - i - 1)) + ); } // filtering @@ -324,19 +326,22 @@ const fieldToAction: { [key: string]: string | undefined; } = { ban: 'Banned', }; + export function banMessage(field: string, value: number) { const action = fieldToAction[field] || 'Did'; if (value === 0) { return `Un${action.toLowerCase()}`; - } else if (value === -1) { - return action; - } else { - return `${action} for (${moment.duration(value - Date.now()).humanize()})`; } + + if (value === -1) { + return action; + } + + return `${action} for (${formatDistanceToNow(new Date(value), { addSuffix: false })})`; } -export function isActive(value: number | undefined): boolean { +export function isActive(value: number | undefined): value is number { return !!value && (value === -1 || value > Date.now()); } diff --git a/src/ts/components/admin/shared/admin-chat-log/admin-chat-log.ts b/src/ts/components/admin/shared/admin-chat-log/admin-chat-log.ts index 018cbb6..8f23e84 100644 --- a/src/ts/components/admin/shared/admin-chat-log/admin-chat-log.ts +++ b/src/ts/components/admin/shared/admin-chat-log/admin-chat-log.ts @@ -1,5 +1,4 @@ import { Component, Input, OnDestroy, ElementRef } from '@angular/core'; -import * as moment from 'moment'; import { AdminModel } from '../../../services/adminModel'; import { Account } from '../../../../common/adminInterfaces'; import { ChatDate, createChatDate, createDateRange } from '../../../../common/adminUtils'; @@ -7,6 +6,7 @@ import { faSearch, faSpinner, faSync, faFileAlt, faTimes, faChevronLeft, faChevr import { removeAllNodes, appendAllNodes, showTextInNewTab } from '../../../../client/htmlUtils'; import { includes } from '../../../../common/utils'; import { replaceSwears } from '../../../../client/adminHtmlUtils'; +import { addDays } from 'date-fns'; @Component({ selector: 'admin-chat-log', @@ -25,7 +25,7 @@ export class AdminChatLog implements OnDestroy { accounts: Account[] = []; search?: string; open = false; - today: ChatDate = createChatDate(moment()); + today: ChatDate = createChatDate(new Date()); dates: ChatDate[] = [/*{ value: 'all', label: 'All' },*/ ...createDateRange(new Date(), 14)]; date?: ChatDate; chatRaw?: string; @@ -133,7 +133,9 @@ export class AdminChatLog implements OnDestroy { } private switchDate(days: number) { const validDate = this.date && this.date.value !== 'all'; - this.date = validDate ? createChatDate(moment(this.date!.value).add(days, 'days')) : this.today; + this.date = validDate + ? createChatDate(addDays(new Date(this.date!.value), days)) + : this.today; this.refresh(); } private stopInterval() { diff --git a/src/ts/components/admin/shared/from-now.ts b/src/ts/components/admin/shared/from-now.ts index 2fcf0e2..ac04f82 100644 --- a/src/ts/components/admin/shared/from-now.ts +++ b/src/ts/components/admin/shared/from-now.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit, OnDestroy, OnChanges, ElementRef, ChangeDetectionStrategy } from '@angular/core'; -import * as moment from 'moment'; import { IntervalUpdateService } from '../../services/intervalUpdateService'; +import { formatDistanceToNow } from 'date-fns'; @Component({ selector: 'from-now', @@ -9,13 +9,13 @@ import { IntervalUpdateService } from '../../services/intervalUpdateService'; }) export class FromNow implements OnInit, OnDestroy, OnChanges { @Input() time?: any; - private moment?: moment.Moment; + private date?: Date; private text?: string; private unsubscribe?: () => void; constructor(private element: ElementRef, private updateService: IntervalUpdateService) { } ngOnChanges() { - this.moment = this.time ? moment(this.time) : undefined; + this.date = this.time ? new Date(this.time) : undefined; this.update(); } ngOnInit() { @@ -26,7 +26,9 @@ export class FromNow implements OnInit, OnDestroy, OnChanges { this.unsubscribe && this.unsubscribe(); } private update() { - const text = this.moment ? this.moment.fromNow(true).replace('seconds', 'secs') : ''; + const text = this.date + ? formatDistanceToNow(this.date, { addSuffix: false }).replace('seconds', 'secs') + : ''; if (this.text !== text) { this.text = text; diff --git a/src/ts/components/shared/chat-log/chat-log.ts b/src/ts/components/shared/chat-log/chat-log.ts index e2206b2..3d332ce 100644 --- a/src/ts/components/shared/chat-log/chat-log.ts +++ b/src/ts/components/shared/chat-log/chat-log.ts @@ -13,8 +13,8 @@ import { faCaretUp, faArrowDown, faSearch } from '../../../client/icons'; import { sampleMessages } from '../../../common/debugData'; import { findEntityById } from '../../../client/worldMap'; import { colorToRGBA, rgb2hsl, HSL, hsl2CSS } from '../../../common/color'; -import * as moment from 'moment'; import { isMobile } from '../../../client/data'; +import { format } from 'date-fns'; interface IndexEntryUser { id: number; @@ -143,13 +143,18 @@ export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage, h replaceNodes(line.message, message); } + function updateTime(line: ChatLogLineDOM, hourMode?: '12' | '24') { line.time.style.display = 'inline'; if (!hourMode) return; - if (hourMode === '24') - replaceNodes(line.timeContent, `[${moment().format('HH:mm:ss')}] `); - if (hourMode === '12') - replaceNodes(line.timeContent, `[${moment().format('LTS')}] `); + + if (hourMode === '24') { + replaceNodes(line.timeContent, `[${format(new Date(), 'HH:mm:ss')}] `); + } + + if (hourMode === '12') { + replaceNodes(line.timeContent, `[${format(new Date(), 'hh:mm:ss a')}] `); + } } function setNameColors(line: ChatLogLineDOM | undefined, colors?: string[]) { diff --git a/src/ts/server/accountUtils.ts b/src/ts/server/accountUtils.ts index 7aa1501..b75a1b9 100644 --- a/src/ts/server/accountUtils.ts +++ b/src/ts/server/accountUtils.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment'; +import { formatDistanceToNow } from 'date-fns'; import { Types } from 'mongoose'; import { uniq, truncate } from 'lodash'; import { AccountState, AccountFlags, AuthBase } from '../common/adminInterfaces'; @@ -38,7 +38,7 @@ export interface CreateAccountOptions extends SuspiciousCheckers { } function getBanInfo(value: number | undefined): string | undefined { - return isActive(value) ? (value === -1 ? 'perma' : moment(value).fromNow(true)) : undefined; + return isActive(value) ? (value === -1 ? 'perma' : formatDistanceToNow(new Date(value))) : undefined; } export function getModInfo({ accountId, account, country }: IClient): ModInfo { diff --git a/src/ts/server/adminServerActions.ts b/src/ts/server/adminServerActions.ts index 6de3c9a..0b0473c 100644 --- a/src/ts/server/adminServerActions.ts +++ b/src/ts/server/adminServerActions.ts @@ -1,7 +1,7 @@ -import * as moment from 'moment'; import { Socket, SocketServer, Method, ClientExtensions } from 'ag-sockets'; import { AccountCounters, Subscription } from '../common/interfaces'; import { HOUR } from '../common/constants'; +import { formatDuration as formatDurationDateFn } from 'date-fns'; import { fromNow, removeItem, formatDuration } from '../common/utils'; import { hasRole } from '../common/accountUtils'; import { @@ -38,6 +38,7 @@ import { splitAccounts } from './api/merge'; import { removeAuth, assignAuth } from './api/admin-auths'; import { removeFriend, addFriend } from './accountUtils'; import { ClientAdminActionsTemplate, ClientUpdate } from '../common/clientAdminActionsTemplate'; +import { intervalToDuration } from 'date-fns'; @Socket({ id: 'admin', @@ -413,7 +414,12 @@ export class AdminServerActions implements IAdminServerActions, SocketServer { } @Method({ promise: true }) async timeoutAccount(accountId: string, timeout: number) { - const message = timeout ? `Timed out ${moment.duration(timeout).humanize()}` : 'Unmuted'; + const message = timeout + ? `Timed out ${formatDurationDateFn( + intervalToDuration({ start: 0, end: timeout }), + { format: ['days', 'hours', 'minutes', 'seconds'] } + )}` + : 'Unmuted'; system(accountId, `${message} ${this.by()}`); await timeoutAccount(accountId, fromNow(timeout | 0)); } diff --git a/src/ts/server/api/account.ts b/src/ts/server/api/account.ts index 0ed5f86..446c4fa 100644 --- a/src/ts/server/api/account.ts +++ b/src/ts/server/api/account.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment'; +import { formatDistanceToNow } from 'date-fns'; import { ACCOUNT_NAME_MAX_LENGTH, ACCOUNT_NAME_MIN_LENGTH, MIN_CHATLOG_RANGE, MAX_CHATLOG_RANGE, HIDES_PER_PAGE } from '../../common/constants'; import { UpdateAccountData, AccountSettings, AccountData, ModAction, EntitiesEditorInfo, EntityNameTypes @@ -179,7 +179,7 @@ export async function getHides(account: IAccount, page: number) { return hideRequests.map((f: any) => ({ id: f._id.toString(), name: f.name, - date: moment(f.date).fromNow(), + date: formatDistanceToNow(new Date(f.date), { addSuffix: true }), })); } diff --git a/src/ts/server/api/admin.ts b/src/ts/server/api/admin.ts index 1862011..35c3dba 100644 --- a/src/ts/server/api/admin.ts +++ b/src/ts/server/api/admin.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import * as moment from 'moment'; +import { format } from 'date-fns'; import { AdminState, eventFields, BaseValues, UpdateOrigin, UserCountStats, AccountDetails, SupporterInvite, InternalGameServerState, InternalLoginServerState, OtherStats, Settings, GameServerSettings @@ -116,7 +116,7 @@ export async function getChat(search: string, date: string, caseInsensitive: boo const options = { maxBuffer: 1 * 1024 * 1024 }; // 1MB async function fetchChatlog(lines: number) { - const logFile = paths.pathTo('logs', `info.${moment(date).format('YYYYMMDD')}.log`); + const logFile = paths.pathTo('logs', `info.${format(date, 'yyyyMMdd')}.log`) const { stdout } = await execAsync(`grep ${flags}"${query}" "${logFile}" | tail -n ${lines}`, options); return stdout; } diff --git a/src/ts/server/move.ts b/src/ts/server/move.ts index 7f9bef3..0055e19 100644 --- a/src/ts/server/move.ts +++ b/src/ts/server/move.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment'; +import { format } from 'date-fns'; import chalk from 'chalk'; import { IClient } from './serverInterfaces'; import { decodeMovement, dirToVector, flagsToSpeed, isMovingRight } from '../common/movementUtils'; @@ -172,7 +172,7 @@ function checkTeleporting( const colY = outY ? chalk.red : chalk.reset; logger.log( - `[${chalk.gray(moment().format('MMM DD HH:mm:ss'))}] [${chalk.yellow('teleport')}] ` + + `[${chalk.gray(format(new Date(), 'MMM dd HH:mm:ss'))}] [${chalk.yellow('teleport')}] ` + `[${chalk.gray(client.accountId)}] (${client.account.name})\n` + `\tdx: ${client.lastX.toFixed(5)} -> ${colX(x.toFixed(5))} [${minX.toFixed(5)}-${maxX.toFixed(5)}]\n` + `\tdy: ${client.lastY.toFixed(5)} -> ${colY(y.toFixed(5))} [${minY.toFixed(5)}-${maxY.toFixed(5)}]\n` + diff --git a/src/ts/server/requestUtils.ts b/src/ts/server/requestUtils.ts index 812650e..00b5ab1 100644 --- a/src/ts/server/requestUtils.ts +++ b/src/ts/server/requestUtils.ts @@ -1,7 +1,7 @@ import { Request, Response, RequestHandler } from 'express'; import * as fs from 'fs'; import * as path from 'path'; -import * as moment from 'moment'; +import { formatDistanceToNow } from 'date-fns'; import rateLimit from 'express-rate-limit'; import { noop } from 'lodash'; import { HASH } from '../generated/hash'; @@ -102,7 +102,9 @@ export function limit(freeRetries: number, lifetime: number) { max: freeRetries, handler(req, res) { logger.warn(`rate limit ${req.url} ${req.ip}`); - const retryAfter = moment(Date.now() + lifetime * 1000).fromNow(); + const retryAfter = formatDistanceToNow(new Date(Date.now() + lifetime * 1000), { + addSuffix: true + }); res.status(429).send(`Too many requests, please try again ${retryAfter}`); } }); diff --git a/src/ts/server/stats.ts b/src/ts/server/stats.ts index 6deee76..eab1d22 100644 --- a/src/ts/server/stats.ts +++ b/src/ts/server/stats.ts @@ -1,11 +1,11 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as moment from 'moment'; import { compact } from 'lodash'; import { Request } from 'express'; import { RequestStats, ServerStats } from '../common/adminInterfaces'; import { HOUR } from '../common/constants'; import { ByteSize } from './utils/byteSize'; +import { format } from 'date-fns'; interface Stats { count: number; @@ -175,7 +175,7 @@ export class StatsTracker { } private submitDailyStats(statsPath: string) { const statsEntry = [ - moment().format('MMM DD'), // DD-MM-YY HH:mm:ss + format(new Date(), 'MMM dd'), // DD-MM-YY HH:mm:ss this.dailyRequestCount.toString(), this.dailyRequestSize.toString(), this.dailySwearing.toString(),