Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  known-users.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

/**
 * Known user tracking — JSON file-based store.
 *
 * Migrated from src/known-users.ts. Dependencies are only Node.js
 * built-ins + log + platform (both zero plugin-sdk).
 */

import fs from "node:fs";
import path from "node:path";
import type { ChatScope } from "../types.js";
import { formatErrorMessage } from "../utils/format.js";
import { debugLog, debugError } from "../utils/log.js";
import { getQQBotDataDir, getQQBotDataPath } from "../utils/platform.js";

/** Persisted record for a user who has interacted with the bot. */
export interface KnownUser {
  openid: string;
  type: ChatScope;
  nickname?: string;
  groupOpenid?: string;
  accountId: string;
  firstSeenAt: number;
  lastSeenAt: number;
  interactionCount: number;
}

let usersCache: Map<string, KnownUser> | null = null;
const SAVE_THROTTLE_MS = 5000;
let saveTimer: ReturnType<typeof setTimeout> | null = null;
let isDirty = false;

function ensureDir(): void {
  getQQBotDataDir("data");
}

function getKnownUsersFile(): string {
  return path.join(getQQBotDataPath("data"), "known-users.json");
}

function makeUserKey(user: Partial<KnownUser>): string {
  const base = `${user.accountId}:${user.type}:${user.openid}`;
  return user.type === "group" && user.groupOpenid ? `${base}:${user.groupOpenid}` : base;
}

function loadUsersFromFile(): Map<string, KnownUser> {
  if (usersCache !== null) {
    return usersCache;
  }
  usersCache = new Map();
  try {
    const knownUsersFile = getKnownUsersFile();
    if (fs.existsSync(knownUsersFile)) {
      const data = fs.readFileSync(knownUsersFile, "utf-8");
      const users = JSON.parse(data) as KnownUser[];
      for (const user of users) {
        usersCache.set(makeUserKey(user), user);
      }
      debugLog(`[known-users] Loaded ${usersCache.size} users`);
    }
  } catch (err) {
    debugError(`[known-users] Failed to load users: ${formatErrorMessage(err)}`);
    usersCache = new Map();
  }
  return usersCache;
}

function saveUsersToFile(): void {
  if (!isDirty || saveTimer) {
    return;
  }
  saveTimer = setTimeout(() => {
    saveTimer = null;
    doSaveUsersToFile();
  }, SAVE_THROTTLE_MS);
}

function doSaveUsersToFile(): void {
  if (!usersCache || !isDirty) {
    return;
  }
  try {
    ensureDir();
    fs.writeFileSync(
      getKnownUsersFile(),
      JSON.stringify(Array.from(usersCache.values()), null, 2),
      "utf-8",
    );
    isDirty = false;
  } catch (err) {
    debugError(`[known-users] Failed to save users: ${formatErrorMessage(err)}`);
  }
}

/** Flush pending writes immediately, typically during shutdown. */
export function flushKnownUsers(): void {
  if (saveTimer) {
    clearTimeout(saveTimer);
    saveTimer = null;
  }
  doSaveUsersToFile();
}

/** Record a known user whenever a message is received. */
export function recordKnownUser(user: {
  openid: string;
  type: ChatScope;
  nickname?: string;
  groupOpenid?: string;
  accountId: string;
}): void {
  const cache = loadUsersFromFile();
  const key = makeUserKey(user);
  const now = Date.now();
  const existing = cache.get(key);

  if (existing) {
    existing.lastSeenAt = now;
    existing.interactionCount++;
    if (user.nickname && user.nickname !== existing.nickname) {
      existing.nickname = user.nickname;
    }
  } else {
    cache.set(key, {
      openid: user.openid,
      type: user.type,
      nickname: user.nickname,
      groupOpenid: user.groupOpenid,
      accountId: user.accountId,
      firstSeenAt: now,
      lastSeenAt: now,
      interactionCount: 1,
    });
    debugLog(`[known-users] New user: ${user.openid} (${user.type})`);
  }
  isDirty = true;
  saveUsersToFile();
}

/** Look up one known user. */
export function getKnownUser(
  accountId: string,
  openid: string,
  type: ChatScope = "c2c",
  groupOpenid?: string,
): KnownUser | undefined {
  return loadUsersFromFile().get(makeUserKey({ accountId, openid, type, groupOpenid }));
}

/** List known users with optional filtering and sorting. */
export function listKnownUsers(options?: {
  accountId?: string;
  type?: ChatScope;
  activeWithin?: number;
  limit?: number;
  sortBy?: "lastSeenAt" | "firstSeenAt" | "interactionCount";
  sortOrder?: "asc" | "desc";
}): KnownUser[] {
  let users = Array.from(loadUsersFromFile().values());
  if (options?.accountId) {
    users = users.filter((u) => u.accountId === options.accountId);
  }
  if (options?.type) {
    users = users.filter((u) => u.type === options.type);
  }
  if (options?.activeWithin) {
    const cutoff = Date.now() - options.activeWithin;
    users = users.filter((u) => u.lastSeenAt >= cutoff);
  }
  const sortBy = options?.sortBy ?? "lastSeenAt";
  const sortOrder = options?.sortOrder ?? "desc";
  users.sort((a, b) => {
    const aV = a[sortBy] ?? 0;
    const bV = b[sortBy] ?? 0;
    return sortOrder === "asc" ? aV - bV : bV - aV;
  });
  if (options?.limit && options.limit > 0) {
    users = users.slice(0, options.limit);
  }
  return users;
}

/** Return summary stats for known users. */
export function getKnownUsersStats(accountId?: string): {
  totalUsers: number;
  c2cUsers: number;
  groupUsers: number;
  activeIn24h: number;
  activeIn7d: number;
} {
  const users = listKnownUsers({ accountId });
  const now = Date.now();
  const day = 86400000;
  return {
    totalUsers: users.length,
    c2cUsers: users.filter((u) => u.type === "c2c").length,
    groupUsers: users.filter((u) => u.type === "group").length,
    activeIn24h: users.filter((u) => now - u.lastSeenAt < day).length,
    activeIn7d: users.filter((u) => now - u.lastSeenAt < 7 * day).length,
  };
}

/** Remove one user record. */
export function removeKnownUser(
  accountId: string,
  openid: string,
  type: ChatScope = "c2c",
  groupOpenid?: string,
): boolean {
  const cache = loadUsersFromFile();
  const key = makeUserKey({ accountId, openid, type, groupOpenid });
  if (cache.has(key)) {
    cache.delete(key);
    isDirty = true;
    saveUsersToFile();
    debugLog(`[known-users] Removed user ${openid}`);
    return true;
  }
  return false;
}

/** Clear all user records, optionally scoped to one account. */
export function clearKnownUsers(accountId?: string): number {
  const cache = loadUsersFromFile();
  let count = 0;
  if (accountId) {
    for (const [key, user] of cache.entries()) {
      if (user.accountId === accountId) {
        cache.delete(key);
        count++;
      }
    }
  } else {
    count = cache.size;
    cache.clear();
  }
  if (count > 0) {
    isDirty = true;
    doSaveUsersToFile();
    debugLog(`[known-users] Cleared ${count} users`);
  }
  return count;
}

/** Return all groups in which a user has interacted. */
export function getUserGroups(accountId: string, openid: string): string[] {
  return listKnownUsers({ accountId, type: "group" })
    .filter((u) => u.openid === openid && u.groupOpenid)
    .map((u) => u.groupOpenid!);
}

/** Return all recorded members for one group. */
export function getGroupMembers(accountId: string, groupOpenid: string): KnownUser[] {
  return listKnownUsers({ accountId, type: "group" }).filter((u) => u.groupOpenid === groupOpenid);
}

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge