-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmain.ts
More file actions
141 lines (129 loc) · 4.37 KB
/
main.ts
File metadata and controls
141 lines (129 loc) · 4.37 KB
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
import { getConfig } from "./src/config/main.ts";
import { parseSubscription } from "./src/parser/main.ts";
// Define a structured response type
interface JsonResponse {
error?: string;
details?: string;
[key: string]: unknown;
}
export default {
async fetch(request: Request): Promise<Response> {
try {
const url = new URL(request.url);
const queryParams = Object.fromEntries(url.searchParams.entries());
// Provide a help message for root path with no query params
if (url.pathname === "/" && Object.keys(queryParams).length === 0) {
return createJsonResponse(
{
message:
"Welcome to v2sing! Use the 'sub' query parameter to provide a subscription URL. For more information, visit: https://github.com/printfer/v2sing",
},
200,
);
}
// Validate the `sub` query parameter
const subscriptionUrl = queryParams.sub;
if (!subscriptionUrl) {
return createJsonResponse(
{ error: "Missing 'sub' query parameter" },
400,
);
}
// Fetch and parse subscription content
const subscriptionContent = await fetchSubscriptionContent(
subscriptionUrl,
);
const [singBoxSubscription, parserInfo] = parseSubscription(
subscriptionContent,
);
// If `config` query parameter is provided, fetch the template
const configTemplateUrl = queryParams.config;
let singBoxConfig;
if (configTemplateUrl) {
try {
const configTemplate = await fetchConfigTemplate(configTemplateUrl);
singBoxConfig = getConfig(singBoxSubscription, configTemplate);
} catch (error) {
return createJsonResponse(
{ error: "Error fetching config template", details: error.message },
500,
);
}
} else {
singBoxConfig = getConfig(singBoxSubscription);
}
// Logs parser success and errors in a structured format
if (parserInfo.failedLines.length > 0) {
console.log("Failed Line Details:");
parserInfo.failedLines.forEach(({ line, error }) => {
console.log(`- Line: ${line}`);
console.log(` Error: ${error}`);
});
}
// Logs parser statistics
console.log("Parser Summary:");
console.log(`- Total Successful Parses: ${parserInfo.totalSuccess}`);
console.log(`- Total Failed Parses: ${parserInfo.totalFailed}`);
// Logs query parameters for debugging
console.log("Query Information:");
Object.entries(queryParams).forEach(([key, value]) => {
console.log(`- ${key}: ${value}`);
});
return createJsonResponse(singBoxConfig);
} catch (error) {
return createJsonResponse(
{ error: "Failed to process subscription", details: error.message },
500,
);
}
},
};
// Function to validate a URL
function validateUrl(url: string): void {
try {
new URL(url); // Throws an error if URL is invalid
} catch {
throw new Error(`Invalid URL provided: ${url}`);
}
}
/**
* Fetches a configuration template from the provided URL.
* @param url - The URL to fetch the configuration template from.
*/
async function fetchConfigTemplate(url: string): Promise<unknown> {
validateUrl(url); // Ensure the config template URL is valid
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Failed to fetch config template from ${url}: ${response.statusText}`,
);
}
return response.json();
}
/**
* Fetches and returns the subscription content, handling BOM removal.
* @param url - The subscription URL to fetch content from.
*/
async function fetchSubscriptionContent(url: string): Promise<string> {
validateUrl(url); // Ensure the subscription URL is valid
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch subscription URL: ${response.statusText}`);
}
// Safely remove BOM if present
return (await response.text()).replace(/^\ufeff/, "");
}
/**
* Creates a standardized JSON response.
* @param body - The JSON object to include in the response.
* @param status - HTTP status code (default: 200).
*/
function createJsonResponse(
body: JsonResponse,
status: number = 200,
): Response {
return new Response(JSON.stringify(body, null, 2), {
status,
headers: { "Content-Type": "application/json" },
});
}