第一次提交

This commit is contained in:
7836246 2024-03-02 15:21:22 +08:00
parent 5ad838c5f3
commit 1ca7e58f24
24 changed files with 8336 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

7
app.vue Normal file
View File

@ -0,0 +1,7 @@
<template>
<NuxtLoadingIndicator />
<NuxtLayout>
<UNotifications />
<NuxtPage />
</NuxtLayout>
</template>

View File

@ -0,0 +1,64 @@
<script lang="ts" setup>
const colorMode = useColorMode()
const availableColor = ref([
{
id: 1,
name: 'system',
icon: 'ph:laptop-duotone',
},
{
id: 2,
name: 'dark',
icon: 'ph:moon-stars-duotone',
},
{
id: 3,
name: 'light',
icon: 'ph:sun-dim-duotone',
},
])
</script>
<template>
<div>
<HeadlessListbox
v-model="$colorMode.preference"
as="div"
class="relative flex items-center"
>
<HeadlessListboxLabel class="sr-only">
Theme
</HeadlessListboxLabel>
<HeadlessListboxButton type="button" title="Change Color">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
>
<Icon name="ph:palette-duotone" class=" text-lg dark:text-white" />
</div>
</HeadlessListboxButton>
<HeadlessListboxOptions
class="absolute top-full right-0 z-[999] mt-2 w-40 overflow-hidden rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
>
<HeadlessListboxOption
v-for="color in availableColor"
:key="color.id"
:value="color.name"
class="flex w-full cursor-pointer items-center justify-between py-2 px-3"
:class="{
'text-white-500 bg-gray-200 dark:bg-gray-500/50':
colorMode.preference === color.name,
'hover:bg-gray-200 dark:hover:bg-gray-700/30':
colorMode.preference !== color.name,
}"
>
<span class="truncate">
{{ color.name }}
</span>
<span class="flex items-center justify-center text-sm">
<Icon :name="color.icon" class="text-base" />
</span>
</HeadlessListboxOption>
</HeadlessListboxOptions>
</HeadlessListbox>
</div>
</template>

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import {SupportedTLDs} from "~/utils/domain";
const isOpen = ref(false)
</script>
<template>
<div>
<div title="Change Color">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
@click="isOpen = true"
>
<Icon name="mdi:about-circle-outline" class=" text-lg dark:text-white" />
</div>
</div>
<USlideover
v-model="isOpen"
side="left"
>
<button>
<Icon name="lets-icons:close-ring-light" class="absolute top-2 right-2 text-gray-500 cursor-pointer" @click="isOpen = false" />
</button>
<div class="p-4 flex-1">
<div>目前仅支持以下后缀</div>
<div class="flex flex-wrap mt-2 p-5 overflow-y-auto max-h-[95vh]">
<span
v-for="item in SupportedTLDs"
:key="item"
class="m-1 px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-200 rounded hover:bg-gray-300"
>
{{ item }}
</span>
</div>
</div>
</USlideover>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,61 @@
<script lang="ts" setup>
const switchLocalePath = useSwitchLocalePath()
const { locale } = useI18n()
const local = computed(() => {
return locale.value
})
const availableLocales = [
{ iso: 'en', name: 'English', flag: 'twemoji:flag-us-outlying-islands' },
{ iso: 'zh', name: '中文简体', flag: 'emojione-v1:flag-for-china' },
{ iso: 'tw', name: '中文繁体', flag: 'flag:tw-4x3' },
// ...
]
</script>
<template>
<div>
<HeadlessListbox
v-model="local"
as="div"
class="relative flex items-center"
>
<HeadlessListboxLabel class="sr-only">
Change Language
</HeadlessListboxLabel>
<HeadlessListboxButton type="button" title="Change Language">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
>
<Icon name="i-ph:translate-bold" class=" text-lg dark:text-white" size="20" />
</div>
</HeadlessListboxButton>
<HeadlessListboxOptions
class="absolute top-full right-0 z-[999] mt-2 w-40 overflow-hidden rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
>
<NuxtLink
v-for="lang in availableLocales"
:key="lang.iso"
:to="switchLocalePath(lang.iso)"
class="flex w-full cursor-pointer items-center justify-between py-2 px-3"
:class="{
'text-white-500 bg-gray-200 dark:bg-gray-500/50':
local === lang.iso,
'hover:bg-gray-200 dark:hover:bg-gray-700/30':
local !== lang.iso,
}"
>
<span class="truncate">
{{ lang.name }}
</span>
<span class="flex items-center justify-center text-sm">
<Icon :name="lang.flag" class="text-base" size="20" />
</span>
</NuxtLink>
</HeadlessListboxOptions>
</HeadlessListbox>
</div>
</template>

View File

@ -0,0 +1,84 @@
<script lang="ts" setup>
import {useTimeStore} from "~/stores/time";
const availableTimeZones = ref([
{ id: 1, name: 'UTC-12', displayName: 'International Date Line West' },
{ id: 2, name: 'UTC-11', displayName: 'Coordinated Universal Time-11' },
{ id: 3, name: 'UTC-10', displayName: 'Hawaii' },
{ id: 4, name: 'UTC-9', displayName: 'Alaska' },
{ id: 5, name: 'UTC-8', displayName: 'Pacific Time (US & Canada)' },
{ id: 6, name: 'UTC-7', displayName: 'Mountain Time (US & Canada)' },
{ id: 7, name: 'UTC-6', displayName: 'Central Time (US & Canada), Mexico City' },
{ id: 8, name: 'UTC-5', displayName: 'Eastern Time (US & Canada), Bogota, Lima' },
{ id: 9, name: 'UTC-4', displayName: 'Atlantic Time (Canada), Caracas, La Paz' },
{ id: 10, name: 'UTC-3', displayName: 'Buenos Aires, Georgetown' },
{ id: 11, name: 'UTC-2', displayName: 'Coordinated Universal Time-02' },
{ id: 12, name: 'UTC-1', displayName: 'Azores' },
{ id: 13, name: 'UTC', displayName: 'Coordinated Universal Time' },
{ id: 14, name: 'UTC+1', displayName: 'Brussels, Copenhagen, Madrid, Paris' },
{ id: 15, name: 'UTC+2', displayName: 'Athens, Bucharest, Istanbul' },
{ id: 16, name: 'UTC+3', displayName: 'Moscow, St. Petersburg, Nairobi' },
{ id: 17, name: 'UTC+3:30', displayName: 'Tehran' },
{ id: 18, name: 'UTC+4', displayName: 'Abu Dhabi, Muscat' },
{ id: 19, name: 'UTC+4:30', displayName: 'Kabul' },
{ id: 20, name: 'UTC+5', displayName: 'Islamabad, Karachi, Tashkent' },
{ id: 21, name: 'UTC+5:30', displayName: 'Chennai, Kolkata, Mumbai, New Delhi' },
{ id: 22, name: 'UTC+5:45', displayName: 'Kathmandu' },
{ id: 23, name: 'UTC+6', displayName: 'Astana, Dhaka' },
{ id: 24, name: 'UTC+6:30', displayName: 'Yangon (Rangoon)' },
{ id: 25, name: 'UTC+7', displayName: 'Bangkok, Hanoi, Jakarta' },
{ id: 26, name: 'UTC+8', displayName: 'Beijing, Hong Kong, Singapore, Taipei' },
{ id: 27, name: 'UTC+9', displayName: 'Osaka, Sapporo, Tokyo' },
{ id: 28, name: 'UTC+9:30', displayName: 'Adelaide, Darwin' },
{ id: 29, name: 'UTC+10', displayName: 'Brisbane, Canberra, Melbourne, Sydney' },
{ id: 30, name: 'UTC+11', displayName: 'Solomon Is., New Caledonia' },
{ id: 31, name: 'UTC+12', displayName: 'Auckland, Wellington' },
{ id: 32, name: 'UTC+13', displayName: 'Nuku alofa' }
]);
const timeStore = useTimeStore()
</script>
<template>
<div>
<HeadlessListbox
v-model="timeStore.timeZones"
as="div"
class="relative flex items-center"
>
<HeadlessListboxLabel class="sr-only">
Theme
</HeadlessListboxLabel>
<HeadlessListboxButton type="button" title="Change Color">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
>
<Icon name="ri:time-zone-line" class=" text-lg dark:text-white" />
</div>
</HeadlessListboxButton>
<HeadlessListboxOptions
class="absolute top-full right-0 z-[999] mt-2 w-40 max-h-60 overflow-y-auto rounded-lg bg-white text-sm font-semibold text-gray-700 shadow-lg shadow-gray-300 outline-none dark:bg-gray-800 dark:text-white dark:shadow-gray-500 dark:ring-0"
>
<HeadlessListboxOption
v-for="item in availableTimeZones"
:key="item.id"
:value="item.name"
class="flex w-full cursor-pointer items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600"
:class="{
'text-white-500 bg-gray-200 dark:bg-gray-500/50':
timeStore.timeZones === item.name,
'hover:bg-gray-200 dark:hover:bg-gray-700/30':
timeStore.timeZones !== item.name,
}"
>
<span class="truncate">
{{ item.name }}
</span>
<span class="flex items-center justify-center text-sm">
</span>
</HeadlessListboxOption>
</HeadlessListboxOptions>
</HeadlessListbox>
</div>
</template>

BIN
img/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

38
lang/en.ts Normal file
View File

@ -0,0 +1,38 @@
export default defineI18nLocale(async locale => {
return {
index: {
tips: 'The information you submit for your query will not be recorded!',
},
error:{
formatDomain: 'Error formatting domain name',
validDomain: 'Domain must contain a valid top-level domain',
notFound: 'Domain not found',
},
result: {
result: 'Query Result',
title: 'WHOIS Query Result',
description: 'Here is the WHOIS information for the domain you queried',
domain: 'Domain Name',
//数据源
source: 'Source',
//注册商
registrar: 'Registrar',
//更新日
updateDate: 'Updated Date',
//注册日
createDate: 'Creation Date',
//到期日
expirationDate: 'Registry Expiry Date',
//IANAID
ianaId: 'IANAID',
//状态
status: 'Domain Status',
//DNS
dns: 'DNS',
//DNSSEC
dnssec: 'DNSSEC',
//原始数据
rawData: 'Raw Data',
}
}
})

38
lang/tw.ts Normal file
View File

@ -0,0 +1,38 @@
export default defineI18nLocale(async locale => {
return {
index: {
tips: '您提交的查詢信息不會被記錄!',
},
error:{
formatDomain: '域名格式錯誤',
validDomain: '域名必須包含有效的頂級域',
notFound: '未找到域名資料',
},
result: {
result: '查詢結果',
title: 'WHOIS查詢結果',
description: '以下是您查詢的域名的WHOIS資訊',
domain: '域名',
//数据源
source: '資料來源',
//注册商
registrar: '註冊商',
//更新日
updateDate: '更新日',
//注册日
createDate: '註冊日',
//到期日
expirationDate: '到期日',
//IANAID
ianaId: 'IANAID',
//状态
status: '狀態',
//DNS
dns: 'DNS',
//DNSSEC
dnssec: 'DNSSEC',
//原始数据
rawData: '原始資料',
}
}
})

39
lang/zh.ts Normal file
View File

@ -0,0 +1,39 @@
export default defineI18nLocale(async locale => {
return {
index: {
tips: '您提交的查询信息不会被记录!',
},
error:{
formatDomain: '域名格式错误',
//域名必须包含有效的顶级域
validDomain: '域名必须包含有效的顶级域',
notFound: '未找到域名信息',
},
result: {
result: '查询结果',
title: 'WHOIS查询结果',
description: '以下是您查询的域名的WHOIS信息',
domain: '域名',
//数据源
source: '数据源',
//注册商
registrar: '注册商',
//更新日
updateDate: '更新日',
//注册日
createDate: '注册日',
//到期日
expirationDate: '到期日',
//IANAID
ianaId: 'IANAID',
//状态
status: '状态',
//DNS
dns: 'DNS',
//DNSSEC
dnssec: 'DNSSEC',
//原始数据
rawData: '原始数据',
}
}
})

128
layouts/default.vue Normal file
View File

@ -0,0 +1,128 @@
<script setup lang="ts">
import {SupportedTLDs} from "~/utils/domain";
const { t } = useI18n()
const state = reactive({
domain: '',
})
const toast = useToast();
const router = useRouter();
const handleAction = async (event: any) => {
//
const domain = state.domain.trim();
//
const parts = domain.split('.');
//
if (parts.length < 2) {
toast.add({
title: '域名格式不正确',
});
return;
}
// TLD
const potentialTLD = parts.slice(-2).join('.');
//
if (!SupportedTLDs.has(parts.slice(-1)[0]) && !SupportedTLDs.has(potentialTLD)) {
toast.add({
title: '域名后缀不合法',
});
return;
}
//
let domainToKeep = domain;
if (SupportedTLDs.has(potentialTLD)) {
//
domainToKeep = parts.length > 2 ? parts.slice(-3).join('.') : domain;
} else {
//
domainToKeep = parts.slice(-2).join('.');
}
// state.domain
state.domain = domainToKeep;
//
await router.push('/result/' + state.domain.replace(/\./g, '_') + '.html');
}
const timeStore = useTimeStore()
</script>
<template>
<div class="w-full min-h-screen text-xs bg-[#F1F3F4] dark:bg-transparent">
<div class=" max-w-screen-lg mx-auto pt-[25vh] px-[1em] pb-[10vh] ">
<nav class=" w-full text-[#464747] h-5 dark:bg-gray-700">
<NuxtLink class="mb-3 font-bold text-2xl inline-block text-current no-underline dark:text-white"
to="/"
>
<h1 class="inline-block text-current no-underline dark:text-white">Nuxt Whois</h1>
<sup class="text-[#59a8d7] dark:text-[#ace4f8]">COM</sup>
</NuxtLink>
</nav>
<div class="mt-6">
<UForm :state="state"
class="flex items-center space-x-2 mb-3 dark:text-white"
@submit="handleAction">
<!-- 容器div用于水平布局 -->
<div class="flex-grow">
<UInput
v-model="state.domain"
placeholder="输入域名"
color="sky"
size="xl"
class="w-full " />
</div>
<!-- 使用v-if或v-show基于state.domain的值来控制按钮的显示 -->
<UButton type="submit" color="sky" size="xl" v-if="state.domain">
提交
</UButton>
</UForm>
</div>
<!-- 公告部分 -->
<div class="bg-gray-200 p-3 rounded-md mb-5 dark:bg-[#5b77af]">
<div class="flex items-center">
<i aria-hidden="true" class="icon fas fa-bullhorn mr-3"></i>
</div>
<div class="flex-grow">
<div class="text-sm text-gray-800 dark:text-white">
{{ t('index.tips') }}
</div>
</div>
</div>
<ClientOnly>
<div class="flex justify-between w-full">
<div>
<!-- 左边的新元素 -->
<UTooltip text="支持列表" :popper="{ placement: 'top' }">
<CommonDomainList />
</UTooltip>
</div>
<div class="flex space-x-2">
<!-- 右边的现有元素 -->
<UTooltip :text="timeStore.timeZones" :popper="{ placement: 'top' }">
<CommonTimeZonesChange />
</UTooltip>
<UTooltip text="主题模式" :popper="{ placement: 'top' }">
<CommonColorChange />
</UTooltip>
<UTooltip text="切换语言" :popper="{ placement: 'top' }">
<CommonLanguageChange />
</UTooltip>
</div>
</div>
</ClientOnly>
<slot />
</div>
</div>
</template>
<style scoped>
</style>

42
nuxt.config.ts Normal file
View File

@ -0,0 +1,42 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@nuxt/ui',
'@nuxtjs/i18n',
'nuxt-headlessui',
'@pinia/nuxt', // needed
'@pinia-plugin-persistedstate/nuxt',
],
app:{
head: {
title: 'Nuxt Whois',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Nuxt Whois' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
}
},
i18n: {
strategy: 'prefix_except_default',
defaultLocale: 'zh',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',
redirectOn: 'root' // recommended
},
locales: [
{ code: 'zh', iso: 'zh-Hans', file: 'zh.ts' },
{ code: 'en', iso: 'en-US', file: 'en.ts' },
{ code: 'tw', iso: 'zh-Hant', file: 'tw.ts' },
],
langDir: 'lang/',
},
headlessui: {
prefix: 'Headless'
},
})

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxt/ui": "^2.14.1",
"@pinia/nuxt": "^0.5.1",
"nuxt": "^3.10.3",
"vue": "^3.4.19",
"vue-router": "^4.3.0",
"xep-whois": "^1.0.2"
},
"devDependencies": {
"@nuxtjs/i18n": "^8.1.1",
"@pinia-plugin-persistedstate/nuxt": "^1.2.0",
"nuxt-headlessui": "^1.1.5"
}
}

11
pages/index.vue Normal file
View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,126 @@
<script setup lang="ts">
import {ParseWhois} from "~/utils/whoisToJson";
import {AdjustTimeToUTCOffset} from "~/utils/utc";
import {useTimeStore} from "~/stores/time";
const route = useRoute();
const {domain} = route.params;
const {t} = useI18n()
const domainData = domain.replace(/_/g, '.')
const {data, pending, error, refresh} = await useAsyncData(
'mountains',
() => $fetch('/api/whois', {
method: 'POST',
body: JSON.stringify({domain: domainData})
})
)
const parsedInfo = ParseWhois(data.value);
const showRawData = ref(false);
const timeStore = useTimeStore()
useHead({
title: `${domainData} - ${t('result.title')}`,
meta: [
{
hid: 'description',
name: 'description',
content: t('result.description', {domain: domainData})
}
]
})
</script>
<template>
<table
v-if="parsedInfo.registrar"
class="w-full bg-[#fffffe] p-4 shadow-lg rounded-lg mt-5 dark:bg-gray-800 dark:text-gray-200 text-white hover:bg-none">
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">
{{ t('result.domain') }}
</th>
<td class="p-4 text-gray-900 dark:text-gray-200">
<p>
<NuxtLink :to="`//${parsedInfo.domainName}`" target="_blank" rel="nofollow"
class="text-blue-600 hover:text-blue-800">
{{ parsedInfo.domainName }}
</NuxtLink>
</p>
</td>
</tr>
<tr class="hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.registrar') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">{{ parsedInfo.registrar }}</td>
</tr>
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.updateDate') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">
{{ AdjustTimeToUTCOffset(parsedInfo.updatedDate, timeStore.timeZones) }}
</td>
</tr>
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.createDate') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">
{{ AdjustTimeToUTCOffset(parsedInfo.creationDate, timeStore.timeZones) }}
</td>
</tr>
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.expirationDate') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">
{{ AdjustTimeToUTCOffset(parsedInfo.registryExpiryDate, timeStore.timeZones) }}
</td>
</tr>
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.ianaId') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">{{ parsedInfo.registrarIANAID }}</td>
</tr>
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.status') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">{{ parsedInfo.domainStatus?.join(', ') }}</td>
</tr>
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.dns') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">
<p v-for="item in parsedInfo.nameServers">{{ item }}</p>
</td>
</tr>
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.dnssec') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">{{ parsedInfo.dnssec }}</td>
</tr>
<tr class="hover:bg-gray-100 text-gray-900 dark:hover:bg-gray-700 text-gray-200">
<th class="p-4 text-left font-semibold text-gray-900 dark:text-gray-200">{{ t('result.rawData') }}</th>
<td class="p-4 text-gray-900 dark:text-gray-200">
<UToggle color="sky" v-model="showRawData"/>
</td>
</tr>
</tbody>
</table>
<!-- 公告部分 -->
<div v-else class="mt-5 bg-gray-200 p-3 rounded-md mb-5 dark:bg-[#5b77af]">
<div class="flex items-center">
<i aria-hidden="true" class="icon fas fa-bullhorn mr-3"></i>
</div>
<div class="flex-grow">
<div class="text-sm text-gray-800 dark:text-white">
<Icon name="bx:error" size="16px" color="red" />
{{ t('error.notFound') }}
</div>
</div>
</div>
<div
class="w-full bg-[#fffffe] mt-5 p-4 shadow-lg rounded-lg whitespace-pre-wrap dark:text-gray-200 dark:bg-gray-800"
v-if="showRawData">
{{ data }}
</div>
</template>
<style scoped>
</style>

7441
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

48
server/api/whois.post.ts Normal file
View File

@ -0,0 +1,48 @@
import {whois, WhoIsOptions} from 'xep-whois';
export default defineEventHandler(async (event) => {
const body = await readBody(event)
try {
const res = await whois(body.domain)
return res._raw
}catch (e) {
return `No match for "${body.domain}".
` +
`>>> Last update of whois database: ${new Date()} <<<
` +
'\n' +
'NOTICE: The expiration date displayed in this record is the date the\n' +
'registrar\'s sponsorship of the domain name registration in the registry is\n' +
'currently set to expire. This date does not necessarily reflect the expiration\n' +
'date of the domain name registrant\'s agreement with the sponsoring\n' +
'registrar. Users may consult the sponsoring registrar\'s Whois database to\n' +
'view the registrar\'s reported date of expiration for this registration.\n' +
'\n' +
'TERMS OF USE: You are not authorized to access or query our Whois\n' +
'database through the use of electronic processes that are high-volume and\n' +
'automated except as reasonably necessary to register domain names or\n' +
'modify existing registrations; the Data in VeriSign Global Registry\n' +
'Services\' ("VeriSign") Whois database is provided by VeriSign for\n' +
'information purposes only, and to assist persons in obtaining information\n' +
'about or related to a domain name registration record. VeriSign does not\n' +
'guarantee its accuracy. By submitting a Whois query, you agree to abide\n' +
'by the following terms of use: You agree that you may use this Data only\n' +
'for lawful purposes and that under no circumstances will you use this Data\n' +
'to: (1) allow, enable, or otherwise support the transmission of mass\n' +
'unsolicited, commercial advertising or solicitations via e-mail, telephone,\n' +
'or facsimile; or (2) enable high volume, automated, electronic processes\n' +
'that apply to VeriSign (or its computer systems). The compilation,\n' +
'repackaging, dissemination or other use of this Data is expressly\n' +
'prohibited without the prior written consent of VeriSign. You agree not to\n' +
'use electronic processes that are automated and high-volume to access or\n' +
'query the Whois database except as reasonably necessary to register\n' +
'domain names or modify existing registrations. VeriSign reserves the right\n' +
'to restrict your access to the Whois database in its sole discretion to ensure\n' +
'operational stability. VeriSign may restrict or terminate your access to the\n' +
'Whois database for failure to abide by these terms of use. VeriSign\n' +
'reserves the right to modify these terms at any time.\n' +
'\n' +
'The Registry database contains ONLY .COM, .NET, .EDU domains and\n' +
'Registrars.'
}
})

3
server/tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

14
stores/time.ts Normal file
View File

@ -0,0 +1,14 @@
import { defineStore } from 'pinia'
export const useTimeStore = defineStore('timeZones', {
state: () => {
return {
timeZones: 'UTC+8',
}
},
persist: {
storage: persistedState.cookiesWithOptions({
sameSite: 'strict',
}),
},
})

4
tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

3
utils/domain.ts Normal file
View File

@ -0,0 +1,3 @@
// br.com,cn.com,de.com,eu.com,gb.com,gb.net,gr.com,hu.com,in.net,no.com,qc.com,ru.com,sa.com,se.com,se.net,uk.com,uk.net,us.com,uy.com,za.com,jpn.com,web.com,com,za.net,net,eu.org,za.org,org,llyw.cymru,gov.scot,gov.wales,edu,gov,int,e164.arpa,arpa,aero,asia,biz,cat,coop,info,jobs,mobi,museum,name,post,pro,tel,travel,xxx,ac,ae,af,ag,ai,am,ar,as,priv.at,at,au,aw,ax,be,bf,bg,bh,bi,bj,bm,bn,bo,br,by,bw,bz,co.ca,ca,cc,cd,ch,ci,cl,cm,edu.cn,cn,uk.co,co,cr,cx,cz,de,dk,dm,do,dz,ec,ee,eu,fi,fj,fm,fo,fr,gd,ge,gf,gg,gh,gi,gl,gp,gq,gs,gy,hk,hm,hn,hr,ht,hu,id,ie,il,im,in,io,iq,ir,is,it,je,jp,ke,kg,ki,kn,kr,kw,ky,kz,la,lb,lc,li,lk,ls,lt,lu,lv,ly,ma,md,me,mg,mk,ml,mm,mn,mq,mr,ms,mt,mu,mw,mx,my,mz,na,nc,nf,ng,nl,no,nu,nz,om,pe,pf,pk,co.pl,pl,pm,pr,ps,pt,pw,qa,re,ro,rs,ac.ru,edu.ru,com.ru,msk.ru,net.ru,nov.ru,org.ru,pp.ru,spb.ru,ru,rw,sa,sb,sc,sd,se,sg,sh,si,sk,sl,sm,sn,so,ss,st,msk.su,nov.su,spb.su,su,sx,sy,tc,td,tf,tg,th,tk,tl,tm,tn,to,tr,tv,tw,tz,biz.ua,co.ua,pp.ua,ua,ug,ac.uk,gov.uk,uk,fed.us,us,uy,uz,vc,ve,vg,vu,wf,ws,yt,ac.za,co.za,gov.za,net.za,org.za,web.za,zm,xn--2scrj9c,xn--3e0b707e,xn--3hcrj9c,xn--45br5cyl,xn--45brj9c,xn--4dbrk0ce,xn--80ao21a,xn--90a3ac,xn--90ae,xn--90ais,xn--clchc0ea0b2g2a9gcd,xn--d1alf,xn--e1a4c,xn--fiqs8s,xn--fiqz9s,xn--fpcrj9c3d,xn--fzc2c9e2c,xn--gecrj9c,xn--h2breg3eve,xn--h2brj9c8c,xn--h2brj9c,xn--j1amh,xn--j6w193g,xn--kprw13d,xn--kpry57d,xn--lgbbat1ad8j,xn--mgb9awbf,xn--mgba3a4f16a,xn--mgbaam7a8h,xn--mgbah1a3hjkrd,xn--mgbbh1a71e,xn--mgbbh1a,xn--mgberp4a5d4ar,xn--mgbgu82a,xn--mgbtx2b,xn--mgbx4cd0ab,xn--node,xn--o3cw4h,xn--ogbpf8fl,xn--p1ai,xn--pgbs0dh,xn--q7ce6a,xn--qxa6a,xn--rvc1e0am3e,xn--s9brj9c,xn--wgbh1c,xn--wgbl6a,xn--xkc2al3hye2a,xn--xkc2dl3a5ee0h,xn--y9a3aq,xn--yfro4i67o,xn--ygbi2ammx
export const SupportedTLDs = new Set(["br.com", "cn.com", "de.com", "eu.com", "gb.com", "gb.net", "gr.com", "hu.com", "in.net", "no.com", "qc.com", "ru.com", "sa.com", "se.com", "se.net", "uk.com", "uk.net", "us.com", "uy.com", "za.com", "jpn.com", "web.com", "com", "za.net", "net", "eu.org", "za.org", "org", "llyw.cymru", "gov.scot", "gov.wales", "edu", "gov", "int", "e164.arpa", "arpa", "aero", "asia", "biz", "cat", "coop", "info", "jobs", "mobi", "museum", "name", "post", "pro", "tel", "travel", "xxx", "ac", "ae", "af", "ag", "ai", "am", "ar", "as", "priv.at", "at", "au", "aw", "ax", "be", "bf", "bg", "bh", "bi", "bj", "bm", "bn", "bo", "br", "by", "bw", "bz", "co.ca", "ca", "cc", "cd", "ch", "ci", "cl", "cm", "edu.cn", "cn", "uk.co", "co", "cr", "cx", "cz", "de", "dk", "dm", "do", "dz", "ec", "ee", "eu", "fi", "fj", "fm", "fo", "fr", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gp", "gq", "gs", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jp", "ke", "kg", "ki", "kn", "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "ls", "lt", "lu", "lv", "ly", "ma", "md", "me", "mg", "mk", "ml", "mm", "mn", "mq", "mr", "ms", "mt", "mu", "mw", "mx", "my", "mz", "na", "nc", "nf", "ng", "nl", "no", "nu", "nz", "om", "pe", "pf", "pk", "co.pl", "pl", "pm", "pr", "ps", "pt", "pw", "qa", "re", "ro", "rs", "ac.ru", "edu.ru", "com.ru", "msk.ru", "net.ru", "nov.ru", "org.ru", "pp.ru", "spb.ru", "ru", "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sk", "sl", "sm", "sn", "so", "ss", "st", "msk.su", "nov.su", "spb.su", "su", "sx", "sy", "tc", "td", "tf", "tg", "th", "tk", "tl", "tm", "tn", "to", "tr", "tv", "tw", "tz", "biz.ua", "co.ua", "pp.ua", "ua", "ug", "ac.uk", "gov.uk", "uk", "fed.us", "us", "uy", "uz", "vc", "ve", "vg", "vu", "wf", "ws", "yt", "ac.za", "co.za", "gov.za", "net.za", "org.za", "web.za", "zm", "xn--2scrj9c", "xn--3e0b707e", "xn--3hcrj9c", "xn--45br5cyl", "xn--45brj9c", "xn--4dbrk0ce", "xn--80ao21a", "xn--90a3ac", "xn--90ae", "xn--90ais", "xn--clchc0ea0b2g2a9gcd", "xn--d1alf", "xn--e1a4c", "xn--fiqs8s", "xn--fiqz9s", "xn--fpcrj9c3d", "xn--fzc2c9e2c", "xn--gecrj9c", "xn--h2breg3eve", "xn--h2brj9c8c", "xn--h2brj9c", "xn--j1amh", "xn--j6w193g", "xn--kprw13d", "xn--kpry57d", "xn--lgbbat1ad8j", "xn--mgb9awbf", "xn--mgba3a4f16a", "xn--mgbaam7a8h", "xn--mgbah1a3hjkrd", "xn--mgbbh1a71e", "xn--mgbbh1a", "xn--mgberp4a5d4ar", "xn--mgbgu82a", "xn--mgbtx2b", "xn--mgbx4cd0ab", "xn--node", "xn--o3cw4h", "xn--ogbpf8fl", "xn--p1ai", "xn--pgbs0dh", "xn--q7ce6a", "xn--qxa6a", "xn--rvc1e0am3e", "xn--s9brj9c", "xn--wgbh1c", "xn--wgbl6a", "xn--xkc2al3hye2a", "xn--xkc2dl3a5ee0h", "xn--y9a3aq", "xn--yfro4i67o", "xn--ygbi2ammx"]);

28
utils/utc.ts Normal file
View File

@ -0,0 +1,28 @@
// 定义一个函数来将单个UTC+8时间字符串转换为指定UTC偏移量的时间
export function AdjustTimeToUTCOffset(timestamp: string, utcOffset: string): string {
// 将时间字符串转换为Date对象
const date = new Date(timestamp);
let offsetInMillis = 0; // 默认偏移量为0毫秒
if (utcOffset !== "UTC") {
// 解析UTC偏移量例如"UTC+8"
const offsetPattern = /UTC([+-])(\d+):?(\d+)?/;
const match = offsetPattern.exec(utcOffset);
if (!match) {
throw new Error('Invalid UTC offset format');
}
const sign = match[1] === '+' ? 1 : -1; // 确定是加时区还是减时区
const hours = parseInt(match[2], 10); // 小时
const minutes = match[3] ? parseInt(match[3], 10) : 0; // 分钟如果没有定义则为0
offsetInMillis = sign * ((hours * 60 + minutes) * 60000); // 将偏移量转换为毫秒
}
// 计算并返回调整后的时间
const targetTime = new Date(date.getTime() + offsetInMillis);
// 返回调整后的时间的ISO字符串
return targetTime.toISOString();
}

65
utils/whoisToJson.ts Normal file
View File

@ -0,0 +1,65 @@
interface WhoisInformation {
domainName?: string;
registryDomainID?: string;
registrarWHOISServer?: string;
registrarURL?: string;
updatedDate?: string;
creationDate?: string;
registryExpiryDate?: string;
registrar?: string;
registrarIANAID?: string;
domainStatus?: string[];
nameServers?: string[];
dnssec?: string;
icannWhoisInaccuracyComplaintFormURL?: string;
}
export function ParseWhois(whoisText: string): WhoisInformation {
const lines = whoisText.split('\n'); // 将文本分割成行
const info: WhoisInformation = {}; // 创建一个空对象来存储提取的信息
lines.forEach(line => {
const [key, value] = line.split(': ').map(part => part.trim());
switch (key) {
case 'Domain Name':
info.domainName = value;
break;
case 'Registry Domain ID':
info.registryDomainID = value;
break;
case 'Registrar WHOIS Server':
info.registrarWHOISServer = value;
break;
case 'Registrar URL':
info.registrarURL = value;
break;
case 'Updated Date':
info.updatedDate = value;
break;
case 'Creation Date':
info.creationDate = value;
break;
case 'Registry Expiry Date':
info.registryExpiryDate = value;
break;
case 'Registrar':
info.registrar = value;
break;
case 'Registrar IANA ID':
info.registrarIANAID = value;
break;
case 'Domain Status':
info.domainStatus = info.domainStatus ? [...info.domainStatus, value] : [value];
break;
case 'Name Server':
info.nameServers = info.nameServers ? [...info.nameServers, value] : [value];
break;
case 'DNSSEC':
info.dnssec = value;
break;
}
});
info.icannWhoisInaccuracyComplaintFormURL = "https://www.icann.org/wicf/";
return info;
}