Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
lingling | a54e9402ac | |
lingling | d7f4630047 | |
lingling | c8aeaad963 | |
lingling | 4cc3befc07 | |
lingling | 9c65fb2491 | |
lingling | 6f551540f0 | |
lingling | 771d4223bc | |
lingling | a51faba9af |
File diff suppressed because it is too large
Load Diff
|
@ -14,14 +14,19 @@
|
||||||
"@quasar/extras": "^1.0.0",
|
"@quasar/extras": "^1.0.0",
|
||||||
"@waiting/base64": "^4.2.9",
|
"@waiting/base64": "^4.2.9",
|
||||||
"axios": "^1.2.1",
|
"axios": "^1.2.1",
|
||||||
|
"chai": "^5.1.2",
|
||||||
"js-base64": "^3.7.5",
|
"js-base64": "^3.7.5",
|
||||||
|
"link-chang": "file:",
|
||||||
|
"mocha": "^10.8.2",
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0",
|
"vue-router": "^4.0.0",
|
||||||
"vuex": "^4.0.1"
|
"vuex": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/preset-typescript": "^7.26.0",
|
||||||
"@quasar/app-vite": "^1.0.0",
|
"@quasar/app-vite": "^1.0.0",
|
||||||
|
"@quasar/quasar-app-extension-testing": "^2.2.0",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^12.20.21",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
"@typescript-eslint/parser": "^5.10.0",
|
||||||
|
@ -33,7 +38,7 @@
|
||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18 || ^16 || ^14.19",
|
"node": "^22 || ^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
node: 'node16',
|
node: 'node16',
|
||||||
},
|
},
|
||||||
gzip: true,
|
gzip: true,
|
||||||
vueRouterMode: 'history', // available values: 'hash', 'history'
|
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
||||||
// vueRouterBase,
|
// vueRouterBase,
|
||||||
// vueDevtools,
|
// vueDevtools,
|
||||||
// vueOptionsAPI: false,
|
// vueOptionsAPI: false,
|
||||||
|
@ -80,6 +80,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||||
devServer: {
|
devServer: {
|
||||||
// https: true
|
// https: true
|
||||||
|
port: 9090,
|
||||||
open: true, // opens browser window automatically
|
open: true, // opens browser window automatically
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"@quasar/testing": {
|
||||||
|
"harnesses": [
|
||||||
|
"unit-jest@beta"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { decode, encode } from 'js-base64'
|
||||||
|
import { getdata } from 'src/api/api';
|
||||||
|
import { VlessLink, vmess } from 'src/models/models';
|
||||||
|
import { is_ip } from './comm';
|
||||||
|
import { vmessDefault } from 'src/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建直连vmess
|
||||||
|
* @param ip 服务器IP地址
|
||||||
|
* @returns vmess链接
|
||||||
|
*/
|
||||||
|
async function CreatVmessDirect(ip: string): Promise<string> {
|
||||||
|
let tmp = ''
|
||||||
|
const api = new getdata;
|
||||||
|
const obj = JSON.parse(decode(vmessDefault))
|
||||||
|
const array = ip.split(/[\s\n]/)
|
||||||
|
for (let index = 0; index < array.length; index++) {
|
||||||
|
if (!is_ip(array[index])) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < array.length; index++) {
|
||||||
|
const name = await api.get_country(array[index])
|
||||||
|
obj.ps = name + array[index]
|
||||||
|
obj.add = array[index]
|
||||||
|
tmp += 'vmess://' + encode(JSON.stringify(obj)) + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangVmessServer() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param type 生成链接的类型 vmess |vless
|
||||||
|
*/
|
||||||
|
function CreateLink(type: 'vmess' | 'vless', linkinfo: vmess | VlessLink) {
|
||||||
|
let tmp = ''
|
||||||
|
switch (true) {
|
||||||
|
case type === 'vless':
|
||||||
|
if ('uuid' in linkinfo) {
|
||||||
|
tmp = createVlessLink(linkinfo)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case type === 'vmess':
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseVlessLink(link: string): VlessLink | null {
|
||||||
|
const regex = /^vless:/;
|
||||||
|
const match = link.match(regex);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
console.error('Invalid VLESS link format.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = match[1];
|
||||||
|
const host = match[2];
|
||||||
|
const port = parseInt(match[3], 10);
|
||||||
|
const params = parseParams(match[4]); // 解析参数
|
||||||
|
const name = match[5] ? decodeURIComponent(match[5].slice(1)) : undefined; // 去掉 "#" 并解码
|
||||||
|
|
||||||
|
return {
|
||||||
|
uuid,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
params,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseParams(paramsString?: string): Record<string, string> | undefined {
|
||||||
|
if (!paramsString || !paramsString.startsWith('?')) return undefined;
|
||||||
|
|
||||||
|
return paramsString
|
||||||
|
.slice(1) // 去掉开头的 "?"
|
||||||
|
.split('&') // 分割每个键值对
|
||||||
|
.reduce<Record<string, string>>((acc, pair) => {
|
||||||
|
const [key, value] = pair.split('=');
|
||||||
|
if (key) acc[key] = value || ''; // 处理可能的无值参数
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建vless链接
|
||||||
|
* @param param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function createVlessLink({ uuid, host, port, params, name }: VlessLink): string {
|
||||||
|
// 基本的 VLESS 链接格式
|
||||||
|
let link = `vless://${uuid}@${host}:${port}`;
|
||||||
|
|
||||||
|
// 如果有查询参数,则拼接它们
|
||||||
|
if (params && Object.keys(params).length > 0) {
|
||||||
|
const queryString = new URLSearchParams(params).toString();
|
||||||
|
link += `?${queryString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有名称字段,将名称放在 # 后面
|
||||||
|
if (name) {
|
||||||
|
link += `#${encodeURIComponent(name)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return link + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CreatVmessDirect, ChangVmessServer, CreateLink, parseVlessLink }
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { copyToClipboard, QVueGlobals } from 'quasar'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制文本
|
||||||
|
* @param string
|
||||||
|
*/
|
||||||
|
function copy(string: string, $q: QVueGlobals) {
|
||||||
|
|
||||||
|
copyToClipboard(string)
|
||||||
|
.then(() => {
|
||||||
|
|
||||||
|
// success!
|
||||||
|
$q.notify({
|
||||||
|
message: '复制成功',
|
||||||
|
color: 'positive',
|
||||||
|
position: 'top'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
|
||||||
|
// fail
|
||||||
|
$q.notify({
|
||||||
|
message: '复制失败',
|
||||||
|
color: 'negative'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*根据延迟计算颜色
|
||||||
|
* @param bili 数值 延迟单位ms
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function getColorForDelay(delay: number): string {
|
||||||
|
// 限制延迟在200到2000之间
|
||||||
|
const clampedDelay = Math.min(Math.max(delay, 200), 2000);
|
||||||
|
|
||||||
|
// 将延迟从[200, 2000]映射到[0, 1]
|
||||||
|
const normalizedDelay = (clampedDelay - 200) / (2000 - 200);
|
||||||
|
|
||||||
|
// 计算从绿色到红色的颜色变化
|
||||||
|
const green = 255 - normalizedDelay * 255;
|
||||||
|
const red = normalizedDelay * 255;
|
||||||
|
|
||||||
|
// 返回 RGB 颜色格式
|
||||||
|
return `rgb(${Math.round(red)}, ${Math.round(green)}, 0)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据延迟计算颜色 就三种颜色
|
||||||
|
* @param bili
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function signal_style(bili: number) {
|
||||||
|
let tmp = ''
|
||||||
|
if (bili >= 0 && bili <= 700) {
|
||||||
|
tmp = 'green'
|
||||||
|
} else if (bili > 700 && bili < 1400) {
|
||||||
|
tmp = '#FF9800'
|
||||||
|
} else {
|
||||||
|
tmp = 'red'
|
||||||
|
}
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
const is_ip = (ip: string) => {
|
||||||
|
const reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||||
|
return reg.test(ip);
|
||||||
|
}
|
||||||
|
function extractIPv4(ip: string): string | null {
|
||||||
|
const ipv4Regex = /(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)/;
|
||||||
|
const matches = ip.match(ipv4Regex);
|
||||||
|
|
||||||
|
// 如果没有匹配到任何IPv4地址,返回 null
|
||||||
|
if (!matches) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一个匹配到的IPv4地址
|
||||||
|
return matches[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export { copy, getColorForDelay, signal_style, is_ip, extractIPv4 }
|
|
@ -0,0 +1,152 @@
|
||||||
|
import { Base64 } from 'js-base64';
|
||||||
|
|
||||||
|
class VLESSQuery {
|
||||||
|
constructor(
|
||||||
|
public security: string,
|
||||||
|
public alpn: string[],
|
||||||
|
public sni: string,
|
||||||
|
public fp: string,
|
||||||
|
public sid: string,
|
||||||
|
public pbk: string,
|
||||||
|
public flow: string,
|
||||||
|
public encryption: string,
|
||||||
|
public type: string,
|
||||||
|
public headerType: string,
|
||||||
|
public path: string,
|
||||||
|
public host: string
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
class VLESS {
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
public uuid: string,
|
||||||
|
public server: string,
|
||||||
|
public port: number,
|
||||||
|
public query: VLESSQuery
|
||||||
|
) { }
|
||||||
|
|
||||||
|
// Base64 解码函数
|
||||||
|
// private static base64Decode(str: string): string {
|
||||||
|
// return Buffer.from(str, 'base64').toString('utf-8');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 编码 VLESS URL
|
||||||
|
encodeURL(): string {
|
||||||
|
const url = new URL(`vless://${this.uuid}@${this.server}:${this.port}`);
|
||||||
|
|
||||||
|
// 设置 Query 参数
|
||||||
|
const query = url.searchParams;
|
||||||
|
query.set('security', this.query.security);
|
||||||
|
query.set('sni', this.query.sni);
|
||||||
|
query.set('fp', this.query.fp);
|
||||||
|
query.set('sid', this.query.sid);
|
||||||
|
query.set('pbk', this.query.pbk);
|
||||||
|
query.set('flow', this.query.flow);
|
||||||
|
query.set('encryption', this.query.encryption);
|
||||||
|
query.set('type', this.query.type);
|
||||||
|
query.set('headerType', this.query.headerType);
|
||||||
|
query.set('path', this.query.path);
|
||||||
|
query.set('host', this.query.host);
|
||||||
|
|
||||||
|
// 移除空值参数
|
||||||
|
for (const [key, value] of query) {
|
||||||
|
if (value === '') {
|
||||||
|
query.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有 name,设置为 Fragment
|
||||||
|
if (this.name) {
|
||||||
|
url.hash = this.name;
|
||||||
|
} else {
|
||||||
|
url.hash = `${this.server}:${this.port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码 VLESS URL
|
||||||
|
static decodeURL(urlStr: string): VLESS {
|
||||||
|
if (!urlStr.startsWith('vless://')) {
|
||||||
|
throw new Error(`Invalid vless URL: ${urlStr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedUrl = urlStr.split('@');
|
||||||
|
const url = new URL(`http://${decodedUrl[1]}`);
|
||||||
|
const regex = /[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}/;
|
||||||
|
const match = urlStr.match(regex);
|
||||||
|
const uuid = match[0];
|
||||||
|
const hostname = url.hostname;
|
||||||
|
const port = parseInt(url.port, 10);
|
||||||
|
|
||||||
|
const query = url.searchParams;
|
||||||
|
const encryption = query.get('encryption') || '';
|
||||||
|
const security = query.get('security') || '';
|
||||||
|
const type = query.get('type') || '';
|
||||||
|
const flow = query.get('flow') || '';
|
||||||
|
const headerType = query.get('headerType') || '';
|
||||||
|
const pbk = query.get('pbk') || '';
|
||||||
|
const sid = query.get('sid') || '';
|
||||||
|
const fp = query.get('fp') || '';
|
||||||
|
const alpns = query.get('alpn') || '';
|
||||||
|
const alpn = alpns ? alpns.split(',') : [];
|
||||||
|
const sni = query.get('sni') || '';
|
||||||
|
const path = query.get('path') || '';
|
||||||
|
const host = query.get('host') || '';
|
||||||
|
|
||||||
|
// 获取 name,若为空则使用 `hostname:port`
|
||||||
|
const name = url.hash ? decodeURIComponent(url.hash.slice(1)) : `${hostname}:${port}`;
|
||||||
|
|
||||||
|
const queryObj = new VLESSQuery(
|
||||||
|
security,
|
||||||
|
alpn,
|
||||||
|
sni,
|
||||||
|
fp,
|
||||||
|
sid,
|
||||||
|
pbk,
|
||||||
|
flow,
|
||||||
|
encryption,
|
||||||
|
type,
|
||||||
|
headerType,
|
||||||
|
path,
|
||||||
|
host
|
||||||
|
);
|
||||||
|
|
||||||
|
return new VLESS(name, uuid, hostname, port, queryObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 示例用法
|
||||||
|
// function callVLESS() {
|
||||||
|
// const vless = new VLESS(
|
||||||
|
// 'Sharon-香港',
|
||||||
|
// '6adb4f43-9813-45f4-abf8-772be7db08sd',
|
||||||
|
// 'ss.com',
|
||||||
|
// 443,
|
||||||
|
// new VLESSQuery(
|
||||||
|
// 'reality',
|
||||||
|
// ['http/1.1'],
|
||||||
|
// 'ss.com',
|
||||||
|
// 'chrome',
|
||||||
|
// '',
|
||||||
|
// 'g-oxbqigzCaXqARxuyD2_vbTYeMD9zn8wnTo02S69QM',
|
||||||
|
// 'xtls-rprx-vision',
|
||||||
|
// 'none',
|
||||||
|
// 'tcp',
|
||||||
|
// 'none',
|
||||||
|
// '',
|
||||||
|
// ''
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const encoded = vless.encodeURL();
|
||||||
|
// console.log('Encoded VLESS URL:', encoded);
|
||||||
|
|
||||||
|
// const decoded = VLESS.decodeURL(encoded);
|
||||||
|
// console.log('Decoded VLESS:', decoded);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// callVLESS();
|
||||||
|
|
||||||
|
export { VLESS }
|
|
@ -0,0 +1,122 @@
|
||||||
|
class Vmess {
|
||||||
|
add?: string;
|
||||||
|
aid?: any; // This could be number or string depending on usage
|
||||||
|
alpn?: string;
|
||||||
|
fp?: string;
|
||||||
|
host?: string;
|
||||||
|
id?: string;
|
||||||
|
net?: string;
|
||||||
|
path?: string;
|
||||||
|
port?: any; // This could be a number or string
|
||||||
|
ps?: string;
|
||||||
|
scy?: string;
|
||||||
|
sni?: string;
|
||||||
|
tls?: string;
|
||||||
|
type?: string;
|
||||||
|
v?: string;
|
||||||
|
|
||||||
|
constructor(data: {
|
||||||
|
add?: string;
|
||||||
|
aid?: any;
|
||||||
|
alpn?: string;
|
||||||
|
fp?: string;
|
||||||
|
host?: string;
|
||||||
|
id?: string;
|
||||||
|
net?: string;
|
||||||
|
path?: string;
|
||||||
|
port?: any;
|
||||||
|
ps?: string;
|
||||||
|
scy?: string;
|
||||||
|
sni?: string;
|
||||||
|
tls?: string;
|
||||||
|
type?: string;
|
||||||
|
v?: string;
|
||||||
|
}) {
|
||||||
|
this.add = data.add;
|
||||||
|
this.aid = data.aid;
|
||||||
|
this.alpn = data.alpn;
|
||||||
|
this.fp = data.fp;
|
||||||
|
this.host = data.host;
|
||||||
|
this.id = data.id;
|
||||||
|
this.net = data.net;
|
||||||
|
this.path = data.path;
|
||||||
|
this.port = data.port;
|
||||||
|
this.ps = data.ps;
|
||||||
|
this.scy = data.scy;
|
||||||
|
this.sni = data.sni;
|
||||||
|
this.tls = data.tls;
|
||||||
|
this.type = data.type;
|
||||||
|
this.v = data.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编码 Vmess URL
|
||||||
|
encodeURL(): string {
|
||||||
|
// 如果备注为空,则使用服务器地址 + 端口
|
||||||
|
if (!this.ps) {
|
||||||
|
this.ps = `${this.add}:${this.port}`;
|
||||||
|
}
|
||||||
|
// 如果版本为空,则默认为 2
|
||||||
|
if (!this.v) {
|
||||||
|
this.v = '2';
|
||||||
|
}
|
||||||
|
|
||||||
|
const param = JSON.stringify(this);
|
||||||
|
return 'vmess://' + this.base64Encode(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码 Vmess URL
|
||||||
|
static decodeURL(url: string): Vmess {
|
||||||
|
if (!url.startsWith('vmess://')) {
|
||||||
|
throw new Error(`Invalid vmess URL: ${url}`);
|
||||||
|
}
|
||||||
|
const param = url.slice(8); // Remove "vmess://"
|
||||||
|
const decoded = Vmess.base64Decode(param.trim());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(decoded);
|
||||||
|
if (parsed.scy === '') {
|
||||||
|
parsed.scy = 'auto';
|
||||||
|
}
|
||||||
|
if (!parsed.ps) {
|
||||||
|
parsed.ps = parsed.add + ':' + parsed.port;
|
||||||
|
}
|
||||||
|
return new Vmess(parsed);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to parse VMESS URL: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 编码
|
||||||
|
private base64Encode(str: string): string {
|
||||||
|
return Buffer.from(str, 'utf-8').toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 解码
|
||||||
|
private static base64Decode(str: string): string {
|
||||||
|
return Buffer.from(str, 'base64').toString('utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开发者测试
|
||||||
|
function callVmessURL() {
|
||||||
|
const vmess = new Vmess({
|
||||||
|
add: 'xx.xxx.ru',
|
||||||
|
port: '2095',
|
||||||
|
aid: 0,
|
||||||
|
scy: 'auto',
|
||||||
|
net: 'ws',
|
||||||
|
type: 'none',
|
||||||
|
id: '7a737f41-b792-4260-94ff-3d864da67380',
|
||||||
|
host: 'xx.xxx.ru',
|
||||||
|
path: '/',
|
||||||
|
tls: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const encoded = vmess.encodeURL();
|
||||||
|
console.log('Encoded VMESS URL:', encoded);
|
||||||
|
|
||||||
|
const decoded = Vmess.decodeURL(encoded);
|
||||||
|
console.log('Decoded VMESS:', decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
callVmessURL();
|
|
@ -1,13 +1,32 @@
|
||||||
import { api } from 'src/boot/axios';
|
import { api } from 'src/boot/axios';
|
||||||
class getdata {
|
class getdata {
|
||||||
async get_server() {
|
async get_server() {
|
||||||
return await api.get('https://api.shagain.club/api.php?type=list');
|
return await api.get('https://api.giaogiao.uk/server/getlist');
|
||||||
}
|
}
|
||||||
async text_server(url: string) {
|
async text_server(url: string) {
|
||||||
return await api.post('https://api.shagain.club/textserver', { url });
|
const res = await api.post(
|
||||||
|
'https://service-7hslob28-1258902677.nj.apigw.tencentcs.com/release/',
|
||||||
|
{ url }
|
||||||
|
);
|
||||||
|
|
||||||
|
const tmp = { 'is_online': res.data.sataus == 400, 'time': res.data.time }
|
||||||
|
return tmp
|
||||||
}
|
}
|
||||||
async get_country(ip: string) {
|
async get_country(ip: string) {
|
||||||
return await api.post('https://api.shagain.club/api.php?type=searchip', { ip });
|
const res = await api.post('https://api.giaogiao.uk/server/searchip', {
|
||||||
|
ip,
|
||||||
|
});
|
||||||
|
// res = res.data.country
|
||||||
|
// if (typeof (res) == 'string') { return res }
|
||||||
|
// console.log(res.data.data.country)
|
||||||
|
return res.data.data.country
|
||||||
|
|
||||||
}
|
}
|
||||||
|
async get_server_ms(ip: string, port: number, to_ip: string, istls: number) {
|
||||||
|
const http = istls == 0 ? 'http' : 'https'
|
||||||
|
return this.text_server(`${http}://${ip}:${port}/${to_ip}`)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getdata };
|
export { getdata };
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
<template>
|
|
||||||
<q-item
|
|
||||||
clickable
|
|
||||||
tag="a"
|
|
||||||
target="_blank"
|
|
||||||
:href="link"
|
|
||||||
>
|
|
||||||
<q-item-section
|
|
||||||
v-if="icon"
|
|
||||||
avatar
|
|
||||||
>
|
|
||||||
<q-icon :name="icon" />
|
|
||||||
</q-item-section>
|
|
||||||
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ title }}</q-item-label>
|
|
||||||
<q-item-label caption>{{ caption }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'EssentialLink',
|
|
||||||
props: {
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
|
|
||||||
caption: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
link: {
|
|
||||||
type: String,
|
|
||||||
default: '#'
|
|
||||||
},
|
|
||||||
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,64 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<p>{{ title }}</p>
|
|
||||||
<ul>
|
|
||||||
<li v-for="todo in todos" :key="todo.id" @click="increment">
|
|
||||||
{{ todo.id }} - {{ todo.content }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>Count: {{ todoCount }} / {{ meta.totalCount }}</p>
|
|
||||||
<p>Active: {{ active ? 'yes' : 'no' }}</p>
|
|
||||||
<p>Clicks on todos: {{ clickCount }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
defineComponent,
|
|
||||||
PropType,
|
|
||||||
computed,
|
|
||||||
ref,
|
|
||||||
toRef,
|
|
||||||
Ref,
|
|
||||||
} from 'vue';
|
|
||||||
import { Todo, Meta } from './models';
|
|
||||||
|
|
||||||
function useClickCount() {
|
|
||||||
const clickCount = ref(0);
|
|
||||||
function increment() {
|
|
||||||
clickCount.value += 1
|
|
||||||
return clickCount.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { clickCount, increment };
|
|
||||||
}
|
|
||||||
|
|
||||||
function useDisplayTodo(todos: Ref<Todo[]>) {
|
|
||||||
const todoCount = computed(() => todos.value.length);
|
|
||||||
return { todoCount };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ExampleComponent',
|
|
||||||
props: {
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
todos: {
|
|
||||||
type: Array as PropType<Todo[]>,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
type: Object as PropType<Meta>,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
active: {
|
|
||||||
type: Boolean
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup (props) {
|
|
||||||
return { ...useClickCount(), ...useDisplayTodo(toRef(props, 'todos')) };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -8,8 +8,7 @@
|
||||||
:style="{ color: signal_style }" /><span>{{ time }}ms</span></div>
|
:style="{ color: signal_style }" /><span>{{ time }}ms</span></div>
|
||||||
<q-input v-model="outtext" filled autogrow readonly />
|
<q-input v-model="outtext" filled autogrow readonly />
|
||||||
<div style="text-align: center;margin-top: 1rem;">
|
<div style="text-align: center;margin-top: 1rem;">
|
||||||
<q-btn color="white" text-color="black" @click="copy('link')" label="复制" />
|
<q-btn color="white" text-color="black" @click="copy_link()" label="复制" />
|
||||||
<!-- <q-btn color="white" text-color="black" @click="copy('sub')" label="复制订阅" /> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,11 +19,13 @@ import {
|
||||||
defineComponent,
|
defineComponent,
|
||||||
computed,
|
computed,
|
||||||
ref,
|
ref,
|
||||||
|
onMounted,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { copyToClipboard } from 'quasar'
|
|
||||||
import { encode, decode } from 'js-base64';
|
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { getdata } from 'src/api/api'
|
import { copy, extractIPv4, getColorForDelay } from 'src/Util/comm';
|
||||||
|
import { vlessLink } from 'src/config';
|
||||||
|
import { getdata } from 'src/api/api';
|
||||||
|
import { CreateLink } from 'src/Util/Link';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'LinkItem',
|
name: 'LinkItem',
|
||||||
props: {
|
props: {
|
||||||
|
@ -32,88 +33,46 @@ export default defineComponent({
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
text: {
|
names: {
|
||||||
type: String
|
type: Array as () => string[],
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const online = ref(false);
|
const online = ref(false);
|
||||||
const isonline = (ip: string) => {
|
|
||||||
let http = props.serve.istls == 0 ? 'http' : 'https'
|
|
||||||
api.text_server(`${http}://${props.serve.ip}:${props.serve.port}/${ip}`).then(res => {
|
|
||||||
online.value = res.data.sataus == 400
|
|
||||||
time.value = res.data.time
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const api = new getdata;
|
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
const time = ref(0)
|
const time = ref(0)
|
||||||
const outtext = computed(() => {
|
const outtext = ref('')
|
||||||
let tmp = ''
|
const api = new getdata;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
let text = props.text!.replace(/\ +/g, '');
|
|
||||||
text = text.replace(/[\r\n]/g, '');
|
|
||||||
let arr = text.split('vmess://');
|
|
||||||
for (let iterator of arr) {
|
|
||||||
if (!(iterator.length > 0)) continue
|
|
||||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
|
||||||
online.value = false
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
let obj = JSON.parse(decode(iterator));
|
|
||||||
obj.add = props.serve.ip
|
|
||||||
obj.port = props.serve.port
|
|
||||||
obj.host = props.serve.host
|
|
||||||
obj.tls = props.serve.istls == 0 ? '' : 'tls'
|
|
||||||
let reg = new RegExp(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
|
|
||||||
obj.path = '/' + obj.ps.match(reg)[0];
|
|
||||||
let ip = obj.ps.match(reg)[0]
|
|
||||||
isonline(ip)
|
|
||||||
tmp += 'vmess://' + encode(JSON.stringify(obj)) + '\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmp
|
|
||||||
})
|
|
||||||
const signal_style = computed(() => {
|
const signal_style = computed(() => {
|
||||||
let tmp = ''
|
return getColorForDelay(time.value)
|
||||||
if (time.value >= 0 && time.value <= 700) {
|
|
||||||
tmp = 'green'
|
|
||||||
} else if (time.value > 700 && time.value < 1400) {
|
|
||||||
tmp = '#FF9800'
|
|
||||||
} else {
|
|
||||||
tmp = 'red'
|
|
||||||
}
|
|
||||||
return tmp
|
|
||||||
})
|
})
|
||||||
const copy = (type: string) => {
|
|
||||||
let tmp = ''
|
onMounted(() => {
|
||||||
switch (type) {
|
for (let index = 0; index < props.names.length; index++) {
|
||||||
case 'sub':
|
let ip = extractIPv4(props.names[index])
|
||||||
tmp = `http://149.129.107.38/link.php?link=${outtext.value}`
|
if (ip == null) { continue }
|
||||||
break;
|
let vless = vlessLink
|
||||||
case 'link':
|
vless.name = props.names[index]
|
||||||
tmp = outtext.value
|
vless.host = props.serve.host
|
||||||
break;
|
vless.port = props.serve.port
|
||||||
default:
|
if (props.serve.istls == 1 && vless.params) {
|
||||||
break;
|
vless.params.security = 'tls'
|
||||||
|
vless.params.path = `/${ip}`
|
||||||
|
vless.params.host = props.serve.host
|
||||||
|
vless.params.sni = props.serve.host
|
||||||
|
}
|
||||||
|
api.get_server_ms(props.serve.host, props.serve.port, ip, props.serve.istls).then((res) => {
|
||||||
|
console.log(res)
|
||||||
|
time.value = res.time
|
||||||
|
online.value = res.is_online
|
||||||
|
})
|
||||||
|
|
||||||
|
outtext.value += CreateLink('vless', vless)
|
||||||
}
|
}
|
||||||
copyToClipboard(tmp)
|
})
|
||||||
.then(() => {
|
const copy_link = () => { copy(outtext.value, $q) }
|
||||||
// success!
|
return { props, online, time, signal_style, outtext, copy_link };
|
||||||
$q.notify({
|
|
||||||
message: '复制成功',
|
|
||||||
color: 'positive',
|
|
||||||
position: 'top'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// fail
|
|
||||||
$q.notify({
|
|
||||||
message: '复制失败',
|
|
||||||
color: 'negative'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return { props, outtext, copy, online, time, signal_style };
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
<div v-show="online" style="text-align: center;"><q-icon title="在线" name="signal_cellular_alt"
|
<div v-show="online" style="text-align: center;"><q-icon title="在线" name="signal_cellular_alt"
|
||||||
:style="{ color: signal_style }" /><span>{{ time }}ms</span></div>
|
:style="{ color: signal_style }" /><span>{{ time }}ms</span></div>
|
||||||
<q-input v-model="outtext" filled autogrow readonly />
|
<q-input v-model="outtext" filled autogrow readonly />
|
||||||
<div style="text-align: center;margin-top: 1rem;display: flex;justify-content: space-around;">
|
<div style="text-align: center;margin-top: 1rem;">
|
||||||
<q-btn color="white" text-color="black" @click="copy('link')" label="复制" />
|
<q-btn color="white" text-color="black" @click="copy_link()" label="复制" />
|
||||||
<!-- <q-btn color="white" text-color="black" @click="copy('sub')" label="复制订阅" /> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,99 +18,54 @@ import {
|
||||||
defineComponent,
|
defineComponent,
|
||||||
computed,
|
computed,
|
||||||
ref,
|
ref,
|
||||||
|
onMounted,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import { copyToClipboard } from 'quasar'
|
|
||||||
import { encode, decode } from 'js-base64';
|
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { getdata } from 'src/api/api'
|
import { getdata } from 'src/api/api'
|
||||||
|
import { vlessLink } from 'src/config';
|
||||||
|
import { copy, extractIPv4, getColorForDelay } from 'src/Util/comm';
|
||||||
|
import { CreateLink } from 'src/Util/Link';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'LinkItem',
|
name: 'LinkItem',
|
||||||
props: {
|
props: {
|
||||||
text: {
|
names: {
|
||||||
type: String
|
type: Array as () => string[],
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const time = ref(0);
|
|
||||||
const online = ref(false);
|
const online = ref(false);
|
||||||
const isonline = (ip: string) => {
|
|
||||||
let http = 'http'
|
|
||||||
api.text_server(`${http}://${ip}:9000/`).then(res => {
|
|
||||||
online.value = res.data.sataus == 400
|
|
||||||
time.value = res.data.time
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const def_link = 'ew0KICAidiI6ICIyIiwNCiAgInBzIjogIjAiLA0KICAiYWRkIjogIjE4NS4yMTguNi4xMDgiLA0KICAicG9ydCI6ICI5MDAwIiwNCiAgImlkIjogIjJlZTU3ODA2LWY2ZTQtNDgyYS1lZjA4LTczNjBjMDRjZDNlNSIsDQogICJhaWQiOiAiMCIsDQogICJzY3kiOiAiYXV0byIsDQogICJuZXQiOiAid3MiLA0KICAidHlwZSI6ICJub25lIiwNCiAgImhvc3QiOiAiIiwNCiAgInBhdGgiOiAiLyIsDQogICJ0bHMiOiAiIiwNCiAgInNuaSI6ICIiLA0KICAiYWxwbiI6ICIiDQp9'
|
|
||||||
const api = new getdata;
|
const api = new getdata;
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
const outtext = computed(() => {
|
const time = ref(0)
|
||||||
let tmp = ''
|
const outtext = ref('')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
let text = props.text!.replace(/\ +/g, '');
|
|
||||||
text = text.replace(/[\r\n]/g, '');
|
|
||||||
let arr = text.split('vmess://');
|
|
||||||
for (let iterator of arr) {
|
|
||||||
if (!(iterator.length > 0)) continue
|
|
||||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
|
||||||
online.value = false
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
let obj = JSON.parse(decode(def_link));
|
|
||||||
let obj_old = JSON.parse(decode(iterator));
|
|
||||||
let reg = new RegExp(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
|
|
||||||
let ip = obj_old.ps.match(reg)[0]
|
|
||||||
obj.ps = obj_old.ps
|
|
||||||
obj.add = ip
|
|
||||||
obj.port = 9000
|
|
||||||
obj.host = ''
|
|
||||||
obj.tls = ''
|
|
||||||
obj.path = '/';
|
|
||||||
isonline(ip)
|
|
||||||
tmp += 'vmess://' + encode(JSON.stringify(obj)) + '\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmp
|
|
||||||
})
|
|
||||||
const signal_style = computed(() => {
|
const signal_style = computed(() => {
|
||||||
let tmp = ''
|
return getColorForDelay(time.value)
|
||||||
if (time.value >= 0 && time.value <= 700) {
|
|
||||||
tmp = 'green'
|
|
||||||
} else if (time.value > 700 && time.value < 1400) {
|
|
||||||
tmp = '#FF9800'
|
|
||||||
} else {
|
|
||||||
tmp = 'red'
|
|
||||||
}
|
|
||||||
return tmp
|
|
||||||
})
|
})
|
||||||
const copy = (type: string) => {
|
|
||||||
let tmp = ''
|
onMounted(() => {
|
||||||
switch (type) {
|
for (let index = 0; index < props.names.length; index++) {
|
||||||
case 'sub':
|
let ip = extractIPv4(props.names[index])
|
||||||
tmp = `http://149.129.107.38/link.php?link=${outtext.value}`
|
if (ip == null) { continue }
|
||||||
break;
|
let vless = vlessLink
|
||||||
case 'link':
|
|
||||||
tmp = outtext.value
|
vless.name = props.names[index]
|
||||||
break;
|
vless.host = ip
|
||||||
default:
|
vless.port = 9000
|
||||||
break;
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
vless.params!.path = '/'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
vless.params!.security = 'none'
|
||||||
|
api.get_server_ms(ip, 9000, '', 0).then((res) => {
|
||||||
|
time.value = res.time
|
||||||
|
online.value = res.is_online
|
||||||
|
})
|
||||||
|
outtext.value += CreateLink('vless', vless)
|
||||||
}
|
}
|
||||||
copyToClipboard(tmp)
|
})
|
||||||
.then(() => {
|
const copy_link = () => { copy(outtext.value, $q) }
|
||||||
// success!
|
return { props, online, time, signal_style, outtext, copy_link };
|
||||||
$q.notify({
|
|
||||||
message: '复制成功',
|
|
||||||
color: 'positive',
|
|
||||||
position: 'top'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// fail
|
|
||||||
$q.notify({
|
|
||||||
message: '复制失败',
|
|
||||||
color: 'negative'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return { props, outtext, copy, online, signal_style, time };
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
export interface Todo {
|
|
||||||
id: number;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Meta {
|
|
||||||
totalCount: number;
|
|
||||||
}
|
|
||||||
export interface server {
|
|
||||||
host: string;
|
|
||||||
id: number;
|
|
||||||
ip: string;
|
|
||||||
istls: number;
|
|
||||||
port: number;
|
|
||||||
tips: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface vmess {
|
|
||||||
v: string;
|
|
||||||
ps: string;
|
|
||||||
add: string;
|
|
||||||
port: number;
|
|
||||||
id: string;
|
|
||||||
aid: string;
|
|
||||||
scy: string;
|
|
||||||
net: string;
|
|
||||||
type: string;
|
|
||||||
host: string;
|
|
||||||
path: string;
|
|
||||||
tls: string;
|
|
||||||
sni: string;
|
|
||||||
alpn: string
|
|
||||||
}
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { VlessLink } from 'src/models/models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vmess模板链接
|
||||||
|
*/
|
||||||
|
const vmessDefault = 'ew0KICAidiI6ICIyIiwNCiAgInBzIjogIjAiLA0KICAiYWRkIjogIjE4NS4yMTguNi4xMDgiLA0KICAicG9ydCI6ICI5MDAwIiwNCiAgImlkIjogIjJlZTU3ODA2LWY2ZTQtNDgyYS1lZjA4LTczNjBjMDRjZDNlNSIsDQogICJhaWQiOiAiMCIsDQogICJzY3kiOiAiYXV0byIsDQogICJuZXQiOiAid3MiLA0KICAidHlwZSI6ICJub25lIiwNCiAgImhvc3QiOiAiIiwNCiAgInBhdGgiOiAiLyIsDQogICJ0bHMiOiAiIiwNCiAgInNuaSI6ICIiLA0KICAiYWxwbiI6ICIiDQp9'
|
||||||
|
const vlessDefault = 'vless://2ee57806-f6e4-482a-ef08-7360c04cd3e5@1.1.1.1:9000?encryption=none&security=none&type=ws&path=%2F#%E9%9F%A9%E5%9B%BD1.1.1.1'
|
||||||
|
/**
|
||||||
|
* vless对象模板
|
||||||
|
*/
|
||||||
|
const vlessLink: VlessLink = {
|
||||||
|
uuid: '2ee57806-f6e4-482a-ef08-7360c04cd3e5',
|
||||||
|
host: '150.109.81.208',
|
||||||
|
port: 9000,
|
||||||
|
params: {
|
||||||
|
encryption: 'none',
|
||||||
|
security: 'none',
|
||||||
|
type: 'ws',
|
||||||
|
path: '/'
|
||||||
|
},
|
||||||
|
name: '韩国150.109.81.208'
|
||||||
|
};
|
||||||
|
export { vmessDefault, vlessDefault, vlessLink }
|
|
@ -0,0 +1,67 @@
|
||||||
|
export interface Todo {
|
||||||
|
id: number;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Meta {
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 服务器结构体
|
||||||
|
*/
|
||||||
|
export interface server {
|
||||||
|
host: string;
|
||||||
|
id: number;
|
||||||
|
ip: string;
|
||||||
|
istls: number;
|
||||||
|
port: number;
|
||||||
|
tips: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* vmess结构体
|
||||||
|
*/
|
||||||
|
export interface vmess {
|
||||||
|
v: string;
|
||||||
|
ps: string;
|
||||||
|
add: string;
|
||||||
|
port: number;
|
||||||
|
id: string;
|
||||||
|
aid: string;
|
||||||
|
scy: string;
|
||||||
|
net: string;
|
||||||
|
type: string;
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
tls: string;
|
||||||
|
sni: string;
|
||||||
|
alpn: string
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* vless结构体
|
||||||
|
*/
|
||||||
|
export interface vless {
|
||||||
|
add: string;
|
||||||
|
port: number;
|
||||||
|
uuid: string;
|
||||||
|
encryption: string;
|
||||||
|
/**传输协议 */
|
||||||
|
net: string;
|
||||||
|
/**伪装类型 */
|
||||||
|
type: string;
|
||||||
|
/**地址 */
|
||||||
|
host: string;
|
||||||
|
/**路径 */
|
||||||
|
path: string;
|
||||||
|
/**tls */
|
||||||
|
tls: string;
|
||||||
|
/**sni */
|
||||||
|
sni: string;
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
export interface VlessLink {
|
||||||
|
uuid: string;
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
params?: Record<string, string>;
|
||||||
|
name?: string; // 允许传入名称
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
<q-input v-model="text" filled autogrow placeholder="IP" />
|
<q-input v-model="text" filled autogrow placeholder="IP" />
|
||||||
<div style="height:1rem"></div>
|
<div style="height:1rem"></div>
|
||||||
<q-input v-model="outlink" filled autogrow />
|
<q-input v-model="outlink" filled autogrow />
|
||||||
<q-btn color="white" text-color="black" @click="copy" label="复制" />
|
<q-btn color="white" text-color="black" @click="copy_link" label="复制" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,69 +19,46 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, watch } from 'vue';
|
import { defineComponent, ref, watch } from 'vue';
|
||||||
import { getdata } from 'src/api/api'
|
import { copy, is_ip } from 'src/Util/comm';
|
||||||
import { decode, encode } from 'js-base64';
|
import { CreateLink, CreatVmessDirect } from 'src/Util/Link';
|
||||||
import { copyToClipboard, useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
import { vlessLink } from 'src/config';
|
||||||
|
import { getdata } from 'src/api/api';
|
||||||
|
import { VLESS } from 'src/Util/node/Vless';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'CreateLink',
|
name: 'CreateLink',
|
||||||
setup() {
|
setup() {
|
||||||
const $q = useQuasar()
|
|
||||||
const api = new getdata;
|
|
||||||
const text = ref('');
|
const text = ref('');
|
||||||
const outlink = ref('');
|
const outlink = ref('');
|
||||||
const def_link = 'ew0KICAidiI6ICIyIiwNCiAgInBzIjogIjAiLA0KICAiYWRkIjogIjE4NS4yMTguNi4xMDgiLA0KICAicG9ydCI6ICI5MDAwIiwNCiAgImlkIjogIjJlZTU3ODA2LWY2ZTQtNDgyYS1lZjA4LTczNjBjMDRjZDNlNSIsDQogICJhaWQiOiAiMCIsDQogICJzY3kiOiAiYXV0byIsDQogICJuZXQiOiAid3MiLA0KICAidHlwZSI6ICJub25lIiwNCiAgImhvc3QiOiAiIiwNCiAgInBhdGgiOiAiLyIsDQogICJ0bHMiOiAiIiwNCiAgInNuaSI6ICIiLA0KICAiYWxwbiI6ICIiDQp9'
|
const $q = useQuasar()
|
||||||
const is_ip = (ip: string) => {
|
const api = new getdata();
|
||||||
var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
|
||||||
return reg.test(ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
watch(() => text.value, (newValue, oldValue) => { //直接监听
|
watch(() => text.value, async (newValue, oldValue) => { //直接监听
|
||||||
let obj = JSON.parse(decode(def_link))
|
|
||||||
let array = newValue.split(/[\s\n]/)
|
let array = newValue.split(/[\s\n]/)
|
||||||
for (let index = 0; index < array.length; index++) {
|
for (let index = 0; index < array.length; index++) {
|
||||||
if (!is_ip(array[index])) {
|
if (!is_ip(array[index])) {
|
||||||
outlink.value = 'erroe ip'
|
outlink.value = 'erroe ip'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
outlink.value = ''
|
outlink.value = ''
|
||||||
for (let index = 0; index < array.length; index++) {
|
for (let index = 0; index < array.length; index++) {
|
||||||
api.get_country(array[index]).then(res => {
|
// outlink.value += await CreatVmessDirect(array[index])
|
||||||
// let name = res.data.country == '中国' ? res.data.province : res.data.country
|
let vless = vlessLink
|
||||||
let name = res.data.country
|
let country = await api.get_country(array[index])
|
||||||
obj.ps = name + array[index]
|
console.log(country)
|
||||||
obj.add = array[index]
|
vless.name = country + array[index]
|
||||||
outlink.value += 'vmess://' + encode(JSON.stringify(obj)) + '\n'
|
vless.host = array[index]
|
||||||
})
|
outlink.value += CreateLink('vless', vless) + '\n'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
const copy_link = () => { copy(outlink.value, $q) }
|
||||||
return {
|
return {
|
||||||
|
|
||||||
text,
|
text,
|
||||||
outlink,
|
outlink,
|
||||||
copy() {
|
copy_link
|
||||||
copyToClipboard(outlink.value)
|
|
||||||
.then(() => {
|
|
||||||
// success!
|
|
||||||
$q.notify({
|
|
||||||
message: '复制成功',
|
|
||||||
color: 'positive',
|
|
||||||
position: 'top'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// fail
|
|
||||||
$q.notify({
|
|
||||||
message: '复制失败',
|
|
||||||
color: 'negative'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,30 +24,6 @@
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="col-sm-8 col-md-4 col-lg-2 col-xl-1 q-pa-md">
|
|
||||||
<q-card class="my-card">
|
|
||||||
<img class="rounded-full q-pa-md" src="img/v2rayng.png">
|
|
||||||
<q-card-section class="text-center">
|
|
||||||
<div class="text-h6 text-center">v2ray</div>
|
|
||||||
<q-btn color="positive">
|
|
||||||
<q-icon left name="file_download" />
|
|
||||||
<div>安卓手机</div>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn class="q-ma-md" color="positive">
|
|
||||||
<q-icon left name="file_download" />
|
|
||||||
<div>模拟器</div>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn color="positive">
|
|
||||||
<q-icon left name="file_download" />
|
|
||||||
<div>PC</div>
|
|
||||||
</q-btn>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section class="q-pt-none">
|
|
||||||
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -72,9 +48,6 @@ export default defineComponent({
|
||||||
// eslint-disable-next-line vue/multi-word-component-names
|
// eslint-disable-next-line vue/multi-word-component-names
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
setup() {
|
setup() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
array,
|
array,
|
||||||
dow(url: string) {
|
dow(url: string) {
|
||||||
|
|
|
@ -17,67 +17,12 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, watch } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { getdata } from 'src/api/api'
|
|
||||||
import { decode, encode } from 'js-base64';
|
|
||||||
import { copyToClipboard, useQuasar } from 'quasar';
|
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// eslint-disable-next-line vue/multi-word-component-names
|
// eslint-disable-next-line vue/multi-word-component-names
|
||||||
name: 'Help',
|
name: 'Help',
|
||||||
setup() {
|
|
||||||
const $q = useQuasar()
|
|
||||||
const api = new getdata;
|
|
||||||
const text = ref('');
|
|
||||||
const outlink = ref('');
|
|
||||||
const def_link = 'ew0KICAidiI6ICIyIiwNCiAgInBzIjogIjAiLA0KICAiYWRkIjogIjE4NS4yMTguNi4xMDgiLA0KICAicG9ydCI6ICI5MDAwIiwNCiAgImlkIjogIjJlZTU3ODA2LWY2ZTQtNDgyYS1lZjA4LTczNjBjMDRjZDNlNSIsDQogICJhaWQiOiAiMCIsDQogICJzY3kiOiAiYXV0byIsDQogICJuZXQiOiAid3MiLA0KICAidHlwZSI6ICJub25lIiwNCiAgImhvc3QiOiAiIiwNCiAgInBhdGgiOiAiLyIsDQogICJ0bHMiOiAiIiwNCiAgInNuaSI6ICIiLA0KICAiYWxwbiI6ICIiDQp9'
|
|
||||||
const is_ip = (ip: string) => {
|
|
||||||
var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
|
||||||
return reg.test(ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
watch(() => text.value, (newValue, oldValue) => { //直接监听
|
|
||||||
let obj = JSON.parse(decode(def_link))
|
|
||||||
console.log(newValue)
|
|
||||||
if (!is_ip(newValue)) {
|
|
||||||
outlink.value = 'erroe ip'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
api.get_country(newValue).then(res => {
|
|
||||||
let name = res.data.country == '中国' ? res.data.province : res.data.country
|
|
||||||
obj.ps = name + newValue
|
|
||||||
obj.add = newValue
|
|
||||||
outlink.value = 'vmess://' + encode(JSON.stringify(obj))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
text,
|
|
||||||
outlink,
|
|
||||||
copy() {
|
|
||||||
copyToClipboard(outlink.value)
|
|
||||||
.then(() => {
|
|
||||||
// success!
|
|
||||||
$q.notify({
|
|
||||||
message: '复制成功',
|
|
||||||
color: 'positive',
|
|
||||||
position: 'top'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// fail
|
|
||||||
$q.notify({
|
|
||||||
message: '复制失败',
|
|
||||||
color: 'negative'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
<div class="col-xs-1 col-md-4"></div>
|
<div class="col-xs-1 col-md-4"></div>
|
||||||
<div class="col-xs-10 col-md-4">
|
<div class="col-xs-10 col-md-4">
|
||||||
<h6 style="text-align: center;margin: 1rem;">节点<q-icon v-show="online" title="在线" name="check_circle_outline"
|
<h6 style="text-align: center;margin: 1rem;">节点<q-icon v-show="online" title="在线" name="check_circle_outline"
|
||||||
style="color: green;" /> <q-icon v-show="!online" title="离线" name="highlight_off" style="color: red;" /></h6>
|
style="color: green;" /> <q-icon v-show="!online" title="离线" name="highlight_off" style="color: red;" />
|
||||||
|
</h6>
|
||||||
<q-input v-model="text" filled autogrow />
|
<q-input v-model="text" filled autogrow />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,13 +13,13 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-1 col-md-4"></div>
|
<div class="col-xs-1 col-md-4"></div>
|
||||||
<div class="col-xs-10 col-md-4">
|
<div class="col-xs-10 col-md-4">
|
||||||
<OriginalLinkItem v-model:text="text"></OriginalLinkItem>
|
<OriginalLinkItem :names="names"></OriginalLinkItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" v-for="(item, index) in serve" :key="index">
|
<div class="row" v-for="(item, index) in serves" :key="index">
|
||||||
<div class="col-xs-1 col-md-4"></div>
|
<div class="col-xs-1 col-md-4"></div>
|
||||||
<div class="col-xs-10 col-md-4">
|
<div class="col-xs-10 col-md-4">
|
||||||
<LinkItem :serve="item" v-model:text="text"></LinkItem>
|
<LinkItem :serve="item" v-model:names="names"></LinkItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,47 +29,56 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { server } from 'components/models';
|
import { server } from 'src/models/models';
|
||||||
import { computed, defineComponent, onMounted, ref } from 'vue';
|
import { computed, defineComponent, onMounted, ref } from 'vue';
|
||||||
import { getdata } from 'src/api/api'
|
import { getdata } from 'src/api/api'
|
||||||
import LinkItem from 'components/LinkItem.vue';
|
import LinkItem from 'components/LinkItem.vue';
|
||||||
import OriginalLinkItem from 'components/OriginalLinkItem.vue';
|
import OriginalLinkItem from 'components/OriginalLinkItem.vue';
|
||||||
import { decode } from 'js-base64';
|
import { VLESS } from 'src/Util/node/Vless';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'IndexPage',
|
name: 'IndexPage',
|
||||||
components: { LinkItem, OriginalLinkItem },
|
components: { LinkItem, OriginalLinkItem },
|
||||||
setup() {
|
setup() {
|
||||||
const serve = ref(<server[]>[])
|
const serves = ref(<server[]>[])
|
||||||
const api = new getdata;
|
const api = new getdata;
|
||||||
const text = ref('');
|
const text = ref('');
|
||||||
const online = ref(false);
|
const online = ref(false);
|
||||||
|
const names = ref(<string[]>[])
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
api.get_server().then((res) => {
|
api.get_server().then((res) => {
|
||||||
for (let index = 0; index < res.data.length; index++) {
|
for (let index = 0; index < res.data.data.length; index++) {
|
||||||
serve.value.push(res.data[index]);
|
serves.value.push(res.data.data[index]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const isobj = computed(() => {
|
const isobj = computed(() => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (text.value.length <= 0) {
|
if (text.value.length <= 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
let texttmp = text.value!.replace(/\ +/g, '');
|
let texttmp = text.value!.replace(/\ +/g, '');
|
||||||
texttmp = texttmp.replace(/[\r\n]/g, '');
|
texttmp = texttmp.replace(/[\r\n]/g, '');
|
||||||
let arr = texttmp.split('vmess://');
|
let arr = texttmp.split('vless://');
|
||||||
|
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||||
|
names.value = [];
|
||||||
for (let iterator of arr) {
|
for (let iterator of arr) {
|
||||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||||
online.value = false
|
online.value = false
|
||||||
if (iterator.length > 0) {
|
if (iterator.length > 0) {
|
||||||
let obj = JSON.parse(decode(iterator));
|
// let tmp = parseVlessLink(iterator)
|
||||||
if (obj.id != '2ee57806-f6e4-482a-ef08-7360c04cd3e5' || obj.net != 'ws') {
|
let tmp = VLESS.decodeURL('vless://' + iterator);
|
||||||
|
if (!(tmp != null && tmp.uuid == '2ee57806-f6e4-482a-ef08-7360c04cd3e5')) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let reg = new RegExp(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
|
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||||
let ip = obj.ps.match(reg)[0];
|
if (tmp.name) { names.value.push(tmp.name) }
|
||||||
isonline(ip)
|
// eslint-disable-next-line vue/no-async-in-computed-properties
|
||||||
|
api.get_server_ms(tmp.server, tmp.port, tmp.query.path, 0).then((res) => {
|
||||||
|
console.log(res)
|
||||||
|
online.value = res.is_online
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -79,16 +89,12 @@ export default defineComponent({
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
const isonline = (ip: string) => {
|
|
||||||
api.text_server(`http://${ip}:9000/`).then(res => {
|
|
||||||
online.value = res.data.sataus == 400
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
serve,
|
serves,
|
||||||
text,
|
text,
|
||||||
isobj,
|
isobj,
|
||||||
online
|
online,
|
||||||
|
names
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,9 +2,13 @@ import { MutationTree } from 'vuex';
|
||||||
import { ExampleStateInterface } from './state';
|
import { ExampleStateInterface } from './state';
|
||||||
|
|
||||||
const mutation: MutationTree<ExampleStateInterface> = {
|
const mutation: MutationTree<ExampleStateInterface> = {
|
||||||
someMutation (/* state: ExampleStateInterface */) {
|
someMutation(/* state: ExampleStateInterface */) {
|
||||||
// your code
|
// your code
|
||||||
}
|
},
|
||||||
|
//改动url
|
||||||
|
set_text_server_url(state, url) {
|
||||||
|
state.text_server_url = url;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mutation;
|
export default mutation;
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
export interface ExampleStateInterface {
|
export interface ExampleStateInterface {
|
||||||
prop: boolean;
|
prop: boolean;
|
||||||
|
text_server_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function state(): ExampleStateInterface {
|
function state(): ExampleStateInterface {
|
||||||
return {
|
return {
|
||||||
prop: false
|
prop: false,
|
||||||
}
|
text_server_url: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default state;
|
export default state;
|
||||||
|
|
Loading…
Reference in New Issue