octopus-ad
Version:
Quickapp Sample Template
823 lines (736 loc) • 19.8 kB
JavaScript
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
}
})
}