Add site contacts/FAQ and accept AWG 2.0 / naive pastes in Add config.
ImportSubscription no longer requires only http(s): paste awg://, AWG .conf/JSON, or naive links; subscription parser also reads hosted AWG and Clash wireguard/naive. Site lists support emails and FAQ; Store listing texts/tools updated. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func setField(all [][]string, header []string, field, col, value string) {
|
||||
colIdx := -1
|
||||
for i, h := range header {
|
||||
if h == col {
|
||||
colIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if colIdx < 0 {
|
||||
panic(col)
|
||||
}
|
||||
for i := 1; i < len(all); i++ {
|
||||
for len(all[i]) < len(header) {
|
||||
all[i] = append(all[i], "")
|
||||
}
|
||||
if all[i][0] == field {
|
||||
all[i][colIdx] = value
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("missing " + field)
|
||||
}
|
||||
|
||||
func getField(all [][]string, header []string, field, col string) string {
|
||||
colIdx := -1
|
||||
for i, h := range header {
|
||||
if h == col {
|
||||
colIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(all); i++ {
|
||||
if len(all[i]) > 0 && all[i][0] == field && colIdx >= 0 && colIdx < len(all[i]) {
|
||||
return all[i][colIdx]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func main() {
|
||||
root := `D:\vpn navi`
|
||||
path := filepath.Join(root, "store.csv")
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r := csv.NewReader(f)
|
||||
r.FieldsPerRecord = -1
|
||||
r.LazyQuotes = true
|
||||
all, err := r.ReadAll()
|
||||
f.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
header := all[0]
|
||||
|
||||
// Relative paths for Partner Center: upload folder with store.csv + screen/
|
||||
ruShots := []string{
|
||||
"screen/1.ru.png",
|
||||
"screen/2.ru.png",
|
||||
"screen/3.ru.png",
|
||||
"screen/4.ru.png",
|
||||
"screen/5.ru.png",
|
||||
"screen/6.ru.png",
|
||||
"screen/12dark.png",
|
||||
"screen/13dark.png",
|
||||
"screen/14dark.png",
|
||||
"screen/15dark.png",
|
||||
"screen/16dark.png",
|
||||
"screen/9.png",
|
||||
}
|
||||
enShots := []string{
|
||||
"screen/7.en.png",
|
||||
"screen/8.en.png",
|
||||
"screen/9.png",
|
||||
"screen/10.en.png",
|
||||
"screen/11.en.png",
|
||||
"screen/12dark.png",
|
||||
"screen/13dark.png",
|
||||
"screen/14dark.png",
|
||||
"screen/15dark.png",
|
||||
"screen/16dark.png",
|
||||
}
|
||||
|
||||
ruCaptions := [][2]string{
|
||||
{"Главный экран: подключение и статус защиты", "Home: connect and protection status"},
|
||||
{"Список серверов с флагами и пингом", "Server list with flags and ping"},
|
||||
{"Настройки: тема, язык, Kill Switch", "Settings: theme, language, Kill Switch"},
|
||||
{"Статистика использования", "Usage statistics"},
|
||||
{"Помощь и полезные ссылки", "Help and useful links"},
|
||||
{"Добавление конфигурации", "Add configuration"},
|
||||
{"Тёмная тема: главный экран", "Dark theme: home"},
|
||||
{"Тёмная тема: серверы", "Dark theme: servers"},
|
||||
{"Тёмная тема: настройки", "Dark theme: settings"},
|
||||
{"Тёмная тема: статистика", "Dark theme: statistics"},
|
||||
{"Тёмная тема: помощь", "Dark theme: help"},
|
||||
{"Общий вид приложения", "App overview"},
|
||||
}
|
||||
enCaptions := []string{
|
||||
"Home: connect and protection status",
|
||||
"Server list with flags and ping",
|
||||
"App overview",
|
||||
"Settings: theme, language, Kill Switch",
|
||||
"Statistics and help",
|
||||
"Dark theme: home",
|
||||
"Dark theme: servers",
|
||||
"Dark theme: settings",
|
||||
"Dark theme: statistics",
|
||||
"Dark theme: help",
|
||||
}
|
||||
|
||||
for i, p := range ruShots {
|
||||
setField(all, header, fmt.Sprintf("DesktopScreenshot%d", i+1), "default", p)
|
||||
}
|
||||
for i, p := range enShots {
|
||||
setField(all, header, fmt.Sprintf("DesktopScreenshot%d", i+1), "en", p)
|
||||
}
|
||||
// Clear en slots beyond enShots so Partner Center falls back to default for RU-only extras
|
||||
for i := len(enShots); i < 30; i++ {
|
||||
setField(all, header, fmt.Sprintf("DesktopScreenshot%d", i+1), "en", "")
|
||||
}
|
||||
// Clear default slots beyond ruShots
|
||||
for i := len(ruShots); i < 30; i++ {
|
||||
setField(all, header, fmt.Sprintf("DesktopScreenshot%d", i+1), "default", "")
|
||||
}
|
||||
|
||||
for i, c := range ruCaptions {
|
||||
field := fmt.Sprintf("DesktopScreenshotCaption%d", i+1)
|
||||
setField(all, header, field, "default", c[0])
|
||||
en := c[1]
|
||||
if i < len(enCaptions) {
|
||||
en = enCaptions[i]
|
||||
}
|
||||
setField(all, header, field, "en", en)
|
||||
}
|
||||
for i := len(ruCaptions); i < 30; i++ {
|
||||
field := fmt.Sprintf("DesktopScreenshotCaption%d", i+1)
|
||||
setField(all, header, field, "default", "")
|
||||
setField(all, header, field, "en", "")
|
||||
}
|
||||
|
||||
out, err := os.Create(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err := out.Write([]byte{0xEF, 0xBB, 0xBF}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w := csv.NewWriter(out)
|
||||
if err := w.WriteAll(all); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.Flush()
|
||||
out.Close()
|
||||
|
||||
for i := 1; i <= 12; i++ {
|
||||
d := getField(all, header, fmt.Sprintf("DesktopScreenshot%d", i), "default")
|
||||
e := getField(all, header, fmt.Sprintf("DesktopScreenshot%d", i), "en")
|
||||
if d == "" && e == "" {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%d default=%s en=%s\n", i, d, e)
|
||||
for _, p := range []string{d, e} {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
full := filepath.Join(root, filepath.FromSlash(p))
|
||||
if _, err := os.Stat(full); err != nil {
|
||||
fmt.Println(" MISSING", full)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("updated", path, "rows", len(all))
|
||||
_ = strings.TrimSpace
|
||||
}
|
||||
Reference in New Issue
Block a user