Readme

Resy bot

This bot books restaurant reservations via Resy. Use it to snipe reservations at your favorite restaurant!

How to use it

Set up a scheduled val to call it like this:

const resyBotCron = async () => { const bookingInfo = await api(@vtdocs.resyBot, { slug: 'amaro-bar', city: 'ldn', day: '2023-07-05', start: '19:00', end: '21:00', partySize: 2, // Use https://www.val.town/settings/secrets for these! email: @me.secrets.resyEmail, password: @me.secrets.resyPassword, }); // If the val doesn't error, it successfully made a booking! // Send yourself an email like this: await @std.email({ text: bookingInfo, subject: 'resy bot made a booking for you!' }); }

How it works

This val makes the same requests that your browser would make when you reserve a slot on Resy (that's why it needs your login info – to request an auth token).

When there isn't a matching slot, this val errors and nothing else happens.

When a booking is available, this val books it and returns a description of the booking so you can email it to yourself (Resy will also email you).

This val will then stop attempting bookings for you until you change one of the arguments you're passing (it concats the non-sensitive arguments and uses this as a key).

Credit to @rlesser and @alp for their existing Resy vals (search for resy on here).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import { set } from "https://esm.town/v/vtdocs/set";
import { resyBookSlot } from "https://esm.town/v/vtdocs/resyBookSlot";
import { resyGetSlotBookingToken } from "https://esm.town/v/vtdocs/resyGetSlotBookingToken";
import { resyGetMatchingSlot } from "https://esm.town/v/vtdocs/resyGetMatchingSlot";
import { resyVenueIdFromSlugAndCity } from "https://esm.town/v/vtdocs/resyVenueIdFromSlugAndCity";
import { resyAuth } from "https://esm.town/v/vtdocs/resyAuth";
let { resyBotData } = await import("https://esm.town/v/vtdocs/resyBotData");
import { sha256 } from "https://esm.town/v/vtdocs/sha256";
export const resyBot = async (opts: {
slug: string; // amaro-bar
city: string; // ldn
day: string; // 2023-07-05
start: string; // 19:00
end: string; // 21:00
partySize: number; // 2
email: string; // resy email
password: string;
}): Promise<string> => {
const { slug, city, day, start, end, partySize, email, password } = opts;
// Avoid duplicate bookings by looking for a successful booking for the current parameters
const key = [
slug,
city,
day,
start,
end,
partySize,
await sha256(email),
].join(",");
if (resyBotData === undefined) {
resyBotData = {};
}
if (resyBotData[key] !== undefined) {
throw new Error(
`not running resy bot – successful booking exists for ${key}`,
);
}
const auth = await resyAuth(email, password);
const venue = await resyVenueIdFromSlugAndCity(
auth.token,
slug,
city,
);
// If there are no matching slots, an error is thrown
const matchingSlot = await resyGetMatchingSlot(
auth.token,
venue.id,
day,
start,
end,
partySize,
);
// At this point, there's a bookable slot (but it could still be sniped from us!)
const { bookToken } = await resyGetSlotBookingToken(
auth.token,
matchingSlot.config.token,
matchingSlot.date.start,
partySize,
);
if (auth.paymentMethods.length === 0) {
throw new Error("no payment methods on account (add one and try again)");
}
const bookingMetadata = await resyBookSlot(
auth.token,
bookToken,
auth.paymentMethods[0].id,
);
// Store successful booking to avoid duplicate bookings
resyBotData[key] = bookingMetadata;
await set("resyBotData", resyBotData);
return `Booked ${slug} in ${city} at ${matchingSlot.date.start} for ${partySize} people.
Check https://resy.com/account/reservations-and-notify for more details!`;
};
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Comments
Nobody has commented on this val yet: be the first!
July 1, 2024