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:
Navis
2026-08-02 15:40:13 +03:00
co-authored by Cursor
parent e416327fbf
commit 50f7f71393
39 changed files with 3128 additions and 112 deletions
+218
View File
@@ -0,0 +1,218 @@
# -*- coding: utf-8 -*-
"""Fill Microsoft Store listing CSV text fields (default=RU, en=EN)."""
import csv
from pathlib import Path
path = Path(__file__).resolve().parents[1] / "store.csv"
ru = {
"Description": (
"EvilFox — приложение для безопасного доступа в интернет на Windows.\n"
"\n"
"Защитите данные в публичных Wi‑Fi сетях: EvilFox шифрует трафик, "
"скрывает ваш реальный IP-адрес и помогает предотвратить утечки DNS. "
"Современный интерфейс на русском и английском, светлая и тёмная темы, "
"удобный выбор серверов и понятный статус подключения.\n"
"\n"
"Возможности:\n"
"• Шифрование трафика с поддержкой современных протоколов\n"
"• Режим прокси и полный режим защиты (TUN) для всего трафика приложений\n"
"• Защита от утечек (Kill Switch) при обрыве соединения\n"
"• Автовыбор сервера с наименьшим пингом\n"
"• Список серверов с флагами стран и группировкой по хосту\n"
"• Добавление собственной конфигурации по URL\n"
"• Мониторинг использования трафика и статистика сессий\n"
"• Интерфейс на русском и английском языках\n"
"\n"
"EvilFox подходит для повседневной работы, путешествий и защиты приватности. "
"Конфигурацию подключения вы добавляете самостоятельно; "
"покупки внутри приложения не требуются.\n"
"\n"
"Сайт: https://evilfox.win/\n"
"Поддержка: https://t.me/evilfox100Bot"
),
"ReleaseNotes": (
"Обновление 4.2: улучшенный интерфейс для Microsoft Store, "
"переключатель языка RU/EN, светлая тема по умолчанию, "
"группировка серверов по IP с отображением доступных протоколов, "
"исправления переключателей темы и языка, реальные флаги стран (SVG), "
"Kill Switch и статистика."
),
"Title": "EvilFox",
"ShortTitle": "EvilFox",
"SortTitle": "EvilFox",
"VoiceTitle": "Evil Fox",
"ShortDescription": (
"Безопасный доступ в интернет: шифрование трафика, скрытие IP, "
"защита от утечек DNS. Русский и английский. Современные протоколы, "
"Kill Switch, удобный выбор серверов."
),
"DevStudio": "EvilFox",
"CopyrightTrademarkInformation": (
"© EvilFox. All rights reserved. EvilFox and the EvilFox logo "
"are trademarks of their respective owners."
),
"AdditionalLicenseTerms": (
"Proprietary software. By installing EvilFox you agree to use the "
"application in accordance with applicable laws. The app provides a "
"client for encrypted network connections; server configurations are "
"supplied by the user. See https://evilfox.win/ for support and terms."
),
"Feature1": "Шифрование трафика и скрытие IP-адреса",
"Feature2": "Режим прокси и полный режим защиты (TUN)",
"Feature3": "Kill Switch — блокировка трафика при обрыве VPN",
"Feature4": "Автовыбор сервера с лучшим пингом",
"Feature5": "Серверы с флагами стран и группировкой протоколов",
"Feature6": "Добавление конфигурации по URL",
"Feature7": "Статистика трафика и сессий",
"Feature8": "Интерфейс на русском и английском",
"Feature9": "Светлая и тёмная темы оформления",
"Feature10": "Защита от утечек DNS",
"MinimumHardwareReq1": "Windows 10 version 1809 or later (x64)",
"MinimumHardwareReq2": "Internet connection",
"MinimumHardwareReq3": "100 MB free disk space",
"RecommendedHardwareReq1": "Windows 11 (x64)",
"RecommendedHardwareReq2": "Stable broadband connection",
"SearchTerm1": "VPN",
"SearchTerm2": "privacy",
"SearchTerm3": "secure internet",
"SearchTerm4": "encrypt traffic",
"SearchTerm5": "EvilFox",
"SearchTerm6": "proxy client",
"SearchTerm7": "kill switch",
"DesktopScreenshotCaption1": "Главный экран: подключение и статус защиты",
"DesktopScreenshotCaption2": "Список серверов с флагами и пингом",
"DesktopScreenshotCaption3": "Настройки: тема, язык, Kill Switch",
"DesktopScreenshotCaption4": "Статистика использования",
}
en = {
"Description": (
"EvilFox is a Windows app for private, secure internet access.\n"
"\n"
"Protect your data on public WiFi: EvilFox encrypts your traffic, "
"hides your real IP address, and helps prevent DNS leaks. "
"A modern interface in English and Russian, light and dark themes, "
"an easy server list, and a clear connection status.\n"
"\n"
"Features:\n"
"• Traffic encryption with modern protocol support\n"
"• Proxy mode and full protection mode (TUN) for all app traffic\n"
"• Leak protection (Kill Switch) if the connection drops\n"
"• Auto-select the server with the lowest ping\n"
"• Server list with country flags and host grouping\n"
"• Add your own configuration via URL\n"
"• Traffic usage monitoring and session stats\n"
"• English and Russian interface\n"
"\n"
"EvilFox is built for everyday browsing, travel, and privacy. "
"You add your own connection configuration; no in-app purchases are required.\n"
"\n"
"Website: https://evilfox.win/\n"
"Support: https://t.me/evilfox100Bot"
),
"ReleaseNotes": (
"Version 4.2: Microsoft Storeready UI polish, RU/EN language switch, "
"light theme by default, servers grouped by IP with available protocols, "
"theme/language toggle fixes, real country flags (SVG), Kill Switch, "
"and usage statistics."
),
"Title": "EvilFox",
"ShortTitle": "EvilFox",
"SortTitle": "EvilFox",
"VoiceTitle": "Evil Fox",
"ShortDescription": (
"Secure internet access: encrypt traffic, hide your IP, help prevent "
"DNS leaks. English & Russian. Modern protocols, Kill Switch, "
"easy server selection."
),
"DevStudio": "EvilFox",
"CopyrightTrademarkInformation": (
"© EvilFox. All rights reserved. EvilFox and the EvilFox logo "
"are trademarks of their respective owners."
),
"AdditionalLicenseTerms": (
"Proprietary software. By installing EvilFox you agree to use the "
"application in accordance with applicable laws. The app provides a "
"client for encrypted network connections; server configurations are "
"supplied by the user. See https://evilfox.win/ for support and terms."
),
"Feature1": "Encrypt traffic and hide your IP address",
"Feature2": "Proxy mode and full protection mode (TUN)",
"Feature3": "Kill Switch blocks traffic if VPN drops",
"Feature4": "Auto-select the lowest-ping server",
"Feature5": "Servers with country flags and protocol grouping",
"Feature6": "Add configuration via URL",
"Feature7": "Traffic and session statistics",
"Feature8": "English and Russian interface",
"Feature9": "Light and dark themes",
"Feature10": "DNS leak protection",
"MinimumHardwareReq1": "Windows 10 version 1809 or later (x64)",
"MinimumHardwareReq2": "Internet connection",
"MinimumHardwareReq3": "100 MB free disk space",
"RecommendedHardwareReq1": "Windows 11 (x64)",
"RecommendedHardwareReq2": "Stable broadband connection",
"SearchTerm1": "VPN",
"SearchTerm2": "privacy",
"SearchTerm3": "secure internet",
"SearchTerm4": "encrypt traffic",
"SearchTerm5": "EvilFox",
"SearchTerm6": "proxy client",
"SearchTerm7": "kill switch",
"DesktopScreenshotCaption1": "Home: connect and protection status",
"DesktopScreenshotCaption2": "Server list with flags and ping",
"DesktopScreenshotCaption3": "Settings: theme, language, Kill Switch",
"DesktopScreenshotCaption4": "Usage statistics",
}
def main() -> None:
with path.open("r", encoding="utf-8-sig", newline="") as f:
reader = csv.reader(f)
header = next(reader)
rows = []
for r in reader:
while len(r) < len(header):
r.append("")
rows.append(r)
# Field, ID, Type, default, en
default_idx = header.index("default")
en_idx = header.index("en")
for r in rows:
if not r or not r[0]:
continue
field = r[0]
if field in ru:
r[default_idx] = ru[field]
if field in en:
r[en_idx] = en[field]
with path.open("w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f, lineterminator="\n")
w.writerow(header)
w.writerows(rows)
with path.open("r", encoding="utf-8-sig", newline="") as f:
for row in csv.DictReader(f):
if row["Field"] in (
"Description",
"Title",
"ShortDescription",
"Feature1",
"AdditionalLicenseTerms",
"SearchTerm1",
):
print(
row["Field"],
"default_len=",
len(row.get("default") or ""),
"en_len=",
len(row.get("en") or ""),
)
print("wrote", path)
if __name__ == "__main__":
main()
+59
View File
@@ -0,0 +1,59 @@
package main
import (
"encoding/csv"
"os"
"path/filepath"
)
func main() {
path := filepath.Join(`D:\vpn navi`, "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)
}
h := all[0]
di, ei := 3, 4
for i, x := range h {
if x == "default" {
di = i
}
if x == "en" {
ei = i
}
}
ruB, _ := os.ReadFile(filepath.Join(`D:\vpn navi`, "dist", "microsoft-store", "AdditionalLicense.ru.txt"))
enB, _ := os.ReadFile(filepath.Join(`D:\vpn navi`, "dist", "microsoft-store", "AdditionalLicense.en.txt"))
ru := string(ruB)
en := string(enB)
for i := 1; i < len(all); i++ {
for len(all[i]) < len(h) {
all[i] = append(all[i], "")
}
if all[i][0] == "AdditionalLicenseTerms" {
all[i][di] = ru
all[i][ei] = en
}
}
out, err := os.Create(path)
if err != nil {
panic(err)
}
_, _ = out.Write([]byte{0xEF, 0xBB, 0xBF})
w := csv.NewWriter(out)
_ = w.WriteAll(all)
w.Flush()
out.Close()
listing := filepath.Join(`D:\vpn navi`, "dist", "microsoft-store", "listing", "store.csv")
if data, err := os.ReadFile(path); err == nil {
_ = os.WriteFile(listing, data, 0644)
}
}
+61
View File
@@ -0,0 +1,61 @@
package main
import (
"encoding/csv"
"os"
"path/filepath"
)
func main() {
path := filepath.Join(`D:\vpn navi`, "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)
}
h := all[0]
di, ei := 3, 4
for i, x := range h {
if x == "default" {
di = i
}
if x == "en" {
ei = i
}
}
ru := "© 2026 EvilFox. Все права защищены. EvilFox, логотип EvilFox и связанные знаки являются товарными знаками или зарегистрированными товарными знаками EvilFox. Прочие названия продуктов и компаний, упомянутые в приложении или на странице продукта, могут быть товарными знаками соответствующих владельцев."
en := "© 2026 EvilFox. All rights reserved. EvilFox, the EvilFox logo, and related marks are trademarks or registered trademarks of EvilFox. Other product and company names mentioned in the app or product listing may be trademarks of their respective owners."
for i := 1; i < len(all); i++ {
for len(all[i]) < len(h) {
all[i] = append(all[i], "")
}
if all[i][0] == "CopyrightTrademarkInformation" {
all[i][di] = ru
all[i][ei] = 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()
listing := filepath.Join(`D:\vpn navi`, "dist", "microsoft-store", "listing", "store.csv")
if data, err := os.ReadFile(path); err == nil {
_ = os.WriteFile(listing, data, 0644)
}
}
+18
View File
@@ -0,0 +1,18 @@
package main
import ("encoding/csv";"os";"path/filepath")
func main() {
path := filepath.Join(`D:\vpn navi`,"store.csv")
f,_ := os.Open(path); r := csv.NewReader(f); r.FieldsPerRecord=-1; r.LazyQuotes=true; all,_ := r.ReadAll(); f.Close()
h := all[0]; di,ei := 3,4
for i,x := range h { if x=="default"{di=i}; if x=="en"{ei=i} }
for i:=1;i<len(all);i++ {
for len(all[i])<len(h){all[i]=append(all[i],"")}
if all[i][0]=="StoreLogo300x300" {
all[i][di]="screen/1.ru.300x300.png"
all[i][ei]="screen/1.ru.300x300.png"
}
}
out,_ := os.Create(path); out.Write([]byte{0xEF,0xBB,0xBF}); w:=csv.NewWriter(out); w.WriteAll(all); w.Flush(); out.Close()
listing := `D:\vpn navi\dist\microsoft-store\listing\store.csv`
data,_ := os.ReadFile(path); _ = os.WriteFile(listing, data, 0644)
}
+191
View File
@@ -0,0 +1,191 @@
package main
import (
"encoding/csv"
"fmt"
"os"
"path/filepath"
)
func main() {
path := filepath.Join(`D:\vpn navi`, "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)
}
if len(all) < 2 {
panic("empty csv")
}
header := all[0]
defIdx, enIdx := -1, -1
for i, h := range header {
switch h {
case "default":
defIdx = i
case "en":
enIdx = i
}
}
if defIdx < 0 || enIdx < 0 {
panic(fmt.Sprintf("columns: %v", header))
}
ru := map[string]string{
"Description": `EvilFox — приложение для безопасного доступа в интернет на Windows.
Защитите данные в публичных Wi‑Fi сетях: EvilFox шифрует трафик, скрывает ваш реальный IP-адрес и помогает предотвратить утечки DNS. Современный интерфейс на русском и английском, светлая и тёмная темы, удобный выбор серверов и понятный статус подключения.
Возможности:
• Шифрование трафика с поддержкой современных протоколов
• Режим прокси и полный режим защиты (TUN) для всего трафика приложений
• Защита от утечек (Kill Switch) при обрыве соединения
• Автовыбор сервера с наименьшим пингом
• Список серверов с флагами стран и группировкой по хосту
• Добавление собственной конфигурации по URL
• Мониторинг использования трафика и статистика сессий
• Интерфейс на русском и английском языках
EvilFox подходит для повседневной работы, путешествий и защиты приватности. Конфигурацию подключения вы добавляете самостоятельно; покупки внутри приложения не требуются.
Сайт: https://evilfox.win/
Поддержка: https://t.me/evilfox100Bot`,
"ReleaseNotes": `Обновление 4.2: улучшенный интерфейс для Microsoft Store, переключатель языка RU/EN, светлая тема по умолчанию, группировка серверов по IP с отображением доступных протоколов, исправления переключателей темы и языка, реальные флаги стран (SVG), Kill Switch и статистика.`,
"Title": "EvilFox",
"ShortTitle": "EvilFox",
"SortTitle": "EvilFox",
"VoiceTitle": "Evil Fox",
"ShortDescription": `Безопасный доступ в интернет: шифрование трафика, скрытие IP, защита от утечек DNS. Русский и английский. Современные протоколы, Kill Switch, удобный выбор серверов.`,
"DevStudio": "EvilFox",
"CopyrightTrademarkInformation": `© EvilFox. All rights reserved. EvilFox and the EvilFox logo are trademarks of their respective owners.`,
"AdditionalLicenseTerms": `Proprietary software. By installing EvilFox you agree to use the application in accordance with applicable laws. The app provides a client for encrypted network connections; server configurations are supplied by the user. See https://evilfox.win/ for support and terms.`,
"Feature1": "Шифрование трафика и скрытие IP-адреса",
"Feature2": "Режим прокси и полный режим защиты (TUN)",
"Feature3": "Kill Switch — блокировка трафика при обрыве VPN",
"Feature4": "Автовыбор сервера с лучшим пингом",
"Feature5": "Серверы с флагами стран и группировкой протоколов",
"Feature6": "Добавление конфигурации по URL",
"Feature7": "Статистика трафика и сессий",
"Feature8": "Интерфейс на русском и английском",
"Feature9": "Светлая и тёмная темы оформления",
"Feature10": "Защита от утечек DNS",
"MinimumHardwareReq1": "Windows 10 version 1809 or later (x64)",
"MinimumHardwareReq2": "Internet connection",
"MinimumHardwareReq3": "100 MB free disk space",
"RecommendedHardwareReq1": "Windows 11 (x64)",
"RecommendedHardwareReq2": "Stable broadband connection",
"SearchTerm1": "VPN",
"SearchTerm2": "privacy",
"SearchTerm3": "secure internet",
"SearchTerm4": "encrypt traffic",
"SearchTerm5": "EvilFox",
"SearchTerm6": "proxy client",
"SearchTerm7": "kill switch",
"DesktopScreenshotCaption1": "Главный экран: подключение и статус защиты",
"DesktopScreenshotCaption2": "Список серверов с флагами и пингом",
"DesktopScreenshotCaption3": "Настройки: тема, язык, Kill Switch",
"DesktopScreenshotCaption4": "Статистика использования",
}
en := map[string]string{
"Description": `EvilFox is a Windows app for private, secure internet access.
Protect your data on public WiFi: EvilFox encrypts your traffic, hides your real IP address, and helps prevent DNS leaks. A modern interface in English and Russian, light and dark themes, an easy server list, and a clear connection status.
Features:
• Traffic encryption with modern protocol support
• Proxy mode and full protection mode (TUN) for all app traffic
• Leak protection (Kill Switch) if the connection drops
• Auto-select the server with the lowest ping
• Server list with country flags and host grouping
• Add your own configuration via URL
• Traffic usage monitoring and session stats
• English and Russian interface
EvilFox is built for everyday browsing, travel, and privacy. You add your own connection configuration; no in-app purchases are required.
Website: https://evilfox.win/
Support: https://t.me/evilfox100Bot`,
"ReleaseNotes": `Version 4.2: Microsoft Storeready UI polish, RU/EN language switch, light theme by default, servers grouped by IP with available protocols, theme/language toggle fixes, real country flags (SVG), Kill Switch, and usage statistics.`,
"Title": "EvilFox",
"ShortTitle": "EvilFox",
"SortTitle": "EvilFox",
"VoiceTitle": "Evil Fox",
"ShortDescription": `Secure internet access: encrypt traffic, hide your IP, help prevent DNS leaks. English & Russian. Modern protocols, Kill Switch, easy server selection.`,
"DevStudio": "EvilFox",
"CopyrightTrademarkInformation": `© EvilFox. All rights reserved. EvilFox and the EvilFox logo are trademarks of their respective owners.`,
"AdditionalLicenseTerms": `Proprietary software. By installing EvilFox you agree to use the application in accordance with applicable laws. The app provides a client for encrypted network connections; server configurations are supplied by the user. See https://evilfox.win/ for support and terms.`,
"Feature1": "Encrypt traffic and hide your IP address",
"Feature2": "Proxy mode and full protection mode (TUN)",
"Feature3": "Kill Switch blocks traffic if VPN drops",
"Feature4": "Auto-select the lowest-ping server",
"Feature5": "Servers with country flags and protocol grouping",
"Feature6": "Add configuration via URL",
"Feature7": "Traffic and session statistics",
"Feature8": "English and Russian interface",
"Feature9": "Light and dark themes",
"Feature10": "DNS leak protection",
"MinimumHardwareReq1": "Windows 10 version 1809 or later (x64)",
"MinimumHardwareReq2": "Internet connection",
"MinimumHardwareReq3": "100 MB free disk space",
"RecommendedHardwareReq1": "Windows 11 (x64)",
"RecommendedHardwareReq2": "Stable broadband connection",
"SearchTerm1": "VPN",
"SearchTerm2": "privacy",
"SearchTerm3": "secure internet",
"SearchTerm4": "encrypt traffic",
"SearchTerm5": "EvilFox",
"SearchTerm6": "proxy client",
"SearchTerm7": "kill switch",
"DesktopScreenshotCaption1": "Home: connect and protection status",
"DesktopScreenshotCaption2": "Server list with flags and ping",
"DesktopScreenshotCaption3": "Settings: theme, language, Kill Switch",
"DesktopScreenshotCaption4": "Usage statistics",
}
for i := 1; i < len(all); i++ {
row := all[i]
for len(row) < len(header) {
row = append(row, "")
}
field := row[0]
if v, ok := ru[field]; ok {
row[defIdx] = v
}
if v, ok := en[field]; ok {
row[enIdx] = v
}
all[i] = row
}
out, err := os.Create(path)
if err != nil {
panic(err)
}
// UTF-8 BOM for Excel / Partner Center
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 _, want := range []string{"Description", "Title", "ShortDescription", "Feature1", "AdditionalLicenseTerms"} {
for _, row := range all[1:] {
if len(row) > 0 && row[0] == want {
fmt.Printf("%s default=%d en=%d\n", want, len(row[defIdx]), len(row[enIdx]))
}
}
}
fmt.Println("wrote", path)
}
+63
View File
@@ -0,0 +1,63 @@
package main
import (
"encoding/csv"
"os"
"path/filepath"
)
func main() {
path := filepath.Join(`D:\vpn navi`, "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]
di, ei := 3, 4
for i, h := range header {
if h == "default" {
di = i
}
if h == "en" {
ei = i
}
}
ru := "EvilFox 4.2 — удобный и понятный клиент для безопасного доступа в интернет.\n\nЧто нового для пользователей:\n• Современный интерфейс: вкладки Защита, Мои серверы, Настройки, Статистика, Помощь\n• Большая кнопка подключения и понятный статус защиты\n• Светлая тема по умолчанию и переключение на тёмную\n• Языки: русский и английский (RU | EN)\n• Флаги стран у серверов (корректно на Windows)\n• Серверы с одним IP объединяются в одну строку с доступными протоколами\n• Добавление конфигурации по URL\n• Режим прокси и полный режим защиты (TUN)\n• Kill Switch — защита от утечек при обрыве соединения\n• Автовыбор сервера с лучшим пингом\n• Статистика трафика и сессий\n• Политика конфиденциальности и условия использования в разделе Помощь\n• Адаптивное окно под любой экран и масштаб Windows"
en := "EvilFox 4.2 — a clearer, more polished client for private internet access.\n\nWhat's new:\n• Modern UI: Protection, My servers, Settings, Statistics, Help\n• Large connect button and clear protection status\n• Light theme by default, with dark theme toggle\n• Russian and English languages (RU | EN)\n• Country flags for servers (rendered correctly on Windows)\n• Same-IP servers grouped into one row with available protocols\n• Add configuration via URL\n• Proxy mode and full protection mode (TUN)\n• Kill Switch — leak protection if the connection drops\n• Auto-select the lowest-ping server\n• Traffic and session statistics\n• Privacy Policy and Terms of Use in Help\n• Adaptive window sizing for any screen and Windows display scale"
for i := 1; i < len(all); i++ {
for len(all[i]) < len(header) {
all[i] = append(all[i], "")
}
if all[i][0] == "ReleaseNotes" {
all[i][di] = ru
all[i][ei] = en
}
}
out, err := os.Create(path)
if err != nil {
panic(err)
}
out.Write([]byte{0xEF, 0xBB, 0xBF})
w := csv.NewWriter(out)
if err := w.WriteAll(all); err != nil {
panic(err)
}
w.Flush()
out.Close()
_ = os.MkdirAll(`D:\vpn navi\dist\microsoft-store`, 0755)
_ = os.WriteFile(`D:\vpn navi\dist\microsoft-store\WhatsNew.ru.txt`, []byte(ru+"\n"), 0644)
_ = os.WriteFile(`D:\vpn navi\dist\microsoft-store\WhatsNew.en.txt`, []byte(en+"\n"), 0644)
listing := `D:\vpn navi\dist\microsoft-store\listing\store.csv`
if _, err := os.Stat(filepath.Dir(listing)); err == nil {
data, _ := os.ReadFile(path)
_ = os.WriteFile(listing, data, 0644)
}
}
+185
View File
@@ -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
}
+82
View File
@@ -0,0 +1,82 @@
package main
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"os"
"path/filepath"
xdraw "golang.org/x/image/draw"
)
func writePNG(path string, img image.Image) {
_ = os.MkdirAll(filepath.Dir(path), 0755)
out, err := os.Create(path)
if err != nil {
panic(err)
}
defer out.Close()
enc := png.Encoder{CompressionLevel: png.BestCompression}
if err := enc.Encode(out, img); err != nil {
panic(err)
}
fmt.Println("wrote", path)
}
func main() {
root := `D:\vpn navi`
srcPath := filepath.Join(root, "screen", "1.ru.png")
f, err := os.Open(srcPath)
if err != nil {
panic(err)
}
src, err := png.Decode(f)
f.Close()
if err != nil {
panic(err)
}
sb := src.Bounds()
sw, sh := sb.Dx(), sb.Dy()
side := sw
if sh < side {
side = sh
}
// Center crop to square — no stretch/distortion.
ox := sb.Min.X + (sw-side)/2
oy := sb.Min.Y + (sh-side)/2
crop := image.NewRGBA(image.Rect(0, 0, side, side))
draw.Draw(crop, crop.Bounds(), src, image.Pt(ox, oy), draw.Src)
dst := image.NewRGBA(image.Rect(0, 0, 300, 300))
xdraw.CatmullRom.Scale(dst, dst.Bounds(), crop, crop.Bounds(), xdraw.Over, nil)
writePNG(filepath.Join(root, "screen", "1.ru.300x300.png"), dst)
writePNG(filepath.Join(root, "dist", "microsoft-store", "listing", "screen", "1.ru.300x300.png"), dst)
writePNG(filepath.Join(root, "packaging", "msix", "Assets", "StoreLogo300x300.png"), dst)
// Padded fit (full frame, letterbox) — no crop.
fit := image.NewRGBA(image.Rect(0, 0, 300, 300))
draw.Draw(fit, fit.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)
scale := float64(300) / float64(sw)
if float64(sh)*scale > 300 {
scale = float64(300) / float64(sh)
}
nw := int(float64(sw) * scale)
nh := int(float64(sh) * scale)
if nw < 1 {
nw = 1
}
if nh < 1 {
nh = 1
}
resized := image.NewRGBA(image.Rect(0, 0, nw, nh))
xdraw.CatmullRom.Scale(resized, resized.Bounds(), src, sb, xdraw.Over, nil)
off := image.Pt((300-nw)/2, (300-nh)/2)
draw.Draw(fit, image.Rect(off.X, off.Y, off.X+nw, off.Y+nh), resized, image.Point{}, draw.Over)
writePNG(filepath.Join(root, "screen", "1.ru.300x300.fit.png"), fit)
fmt.Printf("source %dx%d -> crop %dx%d -> 300x300 (CatmullRom)\n", sw, sh, side, side)
}