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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
/**
* Helper functions to check if the request uses
* corresponding method.
*
*/
const Method = (method) => (req) => req.method.toLowerCase() === method.toLowerCase();
const Get = Method('get');
const Post = Method('post');
const Path = (regExp) => (req) => {
const url = new URL(req.url);
const path = url.pathname;
return path.match(regExp) && path.match(regExp)[0] === path;
};
/*
* The regex to get the bot_token and api_method from request URL
* as the first and second backreference respectively.
*/
const URL_PATH_REGEX = /^\/bot(?<bot_token>[^/]+)\/(?<api_method>[a-z]+)/i;
/**
* Router handles the logic of what handler is matched given conditions
* for each request
*/
class Router {
constructor() {
this.routes = [];
}
handle(conditions, handler) {
this.routes.push({
conditions,
handler,
});
return this;
}
get(url, handler) {
return this.handle([Get, Path(url)], handler);
}
post(url, handler) {
return this.handle([Post, Path(url)], handler);
}
all(handler) {
return this.handler([], handler);
}
route(req) {
const route = this.resolve(req);
if (route) {
return route.handler(req);
}
const description = 'No matching route found';
const error_code = 404;
return new Response(
JSON.stringify({
ok: false,
error_code,
description,
}),
{
status: error_code,
statusText: description,
headers: {
'content-type': 'application/json',
},
}
);
}
/**
* It returns the matching route that returns true
* for all the conditions if any.
*/
resolve(req) {
return this.routes.find((r) => {
if (!r.conditions || (Array.isArray(r) && !r.conditions.length)) {
return true;
}
if (typeof r.conditions === 'function') {
return r.conditions(req);
}
return r.conditions.every((c) => c(req));
});
}
}
/**
* Sends a POST request with JSON data to Telegram Bot API
* and reads in the response body.
* @param {Request} request the incoming request
*/
async function handler(request) {
// Extract the URl method from the request.
const { url, ..._request } = request;
const { pathname: path, search } = new URL(url);
// Leave the first match as we are interested only in backreferences.
const { bot_token, api_method } = path.match(URL_PATH_REGEX).groups;
// Build the URL
const api_url = 'https://api.telegram.org/bot' + bot_token + '/' + api_method + search;
// Get the response from API.
const response = await fetch(api_url, _request);
const result = await response.text();
const res = new Response(result, _request);
res.headers.set('Content-Type', 'application/json');
return res;
}
/**
* Handles the incoming request.
* @param {Request} request the incoming request.
*/
async function handleRequest(request) {
const r = new Router();
r.get(URL_PATH_REGEX, (req) => handler(req));
r.post(URL_PATH_REGEX, (req) => handler(req));
const resp = await r.route(request);
return resp;
}
/**
* Hook into the fetch event.
*/
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request));
});
|