1/** 2 * Channel mapper for Smart Script / attribution. 3 * Computes channel from source, medium, campaign (and link). All PPC sub-types map to one: PPC. Fallback: Other. 4 */ 5(function (root) { 6 'use strict'; 7 8 var seoRoots = [ 9 'chatgpt\\.com', 'openai\\.com', 'claude\\.ai', 'perplexity\\.ai', 'copy\\.ai', 'jasper\\.ai', 10 'blackbox\\.ai', 'quillbot\\.com', 'mistral\\.ai', 'deepseek\\.com', 11 'gemini\\.google\\.com', 'copilot\\.microsoft\\.com', 'chat\\.qwenlm\\.ai', 12 'poe\\.com', 'you\\.com', 'character\\.ai', 'huggingface\\.co', 'meta\\.ai', 'reka\\.ai', 'cohere\\.com', 'grok\\.com', 'qwen\\.ai', 13 'edgeservices\\.bing\\.com', 'ai\\.com', 'phind\\.com', 'openrouter\\.ai', 'lmsys\\.org', 'chat\\.lmsys\\.org', 'x\\.ai', 'bard\\.google\\.com', 14 'writesonic\\.com', 'exa\\.ai', 'bnngpt\\.com', 'nimble\\.ai', 'iask\\.ai', 'aitastic\\.app', 'waldo(?:\\.com)?' 15 ]; 16 17 function prepareKeys(gaKeys) { 18 var result = Object.assign({}, gaKeys); 19 ['source', 'medium', 'campaign', 'link'].forEach(function (field) { 20 result[field] = (result[field] || '').toLowerCase(); 21 }); 22 if (result.medium && result.medium.indexOf(',') !== -1) { 23 var temp = result.medium.split(','); 24 if (temp[0]) result.medium = temp[0].trim(); 25 } 26 return result; 27 } 28 29 function reBill(keys) { return keys.medium === 'rebill' && keys.source === 'rebill'; } 30 function push(keys) { return /(notification|push|app_push)/i.test(keys.medium); } 31 32 function brandPpc(keys) { 33 return (/^(cpc)$/i.test(keys.medium) && /brand/i.test(keys.campaign)) || 34 (/utm/i.test(keys.source) && /cpc/i.test(keys.source) && /brand/i.test(keys.source)); 35 } 36 function nonBrandPpc(keys) { return /cpc/i.test(keys.medium) || (/utm/i.test(keys.source) && /cpc/i.test(keys.source)); } 37 function facebookPpc(keys) { 38 return (keys.medium === 'sponsored' && /(fb|ig|facebook|\(none\))/i.test(keys.source)) || 39 (/utm/i.test(keys.source) && /sponsored/i.test(keys.source)); 40 } 41 function displayPpcNew(keys) { return keys.medium === 'display' && /(criteo|banner)/i.test(keys.source); } 42 function remarketing(keys) { return /remarketing/i.test(keys.campaign); } 43 44 function ppc(keys) { 45 return brandPpc(keys) || nonBrandPpc(keys) || facebookPpc(keys) || displayPpcNew(keys) || remarketing(keys); 46 } 47 48 function partner(keys) { return /(partner|newsletter|native)/i.test(keys.medium); } 49 function addPartner(keys) { return keys.source === 'newsletter'; } 50 51 function seo(keys) { 52 var searchEngines = /(duckduckgo\.com|bing\.com|yahoo|baidu\.com|ecosia\.org|startpage\.com|naver\.com|yandex|qwant\.com|swisscows\.com|sogou\.com|petalsearch\.com|mojeek\.com|kagi\.com|dogpile\.com|ask\.com|you\.com|search\.seznam\.cz|search\.brave\.com|chatgpt\.com|gemini\.google\.com|edgeservices\.bing\.com|claude\.ai|iask\.ai|perplexity\.ai)/; 53 if (keys.medium === 'organic' || (searchEngines.test(keys.source) && keys.medium === 'referral') || 54 (keys.source === 'chatgpt.com' && keys.medium === '(none)') || (keys.source === 'perplexity' &&
54keys.medium === '(none)')) 55 return true; 56 try { 57 var pattern = new RegExp('^(?:[\\w-]+\\.)*(?:' + seoRoots.join('|') + ')$', 'i'); 58 if (pattern.test(keys.source)) return true; 59 } catch (e) {} 60 return false; 61 } 62 63 function affiliate(keys) { 64 return /cpa|affiliate|hasoffers/i.test(keys.medium) || (/affiliate/i.test(keys.source) && keys.medium !== 'referral'); 65 } 66 function email(keys) { 67 return /email/i.test(keys.medium) || (/^ac|activecampaign|iterable|acemail$/i.test(keys.source) && keys.medium !== 'referral' && keys.medium !== 'account_referral'); 68 } 69 function social(keys) { 70 return /^(soc|social|social-network|social-media|sm)$/i.test(keys.medium) || 71 /(instagram|facebook|pinterest|youtube|twitter|tiktok|linkedin)/i.test(keys.source) || 72 (/linkedin/i.test(keys.source) && keys.medium !== 'cpc'); 73 } 74 function newsScreen(keys) { return /(newsscreen|newscreen)/i.test(keys.medium) || /(newsscreen|newscreen)/i.test(keys.source); } 75 function inApp(keys) { 76 return /inapp/i.test(keys.source) || keys.source === 'app' || /app/i.test(keys.medium) || 77 (keys.link && /marketplace/i.test(keys.link)) || 78 (keys.link && /web-view/i.test(keys.link) && !/winstore/i.test(keys.link) && !/promo/i.test(keys.link)); 79 } 80 function referral(keys) { return /referral/i.test(keys.medium) || /referral/i.test(keys.source); } 81 function direct(keys) { 82 return keys.source === '(direct)' || (keys.source === '' && (keys.medium === '(none)' || keys.medium === '')) || 83 (keys.medium === '' && keys.source === '(none)') || (keys.medium === '(none)' && keys.source === '(none)'); 84 } 85 function insiders(keys) { return keys.source === 'insiders'; } 86 87 var checks = [ 88 { name: 'Rebill', method: reBill }, 89 { name: 'Push', method: push }, 90 { name: 'PPC', method: ppc }, 91 { name: 'Partner', method: partner }, 92 { name: 'SEO', method: seo }, 93 { name: 'Affiliate', method: affiliate }, 94 { name: 'Email', method: email }, 95 { name: 'Social', method: social }, 96 { name: 'NewsScreen', method: newsScreen }, 97 { name: 'InApp', method: inApp }, 98 { name: 'Referral', method: referral }, 99 { name: 'Direct', method: direct }, 100 { name: 'Insiders', method: insiders }, 101 { name: 'Partner', method: addPartner } 102 ]; 103 104 function getChannelName(gaKeys) { 105 var keys = prepareKeys(gaKeys || {}); 106 for (var i = 0; i < checks.length; i++) { 107 try { 108 if (checks[i].method(keys)) return checks[i].name; 109 } catch (e) {} 110 } 111 return 'Other'; 112 } 113 114 var ChannelMapper = { getChannelName: getChannelName, prepareKeys: prepareKeys }; 115 if (typeof module !== 'undefined' && module.exports) module.exports = ChannelMapper; 116 else root.ChannelMapper = ChannelMapper; 117 118 if (typeof root !== 'undefined' && root.__channelMapperTest) { 119 var M = root.ChannelMapper; 120 var tests = [ 121 { keys: { source: 'google', medium: 'cpc', campaign: 'brand_luminar' }, expect: 'PPC' }, 122 { keys: { source: 'fb', medium: 'sponsored', campaign: 'any' }, expect: 'PPC' }, 123 { keys: { source: 'utm', medium: 'cpc', campaign: 'remarketing' }, expect: 'PPC' }, 124 { keys: { source: 'google', medium: 'organic' }, expect: 'SEO' }, 125 { keys: { source: 'ac', medium: 'email' }, expect: 'Email' }, 126 { keys: { source: 'facebook.com', medium: 'referral' }, expect: 'Social' }, 127 { keys: { source: '(direct)', medium: '(none)' }, expect: 'Direct' }, 128 { keys: { source: 'unknown', medium: 'xyz' }, expect: 'Other' } 129 ]; 130 var ok = 0, fail = 0; 131 tests.forEach(function (t) { 132 var got = M.getChannelName(t.keys); 133 if (got === t.expect) { ok++; } else { fail++; console.log('[ChannelMapper] FAIL', t.keys, 'expected', t.expect, 'got', got); } 134 }); 135 console.log('[ChannelMapper] tests: ' + ok + ' ok, ' + fail + ' fail'); 136 } 137})(typeof window !== 'undefined' ? window : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.