116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import {
|
|
AuthenticationCreds,
|
|
AuthenticationState,
|
|
proto,
|
|
} from '@adiwajshing/baileys';
|
|
import { BufferJSON, initAuthCreds } from '@adiwajshing/baileys/lib/Utils';
|
|
import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
|
|
import { join } from 'path';
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
const AsyncLock = require('async-lock');
|
|
|
|
// We need to lock files due to the fact that we are using async functions to read and write files
|
|
// https://github.com/WhiskeySockets/Baileys/issues/794
|
|
// https://github.com/nodejs/node/issues/26338
|
|
// Default pending is 1000, set it to infinity
|
|
// https://github.com/rogierschouten/async-lock/issues/63
|
|
const fileLock = new AsyncLock({ maxPending: Infinity });
|
|
|
|
/**
|
|
* stores the full authentication state in a single folder.
|
|
* Far more efficient than singlefileauthstate
|
|
*
|
|
* Again, I wouldn't endorse this for any production level use other than perhaps a bot.
|
|
* Would recommend writing an auth state for use with a proper SQL or No-SQL DB
|
|
* */
|
|
export const useMultiFileAuthState = async (
|
|
folder: string,
|
|
): Promise<{
|
|
state: AuthenticationState;
|
|
saveCreds: () => Promise<void>;
|
|
}> => {
|
|
const writeData = (data: any, file: string) => {
|
|
const filePath = join(folder, fixFileName(file));
|
|
return fileLock.acquire(filePath, () =>
|
|
writeFile(join(filePath), JSON.stringify(data, BufferJSON.replacer)),
|
|
);
|
|
};
|
|
|
|
const readData = async (file: string) => {
|
|
try {
|
|
const filePath = join(folder, fixFileName(file));
|
|
const data = await fileLock.acquire(filePath, () =>
|
|
readFile(filePath, { encoding: 'utf-8' }),
|
|
);
|
|
return JSON.parse(data, BufferJSON.reviver);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const removeData = async (file: string) => {
|
|
try {
|
|
const filePath = join(folder, fixFileName(file));
|
|
await fileLock.acquire(filePath, () => unlink(filePath));
|
|
} catch {}
|
|
};
|
|
|
|
const folderInfo = await stat(folder).catch(() => {
|
|
return null;
|
|
});
|
|
if (folderInfo) {
|
|
if (!folderInfo.isDirectory()) {
|
|
throw new Error(
|
|
`found something that is not a directory at ${folder}, either delete it or specify a different location`,
|
|
);
|
|
}
|
|
} else {
|
|
await mkdir(folder, { recursive: true });
|
|
}
|
|
|
|
const fixFileName = (file?: string) =>
|
|
file?.replace(/\//g, '__')?.replace(/:/g, '-') || '';
|
|
|
|
const creds: AuthenticationCreds =
|
|
(await readData('creds.json')) || initAuthCreds();
|
|
|
|
return {
|
|
state: {
|
|
creds,
|
|
keys: {
|
|
get: async (type, ids) => {
|
|
const data = {};
|
|
await Promise.all(
|
|
ids.map(async (id) => {
|
|
let value = await readData(`${type}-${id}.json`);
|
|
if (type === 'app-state-sync-key' && value) {
|
|
value = proto.Message.AppStateSyncKeyData.fromObject(value);
|
|
}
|
|
|
|
data[id] = value;
|
|
}),
|
|
);
|
|
|
|
return data;
|
|
},
|
|
set: async (data) => {
|
|
const tasks: Promise<void>[] = [];
|
|
for (const category in data) {
|
|
for (const id in data[category]) {
|
|
const value = data[category][id];
|
|
const file = `${category}-${id}.json`;
|
|
tasks.push(value ? writeData(value, file) : removeData(file));
|
|
}
|
|
}
|
|
|
|
await Promise.all(tasks);
|
|
},
|
|
},
|
|
},
|
|
saveCreds: () => {
|
|
return writeData(creds, 'creds.json');
|
|
},
|
|
};
|
|
};
|