Skip to content

Instantly share code, notes, and snippets.

@nadalizadeh
Last active August 3, 2019 15:21
Show Gist options
  • Save nadalizadeh/f9b2d6b2f648c15ab5147adc88dafab1 to your computer and use it in GitHub Desktop.
Save nadalizadeh/f9b2d6b2f648c15ab5147adc88dafab1 to your computer and use it in GitHub Desktop.
IP-Based Tracking and Ad Network Callbacks
💡سوال: شما وقتی تبلیغ میکنید چطور میفهمید که چه قدر مفید بوده؟

⬅️ جواب: قبل از هر چیز برای اینکه ببینید تبلیغ چقدر مفید بوده باید بفهمید که کاربرانی که از طریق تبلیغ وارد بازی شما شده اند، کدومها هستند که بتونید رفتارشون رو تحلیل کنید.

ما قبل از اینکه کاربر رو به صفحه�ی مارکت (بازار، سیبچه و …) بفرستیم، کلیک انجام شده روی بنر رو به سرور خودمون هدایت میکنیم و آی پی کاربر رو به همراه اون اسم بنری که ازش اومده ثبت میکنیم. (هر بنر لینک یکتایی داره که اسمش توی لینک قرار داده شده)

بعد کاربر رو به مسیر دلخواهش هدایت میکنیم که برنامه رو نصب کنه. بعد از نصب و زمانی که برنامه باز میشه، آی پی کسی که برنامه رو باز کرده با آی پی های ثبت شده تطبیق میدیم. اگر یکی بود و توی ۲۴ ساعت اخیر کلیک شده بود، اطلاعات تبلیغ ثبت شده برای اون تبلیغ رو برای این کاربر در نظر میگیریم و منبعی که کاربر رو ازش جذب کردیم، اون تبلیغ ثبت میکنیم.

اینطور میشه کاربرهایی که داریم رو بر اساس مسیری که ازش با محصول ما اشنا شدن، دسته بندی کنیم و ببینیم کدوم گروه کاربری عملکرد بهتری دارند.

💡سوال: آیا این روش همه کاربرهایی که از اون مسیر میان رو رصد میکنه؟

⬅️ جواب: قطعا نه، اما نکته ای که باید در نظر بگیرید اینه که نسبت بین کاربران اضافه ای که رصد نشدن و اونهایی که رصد شدن ثابته و اگر یکبار این نسبت رو حساب کنید میتونید از تعداد کاربران رصد شده تون همیشه کل کاربران رو تخمین بزنید.

<?php
namespace Admin\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\JsonModel;
use Zend\View\Model\ViewModel;
use Redis\Wrapper as RedisWrapper;
class TrackController extends AbstractActionController
{
/**
* Items action gives the available store items. For more information
* look at definition for STORE_ITEMS global variable.
*
* @return JsonModel
*/
public function trackAction()
{
$response = $this->getResponse();
$utm_source = $this->getRequestParam('utm_source');
$utm_medium = $this->getRequestParam('utm_medium');
$utm_campaign = $this->getRequestParam('utm_campaign');
$utm_content = $this->getRequestParam('utm_content');
$utm_term = $this->getRequestParam('utm_term');
$utm_ip = $this->getRequestParam('utm_ip');
$redirect = $this->getRequestParam('redirect');
$udid = $this->getRequestParam('udid');
$provider_metadata = $this->getRequestParam('provider_metadata');
$fruitcraft_userid = $this->getRequestParam('fruitcraft_userid');
if (empty($redirect)) {
$redirect_split_test_indicator = RedisWrapper::get(REDIS_TRACK_SPLIT_TEST_INDICATOR_KEY);
if (!isset($redirect_split_test_indicator) || empty($redirect_split_test_indicator))
$redirect_split_test_indicator = 0;
$redirect_split_test_indicator = intval($redirect_split_test_indicator);
if ($redirect_split_test_indicator > 1000)
$redirect_split_test_indicator = 0;
$redirect_split_test_values = array(
"http://per.city/",
DOWNLOAD_URL_CAFEBAZAAR
);
$redirect = $redirect_split_test_values[$redirect_split_test_indicator % count($redirect_split_test_values)];
RedisWrapper::set(REDIS_TRACK_SPLIT_TEST_INDICATOR_KEY, $redirect_split_test_indicator + 1);
}
$theIP = emptY($utm_ip) ? $_SERVER['REMOTE_ADDR'] : $utm_ip;
$redis_key = REDIS_TRACK_KEY_PREFIX . $theIP;
if (!empty($utm_source))
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_SOURCE_FIELD, $utm_source);
if (!empty($utm_medium))
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_MEDIUM_FIELD, $utm_medium);
if (!empty($utm_campaign))
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_CAMPAIGN_FIELD, $utm_campaign);
if (!empty($utm_content))
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_CONTENT_FIELD, $utm_content);
if (!empty($utm_term))
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_TERM_FIELD, $utm_term);
if (!empty($udid))
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_UDID_FIELD, $udid);
if (!empty($provider_metadata)) {
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_PROVIDER_METADATA_FIELD, $provider_metadata);
// Format for Magnet: "magnet|productId|group|clientTokenId|action"
// Format for Tapsell: "tapsell|clickId"
// Format for Vungle: "vungle|event_id|app_id|ifa|aaid"
}
if (!empty($fruitcraft_userid))
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_FRUITCRAFT_USERID_FIELD, $fruitcraft_userid);
RedisWrapper::hashSetField($redis_key, REDIS_TRACK_REDIRECT_FIELD, $redirect);
RedisWrapper::expire($redis_key, REDIS_TRACK_TIMEOUT);
if (strlen($udid) > 5) {
$udid_redis_key = REDIS_TRACK_UDID_KEY_PREFIX . $udid;
RedisWrapper::set($udid_redis_key, $redis_key);
RedisWrapper::expire($udid_redis_key, REDIS_TRACK_TIMEOUT);
}
LPx("track/track: { source: $utm_source, medium: $utm_medium, campaign: $utm_campaign, ip: $theIP }");
// Do not redirect for Unity Ads
if (!empty($utm_ip)) {
return false;
}
$this->redirect()->toUrl($redirect);
return false; // disable view
}
private function getRequestParam($name)
{
$fromRoute = $this->params()->fromRoute($name);
$fromQuery = $this->params()->fromQuery($name, $fromRoute);
return $this->params()->fromPost($name, $fromQuery);
}
}
/**
* @param $playerId
* @param $marketName
* @param $deviceModel
* @param $deviceUDID
* @param $isExistingPlayer
*/
private function createTrackingRecords($playerId, $marketName, $deviceModel, $deviceUDID, $isExistingPlayer)
{
try {
/* @var $trackTable \V8\Model\Table\Track */
$trackTable = $this->serviceLocator->get('V8\TrackTable');
/** @var ScriptEngine $scriptEngine */
$scriptEngine = $this->serviceLocator->get('V8\ScriptEngine');
$track_redis_key = REDIS_TRACK_KEY_PREFIX . $_SERVER['REMOTE_ADDR'];
$track_keys = RedisWrapper::hashGetAllFields($track_redis_key);
if (empty($track_keys) && strlen($deviceUDID) > 5) {
// Lookup by UDID
$track_udid_redis_key = REDIS_TRACK_UDID_KEY_PREFIX . $deviceUDID;
$original_redis_key = RedisWrapper::get($track_udid_redis_key);
if ($original_redis_key) {
$track_redis_key = $original_redis_key;
// Lookup the original redis key (ip has been changed probably)
$track_keys = RedisWrapper::hashGetAllFields($track_redis_key);
if (!empty($track_keys)) {
LPx("player/signup: Tracking record for UDID $deviceUDID has been found $original_redis_key");
}
}
}
// For existing player do not create track records, only log them
if ($isExistingPlayer) {
$track_key_timepassed = REDIS_TRACK_TIMEOUT - RedisWrapper::ttl($track_redis_key);
if ($track_key_timepassed < REDIS_TRACK_TIMEOUT_EXISTING_PLAYERS) {
$track_info = "";
foreach ($track_keys as $trackKey => $trackValue) {
$track_info .= $trackKey . ": " . $trackValue . ", ";
}
LPx("player/load: Existing player clicked this tracker: ($track_info) from ip $_SERVER[REMOTE_ADDR]");
}
return;
}
if (!empty($track_keys)) {
$newTrack = new \V8\Model\Track();
$newTrack->player_id = $playerId;
$newTrack->ip = "$_SERVER[REMOTE_ADDR]";
$track_info = "";
foreach ($track_keys as $trackKey => $trackValue) {
$track_info .= $trackKey . ": " . $trackValue . ", ";
if ($trackKey == REDIS_TRACK_PROVIDER_METADATA_FIELD) {
self::$adProviderMetadata = $trackValue;
continue;
}
if ($trackKey == REDIS_TRACK_FRUITCRAFT_USERID_FIELD) {
// Cross Promotion Test - Save Fruitcraft User ID in settings
/** @var SettingTable $settingTable */
$settingTable = $this->serviceLocator->get('SettingTable');
$settingTable->setValueInPlace($playerId, SETTINGS_GAMES_FRUITCRAFT_USERID, $trackValue);
continue;
}
$newTrack->$trackKey = $trackValue;
}
LPx("player/signup: Got this track info: ($track_info)");
$trackTable->save($newTrack);
RedisWrapper::delete($track_redis_key);
// Unity Install Tracking
if (strpos($newTrack->term, "|") !== FALSE) {
self::$unityInstallTrackingJob = $newTrack->term;
}
} else {
LPx("player/signup: No tracking record for $track_redis_key was found.", \LogLevel::Info);
}
} catch (\Exception $e) {
LPx("Error - Failed to save track records", \LogLevel::Error, null, $e);
}
}
// ==============================================
public static function submitAdsTrackingPostBacks()
{
if (!empty(self::$unityInstallTrackingJob)) {
$unityAdsClient = new \Rest\UnityAdsClient();
$unityAdsClient->notifyInstallWithPackedData(self::$unityInstallTrackingJob);
} elseif (!empty(self::$adProviderMetadata)) {
LPx("processing ad provider metadata " . self::$adProviderMetadata . " for ad-tracking postback");
if (strtolower(substr(self::$adProviderMetadata, 0, 7)) == "magnet|") {
$magnetAdsClient = new \Rest\MagnetAdsClient();
$magnetAdsClient->notifyInstall(self::$adProviderMetadata);
} elseif (strtolower(substr(self::$adProviderMetadata, 0, 8)) == "tapsell|") {
$tapsellAdsClient = new \Rest\TapsellAdsClient();
$tapsellAdsClient->notifyInstall(self::$adProviderMetadata);
} elseif (strtolower(substr(self::$adProviderMetadata, 0, 8)) == "vungle|") {
$vungleAdsClient = new \Rest\VungleAdsClient();
$vungleAdsClient->notifyInstall(self::$adProviderMetadata);
}
}
<?php
/**
* Created by PhpStorm.
* User: me
* Date: 19/06/2018
* Time: 05:02 PM
*/
namespace Rest;
use Zend\Http\Client;
use Redis\Wrapper as RedisWrapper;
define('TAPSELL_POST_BACK_URL', 'http://api.tapsell.ir/v2/postbacks/by-click');
class TapsellAdsClient
{
public function isPostbackEnabled()
{
$theFlag = RedisWrapper::get(REDIS_KEY_TAPSELL_POSTBACK_ENABLED);
return !empty($theFlag);
}
public function notifyInstall($provider_metadata)
{
list($provider, $clickId) = explode("|", $provider_metadata);
if (strtolower($provider) !== "tapsell")
return;
if (!$this->isPostbackEnabled()) {
LPx("Tapsell postback for $clickId ignored because of admin settings.");
return;
}
$client = new Client();
$client->setUri(TAPSELL_POST_BACK_URL);
$client->setParameterGet([
'clickId' => $clickId, #{full_click_id}
]);
$response = $client->send();
LPx("Tapsell postback for $clickId response: {code: " . $response->getStatusCode() . ", body: " . $response->getBody() . "}");
}
}
<?php
/**
* Created by PhpStorm.
* User: me
* Date: 19/06/2018
* Time: 05:02 PM
*/
namespace Rest;
use Zend\Http\Client;
use Redis\Wrapper as RedisWrapper;
define('VUNGLE_POST_BACK_URL', 'http://api.vungle.com/api/v3/new');
class VungleAdsClient
{
public function isPostbackEnabled()
{
$theFlag = RedisWrapper::get(REDIS_KEY_VUNGLE_POSTBACK_ENABLED);
return !empty($theFlag);
}
public function notifyInstall($provider_metadata)
{
list($provider, $eventId, $app_id, $ifa, $aaid) = explode("|", $provider_metadata);
if (strtolower($provider) !== "vungle")
return;
if (!$this->isPostbackEnabled()) {
LPx("Vungle postback for $eventId ignored because of admin settings.");
return;
}
$client = new Client();
$client->setUri(VUNGLE_POST_BACK_URL);
$client->setParameterGet([
'app_id' => $app_id,
'ifa' => $ifa,
'aaid' => $aaid,
'conversion' => 1,
'trk' => 'mat',
'event_id' => $eventId,
]);
$response = $client->send();
LPx("Vungle postback for $eventId response: {code: " . $response->getStatusCode() . ", body: " . $response->getBody() . "}");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment