update url list format
This commit is contained in:
11
blocklist.go
11
blocklist.go
@@ -83,7 +83,7 @@ func createDefaultURLList() {
|
||||
_, err := os.Stat(urlFileList)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("%s: Using Default url list: %s", time.Now(), urlFileList)
|
||||
writeLines(urlFileList, defaultURLs)
|
||||
writeURLListCSV(urlFileList, defaultURLs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,13 +100,14 @@ func fetchAndMergeSources() {
|
||||
var builder strings.Builder
|
||||
builder.Write(blockData)
|
||||
|
||||
urls, _ := readLines(urlFileList)
|
||||
for _, url := range urls {
|
||||
if strings.HasPrefix(url, "#") {
|
||||
items, _ := readURLListCSV(urlFileList)
|
||||
for _, item := range items {
|
||||
if !item.Enabled {
|
||||
continue
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
resp, err := http.Get(item.URL)
|
||||
if err != nil {
|
||||
log.Printf("%s: Failed to fetch %s: %v", time.Now(), item.Name, err)
|
||||
continue
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
34
config.go
34
config.go
@@ -40,7 +40,7 @@ type Config struct {
|
||||
MergedListTmp string `json:"merged_list_tmp"`
|
||||
|
||||
// Default blocklist URLs
|
||||
DefaultURLs []string `json:"default_urls"`
|
||||
DefaultURLs []URLListItem `json:"default_urls"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -64,25 +64,25 @@ func getDefaultConfig() Config {
|
||||
ProcessName: "coredns",
|
||||
TmpFile: "/sdcard1/combined-blocklist.txt",
|
||||
LastUpdateFile: "/sdcard1/last_update.txt",
|
||||
URLFileList: "/sdcard1/urllist.txt",
|
||||
URLFileList: "/sdcard1/urllist.csv",
|
||||
BlocklistFile: "/run/utm/domain_list/domainlist_0.list",
|
||||
RemoveFile: "/run/utm/domain_list/domainlist_1.list",
|
||||
MergedListTmp: "/tmp/mergedlist.txt",
|
||||
DefaultURLs: []string{
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt",
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt",
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt",
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt",
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt",
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt",
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt",
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt",
|
||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt",
|
||||
"https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt",
|
||||
"https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt",
|
||||
"https://v.firebog.net/hosts/Prigent-Crypto.txt",
|
||||
"https://phishing.army/download/phishing_army_blocklist_extended.txt",
|
||||
"https://v.firebog.net/hosts/static/w3kbl.txt",
|
||||
DefaultURLs: []URLListItem{
|
||||
{Name: "AdGuard DNS filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt"},
|
||||
{Name: "AdGuard Russian filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt"},
|
||||
{Name: "AdGuard Base filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt"},
|
||||
{Name: "AdGuard Tracking Protection", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt"},
|
||||
{Name: "AdGuard Social Media filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt"},
|
||||
{Name: "AdGuard Annoyance filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt"},
|
||||
{Name: "AdGuard Mobile Ads filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt"},
|
||||
{Name: "AdGuard Search Ads filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt"},
|
||||
{Name: "AdGuard Adult filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt"},
|
||||
{Name: "AntiMalware Hosts", Enabled: true, Group: "Default", URL: "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt"},
|
||||
{Name: "DigitalSide Threat Intel", Enabled: true, Group: "Default", URL: "https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt"},
|
||||
{Name: "Firebog Crypto", Enabled: true, Group: "Default", URL: "https://v.firebog.net/hosts/Prigent-Crypto.txt"},
|
||||
{Name: "Phishing Army", Enabled: true, Group: "Default", URL: "https://phishing.army/download/phishing_army_blocklist_extended.txt"},
|
||||
{Name: "W3kbl", Enabled: true, Group: "Default", URL: "https://v.firebog.net/hosts/static/w3kbl.txt"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
123
handlers.go
123
handlers.go
@@ -26,52 +26,87 @@ func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func urlListsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
configMu.RLock()
|
||||
urlFileList := config.URLFileList
|
||||
defaultURLs := config.DefaultURLs
|
||||
configMu.RUnlock()
|
||||
|
||||
if r.Method == "POST" {
|
||||
action := r.FormValue("action")
|
||||
url := sanitizeInput(r.FormValue("url"))
|
||||
indexStr := r.FormValue("index")
|
||||
|
||||
configMu.RLock()
|
||||
urlFileList := config.URLFileList
|
||||
defaultURLs := config.DefaultURLs
|
||||
configMu.RUnlock()
|
||||
|
||||
urls, err := readLines(urlFileList)
|
||||
items, err := readURLListCSV(urlFileList)
|
||||
if err != nil {
|
||||
renderTemplate(w, r, "urllists.html", struct {
|
||||
URLs []string
|
||||
Items []URLListItem
|
||||
Notification string
|
||||
NotificationType string
|
||||
}{
|
||||
URLs: urls,
|
||||
Items: items,
|
||||
Notification: "Error reading URL list",
|
||||
NotificationType: "error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
errorMsg := ""
|
||||
var errorMsg string
|
||||
|
||||
switch action {
|
||||
case "add":
|
||||
if !isValidURL(url) {
|
||||
name := sanitizeInput(r.FormValue("name"))
|
||||
urlStr := sanitizeInput(r.FormValue("url"))
|
||||
group := sanitizeInput(r.FormValue("group"))
|
||||
note := sanitizeInput(r.FormValue("note"))
|
||||
|
||||
if name == "" {
|
||||
errorMsg = "Name is required"
|
||||
} else if !isValidURL(urlStr) {
|
||||
errorMsg = "Invalid URL format. Must start with http:// or https://"
|
||||
} else {
|
||||
urls = append(urls, url)
|
||||
items = append(items, URLListItem{
|
||||
Name: name,
|
||||
Enabled: true,
|
||||
Group: group,
|
||||
URL: urlStr,
|
||||
Note: note,
|
||||
})
|
||||
}
|
||||
|
||||
case "remove":
|
||||
indexStr := r.FormValue("index")
|
||||
index, _ := strconv.Atoi(indexStr)
|
||||
if index >= 0 && index < len(urls) {
|
||||
urls = append(urls[:index], urls[index+1:]...)
|
||||
if index >= 0 && index < len(items) {
|
||||
items = append(items[:index], items[index+1:]...)
|
||||
} else {
|
||||
errorMsg = "Invalid index"
|
||||
}
|
||||
|
||||
case "toggle":
|
||||
indexStr := r.FormValue("index")
|
||||
index, _ := strconv.Atoi(indexStr)
|
||||
if index >= 0 && index < len(urls) {
|
||||
if strings.HasPrefix(urls[index], "#") {
|
||||
urls[index] = strings.TrimPrefix(urls[index], "#")
|
||||
if index >= 0 && index < len(items) {
|
||||
items[index].Enabled = !items[index].Enabled
|
||||
} else {
|
||||
errorMsg = "Invalid index"
|
||||
}
|
||||
|
||||
case "edit":
|
||||
indexStr := r.FormValue("index")
|
||||
name := sanitizeInput(r.FormValue("name"))
|
||||
urlStr := sanitizeInput(r.FormValue("url"))
|
||||
group := sanitizeInput(r.FormValue("group"))
|
||||
note := sanitizeInput(r.FormValue("note"))
|
||||
|
||||
index, _ := strconv.Atoi(indexStr)
|
||||
if index >= 0 && index < len(items) {
|
||||
if name == "" {
|
||||
errorMsg = "Name is required"
|
||||
} else if !isValidURL(urlStr) {
|
||||
errorMsg = "Invalid URL format. Must start with http:// or https://"
|
||||
} else {
|
||||
urls[index] = "#" + urls[index]
|
||||
items[index].Name = name
|
||||
items[index].URL = urlStr
|
||||
items[index].Group = group
|
||||
items[index].Note = note
|
||||
}
|
||||
} else {
|
||||
errorMsg = "Invalid index"
|
||||
@@ -80,54 +115,56 @@ func urlListsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if errorMsg != "" {
|
||||
renderTemplate(w, r, "urllists.html", struct {
|
||||
URLs []string
|
||||
Items []URLListItem
|
||||
Notification string
|
||||
NotificationType string
|
||||
}{
|
||||
URLs: urls,
|
||||
Items: items,
|
||||
Notification: errorMsg,
|
||||
NotificationType: "error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
writeLines(urlFileList, urls)
|
||||
|
||||
// Show success notification
|
||||
if len(urls) == 0 {
|
||||
writeLines(urlFileList, defaultURLs)
|
||||
urls = defaultURLs
|
||||
err = writeURLListCSV(urlFileList, items)
|
||||
if err != nil {
|
||||
renderTemplate(w, r, "urllists.html", struct {
|
||||
Items []URLListItem
|
||||
Notification string
|
||||
NotificationType string
|
||||
}{
|
||||
Items: items,
|
||||
Notification: "Error saving URL list",
|
||||
NotificationType: "error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
renderTemplate(w, r, "urllists.html", struct {
|
||||
URLs []string
|
||||
Items []URLListItem
|
||||
Notification string
|
||||
NotificationType string
|
||||
}{
|
||||
URLs: urls,
|
||||
Items: items,
|
||||
Notification: "URL list updated successfully",
|
||||
NotificationType: "success",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
configMu.RLock()
|
||||
urlFileList := config.URLFileList
|
||||
defaultURLs := config.DefaultURLs
|
||||
configMu.RUnlock()
|
||||
|
||||
urls, _ := readLines(urlFileList)
|
||||
if len(urls) == 0 {
|
||||
writeLines(urlFileList, defaultURLs)
|
||||
urls = defaultURLs
|
||||
items, err := readURLListCSV(urlFileList)
|
||||
if err != nil || len(items) == 0 {
|
||||
// Use default blocklists
|
||||
defaultItems := defaultURLs
|
||||
writeURLListCSV(urlFileList, defaultItems)
|
||||
items = defaultItems
|
||||
}
|
||||
|
||||
data := struct {
|
||||
URLs []string
|
||||
renderTemplate(w, r, "urllists.html", struct {
|
||||
Items []URLListItem
|
||||
}{
|
||||
URLs: urls,
|
||||
}
|
||||
renderTemplate(w, r, "urllists.html", data)
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
|
||||
func domainsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
2
info.txt
2
info.txt
@@ -6,4 +6,6 @@ go build -o unifi-blocklist-app main.go
|
||||
|
||||
tailwindcss -o static/tailwind.css --minify
|
||||
|
||||
tailwindcss -o static/tailwind.css --minify --watch
|
||||
|
||||
git add . && git commit -m "update" && git push
|
||||
File diff suppressed because one or more lines are too long
@@ -1,34 +1,155 @@
|
||||
{{ define "content" }}
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-3xl font-bold mb-6">URL Lists</h1>
|
||||
<form method="POST" class="mb-6 bg-gray-800 p-4 rounded">
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<div class="flex gap-2">
|
||||
<input type="text" name="url" placeholder="Add URL (e.g., https://example.com/blocklist.txt)" class="flex-1 p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
||||
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-bold px-4 py-2 rounded transition">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="space-y-2">
|
||||
{{ range $i, $url := .PageData.URLs }}
|
||||
<li class="flex justify-between items-center bg-gray-800 p-3 rounded">
|
||||
<span class="text-sm text-gray-300 break-all">{{ $url }}</span>
|
||||
<div class="flex gap-2 ml-4">
|
||||
<form method="POST" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||
<input type="hidden" name="action" value="toggle">
|
||||
<input type="hidden" name="index" value="{{ $i }}">
|
||||
<button type="submit" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold px-3 py-1 rounded text-sm transition">Toggle</button>
|
||||
</form>
|
||||
<form method="POST" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||
<input type="hidden" name="action" value="remove">
|
||||
<input type="hidden" name="index" value="{{ $i }}">
|
||||
<button type="submit" class="bg-red-600 hover:bg-red-700 text-white font-bold px-3 py-1 rounded text-sm transition">Remove</button>
|
||||
</form>
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-bold mb-6">URL Blocklists</h1>
|
||||
|
||||
<!-- Add New URL Form -->
|
||||
<div class="bg-gray-800 p-6 rounded mb-6">
|
||||
<h2 class="text-xl font-bold mb-4">Add New Blocklist</h2>
|
||||
<form method="POST" class="space-y-4">
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||
<input type="hidden" name="action" value="add">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-1">Name *</label>
|
||||
<input type="text" name="name" placeholder="e.g., AdGuard Default" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-1">Group</label>
|
||||
<input type="text" name="group" placeholder="e.g., Adblock, Malware" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-1">URL *</label>
|
||||
<input type="text" name="url" placeholder="https://example.com/blocklist.txt" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-1">Note</label>
|
||||
<textarea name="note" placeholder="Optional notes about this blocklist" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-bold px-4 py-2 rounded transition">Add Blocklist</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Blocklists Table -->
|
||||
<div class="bg-gray-800 rounded overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Status</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Name</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Group</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">URL</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Note</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $i, $item := .PageData.Items }}
|
||||
<tr class="border-t border-gray-700 hover:bg-gray-750 transition">
|
||||
<td class="px-4 py-3 text-center">
|
||||
<form method="POST" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||
<input type="hidden" name="action" value="toggle">
|
||||
<input type="hidden" name="index" value="{{ $i }}">
|
||||
<button type="submit" class="px-3 py-1 rounded text-sm font-bold transition {{ if $item.Enabled }}bg-green-600 hover:bg-green-700 text-white{{ else }}bg-gray-600 hover:bg-gray-700 text-gray-200{{ end }}">
|
||||
{{ if $item.Enabled }}Enabled{{ else }}Disabled{{ end }}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-300">{{ $item.Name }}</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-400">{{ if $item.Group }}{{ $item.Group }}{{ else }}<span class="text-gray-600">-</span>{{ end }}</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-400 max-w-xs truncate" title="{{ $item.URL }}">{{ $item.URL }}</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-400 max-w-xs truncate" title="{{ $item.Note }}">{{ if $item.Note }}{{ $item.Note }}{{ else }}<span class="text-gray-600">-</span>{{ end }}</td>
|
||||
<td class="px-4 py-3 text-sm space-x-2">
|
||||
<button onclick="openEditModal('{{ $i }}', '{{ $item.Name }}', '{{ $item.URL }}', '{{ $item.Group }}', '{{ $item.Note }}')" class="bg-blue-600 hover:bg-blue-700 text-white font-bold px-2 py-1 rounded transition">
|
||||
Edit
|
||||
</button>
|
||||
<form method="POST" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||
<input type="hidden" name="action" value="remove">
|
||||
<input type="hidden" name="index" value="{{ $i }}">
|
||||
<button type="submit" onclick="return confirm('Are you sure you want to delete this blocklist?')" class="bg-red-600 hover:bg-red-700 text-white font-bold px-2 py-1 rounded transition">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{ if not .PageData.Items }}
|
||||
<div class="text-center text-gray-400 py-8">
|
||||
No blocklists configured. Add one to get started.
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div id="editModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-gray-800 rounded-lg shadow-lg max-w-md w-full mx-4 p-6">
|
||||
<h2 class="text-xl font-bold mb-4">Edit Blocklist</h2>
|
||||
<form method="POST" class="space-y-4">
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||
<input type="hidden" name="action" value="edit">
|
||||
<input type="hidden" name="index" id="modalIndex" value="">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-1">Name *</label>
|
||||
<input type="text" name="name" id="modalName" placeholder="Name" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-1">Group</label>
|
||||
<input type="text" name="group" id="modalGroup" placeholder="Group" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-1">URL *</label>
|
||||
<input type="text" name="url" id="modalURL" placeholder="URL" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-1">Note</label>
|
||||
<textarea name="note" id="modalNote" placeholder="Note" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 justify-end">
|
||||
<button type="button" onclick="closeEditModal()" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded font-medium transition">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-medium transition">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openEditModal(index, name, url, group, note) {
|
||||
document.getElementById('modalIndex').value = index;
|
||||
document.getElementById('modalName').value = name;
|
||||
document.getElementById('modalURL').value = url;
|
||||
document.getElementById('modalGroup').value = group;
|
||||
document.getElementById('modalNote').value = note;
|
||||
document.getElementById('editModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
document.getElementById('editModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
document.getElementById('editModal').addEventListener('click', function(event) {
|
||||
if (event.target === this) {
|
||||
closeEditModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
15
test/urllist.csv
Normal file
15
test/urllist.csv
Normal file
@@ -0,0 +1,15 @@
|
||||
Name,Enabled,Group,URL,Note
|
||||
AdGuard DNS filter,false,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt,
|
||||
AdGuard Russian filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt,
|
||||
AdGuard Base filter,false,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt,
|
||||
AdGuard Tracking Protection,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt,
|
||||
AdGuard Social Media filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt,
|
||||
AdGuard Annoyance filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt,
|
||||
AdGuard Mobile Ads filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt,
|
||||
AdGuard Search Ads filter,false,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt,
|
||||
AdGuard Adult filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt,
|
||||
AntiMalware Hosts,true,Default,https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt,
|
||||
DigitalSide Threat Intel,true,Default,https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt,
|
||||
Firebog Crypto,true,Default,https://v.firebog.net/hosts/Prigent-Crypto.txt,
|
||||
Phishing Army,true,Default,https://phishing.army/download/phishing_army_blocklist_extended.txt,
|
||||
W3kbl,true,Default,https://v.firebog.net/hosts/static/w3kbl.txt,
|
||||
|
@@ -1,13 +0,0 @@
|
||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt
|
||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
|
||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt
|
||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt
|
||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt
|
||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt
|
||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt
|
||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt
|
||||
https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt
|
||||
https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt
|
||||
https://v.firebog.net/hosts/Prigent-Crypto.txt
|
||||
https://phishing.army/download/phishing_army_blocklist_extended.txt
|
||||
https://v.firebog.net/hosts/static/w3kbl.txt
|
||||
85
utils.go
85
utils.go
@@ -159,6 +159,91 @@ func isValidDomain(d string) bool {
|
||||
return re.MatchString(d)
|
||||
}
|
||||
|
||||
// URLListItem represents a single URL entry in the CSV
|
||||
type URLListItem struct {
|
||||
Name string
|
||||
Enabled bool
|
||||
Group string
|
||||
URL string
|
||||
Note string
|
||||
}
|
||||
|
||||
// readURLListCSV reads the URL list from a CSV file
|
||||
func readURLListCSV(file string) ([]URLListItem, error) {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
var items []URLListItem
|
||||
|
||||
for i, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || (i == 0 && strings.HasPrefix(line, "Name,")) {
|
||||
// Skip empty lines and header
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, ",", 5)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
item := URLListItem{
|
||||
Name: strings.TrimSpace(parts[0]),
|
||||
Enabled: strings.TrimSpace(parts[1]) == "true",
|
||||
Group: "",
|
||||
URL: "",
|
||||
Note: "",
|
||||
}
|
||||
|
||||
if len(parts) > 2 {
|
||||
item.Group = strings.TrimSpace(parts[2])
|
||||
}
|
||||
if len(parts) > 3 {
|
||||
item.URL = strings.TrimSpace(parts[3])
|
||||
}
|
||||
if len(parts) > 4 {
|
||||
item.Note = strings.TrimSpace(parts[4])
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// writeURLListCSV writes the URL list to a CSV file
|
||||
func writeURLListCSV(file string, items []URLListItem) error {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("Name,Enabled,Group,URL,Note\n")
|
||||
|
||||
for _, item := range items {
|
||||
enabled := "false"
|
||||
if item.Enabled {
|
||||
enabled = "true"
|
||||
}
|
||||
// Escape quotes and wrap in quotes if needed
|
||||
name := escapeCSVField(item.Name)
|
||||
group := escapeCSVField(item.Group)
|
||||
url := escapeCSVField(item.URL)
|
||||
note := escapeCSVField(item.Note)
|
||||
|
||||
sb.WriteString(name + "," + enabled + "," + group + "," + url + "," + note + "\n")
|
||||
}
|
||||
|
||||
return os.WriteFile(file, []byte(sb.String()), 0644)
|
||||
}
|
||||
|
||||
// escapeCSVField properly escapes a CSV field
|
||||
func escapeCSVField(field string) string {
|
||||
if strings.ContainsAny(field, ",\"\n") {
|
||||
return "\"" + strings.ReplaceAll(field, "\"", "\"\"") + "\""
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
func generateSecret() string {
|
||||
// This function is kept for backward compatibility but is not currently used
|
||||
// since we're using pquerna/otp for TOTP generation
|
||||
|
||||
Reference in New Issue
Block a user