import { promisify } from "util"; import { pbkdf2, randomBytes } from "crypto"; import dayjs from "dayjs"; import { PoolConnection } from "mariadb"; import { readFile } from "fs"; import { join } from "path"; import { getDatetime } from "taehui-ts/date"; import DB from "src/system/DB"; import Configure from "src/system/Configure"; import { equalCipher } from "src/Utility"; import { wwwAXIOS } from "src/Www"; import { AVATAR_ENTRY_PATH } from "src/TaehuiComponent"; const pw = promisify(pbkdf2); const rf = promisify(readFile); const putTotem = async (db: PoolConnection, avatarID: string) => { const data = await DB.getTotem(db, avatarID); const datetime = getDatetime(); if (data && dayjs(data.date).add(1, "hour").isAfter(datetime)) { await DB.setTotemDateAsAvatarID(db, avatarID, datetime); return data.totem; } return DB.putTotem(db, avatarID, datetime); }; export const getTotem = async ( pendingAvatarID: string, pendingAvatarCipher: string, ) => { return DB.ta(async (db) => { const data = await DB.getAvatarAsAvatarID(db, pendingAvatarID); if (data) { const { avatarID, avatarCipher, avatarName, level, fax, avatarIntro } = data; const isOK = await equalCipher(avatarCipher, pendingAvatarCipher); if (isOK) { await DB.setLastDate(db, avatarID, getDatetime()); return { totem: await putTotem(db, avatarID), avatarID, avatarName, level, fax, avatarIntro, }; } else { return null; } } }); }; const getDBCipher = async (avatarCipher: string) => { const salt = randomBytes(24).toString("base64"); return `sha256:12000:${salt}:${Buffer.from( await pw(avatarCipher, salt, 12000, 24, "sha256"), ).toString("base64")}`; }; export const doModifyAvatar = async ( avatarID: string, avatarCipher: string, avatarCipherModified: string, avatarName: string, fax: string, avatarIntro: string, ) => { if ( avatarCipher && avatarName && avatarName.length <= 16 && (!fax || /^.+@.+$/.test(fax)) ) { return DB.ta(async (db) => { if ( await DB.doModifyAvatar( db, avatarID, avatarCipher, avatarCipherModified && (await getDBCipher(avatarCipherModified)), avatarName, fax, avatarIntro, ) ) { if (avatarCipherModified) { await DB.wipeTotem(db, avatarID); await wwwAXIOS.patch(`${Configure.www.qwilight}/totem`, avatarID); } return true; } else { return false; } }); } else { return false; } }; export const doModifyAvatarIntro = async ( avatarID: string, avatarIntro: string, ) => { return DB.doModifyAvatarIntro(avatarID, avatarIntro); }; export const postAvatar = async ( avatarID: string, avatarCipher: string, avatarName: string, avatarIP: string, fax: string, ) => { return ( avatarID && !avatarID.startsWith("@") && !avatarID.startsWith("#") && !avatarID.startsWith("$") && !avatarID.startsWith("*") && avatarCipher && avatarName && avatarName.length <= 16 && (!fax || /^.+@.+$/.test(fax)) && DB.ta(async (db) => { if (await DB.getAvatarAsAvatarID(db, avatarID)) { return false; } else { return DB.postAvatar( avatarID, await getDBCipher(avatarCipher), avatarName, avatarIP, fax, getDatetime(), ); } }) ); }; export const quitTotem = async (avatarID: string) => { return DB.ta(async (db) => Promise.all([ DB.setLastDate(db, avatarID, getDatetime()), DB.wipeTotem(db, avatarID), ]), ); }; export const getAvatarDrawing = async (avatarID: string) => { try { return await rf(join(AVATAR_ENTRY_PATH, avatarID + ".png")); } catch (e: any) { if (e.code === "ENOENT") { return null; } throw e; } }; export const getLatestAvatars = async () => { return DB.getLatestAvatars(); }; export const doInvalidateDrawing = async (avatarID: string) => { return wwwAXIOS.patch(`${Configure.www.qwilight}/drawing`, avatarID); };