first commit
commit
cc006c8fbb
|
@ -0,0 +1,12 @@
|
|||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
|
@ -0,0 +1,172 @@
|
|||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
\*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
\*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
\*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
\*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
.cache/
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.\*
|
||||
|
||||
# wrangler project
|
||||
|
||||
.dev.vars
|
||||
.wrangler/
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"printWidth": 140,
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"useTabs": true
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "ancient-river-4662",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev",
|
||||
"start": "wrangler dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.4",
|
||||
"wrangler": "^4.14.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.804.0",
|
||||
"@aws-sdk/node-http-handler": "^3.370.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "ancient-river-4662",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ancient-river-4662",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "ancient-river-4662",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,386 @@
|
|||
var __defProp = Object.defineProperty;
|
||||
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
||||
|
||||
// src/worker.js
|
||||
var __defProp2 = Object.defineProperty;
|
||||
var __name2 = /* @__PURE__ */ __name((target, value) => __defProp2(target, "name", { value, configurable: true }), "__name");
|
||||
var worker_default = {
|
||||
async fetch(request, env, ctx) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname.startsWith("/status/")) {
|
||||
const eventId = url.pathname.split("/")[2];
|
||||
return await handleStatusPage(eventId, env);
|
||||
}
|
||||
if (request.method !== "POST") return new Response("Only POST", { status: 405 });
|
||||
const update = await request.json();
|
||||
const message = update.message;
|
||||
const callback = update.callback_query;
|
||||
const inlineQuery = update.inline_query;
|
||||
if (message?.text === "/start") {
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, "Welcome To Gebels Manager! Use /create_event to create an event or /list to see all events.");
|
||||
return new Response("OK");
|
||||
}
|
||||
|
||||
if (message?.text === "/list") {
|
||||
const events = await listEvents(env.EVENTS);
|
||||
if (!events.length) {
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, "\u{1F4ED} No events found.");
|
||||
return new Response("OK");
|
||||
}
|
||||
for (const event of events) {
|
||||
|
||||
const text = `*${event.title}*
|
||||
${event.description}
|
||||
|
||||
[\u2705 View Status](https://ancient-river-4662.siramirmoghi3.workers.dev/status/${event.id})`;
|
||||
const markup = {
|
||||
inline_keyboard: [[
|
||||
{ text: "\u2705 Accept", callback_data: `accept_${event.id}` },
|
||||
{ text: "\u274C Refuse", callback_data: `refuse_${event.id}` },
|
||||
{ text: "🗑️ Delete", callback_data: `delete_${event.id}` },
|
||||
]]
|
||||
};
|
||||
await sendPhoto(env.BOT_TOKEN, message.chat.id, event.image, text, "Markdown", markup);
|
||||
}
|
||||
return new Response("OK");
|
||||
}
|
||||
if (message?.text === "/cancel") {
|
||||
await env.STATES.delete(message.from.id.toString());
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, "\u274C Event creation canceled.");
|
||||
return new Response("OK");
|
||||
}
|
||||
if (message?.text === "/create_event") {
|
||||
await env.STATES.put(message.from.id.toString(), JSON.stringify({ step: "title" }));
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, "Please enter the *title* of the event:", "Markdown");
|
||||
return new Response("OK");
|
||||
}
|
||||
if (inlineQuery) {
|
||||
const query = inlineQuery.query.trim().toLowerCase();
|
||||
const allEvents = await listEvents(env.EVENTS);
|
||||
const filtered = allEvents.filter((e) => e.title.toLowerCase().includes(query)).sort((a, b) => b.createdAt - a.createdAt).slice(0, 5);
|
||||
const results = await Promise.all(filtered.map(async (e) => {
|
||||
return {
|
||||
type: "photo",
|
||||
id: e.id,
|
||||
photo_file_id: e.image,
|
||||
// Using file_id for cached photo
|
||||
caption: `*${e.title}*
|
||||
${e.description}
|
||||
|
||||
[\u2705 View Status](https://ancient-river-4662.siramirmoghi3.workers.dev/status/${e.id})`,
|
||||
parse_mode: "Markdown",
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: "\u2705 Accept", callback_data: `accept_${e.id}` },
|
||||
{ text: "\u274C Refuse", callback_data: `refuse_${e.id}` }
|
||||
]]
|
||||
}
|
||||
};
|
||||
}));
|
||||
return await respondInlineQuery(env.BOT_TOKEN, inlineQuery.id, results);
|
||||
}
|
||||
if (callback) return await handleCallback(callback, env);
|
||||
if (message) {
|
||||
const userId = message.from.id.toString();
|
||||
const stateRaw = await env.STATES.get(userId);
|
||||
if (!stateRaw) return new Response("No state");
|
||||
const state = JSON.parse(stateRaw);
|
||||
const { step, data = {} } = state;
|
||||
if (step === "title") {
|
||||
data.title = message.text;
|
||||
await env.STATES.put(userId, JSON.stringify({ step: "description", data }));
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, "Enter the *description*:", "Markdown");
|
||||
} else if (step === "description") {
|
||||
data.description = message.text;
|
||||
await env.STATES.put(userId, JSON.stringify({ step: "image", data }));
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, "Please send an *image* for the event.", "Markdown");
|
||||
} else if (step === "image" && message.photo) {
|
||||
const fileId = message.photo.at(-1).file_id;
|
||||
await env.STATES.put(userId, JSON.stringify({ step: 'limit', data: { ...data, fileId } }));
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, `Please enter a valid number for the acceptance limit or enter 0 for unlimited`, "Markdown");
|
||||
} else if (step === 'limit') {
|
||||
const limit = parseInt(message.text);
|
||||
if (isNaN(limit)) {
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, 'Please enter a valid number for the acceptance limit or enter 0 for unlimited.');
|
||||
} else {
|
||||
data.limit = (limit === 0) ? null : limit; // 0 means no limit
|
||||
const createdAt = new Date().toISOString(); // Track creation time
|
||||
const eventId = crypto.randomUUID();
|
||||
const eventData = {
|
||||
...data,
|
||||
limit: data.limit,
|
||||
image: data.fileId, // Move fileId here in the event creation
|
||||
id: eventId,
|
||||
creatorId: message.from.id,
|
||||
createdAt, // Add the creation timestamp
|
||||
};
|
||||
|
||||
// Store event in the database
|
||||
await env.EVENTS.put(eventId, JSON.stringify(eventData));
|
||||
|
||||
// Clear the state after event creation
|
||||
await env.STATES.delete(userId);
|
||||
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, `✅ Event created: *${data.title}*`, 'Markdown');
|
||||
}
|
||||
} else {
|
||||
await sendMessage(env.BOT_TOKEN, message.chat.id, "Unexpected input. Try /create_event again.");
|
||||
await env.STATES.delete(userId);
|
||||
}
|
||||
}
|
||||
return new Response("OK");
|
||||
} catch (err) {
|
||||
console.error("Unhandled Exception:", {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
name: err.name
|
||||
});
|
||||
return new Response("Internal Error", { status: 500 });
|
||||
}
|
||||
}
|
||||
};
|
||||
async function handleCallback(callback, env) {
|
||||
const data = callback.data;
|
||||
const [action, eventId] = data.split("_");
|
||||
const user = callback.from.username || callback.from.first_name || "unknown";
|
||||
const [rawAccepts, rawRefuses] = await Promise.all([
|
||||
env.ACCEPTS.get(eventId),
|
||||
env.REFUSES.get(eventId)
|
||||
]);
|
||||
const accepts = new Set(rawAccepts ? JSON.parse(rawAccepts) : []);
|
||||
const refuses = new Set(rawRefuses ? JSON.parse(rawRefuses) : []);
|
||||
accepts.delete(user);
|
||||
refuses.delete(user);
|
||||
const eventRaw = await env.EVENTS.get(eventId);
|
||||
if (!eventRaw) return new Response("Event not found");
|
||||
const event = JSON.parse(eventRaw);
|
||||
if (action === "delete") {
|
||||
// Only allow creator to delete the event
|
||||
|
||||
console.log(callback.from.id, event.creatorId);
|
||||
if (callback.from.id !== event.creatorId) {
|
||||
await sendMessage(env.BOT_TOKEN, callback.message.chat.id, "Only the event creator can delete this event.");
|
||||
return new Response("Unauthorized");
|
||||
}
|
||||
|
||||
// Delete event and all related data
|
||||
await Promise.all([
|
||||
env.EVENTS.delete(eventId),
|
||||
env.ACCEPTS.delete(eventId),
|
||||
env.REFUSES.delete(eventId)
|
||||
]);
|
||||
|
||||
// Edit message or send update
|
||||
await fetch(`https://api.telegram.org/bot${env.BOT_TOKEN}/editMessageText`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
chat_id: callback.message.chat.id,
|
||||
message_id: callback.message.message_id,
|
||||
text: "🗑️ Event deleted by creator."
|
||||
})
|
||||
});
|
||||
|
||||
return new Response("Event deleted");
|
||||
}
|
||||
if (action === "accept") {
|
||||
|
||||
// Check if the acceptance limit has been reached
|
||||
if (accepts.has(user)) {
|
||||
await sendMessage(env.BOT_TOKEN, callback.message.chat.id, `You have already accepted the event *${event.title}*`, 'Markdown');
|
||||
return new Response('OK');
|
||||
}
|
||||
if (event.limit !== 0 && event.limit !== null && accepts.size >= event.limit) {
|
||||
await sendMessage(env.BOT_TOKEN, callback.message.chat.id, `Sorry, the event *${event.title}* has reached its acceptance limit.`, 'Markdown');
|
||||
return new Response('OK');
|
||||
}
|
||||
|
||||
accepts.add(user);
|
||||
} else if (action === "refuse") {
|
||||
refuses.add(user);
|
||||
}
|
||||
await Promise.all([
|
||||
env.ACCEPTS.put(eventId, JSON.stringify([...accepts])),
|
||||
env.REFUSES.put(eventId, JSON.stringify([...refuses]))
|
||||
]);
|
||||
|
||||
|
||||
|
||||
const creatorId = event.creatorId;
|
||||
const creatorMessage = `Your event *${event.title}* has received a new ${action === "accept" ? "acceptance ✅" : "refusal ❌"}.`;
|
||||
await sendMessage(env.BOT_TOKEN, creatorId, creatorMessage, "Markdown");
|
||||
|
||||
|
||||
|
||||
// Wait for all notifications to be sent
|
||||
|
||||
if (!eventRaw) return new Response("Event not found");
|
||||
await fetch(`https://api.telegram.org/bot${env.BOT_TOKEN}/answerCallbackQuery`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
callback_query_id: callback.id,
|
||||
text: `You selected ${action === "accept" ? "\u2705 Accept" : "\u274C Refuse"}`
|
||||
})
|
||||
});
|
||||
return new Response("OK");
|
||||
}
|
||||
__name(handleCallback, "handleCallback");
|
||||
async function sendMessage(token, chat_id, text, parse_mode) {
|
||||
return fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ chat_id, text, parse_mode })
|
||||
});
|
||||
}
|
||||
__name(sendMessage, "sendMessage");
|
||||
async function respondInlineQuery(token, queryId, results) {
|
||||
return fetch(`https://api.telegram.org/bot${token}/answerInlineQuery`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ inline_query_id: queryId, results, cache_time: 0 })
|
||||
});
|
||||
}
|
||||
__name(respondInlineQuery, "respondInlineQuery");
|
||||
async function listEvents(KV) {
|
||||
const list = await KV.list();
|
||||
return await Promise.all(
|
||||
list.keys.map(async (k) => {
|
||||
const raw = await KV.get(k.name);
|
||||
return JSON.parse(raw);
|
||||
})
|
||||
);
|
||||
}
|
||||
__name(listEvents, "listEvents");
|
||||
async function handleStatusPage(eventId, env) {
|
||||
const [acceptsRaw, refusesRaw, eventRaw] = await Promise.all([
|
||||
env.ACCEPTS.get(eventId),
|
||||
env.REFUSES.get(eventId),
|
||||
env.EVENTS.get(eventId)
|
||||
]);
|
||||
console.log("eventRaw", eventId, acceptsRaw, refusesRaw, eventRaw);
|
||||
if (!eventRaw) return new Response("Event not found", { status: 404 });
|
||||
const event = JSON.parse(eventRaw);
|
||||
const accepts = acceptsRaw ? JSON.parse(acceptsRaw) : [];
|
||||
const refuses = refusesRaw ? JSON.parse(refusesRaw) : [];
|
||||
const fileResponse = await fetch(`https://api.telegram.org/bot${env.BOT_TOKEN}/getFile?file_id=${event.image}`);
|
||||
const fileData = await fileResponse.json();
|
||||
const fileUrl = `https://api.telegram.org/file/bot${env.BOT_TOKEN}/${fileData.result.file_path}`;
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Gebels - ${event.title}</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=UnifrakturCook:wght@700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: linear-gradient(to bottom right, #2e003e, #120024);
|
||||
font-family: 'UnifrakturCook', cursive;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 2px solid #b47cff;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 4px 20px rgba(180, 124, 255, 0.3);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin-bottom: 10px;
|
||||
color: #ffe86a;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.3em;
|
||||
margin-top: 20px;
|
||||
color: #94f0ff;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 5px 10px;
|
||||
margin: 5px 0;
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ffe86a;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
color: #fff4c2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>${event.title}</h1>
|
||||
<img src="${fileUrl}" alt="${event.title}" style="width: 100%; border-radius: 8px; margin-bottom: 20px;" />
|
||||
<p>${event.description}</p>
|
||||
<h2>\u2705 Accepted</h2>
|
||||
<ul>
|
||||
${accepts.map((u) => `<li><a href="https://t.me/${u}" target="_blank">@${u}</a></li>`).join("")}
|
||||
</ul>
|
||||
<h2>\u274C Refused</h2>
|
||||
<ul>
|
||||
${refuses.map((u) => `<li><a href="https://t.me/${u}" target="_blank">@${u}</a></li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`;
|
||||
return new Response(html, {
|
||||
headers: { "Content-Type": "text/html" }
|
||||
});
|
||||
}
|
||||
__name(handleStatusPage, "handleStatusPage");
|
||||
async function sendPhoto(token, chat_id, photo, caption, parse_mode = "Markdown", reply_markup = null) {
|
||||
const payload = {
|
||||
chat_id,
|
||||
photo,
|
||||
caption,
|
||||
parse_mode,
|
||||
...reply_markup ? { reply_markup } : {}
|
||||
};
|
||||
return fetch(`https://api.telegram.org/bot${token}/sendPhoto`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
}
|
||||
__name(sendPhoto, "sendPhoto");
|
||||
__name2(handleCallback, "handleCallback");
|
||||
export {
|
||||
worker_default as default
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
name = "ancient-river-4662"
|
||||
main = "src/worker.js"
|
||||
workers_dev = true
|
||||
compatibility_date = "2025-05-08"
|
||||
account_id = "6a8d4d305440e990b98e950f6453f950"
|
||||
|
||||
kv_namespaces = [
|
||||
{ binding = "EVENTS", id = "0fc103ecca3b41d9af481cfb01ed9d52" },
|
||||
{ binding = "ACCEPTS", id = "831bf99c66a44ec5a35e10c0dc4d086c" },
|
||||
{ binding = "REFUSES", id = "36e31a9aebf04480a321234917a150a2" },
|
||||
{ binding = "STATES", id = "af6bc31086de41f38055ced84b7c9606" }
|
||||
]
|
||||
|
||||
[observability]
|
||||
enabled = true
|
||||
|
||||
|
Loading…
Reference in New Issue