groceries: add Picnic store (picnic.app storefront API, full range via category pages)
Deploy production / deploy (push) Successful in 1m24s

- picnic.ts: crawls the category tree, subcategory and overview pages (RSC), maps
  Picnic categories to the shared list; no EANs, so matching is by name/brand/size
- picnic-auth.ts + scripts/picnic-login.ts: one-shot login from
  /etc/grocery-tracker/picnic.env, token kept in ~/.config/groceries/picnic.env;
  2FA is never requested unattended (run picnic-login.ts by hand)
- migration: store row picnic; Picnic as own brand in stats
This commit is contained in:
stan committed 2026-10-27 09:04:26 +00:00
1 parent 079399635d
commit c094754c5b
11 files changed
+46305 -2

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
// Picnic session setup for the grocery importer (see src/lib/server/groceries/picnic-auth.ts).
//
// bun scripts/picnic-login.ts check the saved session; if missing/expired, log in once
// and, when Picnic asks for 2FA, have it text the code
// bun scripts/picnic-login.ts verify N finish that login with the texted code N
// bun scripts/picnic-login.ts resend C send the pending login's code again via C (SMS or EMAIL)
// bun scripts/picnic-login.ts status only check the saved session
//
// Never prints credentials or the auth key. One login attempt per run.
import {
PicnicAuthError,
checkAuth,
loadState,
login,
requestSmsCode,
saveState,
stateFile,
verifySmsCode
} from '../src/lib/server/groceries/picnic-auth';
const [cmd, arg] = process.argv.slice(2);
const s = loadState();
try {
if (cmd === 'verify') {
if (!arg || !/^\d{4,8}$/.test(arg)) throw new PicnicAuthError('usage: verify <code>');
await verifySmsCode(s, arg);
console.log(`picnic: 2FA verified; session saved to ${stateFile()}`);
console.log(`picnic: session ${(await checkAuth(s)) ? 'works' : 'REJECTED'}`);
} else if (cmd === 'resend') {
const channel = (arg ?? 'SMS').toUpperCase();
if (channel !== 'SMS' && channel !== 'EMAIL')
throw new PicnicAuthError('usage: resend SMS|EMAIL');
await requestSmsCode(s, channel);
console.log(
`picnic: 2FA code requested via ${channel}. Run: bun scripts/picnic-login.ts verify <code>`
);
} else if (cmd === 'status') {
console.log(`picnic: session ${(await checkAuth(s)) ? 'works' : 'missing or expired'}`);
} else if (!cmd) {
if (await checkAuth(s)) {
console.log('picnic: saved session still works; nothing to do');
} else {
saveState(s); // persist the device id before logging in with it
const r = await login(s);
if (r === 'ok') console.log(`picnic: logged in; session saved to ${stateFile()}`);
else {
await requestSmsCode(s);
console.log(
'picnic: 2FA required; Picnic is texting a code. Run: bun scripts/picnic-login.ts verify <code>'
);
process.exitCode = 2;
}
}
} else throw new PicnicAuthError(`unknown command ${cmd}`);
} catch (e) {
console.error(e instanceof Error ? e.message : String(e));
process.exitCode = 1;
}