basic functions
This commit is contained in:
parent
2c4443bb26
commit
fe8272e0c5
@ -1,13 +0,0 @@
|
|||||||
# http://editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = tab
|
|
||||||
tab_width = 2
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.yml]
|
|
||||||
indent_style = space
|
|
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
65
.idea/codeStyles/Project.xml
Normal file
65
.idea/codeStyles/Project.xml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<HTMLCodeStyleSettings>
|
||||||
|
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
<option name="HTML_QUOTE_STYLE" value="Single" />
|
||||||
|
<option name="HTML_ENFORCE_QUOTES" value="true" />
|
||||||
|
</HTMLCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<VueCodeStyleSettings>
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||||
|
</VueCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<option name="SOFT_MARGINS" value="140" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="SOFT_MARGINS" value="140" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<option name="SOFT_MARGINS" value="140" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Vue">
|
||||||
|
<option name="SOFT_MARGINS" value="140" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
13
.idea/forward-air.iml
Normal file
13
.idea/forward-air.iml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.wrangler" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
12
.idea/inspectionProfiles/Project_Default.xml
Normal file
12
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="customHeaders">
|
||||||
|
<set>
|
||||||
|
<option value="X-Auth-Token" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/forward-air.iml" filepath="$PROJECT_DIR$/.idea/forward-air.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
7
.idea/prettier.xml
Normal file
7
.idea/prettier.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PrettierConfiguration">
|
||||||
|
<option name="myConfigurationMode" value="AUTOMATIC" />
|
||||||
|
<option name="myRunOnSave" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"printWidth": 140,
|
|
||||||
"singleQuote": true,
|
|
||||||
"semi": true,
|
|
||||||
"useTabs": true
|
|
||||||
}
|
|
8
src/.prettierrc
Normal file
8
src/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"printWidth": 140,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
42
src/func/hook.ts
Normal file
42
src/func/hook.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Bot, Message } from '../lib/bot';
|
||||||
|
import { Env } from '../index';
|
||||||
|
import { IsValidUUID } from '../lib/uuid';
|
||||||
|
import { Status } from '../lib/status';
|
||||||
|
import { Wrap } from '../lib/wrapper';
|
||||||
|
|
||||||
|
export async function FuncHook(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||||
|
if (request.method !== 'POST') return Wrap(Status.BadRequest, 'Only POST method is allowed');
|
||||||
|
|
||||||
|
const token = request.headers.get('X-Telegram-Bot-Api-Secret-Token');
|
||||||
|
if (token !== env.TG_HOOK_TOKEN) return Wrap(Status.BadRequest, 'Invalid Token');
|
||||||
|
|
||||||
|
const message = ((await request.json()) as Message).message;
|
||||||
|
if (message.from.is_bot || message.chat.type !== 'private') return Wrap(Status.OK, 'Only private from user is allowed');
|
||||||
|
|
||||||
|
const chat_id = message.chat.id;
|
||||||
|
const text = message.text;
|
||||||
|
|
||||||
|
console.debug(`[Hook] ${chat_id} ${text}`);
|
||||||
|
|
||||||
|
if (text.startsWith('BIND:')) {
|
||||||
|
const uuid = text.split(':')[1];
|
||||||
|
if (!IsValidUUID(uuid)) return Wrap(Status.BadRequest, 'Invalid UUID');
|
||||||
|
await env.KV.put(uuid, chat_id.toString());
|
||||||
|
await new Bot(env.TG_BOT_TOKEN).send(chat_id, '[BOT] Bind Success');
|
||||||
|
return Wrap(Status.OK, 'Bind successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.startsWith('UNBIND:')) {
|
||||||
|
const uuid = text.split(':')[1];
|
||||||
|
if (!IsValidUUID(uuid)) return Wrap(Status.BadRequest, 'Invalid UUID');
|
||||||
|
await env.KV.delete(uuid);
|
||||||
|
await new Bot(env.TG_BOT_TOKEN).send(chat_id, '[BOT] Unbind Success');
|
||||||
|
return Wrap(Status.OK, 'Unbind successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle "SEND:" command
|
||||||
|
|
||||||
|
// TODO: Handle Cited Message (Text or Voice) with command
|
||||||
|
|
||||||
|
return Wrap(Status.OK, 'Nothing Executed');
|
||||||
|
}
|
24
src/func/push.ts
Normal file
24
src/func/push.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Bot } from '../lib/bot';
|
||||||
|
import { Env } from '../index';
|
||||||
|
import { IsValidUUID } from '../lib/uuid';
|
||||||
|
import { Status } from '../lib/status';
|
||||||
|
import { Wrap } from '../lib/wrapper';
|
||||||
|
|
||||||
|
export async function FuncPush(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||||
|
if (request.method !== 'POST') return Wrap(Status.BadRequest, 'Only POST method is allowed');
|
||||||
|
|
||||||
|
const token = request.headers.get('X-Auth-Token');
|
||||||
|
if (!token) return Wrap(Status.BadRequest, 'Missing X-Auth-Token header');
|
||||||
|
if (!IsValidUUID(token)) return Wrap(Status.BadRequest, 'Invalid Token');
|
||||||
|
|
||||||
|
const chat_id = await env.KV.get(token);
|
||||||
|
if (!chat_id) return Wrap(Status.BadRequest, 'Invalid Token');
|
||||||
|
|
||||||
|
const bot = new Bot(env.TG_BOT_TOKEN);
|
||||||
|
const message = await request.text();
|
||||||
|
await bot.send(+chat_id, message);
|
||||||
|
|
||||||
|
console.debug(`[Push] ${chat_id} ${message}`);
|
||||||
|
|
||||||
|
return Wrap(Status.OK, null);
|
||||||
|
}
|
45
src/index.ts
45
src/index.ts
@ -1,32 +1,29 @@
|
|||||||
/**
|
import { NotFound } from './lib/wrapper';
|
||||||
* Welcome to Cloudflare Workers! This is your first worker.
|
import { FuncPush } from './func/push';
|
||||||
*
|
import { FuncHook } from './func/hook';
|
||||||
* - Run `npm run dev` in your terminal to start a development server
|
|
||||||
* - Open a browser tab at http://localhost:8787/ to see your worker in action
|
|
||||||
* - Run `npm run deploy` to publish your worker
|
|
||||||
*
|
|
||||||
* Learn more at https://developers.cloudflare.com/workers/
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
|
KV: KVNamespace;
|
||||||
// MY_KV_NAMESPACE: KVNamespace;
|
BUCKET: R2Bucket;
|
||||||
//
|
TG_BOT_TOKEN: string;
|
||||||
// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
|
TG_HOOK_TOKEN: string;
|
||||||
// MY_DURABLE_OBJECT: DurableObjectNamespace;
|
|
||||||
//
|
|
||||||
// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
|
|
||||||
// MY_BUCKET: R2Bucket;
|
|
||||||
//
|
|
||||||
// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
|
|
||||||
// MY_SERVICE: Fetcher;
|
|
||||||
//
|
|
||||||
// Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/
|
|
||||||
// MY_QUEUE: Queue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Handler = (request: Request, env: Env, ctx: ExecutionContext) => Promise<Response>;
|
||||||
|
|
||||||
|
const routes: { [page: string]: Handler } = {
|
||||||
|
'/push': FuncPush,
|
||||||
|
'/hook': FuncHook,
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||||
return new Response('Hello World!');
|
const { pathname } = new URL(request.url);
|
||||||
|
for (const route in routes) {
|
||||||
|
if (pathname === route) {
|
||||||
|
return routes[route](request, env, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NotFound();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
69
src/lib/bot.ts
Normal file
69
src/lib/bot.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// import { console } from '@cloudflare/workers-types';
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
update_id: number;
|
||||||
|
message: {
|
||||||
|
message_id: number;
|
||||||
|
from: {
|
||||||
|
id: number;
|
||||||
|
is_bot: boolean;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
chat: {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
date: number;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Bot {
|
||||||
|
readonly token: string = '';
|
||||||
|
|
||||||
|
constructor(token: string) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch(): Promise<Message[]> {
|
||||||
|
const url = `https://api.telegram.org/bot${this.token}/getUpdates`;
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.debug(`Fetch message response: ${response.status} ${response.body}`);
|
||||||
|
|
||||||
|
const body = (await response.json()) as { ok: boolean; result: Message[] };
|
||||||
|
|
||||||
|
if (response.ok && body.ok) return body.result;
|
||||||
|
else return Promise.reject(`Fetch message failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(chat_id: number, message: string): Promise<void> {
|
||||||
|
console.log(`Message sent to ${chat_id}: ${message}`);
|
||||||
|
|
||||||
|
const url = `https://api.telegram.org/bot${this.token}/sendMessage`;
|
||||||
|
const body = {
|
||||||
|
chat_id: chat_id,
|
||||||
|
text: message,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.debug(`Send message response: ${response.status} ${response.body}`);
|
||||||
|
|
||||||
|
if (response.ok) return Promise.resolve();
|
||||||
|
else return Promise.reject(`Send message failed`);
|
||||||
|
}
|
||||||
|
}
|
6
src/lib/status.ts
Normal file
6
src/lib/status.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const enum Status {
|
||||||
|
OK = 200,
|
||||||
|
BadRequest = 400,
|
||||||
|
NotFound = 404,
|
||||||
|
InternalServerError = 500,
|
||||||
|
}
|
9
src/lib/uuid.ts
Normal file
9
src/lib/uuid.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function GenUUID(): string {
|
||||||
|
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) =>
|
||||||
|
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IsValidUUID(uuid: string): boolean {
|
||||||
|
return uuid.match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i) !== null;
|
||||||
|
}
|
20
src/lib/wrapper.ts
Normal file
20
src/lib/wrapper.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Status } from './status';
|
||||||
|
|
||||||
|
export async function Wrap(status: Status, body: any): Promise<Response> {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: status,
|
||||||
|
body: status == Status.OK ? body : null,
|
||||||
|
message: status == Status.OK ? null : body,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function NotFound(): Promise<Response> {
|
||||||
|
return Wrap(Status.NotFound, 'Not Found');
|
||||||
|
}
|
@ -10,15 +10,15 @@ compatibility_date = "2023-11-21"
|
|||||||
|
|
||||||
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
|
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
|
||||||
# Docs: https://developers.cloudflare.com/workers/runtime-apis/kv
|
# Docs: https://developers.cloudflare.com/workers/runtime-apis/kv
|
||||||
# [[kv_namespaces]]
|
[[kv_namespaces]]
|
||||||
# binding = "MY_KV_NAMESPACE"
|
binding = "KV"
|
||||||
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
id = "b1d40d195c9b4bc8ab53d94e15564faa"
|
||||||
|
|
||||||
# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
|
# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
|
||||||
# Docs: https://developers.cloudflare.com/r2/api/workers/workers-api-usage/
|
# Docs: https://developers.cloudflare.com/r2/api/workers/workers-api-usage/
|
||||||
# [[r2_buckets]]
|
[[r2_buckets]]
|
||||||
# binding = "MY_BUCKET"
|
binding = "BUCKET"
|
||||||
# bucket_name = "my-bucket"
|
bucket_name = "forward-air"
|
||||||
|
|
||||||
# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
|
# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
|
||||||
# Docs: https://developers.cloudflare.com/queues/get-started
|
# Docs: https://developers.cloudflare.com/queues/get-started
|
||||||
|
Loading…
Reference in New Issue
Block a user