update playworks checker

This commit is contained in:
2026-05-28 14:17:26 +08:00
parent 4f17523f26
commit cce30c0729
20 changed files with 616 additions and 54 deletions

View File

@@ -82,6 +82,7 @@ func PlayworksPage(w http.ResponseWriter, r *http.Request) {
<label class="net-row"><input type="checkbox" class="net-cb" data-tag="mo" checked /><span class="net-label">Moloco <span class="net-tag">mo</span></span><span class="net-note">HTML + FbPlayableAd CTA</span></label>
<label class="net-row"><input type="checkbox" class="net-cb" data-tag="vu" checked /><span class="net-label">Vungle <span class="net-tag">vu</span></span><span class="net-note">ZIP + __VUNGLE__ flag</span></label>
<label class="net-row"><input type="checkbox" class="net-cb" data-tag="mtg" checked /><span class="net-label">Mintegral <span class="net-tag">mtg</span></span><span class="net-note">ZIP + onload gameReady</span></label>
<label class="net-row"><input type="checkbox" class="net-cb" data-tag="un" checked /><span class="net-label">Unity <span class="net-tag">un</span></span><span class="net-note">HTML + mraid.js in head</span></label>
<label class="net-row"><input type="checkbox" class="net-cb" data-tag="tt" /><span class="net-label">TikTok <span class="net-tag">tt</span></span><span class="net-note">HTML + __TIKTOK__ flag</span></label>
</div>
</div>
@@ -234,6 +235,12 @@ var playworksExitapiScriptRx = regexp.MustCompile(`(?i)<script\b[^>]*\bsrc\s*=\s
var playworksHeadCloseRx = regexp.MustCompile(`(?i)</head>`)
var playworksBodyOpenRx = regexp.MustCompile(`(?i)<body\b[^>]*>`)
var playworksBodyTagRx = regexp.MustCompile(`(?i)<body\b([^>]*)>`)
var playworksHtmlCloseRx = regexp.MustCompile(`(?i)</html\s*>`)
var playworksRemoteDebugScriptRx = regexp.MustCompile(`(?is)<script\b[^>]*>.*?insertYourRemoteDebuggingTokenHere.*?</script>\s*`)
var playworksStatsURLRx = regexp.MustCompile(`(?i)https://mrdoob\.github\.io/stats\.js/build/stats\.min\.js`)
var playworksCrossoriginRx = regexp.MustCompile(`(?i)\s+crossorigin(?:\s*=\s*("[^"]*"|'[^']*'|[^\s>]+))?`)
var playworksTypeModuleRx = regexp.MustCompile(`(?i)\s+type\s*=\s*["']module["']`)
var playworksConsoleErrorRx = regexp.MustCompile(`\bconsole\.error\s*\(`)
func playworksSourceBaseName(sourcePath string) string {
base := strings.TrimSuffix(filepath.Base(sourcePath), filepath.Ext(sourcePath))
@@ -305,6 +312,8 @@ func playworksNetworkOutputFolder(network string) string {
return "Vungle"
case "mtg":
return "Mintegral"
case "un":
return "Unity"
case "tt":
return "TikTok"
default:
@@ -317,12 +326,12 @@ func playworksNeedsZip(network string) bool {
}
func playworksIsMraidNetwork(network string) bool {
return network == "al" || network == "is"
return network == "al" || network == "is" || network == "un"
}
func playworksIsSupportedNetwork(network string) bool {
switch network {
case "al", "is", "fb", "gg", "mo", "vu", "mtg", "tt":
case "al", "is", "fb", "gg", "mo", "vu", "mtg", "un", "tt":
return true
default:
return false
@@ -330,11 +339,10 @@ func playworksIsSupportedNetwork(network string) bool {
}
func transformPlayworksHTML(htmlSrc, network string) string {
result := htmlSrc
result = removePlayworksNetworkScripts(result, network)
result := sanitizePlayworksHTML(htmlSrc)
headInject := buildPlayworksLifecycleScript()
if playworksIsMraidNetwork(network) {
headInject += `<script src="mraid.js"></script>`
headInject += `<script src="mraid.js"></script>` + buildPlayworksMraidComplianceScript()
} else if network == "gg" {
headInject += `<script src="exitapi.js"></script>`
}
@@ -349,23 +357,64 @@ func transformPlayworksHTML(htmlSrc, network string) string {
}
result = replacePlayworksCTAScript(result, network)
return finalizePlayworksNetworkHTML(result, network)
}
func sanitizePlayworksHTML(htmlSrc string) string {
return removePlayworksNetworkScripts(cleanupPlayworksHTML(htmlSrc))
}
func cleanupPlayworksHTML(htmlSrc string) string {
result := trimPlayworksAfterFirstHTMLClose(htmlSrc)
result = playworksRemoteDebugScriptRx.ReplaceAllString(result, "")
result = playworksStatsURLRx.ReplaceAllString(result, "data:text/javascript,")
result = playworksCrossoriginRx.ReplaceAllString(result, "")
result = playworksTypeModuleRx.ReplaceAllString(result, "")
result = playworksConsoleErrorRx.ReplaceAllString(result, "console.warn(")
return result
}
func removePlayworksNetworkScripts(htmlSrc, network string) string {
result := htmlSrc
if playworksIsMraidNetwork(network) {
result = playworksMraidScriptRx.ReplaceAllString(result, "")
func trimPlayworksAfterFirstHTMLClose(htmlSrc string) string {
loc := playworksHtmlCloseRx.FindStringIndex(htmlSrc)
if loc == nil {
return htmlSrc
}
if network == "gg" {
result = playworksExitapiScriptRx.ReplaceAllString(result, "")
return htmlSrc[:loc[1]]
}
func finalizePlayworksNetworkHTML(htmlSrc, network string) string {
result := cleanupPlayworksHTML(htmlSrc)
result = dedupePlayworksScriptSrc(result, "mraid.js", playworksIsMraidNetwork(network))
result = dedupePlayworksScriptSrc(result, "exitapi.js", network == "gg")
return result
}
func removePlayworksNetworkScripts(htmlSrc string) string {
result := playworksMraidScriptRx.ReplaceAllString(htmlSrc, "")
result = playworksExitapiScriptRx.ReplaceAllString(result, "")
return result
}
func dedupePlayworksScriptSrc(htmlSrc, src string, shouldExist bool) string {
rx := regexp.MustCompile(`(?i)<script\b[^>]*\bsrc\s*=\s*["']` + regexp.QuoteMeta(src) + `["'][^>]*>\s*</script>\s*`)
seen := false
result := rx.ReplaceAllStringFunc(htmlSrc, func(match string) string {
if shouldExist && !seen {
seen = true
return `<script src="` + src + `"></script>`
}
return ""
})
if shouldExist && !seen {
result = injectPlayworksBeforeHeadClose(result, `<script src="`+src+`"></script>`)
}
return result
}
func injectPlayworksBeforeHeadClose(htmlSrc, injection string) string {
if playworksHeadCloseRx.MatchString(htmlSrc) {
return playworksHeadCloseRx.ReplaceAllString(htmlSrc, injection+"\n</head>")
loc := playworksHeadCloseRx.FindStringIndex(htmlSrc)
if loc != nil {
return htmlSrc[:loc[0]] + injection + "\n" + htmlSrc[loc[0]:]
}
return injection + "\n" + htmlSrc
}
@@ -489,12 +538,34 @@ func buildPlayworksLifecycleScript() string {
}, "")
}
func buildPlayworksMraidComplianceScript() string {
return strings.Join([]string{
`<script>(function(){`,
`var m=null,scene=null,viewable=true,exposed=true,volume=null;`,
`function get(){return window.mraid||m;}`,
`function emit(name,detail){try{window.dispatchEvent(new CustomEvent(name,{detail:detail}));}catch(e){}}`,
`function apply(){emit("hpl:mraid:visibility",{viewable:viewable,exposed:exposed,hidden:!viewable||!exposed});if(scene&&scene.sound&&typeof scene.sound.setMute==="function")scene.sound.setMute(!viewable||!exposed);}`,
`function onViewable(v){viewable=!!v;apply();}`,
`function onExposure(p){if(typeof p==="number")exposed=p>0;apply();}`,
`function onVolume(v){if(typeof v==="number"){volume=v/100;emit("hpl:mraid:volume",volume);if(scene&&scene.sound&&typeof scene.sound.setVolume==="function")scene.sound.setVolume(volume);}}`,
`function add(name,fn){var x=get();try{if(x&&typeof x.addEventListener==="function")x.addEventListener(name,fn);}catch(e){}}`,
`function unload(){try{if(typeof mraid!=="undefined"&&typeof mraid.unload==="function")mraid.unload();}catch(e){}}`,
`window.__hplMraidUnload=unload;`,
`function setup(){var x=get();if(!x||setup.done)return;setup.done=true;m=x;try{if(typeof mraid!=="undefined"&&typeof mraid.isViewable==="function")viewable=!!mraid.isViewable();else if(typeof x.isViewable==="function")viewable=!!x.isViewable();}catch(e){}try{if(typeof mraid!=="undefined"&&typeof mraid.addEventListener==="function"){mraid.addEventListener("error",function(message,action){console.warn("[MRAID error]",{message:message,action:action});});mraid.addEventListener("stateChange",function(state){console.log("[MRAID stateChange]",state);});mraid.addEventListener("exposureChange",onExposure);mraid.addEventListener("viewableChange",onViewable);mraid.addEventListener("audioVolumeChange",onVolume);apply();return;}}catch(e){}add("error",function(message,action){console.warn("[MRAID error]",{message:message,action:action});});add("stateChange",function(state){console.log("[MRAID stateChange]",state);});add("exposureChange",onExposure);add("viewableChange",onViewable);add("audioVolumeChange",onVolume);apply();}`,
`function ready(){var x=get();if(!x)return false;try{if(typeof mraid!=="undefined"&&typeof mraid.getState==="function"&&mraid.getState()==="loading"){mraid.addEventListener("ready",setup);return true;}if(typeof x.getState==="function"&&x.getState()==="loading"){add("ready",setup);return true;}}catch(e){}setup();return true;}`,
`window.__hplMraidBindScene=function(s){scene=s;apply();if(typeof volume==="number"&&scene&&scene.sound&&typeof scene.sound.setVolume==="function")scene.sound.setVolume(volume);};`,
`if(!ready()){var end=Date.now()+500;(function poll(){if(ready()||Date.now()>end)return;setTimeout(poll,50);})();}`,
`setTimeout(setup,2000);`,
`})();</script>`,
}, "")
}
func buildPlayworksCTAScript(network string) string {
urlSetup := `n=n||window.$environment.packageConfig.iosLink,i=i||window.$environment.packageConfig.androidLink;var o=/iphone|ipad|ipod|macintosh/i.test(window.navigator.userAgent.toLowerCase())?n:i;`
closeCall := `try{window.gameClose();}catch(e){}`
ctaLogic := closeCall + `window.open(o,"_blank");`
switch network {
case "al", "is":
case "al", "is", "un":
ctaLogic = closeCall + `if(typeof mraid!=="undefined"&&typeof mraid.open==="function"){var s=typeof mraid.getState==="function"?mraid.getState():"default";if(s!=="loading"){mraid.open(o);return;}}window.open(o,"_blank");`
case "fb":
ctaLogic = closeCall + `if(typeof FbPlayableAd!=="undefined"&&typeof FbPlayableAd.onCTAClick==="function"){FbPlayableAd.onCTAClick();return;}window.open(o,"_blank");`