This commit is contained in:
186526 2023-01-17 14:38:36 +00:00
parent 1c1dabbb33
commit 2a5e1b74c9
9 changed files with 1519 additions and 0 deletions

2
.gitignore vendored
View File

@ -102,3 +102,5 @@ dist
# TernJS port file
.tern-port
src/config.ts

1270
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "net186-bot",
"version": "0.0.1",
"description": "A looking glass telegram bot using hyperglass' openapi.",
"main": "dist/app.js",
"scripts": {
"build": "rm -rf dist/* && tsc",
"start": "node dist/app.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/186526/net186-bot.git"
},
"author": "real186526 <i@186526.xyz>",
"license": "MIT",
"bugs": {
"url": "https://github.com/186526/net186-bot/issues"
},
"homepage": "https://github.com/186526/net186-bot#readme",
"devDependencies": {
"@types/node": "^18.11.18",
"tslint": "^6.1.3",
"typescript": "^4.9.4"
},
"dependencies": {
"axios": "^1.2.2",
"ip-address": "^8.1.0",
"telegraf": "^4.11.2"
}
}

131
src/adapter/hyperglass.ts Normal file
View File

@ -0,0 +1,131 @@
import axios, { AxiosInstance } from "axios";
import { adapter, capabilities, device, info, queryError, querySuccess } from "../types/adapter";
import { Address4, Address6 } from 'ip-address';
interface deviceRaw {
name: string;
network: {
name: string;
display_name: string;
};
vrfs: {
name: string;
display_name: string;
}[];
}
interface queryTypeRaw {
name: capabilities;
display_name: string;
enable: boolean;
}
export default class Hyperglass implements adapter {
private axiosInstance: AxiosInstance;
constructor(link: string) {
this.axiosInstance = axios.create({
baseURL: link,
headers: {
"Content-Type": "application/json",
},
transformRequest: [
(data: object) => {
return JSON.stringify(data);
},
],
transformResponse: [
(data: string) => {
return JSON.parse(data);
},
],
validateStatus: function (status) {
return status < 500;
}
});
this.axiosInstance.get("/api/info").then((r) => {
if (!(r.data.version as string).includes("hyperglass")) {
throw new Error(
"Not found hyperglass in /api/info. Please check your Hyperglass URL."
);
}
});
}
async info(): Promise<info> {
const response = await this.axiosInstance.get("/api/info");
return {
name: response.data.name,
organization: response.data.organization,
};
}
async devicesRaw(): Promise<deviceRaw[]> {
const response = await this.axiosInstance.get("/api/devices");
return response.data as deviceRaw[];
}
async listQueiesTypeRaw(): Promise<queryTypeRaw[]> {
const response = await this.axiosInstance.get("/api/queries");
return response.data as queryTypeRaw[];
}
async capabilities(): Promise<capabilities[]> {
return (await this.listQueiesTypeRaw()).reduce(
(previous: capabilities[], current) => {
if (current.enable) {
previous.push(current.name);
}
return previous;
},
[]
);
}
async devices(): Promise<device[]> {
const devicesRaw = await this.devicesRaw();
const globalCapabilities = await this.capabilities();
return devicesRaw.map((k) => {
return {
name: k.name,
group: k.network.display_name,
capabilities: globalCapabilities,
vrfs: k.vrfs.map((k) => k.name),
};
});
}
async query(capability: capabilities, device: device, vrf: string, target: Address4 | Address6): Promise<queryError | querySuccess> {
const response = await this.axiosInstance.post("api/query/", {
query_location: device.name.replace(/[^A-Za-z0-9\_\-\s]/g,"").replaceAll(" ","_").toLowerCase(),
query_target: target.address,
query_vrf: vrf,
query_type: capability,
});
if (response.status == 200) {
return {
level: response.data.level as "success",
output: response.data.output as string,
timestamp: new Date(response.data.timestamp),
} as querySuccess;
} else if (response.status == 400) {
return {
level: response.data.level as "danger",
output: "Request Content Error: " + response.data.output as string,
} as queryError;
} else if (response.status == 422) {
return {
level: response.data.level as "danger",
output: "Request Format Error: " + response.data.output as string,
} as queryError;
} else if (response.status == 500) {
return {
level: response.data.level as "danger",
output: "Server Error: " + response.data.output as string,
} as queryError;
} else {
return {
level: "danger",
output: "Unknown Error.",
} as queryError;
}
}
}

10
src/app.ts Normal file
View File

@ -0,0 +1,10 @@
import { Context, Telegraf } from 'telegraf';
import { Update } from 'typegram';
import handleInfo from './handler/info';
import config from "./config";
const bot: Telegraf<Context<Update>> = new Telegraf(config.token);
bot.start(handleInfo(config.adapter));
bot.launch();

9
src/handler/info.ts Normal file
View File

@ -0,0 +1,9 @@
import { Context } from "telegraf";
import { adapter } from "../types/adapter";
export default function handleInfo(adapter: adapter) {
return async (ctx: Context)=>{
const info = await adapter.info();
ctx.reply(`${info.name} Bot from ${info.organization}.`)
}
}

44
src/types/adapter.d.ts vendored Normal file
View File

@ -0,0 +1,44 @@
import { Address4, Address6 } from "ip-address";
enum capabilities {
"BGP Route" = "bgp_route",
"Ping" = "ping",
"Traceroute" = "traceroute",
}
interface info {
name: string;
organization: string;
}
interface device {
name: string;
group: string;
capabilities: capabilities[];
vrfs: string[];
}
interface queryResponse {
level: "success" | "warning" | "error" | "danger";
}
interface queryError extends queryResponse {
output?: string;
}
interface querySuccess extends queryResponse {
level: "success";
output: string;
timestamp: Date;
}
interface adapter {
info(): Promise<info>;
devices(): Promise<device[]>;
query(
capability: capabilities,
device: device,
vrf: string,
target: Address4 | Address6
): Promise<queryError | querySuccess | queryResponse>;
}

4
src/types/bot.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
interface config {
token: string; // Telegram Bot token.
adapter: adapter;
}

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es2021",
"moduleResolution": "node",
"strict": true,
"sourceMap": true,
"lib": [
"es2021"
],
"skipLibCheck": true,
"rootDir": "src",
"outDir": "dist"
},
"typeRoots": [
"./src/types"
]
}