147 lines
4.1 KiB
TypeScript
147 lines
4.1 KiB
TypeScript
import { loadState, saveState } from "./state.ts";
|
|
import sendMessage from "./webhook.ts";
|
|
|
|
// grab this from https://octopus.energy/dashboard/new/accounts/personal-details/api-access
|
|
const OCTOPUS_API_KEY = Deno.env.get("OCTOPUS_API_KEY");
|
|
// grab this from octopus dashboard - looks something like A-XXXXXXXX
|
|
const OCTOPUS_ACCOUNT_NUMBER = Deno.env.get("OCTOPUS_ACCOUNT_NUMBER");
|
|
// webhook url
|
|
const WEBHOOK_URL = Deno.env.get("WEBHOOK_URL");
|
|
|
|
const OCTOPUS_API_ENDPOINT = "https://api.octopus.energy/v1/graphql/";
|
|
|
|
const OFFER_SLUG_MAP: { [slug: string]: string } = {
|
|
"greggs": "Greegs",
|
|
"caffe-nero": "Caffè Nero"
|
|
};
|
|
|
|
if (OCTOPUS_API_KEY === undefined || OCTOPUS_ACCOUNT_NUMBER === undefined) {
|
|
throw new Error("Octopus API key and account number are required!");
|
|
}
|
|
|
|
if (WEBHOOK_URL === undefined) {
|
|
throw new Error("Webhook URL is required!");
|
|
}
|
|
|
|
async function graphQlRequest(query: string, variables: { [key: string]: any }, auth?: string): Promise<any> {
|
|
const headers: Headers = new Headers({ "Content-Type": "application/json" });
|
|
|
|
if (auth) {
|
|
headers.set("Authorization", auth);
|
|
}
|
|
|
|
const res = await fetch(OCTOPUS_API_ENDPOINT, {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
query,
|
|
variables
|
|
}),
|
|
headers
|
|
});
|
|
|
|
if (res.status !== 200) {
|
|
throw new Error(`GraphQL request failed with status ${res.status}`);
|
|
}
|
|
|
|
const jsonData = await res.json();
|
|
|
|
if (jsonData.errors) {
|
|
// deno-lint-ignore no-explicit-any
|
|
const errorMessages = jsonData.errors.map((m: any) => m.message);
|
|
throw new Error(`GraphQL request failed: ${errorMessages.join(". ")}.`);
|
|
}
|
|
|
|
return jsonData.data;
|
|
}
|
|
|
|
async function checkCoffee(): Promise<void> {
|
|
console.log("Fetching API token...");
|
|
const token = (await graphQlRequest(
|
|
/* GraphQL */ `
|
|
mutation ($apiKey: String!) {
|
|
obtainKrakenToken(input: {
|
|
APIKey: $apiKey
|
|
}) {
|
|
token
|
|
}
|
|
}`,
|
|
{ apiKey: OCTOPUS_API_KEY }
|
|
)).obtainKrakenToken.token;
|
|
|
|
const state = await loadState();
|
|
|
|
console.log("Fetching offers...");
|
|
const offers = await graphQlRequest(
|
|
/* GraphQL */ `
|
|
query ($accountNumber: String!, $first: Int!) {
|
|
octoplusOfferGroups(accountNumber: $accountNumber, first: $first) {
|
|
edges {
|
|
node {
|
|
octoplusOffers {
|
|
slug
|
|
claimAbility {
|
|
canClaimOffer
|
|
cannotClaimReason
|
|
}
|
|
claimBy
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`,
|
|
{
|
|
accountNumber: OCTOPUS_ACCOUNT_NUMBER,
|
|
first: 12,
|
|
},
|
|
token,
|
|
);
|
|
|
|
console.log(`Found ${offers.octoplusOfferGroups.edges.length} offers`);
|
|
|
|
const messages = [];
|
|
|
|
for (const { node } of offers.octoplusOfferGroups.edges) {
|
|
for (const offer of node.octoplusOffers) {
|
|
if (offer.slug !== "greggs" && offer.slug !== "caffe-nero") continue;
|
|
|
|
const friendlyName = `${OFFER_SLUG_MAP[offer.slug]} coffee offer`;
|
|
|
|
if (offer.slug in state) {
|
|
if (offer.claimAbility.canClaimOffer !== state[offer.slug].canClaimOffer) {
|
|
if (offer.claimAbility.canClaimOffer) {
|
|
messages.push(`${friendlyName} is now claimable! Claim by ${offer.claimBy}`);
|
|
} else {
|
|
messages.push(`${friendlyName} is no longer claimable (${offer.claimAbility.cannotClaimReason})`);
|
|
}
|
|
} else if (offer.claimBy !== state[offer.slug].claimBy) {
|
|
messages.push(`${friendlyName} must be claimed by ${offer.claimBy}.`);
|
|
}
|
|
} else if (offer.claimAbility.canClaimOffer) {
|
|
messages.push(`New ${friendlyName} found! Claim it by ${offer.claimBy}`);
|
|
}
|
|
|
|
state[offer.slug] = {
|
|
canClaimOffer: offer.claimAbility.canClaimOffer,
|
|
claimBy: offer.claimBy,
|
|
cannotClaimReason: offer.claimAbility.cannotClaimReason,
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`Sending ${messages.length} notifications...`);
|
|
|
|
for (const msg of messages) {
|
|
await sendMessage(WEBHOOK_URL!, msg);
|
|
}
|
|
|
|
await saveState(state);
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
try {
|
|
checkCoffee();
|
|
} catch (ex) {
|
|
await sendMessage(WEBHOOK_URL!, `Failed to check for coffee: ${ex}`);
|
|
throw ex;
|
|
}
|
|
}
|