提取IP添加主机格式选项功能

This commit is contained in:
Eamon-meng
2026-05-08 11:37:36 +08:00
parent 9dea370a87
commit 84a5e27c05
5 changed files with 95 additions and 11 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "lanhu-web", "name": "lanhu-web",
"version": "1.11.0", "version": "1.12.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -H 0.0.0.0 --turbopack", "dev": "next dev -H 0.0.0.0 --turbopack",

View File

@@ -17,6 +17,7 @@ export async function listChannels(props: {
} }
type CreateChannelsResp = { type CreateChannelsResp = {
ip: string
host: string host: string
port: string port: string
username?: string username?: string
@@ -31,6 +32,7 @@ export async function createChannels(params: {
prov?: string prov?: string
city?: string city?: string
isp?: number isp?: number
host_format?: number
}) { }) {
return callPublic<CreateChannelsResp[]>('/api/channel/create', params) return callPublic<CreateChannelsResp[]>('/api/channel/create', params)
} }

View File

@@ -23,6 +23,7 @@ export async function GET(req: NextRequest) {
const prov = params.get('a') || undefined const prov = params.get('a') || undefined
const city = params.get('b') || undefined const city = params.get('b') || undefined
const isp = params.get('s') || undefined const isp = params.get('s') || undefined
const hostFormat = params.get('rh') || 'domain'
const result = await createChannels({ const result = await createChannels({
resource_id: Number(resource_id), resource_id: Number(resource_id),
@@ -32,7 +33,9 @@ export async function GET(req: NextRequest) {
prov, prov,
city, city,
isp: Number(isp), isp: Number(isp),
host_format: hostFormat === 'domain' ? 1 : 2,
}) })
if (!result.success) { if (!result.success) {
throw new Error(result.message) throw new Error(result.message)
} }
@@ -46,10 +49,32 @@ export async function GET(req: NextRequest) {
switch (format) { switch (format) {
case 'json': case 'json':
return NextResponse.json(result.data) if (hostFormat === 'domain') {
const domainFormatData = result.data.map(item => ({
host: item.host,
port: item.port,
...(item.username && item.password ? {username: item.username, password: item.password} : {}),
}))
return NextResponse.json(domainFormatData)
}
else {
const ipFormatData = result.data.map(item => ({
ip: item.ip,
port: item.port,
...(item.username && item.password ? {username: item.username, password: item.password} : {}),
}))
return NextResponse.json(ipFormatData)
}
case 'text': case 'text':
const text = result.data.map((item) => { const text = result.data.map((item) => {
const list = [item.host, item.port] let hostValue: string
if (hostFormat === 'domain') {
hostValue = item.host
}
else {
hostValue = item.ip
}
const list = [hostValue, String(item.port)]
if (item.username && item.password) { if (item.username && item.password) {
list.push(item.username) list.push(item.username)
list.push(item.password) list.push(item.password)

View File

@@ -14,7 +14,8 @@
| b | string | 否 | 归属地城市。默认全局随机 | | b | string | 否 | 归属地城市。默认全局随机 |
| s | string | 否 | 归属地运营商。默认全局随机 | | s | string | 否 | 归属地运营商。默认全局随机 |
| d | string | 否 | 是否去重1 - 是0 - 否。默认为是 | | d | string | 否 | 是否去重1 - 是0 - 否。默认为是 |
| rt | string | 否 | 返回类型1 - TXT2 - JSON。默认 TXT | | rt | string | 否 | 返回类型1 - TXT2 - JSON。默认 TXT
| rh | string | 否 | 返回时主机字段的格式1 - 域名2 - IP。默认为域名 |
| rs | number[] | 否 | 返回时要使用的分隔符,值为该字符的 ascii 编码,可以有多个字符,多个字符用半角逗号连接。默认为 13,10即回车 + 换行(\r\n | | rs | number[] | 否 | 返回时要使用的分隔符,值为该字符的 ascii 编码,可以有多个字符,多个字符用半角逗号连接。默认为 13,10即回车 + 换行(\r\n |
| rb | number[] | 否 | 返回时要使用的换行符,值为该字符的 ascii 编码,可以有多个字符,多个字符用半角逗号连接。默认为 124即垂直线 \| | | rb | number[] | 否 | 返回时要使用的换行符,值为该字符的 ascii 编码,可以有多个字符,多个字符用半角逗号连接。默认为 124即垂直线 \| |
| n | number | 否 | 提取数量。默认为 1 | | n | number | 否 | 提取数量。默认为 1 |
@@ -33,11 +34,11 @@
| password | string | 代理服务器密码(仅在认证类型为密码时返回) | | password | string | 代理服务器密码(仅在认证类型为密码时返回) |
## 示例 ## 示例1
### 请求示例 ### 请求示例
```http ```http
GET https://lanhuip.com/api/extract?i=1&t=2&a=广东省&b=广州市&s=移动&d=1&rt=2&n=3 GET https://lanhuip.com/api/extract?i=1&t=2&a=广东省&b=广州市&s=移动&d=1&rt=2&n=3
``` ```
@@ -65,3 +66,36 @@ GET https://lanhuip.com/api/extract?i=1&t=2&a=广东省&b=广州市&s=移动&d=1
} }
] ]
``` ```
## 示例2
### 请求示例
```http
GET https://lanhuip.com/api/extract?i=24&t=1&a=广东省&b=广州市&d=1&rt=text&rh=ip&rs=124&rb=13%2C10&n=1
```
### 响应示例
```json
[
{
"ip": "127.0.0.1",
"port": 20000,
"username": "user1",
"password": "pass1"
},
{
"ip": "127.0.0.1",
"port": 20001,
"username": "user2",
"password": "pass2"
},
{
"ip": "127.0.0.1",
"port": 20002,
"username": "user3",
"password": "pass3"
}
]
```

View File

@@ -28,10 +28,11 @@ const schema = z.object({
city: z.string().optional(), city: z.string().optional(),
regionType: z.enum(['unlimited', 'specific']).default('unlimited'), regionType: z.enum(['unlimited', 'specific']).default('unlimited'),
isp: z.enum(['all', '1', '2', '3'], {required_error: '请选择运营商'}), isp: z.enum(['all', '1', '2', '3'], {required_error: '请选择运营商'}),
proto: z.enum(['all', '1', '2', '3'], {required_error: '请选择协议'}), proto: z.enum(['all', '1', '2'], {required_error: '请选择协议'}),
authType: z.enum(['1', '2'], {required_error: '请选择认证方式'}), authType: z.enum(['1', '2'], {required_error: '请选择认证方式'}),
distinct: z.enum(['1', '0'], {required_error: '请选择去重选项'}), distinct: z.enum(['1', '0'], {required_error: '请选择去重选项'}),
format: z.enum(['text', 'json'], {required_error: '请选择导出格式'}), format: z.enum(['text', 'json'], {required_error: '请选择导出格式'}),
hostFormat: z.enum(['domain', 'ip'], {required_error: '请选择主机格式'}),
separator: z.string({required_error: '请选择分隔符'}), separator: z.string({required_error: '请选择分隔符'}),
breaker: z.string({required_error: '请选择换行符'}), breaker: z.string({required_error: '请选择换行符'}),
count: z.number({required_error: '请输入有效的数量'}).min(1), count: z.number({required_error: '请输入有效的数量'}).min(1),
@@ -53,6 +54,7 @@ export default function Extract(props: ExtractProps) {
authType: '1', authType: '1',
count: 1, count: 1,
distinct: '1', distinct: '1',
hostFormat: 'domain',
format: 'text', format: 'text',
breaker: '13,10', breaker: '13,10',
separator: '124', separator: '124',
@@ -163,12 +165,12 @@ const FormFields = memo(() => {
<RadioGroupItem value="1" id={`${id}-v-http`} className="mr-2"/> <RadioGroupItem value="1" id={`${id}-v-http`} className="mr-2"/>
<span>HTTP</span> <span>HTTP</span>
</FormLabel> </FormLabel>
<FormLabel htmlFor={`${id}-v-https`} className="px-3 h-10 border rounded-md flex items-center text-sm"> {/* <FormLabel htmlFor={`${id}-v-https`} className="px-3 h-10 border rounded-md flex items-center text-sm">
<RadioGroupItem value="2" id={`${id}-v-https`} className="mr-2"/> <RadioGroupItem value="2" id={`${id}-v-https`} className="mr-2"/>
<span>HTTPS</span> <span>HTTPS</span>
</FormLabel> </FormLabel> */}
<FormLabel htmlFor={`${id}-v-socks5`} className="px-3 h-10 border rounded-md flex items-center text-sm"> <FormLabel htmlFor={`${id}-v-socks5`} className="px-3 h-10 border rounded-md flex items-center text-sm">
<RadioGroupItem value="3" id={`${id}-v-socks5`} className="mr-2"/> <RadioGroupItem value="2" id={`${id}-v-socks5`} className="mr-2"/>
<span>SOCKS5</span> <span>SOCKS5</span>
</FormLabel> </FormLabel>
</RadioGroup> </RadioGroup>
@@ -233,6 +235,26 @@ const FormFields = memo(() => {
)} )}
</FormField> </FormField>
{/* 主机格式 */}
<FormField name="hostFormat" className="md:max-w-[calc(160px*2+1rem)]" label="主机格式" classNames={{label: 'max-md:text-sm'}}>
{({id, field}) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4"
>
<FormLabel htmlFor={`${id}-v-domain`} className="px-3 h-10 flex-1 border rounded-md flex items-center text-sm">
<RadioGroupItem value="domain" id={`${id}-v-domain`} className="mr-2"/>
<span></span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-ip`} className="px-3 h-10 flex-1 border rounded-md flex items-center text-sm">
<RadioGroupItem value="ip" id={`${id}-v-ip`} className="mr-2"/>
<span>IP</span>
</FormLabel>
</RadioGroup>
)}
</FormField>
{/* 分隔符 */} {/* 分隔符 */}
<FormField name="separator" className="md:max-w-[calc(160px*3+1rem*2)]" label="分隔符" classNames={{label: 'max-md:text-sm'}}> <FormField name="separator" className="md:max-w-[calc(160px*3+1rem*2)]" label="分隔符" classNames={{label: 'max-md:text-sm'}}>
{({id, field}) => ( {({id, field}) => (
@@ -648,7 +670,7 @@ function ApplyLink() {
} }
function link(values: Schema) { function link(values: Schema) {
const {resource, prov, city, isp, proto, authType, distinct, format: formatType, separator, breaker, count} = values const {resource, prov, city, isp, proto, authType, distinct, format: formatType, hostFormat, separator, breaker, count} = values
const sp = new URLSearchParams() const sp = new URLSearchParams()
if (resource) sp.set('i', String(resource)) if (resource) sp.set('i', String(resource))
@@ -660,6 +682,7 @@ function link(values: Schema) {
if (isp != 'all') sp.set('s', isp) if (isp != 'all') sp.set('s', isp)
sp.set('d', distinct) sp.set('d', distinct)
sp.set('rt', formatType) sp.set('rt', formatType)
sp.set('rh', hostFormat)
sp.set('rs', separator) sp.set('rs', separator)
sp.set('rb', breaker) sp.set('rb', breaker)
sp.set('n', String(count)) sp.set('n', String(count))