UNPKG

octopus-ad

Version:

Quickapp Sample Template

823 lines (736 loc) 19.8 kB
import clipboard from '@system.clipboard'; import downloadtask from '@system.downloadtask'; import fetch from '@system.fetch'; import file from '@system.file'; import app from '@system.app' import pkg from '@system.package'; import prompt from '@system.prompt'; import router from '@system.router'; import sensor from '@system.sensor'; import storage from '@system.storage'; import { getDeviceInfo, getNetworkType, getSimOperator, } from './device'; import { Base64 } from './base64'; const ENV = 'prod'; const isDev = ENV === 'dev'; export const constants = { show: 'SHOW_COMPONENT', hide: 'HIDE_COMPONENT', click: 'CLICK', wholeClick: 'WHOLE_CLICK', close: 'CLOSE_COMPONENT', touchMove: 'TOUCH_MOVE', videoFinished: 'VIDEO_FINISHED', tick: 'TICK', } export const videoPath = ''; const PREV_PKG_CONFIG = 'PREV_PKG_CONFIG'; export const PREV_PKG_IDS = 'PREV_PKG_IDS'; const PREV_PKG_VERSION = 'PREV_PKG_VERSION'; const PREV_PKG_DURATION = 'PREV_PKG_DURATION'; export const debounce = (fn, delay) => { let timer = null return function() { clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, arguments) }, delay) } } // 生成固定长度的UUID export const generateUUID = (length) => { let uuid = '' const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' const charactersLength = characters.length for (let i = 0; i < length; i++) { uuid += characters.charAt(Math.floor(Math.random() * charactersLength)) } return uuid } export const promisify = (func) => (args) => new Promise((resolve, reject) => { func( Object.assign(args || {}, { success: resolve, fail: reject, }), ) }) export const baseURL = 'http://test.s.adintl.cn/api/zysdk?isEncrypt=0&v=1' export const boostURL = 'http://test.s.adintl.cn/api/zysdksd?isEncrypt=0&v=1'; export const baseParams = { reqid: 'test-2023-4-26', version: '3.4.20.13', reqType: 'REQ_AD', srcType: 'SRC_QUICKAPP', timeStamp: new Date().getTime(), appid: '277', appVersion: '1.0', appName: 'com.beizi.adsdkdemo', // appInstallTime: 1666590646738, // appUpdateTime: 1666681014754, } // 通过key获取对象中的值, 支持多级对象 export const getValue = (obj, key) => { const keys = key.split('.') let result = obj for (let i = 0; i < keys.length; i++) { result = result[keys[i]] } return result } export const actions = { // spaceInfo[0].adResponse[0].adLogo getAdLogo(response) { const { adLogo } = response.spaceInfo[0].adResponse[0] return { logo: adLogo.sourceUrl, label: adLogo.adLabelUrl, } }, getTemplate(response) { const target = response.spaceInfo[0].adResponse[0].contentInfo[0].template return target ? JSON.parse(target) : null }, getInterEvent(response) { return response.spaceInfo[0].interEvent }, getInteractInfo(response) { return response.spaceInfo[0].adResponse[0].interactInfo }, getUrls(response) { const urls = response.spaceInfo[0].adResponse[0].interactInfo.thirdpartInfo const click = urls[0].clickUrl const view = urls[0].viewUrl const nurl = urls[2].nurl const lurl = urls[3].lurl const dpSucessUrl = urls[4].dpSucessUrl const downSucessUrl = urls[5].downSucessUrl return { click, view, nurl, lurl, dpSucessUrl, downSucessUrl } }, getAdpType(response) { return response.spaceInfo[0].adpType }, getAdImage(response) { return response.spaceInfo[0].adResponse[0].contentInfo[0].adcontentSlot[0] .content }, getAdVideo(response) { return response.spaceInfo[0].adResponse[0].contentInfo[0].adcontentSlot[0].content }, getInterEvent(response) { return response.spaceInfo[0].interEvent }, getStrategy(response) { return response.spaceInfo[0].strategy || {} }, getStrategies(response) { const strategy = this.getStrategy(response); const { pass, expBack, callBack, aoClk, nDpUp } = strategy; return { // 跳过 pass, expBack, callBack, aoClk, nDpUp } }, getEvents(response) { try { const interEvent = this.getInterEvent(response); const adpType = this.getAdpType(response); const { shakeEvent, fullScreenClk, regionScreenClk, scrollEvent } = interEvent; // 状态 const shake = shakeEvent?.v === 1 || !shakeEvent; const fullScreen = fullScreenClk?.v > 0 || (adpType !== 2 && adpType !== 1); const regionScreen = regionScreenClk?.v === 1; const scroll = scrollEvent?.v === 1; return { shakeEvent, fullScreenClk, regionScreenClk, scrollEvent, status: { shake, fullScreen, regionScreen, scroll } } } catch (error) { console.error(error); } }, getFilter(response) { return response.spaceInfo[0].filter }, getPrice(response) { return response.spaceInfo[0].adResponse[0].price }, getContentMine(response) { return response.spaceInfo[0].adResponse[0].contentInfo[0].adcontentSlot[0].mime; }, getContentUrl(response) { const url = response.spaceInfo[0].adResponse[0].contentInfo[0].adcontentSlot[0].content; return url }, getRenderType(response) { const renderType = response.spaceInfo[0].adResponse[0].contentInfo[0].renderType return renderType }, getComplianceInfo(response) { return response.spaceInfo[0].adResponse[0].interactInfo.complianceInfo; } } export const getRandomNumber = (min = 0, max = 100) => { return Math.floor(Math.random() * (max - min) + min) } export const report = async (url, opt = 0, enabled = true) => { // 302上报策略:当曝光和点击上报的链接包含 "?rv=1" 这个参数,则不上报这个链接,直接浏览器打开。 if (url.includes('?rv=1')) { router.push({ uri }) return; } if (!enabled) { return new Promise((resolve) => { resolve() }) } await delay(100); return fetch.fetch({ url: url + `&opt=${opt}`, method: 'GET', header: { 'content-type': 'application/json', }, }) } export const passStrategy = (v, normalCallback, specialCallback) => { v = v || 0; if (v === 0) { normalCallback(); } else if (v === 100) { specialCallback(); } else { const num = getRandomNumber(); if (num <= v) { specialCallback(); } } } export const autoClickStrategy = ({ co, ct, cr, callback }) => { const cb = setTimeout(() => { callback(); }, ct) if (co === 1) { this.adClick() this.emitAdCallback('clicked'); debug('触发:自动点击') if (cr === 100) { cb(true) return } else if (cr > 0) { const num = getRandomNumber(); if (num <= cr) { cb(true); return } } cb(false); } } export const expBackStrategy = (v, callback) => { v = v || 0; if (v === 100) { callback(); } else { const num = getRandomNumber(); if (num <= v) { callback(); } } } export const clickStrategy = (v, callback) => { if (v > 0) { if (v === 100) { callback() } else { const num = getRandomNumber(); if (num <= v) { callback() } } } } export const fullScreenClickStrategy = (v, callback) => { if (v > 0) { if (v === 100) { callback() } else { const num = getRandomNumber(); if (num <= v) { callback() } } } } // 视频预加载 export const preloadVideo = (appId, adId,) => { return new Promise((resolve,) => { fetchAdDetails(appId, adId).then((resp) => { if (resp.status === '0') { const adpType = actions.getAdpType(resp); if (adpType === 1) { const videoSrc = actions.getAdVideo(resp); downloadtask.downloadFile({ url: videoSrc, success: res => { storage.set({ key: `${appId}-${adId}`, value: res.filePath, success: () => { resolve(res.filePath); } }) }, fail: (data, code) => { prompt.showToast({ message: `${data}-${code}` }) } }) } } }).catch(e => { console.error(e, '()()()()'); }) }) } export const toast = (message) => { if (isDev) { prompt.showToast({ message, }) } } export const dialog = (title) => { if (isDev) { prompt.showDialog({ title, }) } } export const delay = (time = 1500) => { return new Promise(resolve => { setTimeout(() => { resolve() }, time) }) } export const debug = async (message, enabled = true) => { if (isDev && enabled) { prompt.showToast({ message, duration: 1500 }) await delay(); } } export const analysisShakeEvent = function(response) { const events = actions.getEvents(response); if (events.status.shake) { this.shakable = events.status.shake; const { sr, v } = events.shakeEvent; if (v === 1) { sensor.subscribeAccelerometer({ callback: async (res) => { if (res.x >= sr * 2) { if (this.visible) { sensor.unsubscribeAccelerometer() this.shakable = false await debug('触发:摇一摇自动点击') this.adClick(0, false) report(this.urls.click, 7) this.emitAdCallback('clicked'); } } }, }) } } } export const analysisFullScreenEvent = function(response) { const events = actions.getEvents(response); this.fullScreenClickable = events.status.fullScreen; } export const analysisRegionScreenEvent = function(response) { const events = actions.getEvents(response); this.regionScreenClickable = events.status.regionScreen; } export const analysisScrollEvent = function(response) { const events = actions.getEvents(response); this.scrollClickable = events.status.scroll; } // 调起策略 export const autoSetupStrategy = function(strategy, reportFn, clickFn) { const { eo, co, et, ct } = strategy; if (eo === 1) { setTimeout(() => { reportFn() }, et || 2000) } if (ct === 1) { setTimeout(() => { clickFn() }, (et || 2000) + (co || ct)) } this.callbackable = false; } // 补量策略 // 复制口令 // 复制口令策略:如果响应参数wordText不为空,不管填没填充, // 都执行复制口令策略:如果wordText包含http://或https://,去请求口令内容复制到剪切版上, // 口令接口返回示例:{"code":0,"message":"OK","data":"口令内容"}。 // 如果wordText不是http或https地址,测直接复制wordText字段内容到剪切版上面。 export const copyText = (text) => { if (!text) return; // text 为https或http开头的链接 if (text.startsWith('http') || text.startsWith('https')) { fetch.fetch({ url: text, method: 'GET', responseType: 'json', success: (resp) => { const { code, data } = resp; if (code === '0') { clipboard.set({ text: data, success: () => { toast('复制口令成功') } }) } } }) } else { clipboard.set({ text, success: () => { toast('复制口令成功') } }) } } // 处理response,取值赋值 export const handleResponse = function(resp) { try { const logo = actions.getAdLogo(resp) const template = actions.getTemplate(resp) const interEvent = actions.getInterEvent(resp) const interactInfo = actions.getInteractInfo(resp) const adpType = actions.getAdpType(resp) const strategies = actions.getStrategies(resp) const events = actions.getEvents(resp) const urls = actions.getUrls(resp) const filter = actions.getFilter(resp); const price = actions.getPrice(resp); const renderType = actions.getRenderType(resp); const complianceInfo = actions.getComplianceInfo(resp); const packageName = interactInfo.packageName; const _this = this; pkg.hasInstalled({ package: packageName, success: (data) => { if (data.result) { _this.isInstalled = true; } else { _this.isInstalled = false; } } }) this.logo = logo this.template = template this.interEvent = interEvent this.interactInfo = interactInfo this.renderType = renderType; this.adpType = adpType this.events = events this.strategies = strategies || {} this.urls = urls; this.filter = filter; this.price = price; this.complianceInfo = complianceInfo; } catch (error) { console.error("🚀🚀🚀 index.js-528行", error) } } // 处理事件及监听 export const handleEvents = function(resp) { try { // 赋值口令 copyText(resp.wordText); const { adpType, appId, adId, filter, } = this // 调起策略 if (filter === 1) { this.autoSetupStrategy() } // 开屏、插屏图片 if (adpType === 2 || adpType === 3) { const image = actions.getAdImage(resp) this.image = image this.contentUrl = actions.getContentUrl(resp); } // 视频素材url,预加载 || 接口url if (adpType === 1) { const videoSrc = actions.getAdVideo(resp) const key = `${appId}-${adId}` storage.get({ key, success: (res) => { file.access({ uri: res, success: () => { this.videoSrc = res; this.contentUrl = res; }, fail: () => { this.videoSrc = videoSrc this.contentUrl = videoSrc; }, }) }, fail: () => { this.contentUrl = videoSrc; }, }) } // 摇一摇 analysisShakeEvent.call(this, resp) // // 全屏 analysisFullScreenEvent.call(this, resp) // // 区域 analysisRegionScreenEvent.call(this, resp) // // 滑动 analysisScrollEvent.call(this, resp) //事件监听 this.eventListener() this.emitAdCallback('loaded') this.loaded = true; } catch (error) { console.error("🚀🚀🚀 index.js-584行", error) } } export const compareComponentType = function(response, type) { const adpType = actions.getAdpType(response); let matched = true; if (type === 'native' && adpType !== 6) { matched = false } if (type === 'interstitial' && adpType !== 3) { matched = false } if (type === 'splash' && adpType !== 2) { matched = false } if (type === 'rewardVideo' && adpType !== 1) { matched = false } if (!['native', 'interstitial', 'splash', 'rewardVideo'].includes(type)) { matched = false; } if (adpType > 6) { matched = false; } if (!matched) { console.error('广告组件参数type与广告id不匹配') this.showable = false; } } export const getStorage = async (key) => { try { const res = await storage.get({ key, }); return res.data; } catch (error) { return null; } } // base64解码 export const base64Decode = (str) => { try { const result = Base64.decode(str) return result } catch (error) { dialog(error); } } export const fetchPkgConfig = async (v) => { const hasInstalled = promisify(pkg.hasInstalled); const res = await fetch.fetch({ url: `http://sdkcfg.adintl.cn/sdk/pkgConfig?version=${v}`, }); storage.set({ key: PREV_PKG_CONFIG, value: res.data.data, }) let result = base64Decode(res.data.data.slice(2)); result = JSON.parse(result); const { version, duration, data } = result storage.set({ key: PREV_PKG_VERSION, value: version }) storage.set({ key: PREV_PKG_DURATION, value: duration }) const ids = []; if (data && data.length > 0) { for (let i = 0; i < data.length; i++) { const res = await hasInstalled({ package: data[i].pkg }) if (res.result) { ids.push(data[i].sid) } } } storage.set({ key: PREV_PKG_IDS, value: ids.join(','), }) toast('appList:' + ids); return { appList: ids }; } export const getPkgConfig = async () => { storage.clear(); try { // 获取当前秒数 const now = new Date().getTime() / 1000; const prevConfig = await getStorage(PREV_PKG_CONFIG); const version = await getStorage(PREV_PKG_VERSION); const duration = await getStorage(PREV_PKG_DURATION); let ids = ''; if (prevConfig) { let result = base64Decode(prevConfig.slice(2)) result = JSON.parse(result); const diff = now - version; if (diff > +duration) { const { appList } = await fetchPkgConfig(now); ids = appList.join(','); } ids = await getStorage(PREV_PKG_IDS); } else { const { appList } = await fetchPkgConfig(now); ids = appList.join(','); } return ids; } catch (error) { console.error("🚀🚀🚀 index.js-771行", error) return ''; } } // banner: 7377 信息流: 7045 开屏: 7046 插屏: 7047 激励视频: 7048 // 信息流 0:单张大图,1:上文下图,2:上图下文,3:左图右文,4:左文右图 export const fetchAdDetails = async (appId, adId, isBoost = false) => { const devInfo = await getDeviceInfo() const networkType = await getNetworkType() const d = await getStorage(PREV_PKG_IDS); const adReqInfo = { slotId: adId, spaceParam: '{"mOrientation":"v"}', requestUUID: generateUUID(32), } const isp = await getSimOperator() const envInfo = { net: `NET_${networkType}`, // 网络类型 isp: isp } // const data = { // reqid: 'test-2023-4-26', // version: '3.4.20.13', // srcType: 'SRC_APP', // reqType: 'REQ_AD', // timeStamp: 1666681016675, // appid: '277', // appVersion: '1.0', // apkName: 'com.beizi.adsdkdemo', // appInstallTime: 1666590646738, // appUpdateTime: 1666681014754, // devInfo: { // sdkUID: '', // idfa: '', // os: '29 (10)', // platform: 'PLATFORM_ANDROID', // devType: 'DEVICE_PHONE', // brand: 'HONOR', // model: 'COL-AL10', // manufacturer: 'HUAWEI', // resolution: '1080_2190', // screenSize: '5.64', // language: 'zh', // density: '3.0', // root: 'no', // oaid: '', // gaid: '', // bootMark: '9a361184-75fc-4329-8788-98504241e4e2', // updateMark: '1534646876.490000000', // ag_vercode: '120501300', // wx_installed: true, // physicalMemory: '5926273024', // harddiskSize: '56583258112', // }, // envInfo: { // net: 'NET_WIFI', // isp: 'ISP_OTHER', // }, // adReqInfo: [ // { // slotId: '7045', // spaceParam: '{"mOrientation":"v"}', // requestUUID: '13dc105e6e5350fa53cf0096c23ba6e9', // channelReserveTs: 0, // }, // ], // } const appInfo = app.getInfo(); const { name, packageName, versionName } = appInfo; return fetch .fetch({ url: isBoost ? boostURL : baseURL, method: 'POST', header: { 'content-type': 'application/json', }, // data, data: { ...baseParams, appName: name, packageName, appVersion: versionName, appid: appId, devInfo: { ...devInfo, appList: d, }, adReqInfo: [adReqInfo], envInfo, }, responseType: 'json', }) .then((res) => { if (res.data.code === 200) { return res.data.data } }) }