fix
This commit is contained in:
BIN
dist/hpl-toolbox-0.1.2.exe
vendored
BIN
dist/hpl-toolbox-0.1.2.exe
vendored
Binary file not shown.
BIN
dist/hpl-toolbox-0.1.2.vsix
vendored
BIN
dist/hpl-toolbox-0.1.2.vsix
vendored
Binary file not shown.
BIN
dist/hpl-toolbox-0.1.3.exe
vendored
BIN
dist/hpl-toolbox-0.1.3.exe
vendored
Binary file not shown.
BIN
dist/hpl-toolbox-0.1.3.vsix
vendored
BIN
dist/hpl-toolbox-0.1.3.vsix
vendored
Binary file not shown.
11
package.json
11
package.json
@@ -90,6 +90,17 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
"description": "Value of the __Host-APPLOVINID cookie for p.applov.in (log in at https://p.applov.in/playablePreview?create=1&qr=1 and copy the cookie from DevTools)."
|
"description": "Value of the __Host-APPLOVINID cookie for p.applov.in (log in at https://p.applov.in/playablePreview?create=1&qr=1 and copy the cookie from DevTools)."
|
||||||
|
},
|
||||||
|
"hplToolbox.applovin.delayMs": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 5000,
|
||||||
|
"minimum": 0,
|
||||||
|
"description": "Delay (in milliseconds) between consecutive file uploads, to avoid AppLovin rate limiting / 403 responses."
|
||||||
|
},
|
||||||
|
"hplToolbox.applovin.randomizeUserAgent": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Randomize the User-Agent header on every request to p.applov.in to reduce bot detection."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,9 +50,30 @@ export function openApplovinUpload(_context: vscode.ExtensionContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const USER_AGENTS = [
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0',
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15',
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0',
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||||
|
];
|
||||||
|
|
||||||
|
function pickUserAgent(randomize: boolean): string {
|
||||||
|
if (!randomize) return USER_AGENTS[0];
|
||||||
|
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
async function handleUpload(panel: vscode.WebviewPanel, filePaths: string[]) {
|
async function handleUpload(panel: vscode.WebviewPanel, filePaths: string[]) {
|
||||||
const cfg = vscode.workspace.getConfiguration('hplToolbox.applovin');
|
const cfg = vscode.workspace.getConfiguration('hplToolbox.applovin');
|
||||||
const cookie = (cfg.get<string>('cookie') || '').trim();
|
const cookie = (cfg.get<string>('cookie') || '').trim();
|
||||||
|
const delayMs = Math.max(0, cfg.get<number>('delayMs') ?? 1500);
|
||||||
|
const randomizeUA = cfg.get<boolean>('randomizeUserAgent') ?? true;
|
||||||
|
|
||||||
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
|
const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
|
||||||
if (!paths.length) {
|
if (!paths.length) {
|
||||||
@@ -60,14 +81,17 @@ async function handleUpload(panel: vscode.WebviewPanel, filePaths: string[]) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildHeaders = (): Record<string, string> => {
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||||
'Origin': 'https://p.applov.in',
|
'Origin': 'https://p.applov.in',
|
||||||
'Referer': 'https://p.applov.in/playablePreview?create=1&qr=1',
|
'Referer': 'https://p.applov.in/playablePreview?create=1&qr=1',
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
'User-Agent': 'Mozilla/5.0 (HPL-Toolbox-VSCode)',
|
'User-Agent': pickUserAgent(randomizeUA),
|
||||||
};
|
};
|
||||||
if (cookie) headers['Cookie'] = `__Host-APPLOVINID=${cookie}`;
|
if (cookie) headers['Cookie'] = `__Host-APPLOVINID=${cookie}`;
|
||||||
|
return headers;
|
||||||
|
};
|
||||||
|
|
||||||
const buildForm = (filePath: string): FormData => {
|
const buildForm = (filePath: string): FormData => {
|
||||||
const buf = fs.readFileSync(filePath);
|
const buf = fs.readFileSync(filePath);
|
||||||
@@ -90,12 +114,17 @@ async function handleUpload(panel: vscode.WebviewPanel, filePaths: string[]) {
|
|||||||
if (!filePath || !fs.existsSync(filePath)) { reportError('File missing'); continue; }
|
if (!filePath || !fs.existsSync(filePath)) { reportError('File missing'); continue; }
|
||||||
if (!/\.html?$/i.test(filePath)) { reportError('Must be .html'); continue; }
|
if (!/\.html?$/i.test(filePath)) { reportError('Must be .html'); continue; }
|
||||||
|
|
||||||
|
if (i > 0 && delayMs > 0) {
|
||||||
|
panel.webview.postMessage({ type: 'status', message: `[${idx}/${paths.length}] Waiting ${delayMs}ms before ${fileName}...` });
|
||||||
|
await sleep(delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
panel.webview.postMessage({ type: 'status', message: `[${idx}/${paths.length}] Validating ${fileName}...` });
|
panel.webview.postMessage({ type: 'status', message: `[${idx}/${paths.length}] Validating ${fileName}...` });
|
||||||
|
|
||||||
let res: Response;
|
let res: Response;
|
||||||
try {
|
try {
|
||||||
res = await fetch('https://p.applov.in/validateHTMLContent', {
|
res = await fetch('https://p.applov.in/validateHTMLContent', {
|
||||||
method: 'POST', body: buildForm(filePath), headers,
|
method: 'POST', body: buildForm(filePath), headers: buildHeaders(),
|
||||||
});
|
});
|
||||||
} catch (err: any) { reportError('Network error (validate): ' + (err?.message || err)); continue; }
|
} catch (err: any) { reportError('Network error (validate): ' + (err?.message || err)); continue; }
|
||||||
const validateText = await res.text();
|
const validateText = await res.text();
|
||||||
@@ -110,7 +139,7 @@ async function handleUpload(panel: vscode.WebviewPanel, filePaths: string[]) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
res = await fetch('https://p.applov.in/getCachedAdURL', {
|
res = await fetch('https://p.applov.in/getCachedAdURL', {
|
||||||
method: 'POST', body: buildForm(filePath), headers,
|
method: 'POST', body: buildForm(filePath), headers: buildHeaders(),
|
||||||
});
|
});
|
||||||
} catch (err: any) { reportError('Network error (cache): ' + (err?.message || err)); continue; }
|
} catch (err: any) { reportError('Network error (cache): ' + (err?.message || err)); continue; }
|
||||||
const cacheText = await res.text();
|
const cacheText = await res.text();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
urlpkg "net/url"
|
urlpkg "net/url"
|
||||||
@@ -12,8 +13,26 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var applovinUserAgents = []string{
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickApplovinUserAgent(randomize bool) string {
|
||||||
|
if !randomize {
|
||||||
|
return applovinUserAgents[0]
|
||||||
|
}
|
||||||
|
return applovinUserAgents[rand.Intn(len(applovinUserAgents))]
|
||||||
|
}
|
||||||
|
|
||||||
func ApplovinPage(w http.ResponseWriter, r *http.Request) {
|
func ApplovinPage(w http.ResponseWriter, r *http.Request) {
|
||||||
body := `
|
body := `
|
||||||
<h2>AppLovin Playable Preview (QR)</h2>
|
<h2>AppLovin Playable Preview (QR)</h2>
|
||||||
@@ -237,7 +256,16 @@ func ApplovinUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cookie := LoadConfig().Applovin.Cookie
|
cfg := LoadConfig().Applovin
|
||||||
|
cookie := cfg.Cookie
|
||||||
|
delayMs := cfg.DelayMs
|
||||||
|
if delayMs < 0 {
|
||||||
|
delayMs = 0
|
||||||
|
}
|
||||||
|
randomizeUA := true
|
||||||
|
if cfg.RandomizeUserAgent != nil {
|
||||||
|
randomizeUA = *cfg.RandomizeUserAgent
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-ndjson")
|
w.Header().Set("Content-Type", "application/x-ndjson")
|
||||||
flusher, _ := w.(http.Flusher)
|
flusher, _ := w.(http.Flusher)
|
||||||
@@ -282,7 +310,7 @@ func ApplovinUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
req.Header.Set("Origin", "https://p.applov.in")
|
req.Header.Set("Origin", "https://p.applov.in")
|
||||||
req.Header.Set("Referer", "https://p.applov.in/playablePreview?create=1&qr=1")
|
req.Header.Set("Referer", "https://p.applov.in/playablePreview?create=1&qr=1")
|
||||||
req.Header.Set("X-Requested-With", "XMLHttpRequest")
|
req.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0 (HPL-Toolbox-Standalone)")
|
req.Header.Set("User-Agent", pickApplovinUserAgent(randomizeUA))
|
||||||
if cookie != "" {
|
if cookie != "" {
|
||||||
req.Header.Set("Cookie", "__Host-APPLOVINID="+cookie)
|
req.Header.Set("Cookie", "__Host-APPLOVINID="+cookie)
|
||||||
}
|
}
|
||||||
@@ -311,6 +339,11 @@ func ApplovinUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if i > 0 && delayMs > 0 {
|
||||||
|
send(map[string]any{"type": "status", "message": fmt.Sprintf("[%d/%d] Waiting %dms before %s...", idx, len(req.Paths), delayMs, fileName)})
|
||||||
|
time.Sleep(time.Duration(delayMs) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
send(map[string]any{"type": "status", "message": fmt.Sprintf("[%d/%d] Validating %s...", idx, len(req.Paths), fileName)})
|
send(map[string]any{"type": "status", "message": fmt.Sprintf("[%d/%d] Validating %s...", idx, len(req.Paths), fileName)})
|
||||||
|
|
||||||
body, ct, err := buildForm(filePath)
|
body, ct, err := buildForm(filePath)
|
||||||
|
|||||||
Binary file not shown.
@@ -19,7 +19,7 @@ import (
|
|||||||
var currentHwnd atomic.Uintptr
|
var currentHwnd atomic.Uintptr
|
||||||
|
|
||||||
func setupFileLogging() {
|
func setupFileLogging() {
|
||||||
logPath := filepath.Join(appDir(), "debug.log")
|
logPath := filepath.Join(userDataDir(), "debug.log")
|
||||||
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -57,9 +57,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer removeLockFile()
|
defer removeLockFile()
|
||||||
|
|
||||||
|
webviewTmp, err := os.MkdirTemp("", "hpltoolbox-wv2-*")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create webview temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(webviewTmp)
|
||||||
|
|
||||||
w := webview.NewWithOptions(webview.WebViewOptions{
|
w := webview.NewWithOptions(webview.WebViewOptions{
|
||||||
Debug: false,
|
Debug: false,
|
||||||
AutoFocus: true,
|
AutoFocus: true,
|
||||||
|
DataPath: webviewTmp,
|
||||||
WindowOptions: webview.WindowOptions{
|
WindowOptions: webview.WindowOptions{
|
||||||
Title: "HPL Toolbox",
|
Title: "HPL Toolbox",
|
||||||
Width: 1100,
|
Width: 1100,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ type PlecConfig struct {
|
|||||||
|
|
||||||
type ApplovinConfig struct {
|
type ApplovinConfig struct {
|
||||||
Cookie string `json:"cookie"`
|
Cookie string `json:"cookie"`
|
||||||
|
DelayMs int `json:"delayMs"`
|
||||||
|
RandomizeUserAgent *bool `json:"randomizeUserAgent,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendToMobileConfig struct {
|
type SendToMobileConfig struct {
|
||||||
@@ -38,6 +40,23 @@ type AppConfig struct {
|
|||||||
|
|
||||||
var configMu sync.Mutex
|
var configMu sync.Mutex
|
||||||
|
|
||||||
|
// userDataDir returns %LOCALAPPDATA%\HPLToolbox, creating it if needed.
|
||||||
|
// All mutable app files (config, lock, logs, WebView2 cache) live here so
|
||||||
|
// they are never placed next to the executable, which may be on a synced or
|
||||||
|
// antivirus-watched drive and would cause message-loop stalls.
|
||||||
|
func userDataDir() string {
|
||||||
|
local := os.Getenv("LOCALAPPDATA")
|
||||||
|
if local == "" {
|
||||||
|
local = os.Getenv("APPDATA")
|
||||||
|
}
|
||||||
|
if local == "" {
|
||||||
|
return appDir()
|
||||||
|
}
|
||||||
|
dir := filepath.Join(local, "HPLToolbox")
|
||||||
|
_ = os.MkdirAll(dir, 0755)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
func defaultConfig() AppConfig {
|
func defaultConfig() AppConfig {
|
||||||
return AppConfig{
|
return AppConfig{
|
||||||
Plec: PlecConfig{
|
Plec: PlecConfig{
|
||||||
@@ -46,7 +65,7 @@ func defaultConfig() AppConfig {
|
|||||||
PreviewBase: "https://playable.applovindemo.com/phaser/",
|
PreviewBase: "https://playable.applovindemo.com/phaser/",
|
||||||
SkipExistsCheck: false,
|
SkipExistsCheck: false,
|
||||||
},
|
},
|
||||||
Applovin: ApplovinConfig{Cookie: ""},
|
Applovin: ApplovinConfig{Cookie: "", DelayMs: 5000},
|
||||||
SendToMobile: SendToMobileConfig{Port: 0},
|
SendToMobile: SendToMobileConfig{Port: 0},
|
||||||
DailyUpdate: DailyUpdateConfig{LastProject: ""},
|
DailyUpdate: DailyUpdateConfig{LastProject: ""},
|
||||||
}
|
}
|
||||||
@@ -72,7 +91,7 @@ func appDir() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func configPath() string {
|
func configPath() string {
|
||||||
return filepath.Join(appDir(), "config.json")
|
return filepath.Join(userDataDir(), "config.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig() AppConfig {
|
func LoadConfig() AppConfig {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func lockFilePath() string {
|
func lockFilePath() string {
|
||||||
return filepath.Join(appDir(), ".hpltoolbox.lock")
|
return filepath.Join(userDataDir(), ".hpltoolbox.lock")
|
||||||
}
|
}
|
||||||
|
|
||||||
// focusExistingInstance returns true if another instance was found and
|
// focusExistingInstance returns true if another instance was found and
|
||||||
|
|||||||
Reference in New Issue
Block a user