🚩 增加Dns功能 优化性能

This commit is contained in:
7836246 2024-03-02 19:48:03 +08:00
parent 39f1774a2b
commit fcacfb9a04
15 changed files with 294 additions and 191 deletions

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
const emit = defineEmits(['action'])
const handleClick = () => {
const urlParam = 'dns' // handleAction
emit('action', urlParam)
}
</script>
<template>
<div>
<div title="Change Color">
<div
class="cursor-pointer flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-700"
@click="handleClick"
>
<Icon name="eos-icons:dns" class=" text-lg dark:text-white" />
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -9,7 +9,7 @@ const isOpen = ref(false)
<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"
class="cursor-pointer 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" />

41
components/tab/list.vue Normal file
View File

@ -0,0 +1,41 @@
<script setup lang="ts">
const timeStore = useTimeStore()
const emit = defineEmits(['action'])
const handleActionFromDnsList = (urlParam:string) => {
emit('action', urlParam)
}
</script>
<template>
<ClientOnly>
<div class="flex justify-between w-full">
<div>
<!-- 左边的新元素 -->
<UTooltip text="支持列表" :popper="{ placement: 'top' }">
<CommonDomainList />
</UTooltip>
<UTooltip text="Dns查询" :popper="{ placement: 'top' }">
<CommonDnsList @action="handleActionFromDnsList" />
</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>
</template>
<style scoped>
</style>

View File

@ -2,6 +2,7 @@ export default defineI18nLocale(async locale => {
return {
index: {
tips: 'The information you submit for your query will not be recorded!',
placeholder: 'Please enter a domain name',
},
error:{
formatDomain: 'Error formatting domain name',

View File

@ -2,6 +2,7 @@ export default defineI18nLocale(async locale => {
return {
index: {
tips: '您提交的查詢信息不會被記錄!',
placeholder: '請輸入域名',
},
error:{
formatDomain: '域名格式錯誤',

View File

@ -2,6 +2,7 @@ export default defineI18nLocale(async locale => {
return {
index: {
tips: '您提交的查询信息不会被记录!',
placeholder: '请输入域名',
},
error:{
formatDomain: '域名格式错误',

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import {SupportedTLDs} from "~/utils/domain";
import {useStyleStore} from "~/stores/style";
const { t } = useI18n()
@ -9,72 +10,94 @@ const state = reactive({
const toast = useToast();
const router = useRouter();
const handleAction = async (event: any) => {
//
const domain = state.domain.trim();
//
const parts = domain.split('.');
const runtimeConfig = useRuntimeConfig()
//
if (parts.length < 2) {
toast.add({
title: '域名格式不正确',
});
return;
}
const handleAction = async (url: any) => {
if (!state.domain) return toast.add({ title: '请输入域名' })
let domain = trimDomain(state.domain);
const parts = splitDomain(domain);
// TLD
const potentialTLD = parts.slice(-2).join('.');
if (!validateDomain(parts) || !isTLDValid(parts)) return;
//
if (!SupportedTLDs.has(parts.slice(-1)[0]) && !SupportedTLDs.has(potentialTLD)) {
toast.add({
title: '域名后缀不合法',
});
return;
}
domain = updateDomainForTLD(parts);
state.domain = domain;
//
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');
await router.push(`/${url}/${state.domain.replace(/\./g, '_')}.html`);
}
const timeStore = useTimeStore()
const trimDomain = (domain: string): string => {
return domain.trim();
}
const splitDomain = (domain: string): string[] => {
return domain.split('.');
}
const validateDomain = (parts: string[]): boolean => {
if (parts.length < 2) {
toast.add({ title: '域名格式不正确' });
return false;
}
return true;
}
const isTLDValid = (parts: string[]): boolean => {
const potentialTLD = parts.slice(-2).join('.');
if (!SupportedTLDs.has(parts.slice(-1)[0]) && !SupportedTLDs.has(potentialTLD)) {
toast.add({ title: '域名后缀不合法' });
return false;
}
return true;
}
const updateDomainForTLD = (parts: string[]): string => {
const potentialTLD = parts.slice(-2).join('.');
let domainToKeep: string;
if (SupportedTLDs.has(potentialTLD)) {
domainToKeep = parts.length > 2 ? parts.slice(-3).join('.') : parts.join('.');
} else {
domainToKeep = parts.slice(-2).join('.');
}
return domainToKeep;
}
const styleStore = useStyleStore()
const clientMounted = ref(false);
onMounted(() => {
clientMounted.value = true;
});
</script>
<template>
<div class="w-full h-[90vh] text-xs bg-[#F1F3F4] dark:bg-transparent">
<div class=" max-w-screen-lg mx-auto pt-[25vh] px-[1em] pb-[10vh] ">
<div
class="w-full text-xs bg-[#F1F3F4] dark:bg-transparent"
:class="{ 'h-[90vh]': !styleStore.isPage && clientMounted }"
>
<div
class=" max-w-screen-lg mx-auto px-[1em] pb-[10vh] "
:class="{ 'pt-[25vh]': !styleStore.isPage && clientMounted, 'pt-[5vh]': styleStore.isPage || !clientMounted }"
>
<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>
<h1 class="inline-block text-current no-underline dark:text-white">{{ runtimeConfig?.public?.Domain }}</h1>
<sup class="text-[#59a8d7] dark:text-[#ace4f8]">{{ runtimeConfig?.public?.DomainSuffix }}</sup>
</NuxtLink>
</nav>
<div class="mt-6">
<UForm :state="state"
class="flex items-center space-x-2 mb-3 dark:text-white"
@submit="handleAction">
@submit="handleAction('whois')">
<!-- 容器div用于水平布局 -->
<div class="flex-grow">
<UInput
v-model="state.domain"
placeholder="输入域名"
:placeholder="t('index.placeholder')"
color="sky"
size="xl"
class="w-full " />
@ -86,28 +109,8 @@ const timeStore = useTimeStore()
</UForm>
</div>
<CommonBulletin :text="`➡️ ${t('index.tips') }`" />
<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>
<TabList @action="handleAction" />
<slot />
</div>
</div>

View File

@ -1,119 +0,0 @@
<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 text-xs bg-[#F1F3F4] dark:bg-transparent">
<div class=" max-w-screen-lg mx-auto pt-[5vh] 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>
<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>
<CommonFooter />
</template>
<style scoped>
</style>

View File

@ -2,12 +2,19 @@
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@nuxt/devtools',
'@nuxt/ui',
'@nuxtjs/i18n',
'nuxt-headlessui',
'@pinia/nuxt', // needed
'@pinia-plugin-persistedstate/nuxt',
],
runtimeConfig: {
public: {
Domain: 'Nuxt Whois',
DomainSuffix: 'Dns',
}
},
app:{
head: {
title: 'Nuxt Whois',

View File

@ -0,0 +1,93 @@
<script setup lang="ts">
import {useStyleStore} from "~/stores/style";
const {t} = useI18n()
const route = useRoute();
const {domain} = route.params;
const domainData = domain.replace(/_/g, '.')
const {data, pending, error, refresh} = await useAsyncData(
'dns',
() => $fetch('/api/dns', {
method: 'POST',
body: JSON.stringify({domain: 'xukangr.com'})
})
)
const styleStore = useStyleStore()
styleStore.setIsPage(true)
useHead({
title: `${domainData} - ${t('result.title')}`,
meta: [
{
hid: 'description',
name: 'description',
content: t('result.description', {domain: domainData})
}
]
})
</script>
<template>
<div class="mt-5">
<div class="bg-white shadow-lg rounded-lg overflow-hidden">
<div class="p-6">
<h2 class="text-2xl font-bold text-gray-800 mb-6">DNS查询结果</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="font-semibold text-lg text-blue-600 mb-2">A记录</h3>
<div class="border rounded-lg p-4 bg-blue-50">
<ul class="list-none space-y-2">
<li v-for="(record, index) in data.A" :key="'a-record-' + index" class="flex justify-between items-center">
<span class="font-medium text-gray-700">IP:</span>
<span class="font-normal text-gray-600">{{ record.Record }}</span>
<span class="text-sm text-gray-500">TTL: {{ record.TTL }}</span>
</li>
</ul>
</div>
</div>
<div>
<h3 class="font-semibold text-lg text-green-600 mb-2">NS记录</h3>
<div class="border rounded-lg p-4 bg-green-50">
<ul class="list-none space-y-2">
<li v-for="(record, index) in data.NS" :key="'ns-record-' + index" class="flex justify-between items-center">
<span class="font-normal text-gray-600">{{ record.Record }}</span>
<span class="text-sm text-gray-500">TTL: {{ record.TTL }}</span>
</li>
</ul>
</div>
</div>
<div class="md:col-span-2">
<h3 class="font-semibold text-lg text-purple-600 mb-2">SOA记录</h3>
<div class="border rounded-lg p-4 bg-purple-50">
<ul class="list-none space-y-2">
<li><span class="font-medium text-gray-700">MName:</span> <span class="font-normal text-gray-600">{{ data.SOA.MName }}</span></li>
<li><span class="font-medium text-gray-700">Email:</span> <span class="font-normal text-gray-600">{{ data.SOA.Email }}</span></li>
<li><span class="font-medium text-gray-700">Serial:</span> <span class="font-normal text-gray-600">{{ data.SOA.Serial }}</span></li>
<li><span class="font-medium text-gray-700">Refresh:</span> <span class="font-normal text-gray-600">{{ data.SOA.Refresh }}</span></li>
<li><span class="font-medium text-gray-700">Retry:</span> <span class="font-normal text-gray-600">{{ data.SOA.Retry }}</span></li>
<li><span class="font-medium text-gray-700">Expire:</span> <span class="font-normal text-gray-600">{{ data.SOA.Expire }}</span></li>
<li><span class="font-medium text-gray-700">Minimum TTL:</span> <span class="font-normal text-gray-600">{{ data.SOA.MinimumTTL }}</span></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 公告部分 -->
<CommonBulletin v-if="error" class="mt-5" >
<template #text>
<Icon name="bx:error" size="16px" color="red" />
{{ t('error.notFound') }}
</template>
</CommonBulletin>
</template>
<style scoped>
</style>

View File

@ -1,5 +1,8 @@
<script setup lang="ts">
import {useStyleStore} from "~/stores/style";
const styleStore = useStyleStore()
styleStore.setIsPage(false)
</script>
<template>

View File

@ -2,10 +2,8 @@
import {ParseWhois} from "~/utils/whoisToJson";
import {AdjustTimeToUTCOffset} from "~/utils/utc";
import {useTimeStore} from "~/stores/time";
import {useStyleStore} from "~/stores/style";
definePageMeta({
layout: 'result',
})
const route = useRoute();
const {domain} = route.params;
@ -24,7 +22,8 @@ const {data, pending, error, refresh} = await useAsyncData(
const parsedInfo = ParseWhois(data.value);
const showRawData = ref(false);
const timeStore = useTimeStore()
const styleStore = useStyleStore()
styleStore.setIsPage(true)
useHead({
title: `${domainData} - ${t('result.title')}`,
meta: [
@ -39,7 +38,6 @@ useHead({
<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">
@ -105,7 +103,7 @@ useHead({
</table>
<!-- 公告部分 -->
<CommonBulletin v-else class="mt-5" >
<CommonBulletin v-if="error" class="mt-5" >
<template #text>
<Icon name="bx:error" size="16px" color="red" />
{{ t('error.notFound') }}

31
server/api/dns.post.ts Normal file
View File

@ -0,0 +1,31 @@
import dns from 'node:dns/promises';
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const domain = body.domain;
if (!domain) {
return { error: 'Missing domain' };
}
try {
const aRecords = await dns.resolve(domain, 'A');
const nsRecords = await dns.resolve(domain, 'NS');
const soaRecord = await dns.resolveSoa(domain);
return {
A: aRecords.map(ip => ({ TTL: '600', Record: ip })), // 示例中的TTL是假设的
NS: nsRecords.map(ns => ({ TTL: '21600', Record: ns })), // 示例中的TTL是假设的
SOA: {
MName: soaRecord.nsname,
Email: soaRecord.hostmaster,
Serial: soaRecord.serial,
Refresh: soaRecord.refresh,
Retry: soaRecord.retry,
Expire: soaRecord.expire,
MinimumTTL: soaRecord.minttl,
}
};
} catch (error) {
console.error(`Error fetching DNS records for ${domain}:`, error);
return { error: 'Failed to fetch DNS records' };
}
})

14
stores/style.ts Normal file
View File

@ -0,0 +1,14 @@
import { defineStore } from 'pinia'
export const useStyleStore = defineStore('style', {
state: () => {
return {
isPage: true,
}
},
actions: {
setIsPage(isPage: boolean) {
this.isPage = isPage
},
},
})

View File

@ -1,3 +1,6 @@
// 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"]);