From 429dd9fcb0a6f492b532ae0ca684e11de5bfa069 Mon Sep 17 00:00:00 2001 From: Johannes Trum Date: Fri, 3 Mar 2017 23:47:41 +0100 Subject: [PATCH] Adds tracking --- resources/language/English/strings.po | 5 + resources/language/German/strings.po | 4 + resources/lib/KodiHelper.py | 23 + resources/lib/MSLHttpRequestHandler.py | 2 + resources/lib/UniversalAnalytics/HTTPLog.py | 121 +++++ resources/lib/UniversalAnalytics/LICENSE | 29 ++ resources/lib/UniversalAnalytics/Tracker.py | 454 +++++++++++++++++++ resources/lib/UniversalAnalytics/__init__.py | 1 + resources/settings.xml | 2 + 9 files changed, 641 insertions(+) create mode 100644 resources/lib/UniversalAnalytics/HTTPLog.py create mode 100644 resources/lib/UniversalAnalytics/LICENSE create mode 100644 resources/lib/UniversalAnalytics/Tracker.py create mode 100644 resources/lib/UniversalAnalytics/__init__.py diff --git a/resources/language/English/strings.po b/resources/language/English/strings.po index f1f0ab2..cd4d8d2 100644 --- a/resources/language/English/strings.po +++ b/resources/language/English/strings.po @@ -144,3 +144,8 @@ msgstr "" msgctxt "#30031" msgid "Change library title" msgstr "" + +msgctxt "#30032" +msgid "Tracking" +msgstr "" + diff --git a/resources/language/German/strings.po b/resources/language/German/strings.po index 344f028..6986359 100644 --- a/resources/language/German/strings.po +++ b/resources/language/German/strings.po @@ -144,3 +144,7 @@ msgstr "Aus Bibliothek entfernen" msgctxt "#30031" msgid "Change library title" msgstr "Export titel ändern" + +msgctxt "#30032" +msgid "Tracking" +msgstr "Tracking" diff --git a/resources/lib/KodiHelper.py b/resources/lib/KodiHelper.py index 04d268e..9fdb8bf 100644 --- a/resources/lib/KodiHelper.py +++ b/resources/lib/KodiHelper.py @@ -10,6 +10,8 @@ import xbmcgui import xbmcaddon import xbmc import json +import uuid +from UniversalAnalytics import Tracker try: import cPickle as pickle except: @@ -638,6 +640,9 @@ class KodiHelper: self.log(msg='Inputstream addon not found') return False + # track play event + self.track_event('playVideo') + # inputstream addon properties msl_service_url = 'http://localhost:' + str(self.addon.getSetting('msl_service_port')) play_item = xbmcgui.ListItem(path=msl_service_url + '/manifest?id=' + video_id) @@ -895,3 +900,21 @@ class KodiHelper: instance of the Library class """ self.library = library + + def track_event(self, event): + """ + Send a tracking event if tracking is enabled + :param event: the string idetifier of the event + :return: None + """ + # Check if tracking is enabled + enable_tracking = (self.addon.getSetting('enable_logging') == 'true') + if enable_tracking: + #Get or Create Tracking id + tracking_id = self.addon.getSetting('tracking_id') + if tracking_id is '': + tracking_id = str(uuid.uuid4()) + self.addon.setSetting('tracking_id', tracking_id) + # Send the tracking event + tracker = Tracker.create('UA-46081640-5', client_id=tracking_id) + tracker.send('event', event) diff --git a/resources/lib/MSLHttpRequestHandler.py b/resources/lib/MSLHttpRequestHandler.py index 25abf54..e7682fc 100644 --- a/resources/lib/MSLHttpRequestHandler.py +++ b/resources/lib/MSLHttpRequestHandler.py @@ -33,8 +33,10 @@ class MSLHttpRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.wfile.write(base64.standard_b64decode(b64license)) self.finish() else: + kodi_helper.log(msg='Error getting License') self.send_response(400) else: + kodi_helper.log(msg='Error in License Request') self.send_response(400) def do_GET(self): diff --git a/resources/lib/UniversalAnalytics/HTTPLog.py b/resources/lib/UniversalAnalytics/HTTPLog.py new file mode 100644 index 0000000..4fd8893 --- /dev/null +++ b/resources/lib/UniversalAnalytics/HTTPLog.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +############################################################################### +# Formatting filter for urllib2's HTTPHandler(debuglevel=1) output +# Copyright (c) 2013, Analytics Pros +# +# This project is free software, distributed under the BSD license. +# Analytics Pros offers consulting and integration services if your firm needs +# assistance in strategy, implementation, or auditing existing work. +############################################################################### + + +import sys, re, os +from cStringIO import StringIO + + + +class BufferTranslator(object): + """ Provides a buffer-compatible interface for filtering buffer content. + """ + parsers = [] + + def __init__(self, output): + self.output = output + self.encoding = getattr(output, 'encoding', None) + + def write(self, content): + content = self.translate(content) + self.output.write(content) + + + @staticmethod + def stripslashes(content): + return content.decode('string_escape') + + @staticmethod + def addslashes(content): + return content.encode('string_escape') + + def translate(self, line): + for pattern, method in self.parsers: + match = pattern.match(line) + if match: + return method(match) + + return line + + + +class LineBufferTranslator(BufferTranslator): + """ Line buffer implementation supports translation of line-format input + even when input is not already line-buffered. Caches input until newlines + occur, and then dispatches translated input to output buffer. + """ + def __init__(self, *a, **kw): + self._linepending = [] + super(LineBufferTranslator, self).__init__(*a, **kw) + + def write(self, _input): + lines = _input.splitlines(True) + for i in range(0, len(lines)): + last = i + if lines[i].endswith('\n'): + prefix = len(self._linepending) and ''.join(self._linepending) or '' + self.output.write(self.translate(prefix + lines[i])) + del self._linepending[0:] + last = -1 + + if last >= 0: + self._linepending.append(lines[ last ]) + + + def __del__(self): + if len(self._linepending): + self.output.write(self.translate(''.join(self._linepending))) + + +class HTTPTranslator(LineBufferTranslator): + """ Translates output from |urllib2| HTTPHandler(debuglevel = 1) into + HTTP-compatible, readible text structures for human analysis. + """ + + RE_LINE_PARSER = re.compile(r'^(?:([a-z]+):)\s*(\'?)([^\r\n]*)\2(?:[\r\n]*)$') + RE_LINE_BREAK = re.compile(r'(\r?\n|(?:\\r)?\\n)') + RE_HTTP_METHOD = re.compile(r'^(POST|GET|HEAD|DELETE|PUT|TRACE|OPTIONS)') + RE_PARAMETER_SPACER = re.compile(r'&([a-z0-9]+)=') + + @classmethod + def spacer(cls, line): + return cls.RE_PARAMETER_SPACER.sub(r' &\1= ', line) + + def translate(self, line): + + parsed = self.RE_LINE_PARSER.match(line) + + if parsed: + value = parsed.group(3) + stage = parsed.group(1) + + if stage == 'send': # query string is rendered here + return '\n# HTTP Request:\n' + self.stripslashes(value) + elif stage == 'reply': + return '\n\n# HTTP Response:\n' + self.stripslashes(value) + elif stage == 'header': + return value + '\n' + else: + return value + + + return line + + +def consume(outbuffer = None): # Capture standard output + sys.stdout = HTTPTranslator(outbuffer or sys.stdout) + return sys.stdout + + +if __name__ == '__main__': + consume(sys.stdout).write(sys.stdin.read()) + print '\n' + +# vim: set nowrap tabstop=4 shiftwidth=4 softtabstop=0 expandtab textwidth=0 filetype=python foldmethod=indent foldcolumn=4 diff --git a/resources/lib/UniversalAnalytics/LICENSE b/resources/lib/UniversalAnalytics/LICENSE new file mode 100644 index 0000000..9e8e80a --- /dev/null +++ b/resources/lib/UniversalAnalytics/LICENSE @@ -0,0 +1,29 @@ +- BSD 3-Clause license - + +Copyright (c) 2013, Analytics Pros +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +* Neither the name of Analytics Pros nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/resources/lib/UniversalAnalytics/Tracker.py b/resources/lib/UniversalAnalytics/Tracker.py new file mode 100644 index 0000000..cd76e44 --- /dev/null +++ b/resources/lib/UniversalAnalytics/Tracker.py @@ -0,0 +1,454 @@ +############################################################################### +# Universal Analytics for Python +# Copyright (c) 2013, Analytics Pros +# +# This project is free software, distributed under the BSD license. +# Analytics Pros offers consulting and integration services if your firm needs +# assistance in strategy, implementation, or auditing existing work. +############################################################################### + +from urllib2 import urlopen, build_opener, install_opener +from urllib2 import Request, HTTPSHandler +from urllib2 import URLError, HTTPError +from urllib import urlencode + +import random +import datetime +import time +import uuid +import hashlib +import socket + + + +def generate_uuid(basedata = None): + """ Provides a _random_ UUID with no input, or a UUID4-format MD5 checksum of any input data provided """ + if basedata is None: + return str(uuid.uuid4()) + elif isinstance(basedata, basestring): + checksum = hashlib.md5(basedata).hexdigest() + return '%8s-%4s-%4s-%4s-%12s' % (checksum[0:8], checksum[8:12], checksum[12:16], checksum[16:20], checksum[20:32]) + + +class Time(datetime.datetime): + """ Wrappers and convenience methods for processing various time representations """ + + @classmethod + def from_unix(cls, seconds, milliseconds = 0): + """ Produce a full |datetime.datetime| object from a Unix timestamp """ + base = list(time.gmtime(seconds))[0:6] + base.append(milliseconds * 1000) # microseconds + return cls(* base) + + @classmethod + def to_unix(cls, timestamp): + """ Wrapper over time module to produce Unix epoch time as a float """ + if not isinstance(timestamp, datetime.datetime): + raise TypeError, 'Time.milliseconds expects a datetime object' + base = time.mktime(timestamp.timetuple()) + return base + + @classmethod + def milliseconds_offset(cls, timestamp, now = None): + """ Offset time (in milliseconds) from a |datetime.datetime| object to now """ + if isinstance(timestamp, (int, float)): + base = timestamp + else: + base = cls.to_unix(timestamp) + base = base + (timestamp.microsecond / 1000000) + if now is None: + now = time.time() + return (now - base) * 1000 + + + +class HTTPRequest(object): + """ URL Construction and request handling abstraction. + This is not intended to be used outside this module. + + Automates mapping of persistent state (i.e. query parameters) + onto transcient datasets for each query. + """ + + endpoint = 'https://www.google-analytics.com/collect' + + + @staticmethod + def debug(): + """ Activate debugging on urllib2 """ + handler = HTTPSHandler(debuglevel = 1) + opener = build_opener(handler) + install_opener(opener) + + # Store properties for all requests + def __init__(self, user_agent = None, *args, **opts): + self.user_agent = user_agent or 'Analytics Pros - Universal Analytics (Python)' + + + @classmethod + def fixUTF8(cls, data): # Ensure proper encoding for UA's servers... + """ Convert all strings to UTF-8 """ + for key in data: + if isinstance(data[ key ], basestring): + data[ key ] = data[ key ].encode('utf-8') + return data + + + + # Apply stored properties to the given dataset & POST to the configured endpoint + def send(self, data): + request = Request( + self.endpoint + '?' + urlencode(self.fixUTF8(data)), + headers = { + 'User-Agent': self.user_agent + } + ) + self.open(request) + + def open(self, request): + try: + return urlopen(request) + except HTTPError as e: + return False + except URLError as e: + self.cache_request(request) + return False + + def cache_request(self, request): + # TODO: implement a proper caching mechanism here for re-transmitting hits + # record = (Time.now(), request.get_full_url(), request.get_data(), request.headers) + pass + + + + +class HTTPPost(HTTPRequest): + + # Apply stored properties to the given dataset & POST to the configured endpoint + def send(self, data): + request = Request( + self.endpoint, + data = urlencode(self.fixUTF8(data)), + headers = { + 'User-Agent': self.user_agent + } + ) + self.open(request) + + + + + + +class Tracker(object): + """ Primary tracking interface for Universal Analytics """ + params = None + parameter_alias = {} + valid_hittypes = ('pageview', 'event', 'social', 'screenview', 'transaction', 'item', 'exception', 'timing') + + + @classmethod + def alias(cls, typemap, base, *names): + """ Declare an alternate (humane) name for a measurement protocol parameter """ + cls.parameter_alias[ base ] = (typemap, base) + for i in names: + cls.parameter_alias[ i ] = (typemap, base) + + @classmethod + def coerceParameter(cls, name, value = None): + if isinstance(name, basestring) and name[0] == '&': + return name[1:], str(value) + elif name in cls.parameter_alias: + typecast, param_name = cls.parameter_alias.get(name) + return param_name, typecast(value) + else: + raise KeyError, 'Parameter "{0}" is not recognized'.format(name) + + + def payload(self, data): + for key, value in data.iteritems(): + try: + yield self.coerceParameter(key, value) + except KeyError: + continue + + + + option_sequence = { + 'pageview': [ (basestring, 'dp') ], + 'event': [ (basestring, 'ec'), (basestring, 'ea'), (basestring, 'el'), (int, 'ev') ], + 'social': [ (basestring, 'sn'), (basestring, 'sa'), (basestring, 'st') ], + 'timing': [ (basestring, 'utc'), (basestring, 'utv'), (basestring, 'utt'), (basestring, 'utl') ] + } + + @classmethod + def consume_options(cls, data, hittype, args): + """ Interpret sequential arguments related to known hittypes based on declared structures """ + opt_position = 0 + data[ 't' ] = hittype # integrate hit type parameter + if hittype in cls.option_sequence: + for expected_type, optname in cls.option_sequence[ hittype ]: + if opt_position < len(args) and isinstance(args[opt_position], expected_type): + data[ optname ] = args[ opt_position ] + opt_position += 1 + + + + + @classmethod + def hittime(cls, timestamp = None, age = None, milliseconds = None): + """ Returns an integer represeting the milliseconds offset for a given hit (relative to now) """ + if isinstance(timestamp, (int, float)): + return int(Time.milliseconds_offset(Time.from_unix(timestamp, milliseconds = milliseconds))) + if isinstance(timestamp, datetime.datetime): + return int(Time.milliseconds_offset(timestamp)) + if isinstance(age, (int, float)): + return int(age * 1000) + (milliseconds or 0) + + + + @property + def account(self): + return self.params.get('tid', None) + + + def __init__(self, account, name = None, client_id = None, hash_client_id = False, user_id = None, user_agent = None, use_post = True): + + if use_post is False: + self.http = HTTPRequest(user_agent = user_agent) + else: + self.http = HTTPPost(user_agent = user_agent) + + self.params = { 'v': 1, 'tid': account } + + if client_id is None: + client_id = generate_uuid() + + self.params[ 'cid' ] = client_id + + self.hash_client_id = hash_client_id + + if user_id is not None: + self.params[ 'uid' ] = user_id + + + def set_timestamp(self, data): + """ Interpret time-related options, apply queue-time parameter as needed """ + if 'hittime' in data: # an absolute timestamp + data['qt'] = self.hittime(timestamp = data.pop('hittime', None)) + if 'hitage' in data: # a relative age (in seconds) + data['qt'] = self.hittime(age = data.pop('hitage', None)) + + + def send(self, hittype, *args, **data): + """ Transmit HTTP requests to Google Analytics using the measurement protocol """ + + if hittype not in self.valid_hittypes: + raise KeyError('Unsupported Universal Analytics Hit Type: {0}'.format(repr(hittype))) + + self.set_timestamp(data) + self.consume_options(data, hittype, args) + + for item in args: # process dictionary-object arguments of transcient data + if isinstance(item, dict): + for key, val in self.payload(item): + data[ key ] = val + + for k, v in self.params.iteritems(): # update only absent parameters + if k not in data: + data[ k ] = v + + + data = dict(self.payload(data)) + + if self.hash_client_id: + data[ 'cid' ] = generate_uuid(data[ 'cid' ]) + + # Transmit the hit to Google... + self.http.send(data) + + + + + # Setting persistent attibutes of the session/hit/etc (inc. custom dimensions/metrics) + def set(self, name, value = None): + if isinstance(name, dict): + for key, value in name.iteritems(): + try: + param, value = self.coerceParameter(key, value) + self.params[param] = value + except KeyError: + pass + elif isinstance(name, basestring): + try: + param, value = self.coerceParameter(name, value) + self.params[param] = value + except KeyError: + pass + + + + def __getitem__(self, name): + param, value = self.coerceParameter(name, None) + return self.params.get(param, None) + + def __setitem__(self, name, value): + param, value = self.coerceParameter(name, value) + self.params[param] = value + + def __delitem__(self, name): + param, value = self.coerceParameter(name, None) + if param in self.params: + del self.params[param] + +def safe_unicode(obj): + """ Safe convertion to the Unicode string version of the object """ + try: + return unicode(obj) + except UnicodeDecodeError: + return obj.decode('utf-8') + + +# Declaring name mappings for Measurement Protocol parameters +MAX_CUSTOM_DEFINITIONS = 200 +MAX_EC_LISTS = 11 # 1-based index +MAX_EC_PRODUCTS = 11 # 1-based index +MAX_EC_PROMOTIONS = 11 # 1-based index + +Tracker.alias(int, 'v', 'protocol-version') +Tracker.alias(safe_unicode, 'cid', 'client-id', 'clientId', 'clientid') +Tracker.alias(safe_unicode, 'tid', 'trackingId', 'account') +Tracker.alias(safe_unicode, 'uid', 'user-id', 'userId', 'userid') +Tracker.alias(safe_unicode, 'uip', 'user-ip', 'userIp', 'ipaddr') +Tracker.alias(safe_unicode, 'ua', 'userAgent', 'userAgentOverride', 'user-agent') +Tracker.alias(safe_unicode, 'dp', 'page', 'path') +Tracker.alias(safe_unicode, 'dt', 'title', 'pagetitle', 'pageTitle' 'page-title') +Tracker.alias(safe_unicode, 'dl', 'location') +Tracker.alias(safe_unicode, 'dh', 'hostname') +Tracker.alias(safe_unicode, 'sc', 'sessioncontrol', 'session-control', 'sessionControl') +Tracker.alias(safe_unicode, 'dr', 'referrer', 'referer') +Tracker.alias(int, 'qt', 'queueTime', 'queue-time') +Tracker.alias(safe_unicode, 't', 'hitType', 'hittype') +Tracker.alias(int, 'aip', 'anonymizeIp', 'anonIp', 'anonymize-ip') + + +# Campaign attribution +Tracker.alias(safe_unicode, 'cn', 'campaign', 'campaignName', 'campaign-name') +Tracker.alias(safe_unicode, 'cs', 'source', 'campaignSource', 'campaign-source') +Tracker.alias(safe_unicode, 'cm', 'medium', 'campaignMedium', 'campaign-medium') +Tracker.alias(safe_unicode, 'ck', 'keyword', 'campaignKeyword', 'campaign-keyword') +Tracker.alias(safe_unicode, 'cc', 'content', 'campaignContent', 'campaign-content') +Tracker.alias(safe_unicode, 'ci', 'campaignId', 'campaignID', 'campaign-id') + +# Technical specs +Tracker.alias(safe_unicode, 'sr', 'screenResolution', 'screen-resolution', 'resolution') +Tracker.alias(safe_unicode, 'vp', 'viewport', 'viewportSize', 'viewport-size') +Tracker.alias(safe_unicode, 'de', 'encoding', 'documentEncoding', 'document-encoding') +Tracker.alias(int, 'sd', 'colors', 'screenColors', 'screen-colors') +Tracker.alias(safe_unicode, 'ul', 'language', 'user-language', 'userLanguage') + +# Mobile app +Tracker.alias(safe_unicode, 'an', 'appName', 'app-name', 'app') +Tracker.alias(safe_unicode, 'cd', 'contentDescription', 'screenName', 'screen-name', 'content-description') +Tracker.alias(safe_unicode, 'av', 'appVersion', 'app-version', 'version') +Tracker.alias(safe_unicode, 'aid', 'appID', 'appId', 'application-id', 'app-id', 'applicationId') +Tracker.alias(safe_unicode, 'aiid', 'appInstallerId', 'app-installer-id') + +# Ecommerce +Tracker.alias(safe_unicode, 'ta', 'affiliation', 'transactionAffiliation', 'transaction-affiliation') +Tracker.alias(safe_unicode, 'ti', 'transaction', 'transactionId', 'transaction-id') +Tracker.alias(float, 'tr', 'revenue', 'transactionRevenue', 'transaction-revenue') +Tracker.alias(float, 'ts', 'shipping', 'transactionShipping', 'transaction-shipping') +Tracker.alias(float, 'tt', 'tax', 'transactionTax', 'transaction-tax') +Tracker.alias(safe_unicode, 'cu', 'currency', 'transactionCurrency', 'transaction-currency') # Currency code, e.g. USD, EUR +Tracker.alias(safe_unicode, 'in', 'item-name', 'itemName') +Tracker.alias(float, 'ip', 'item-price', 'itemPrice') +Tracker.alias(float, 'iq', 'item-quantity', 'itemQuantity') +Tracker.alias(safe_unicode, 'ic', 'item-code', 'sku', 'itemCode') +Tracker.alias(safe_unicode, 'iv', 'item-variation', 'item-category', 'itemCategory', 'itemVariation') + +# Events +Tracker.alias(safe_unicode, 'ec', 'event-category', 'eventCategory', 'category') +Tracker.alias(safe_unicode, 'ea', 'event-action', 'eventAction', 'action') +Tracker.alias(safe_unicode, 'el', 'event-label', 'eventLabel', 'label') +Tracker.alias(int, 'ev', 'event-value', 'eventValue', 'value') +Tracker.alias(int, 'ni', 'noninteractive', 'nonInteractive', 'noninteraction', 'nonInteraction') + + +# Social +Tracker.alias(safe_unicode, 'sa', 'social-action', 'socialAction') +Tracker.alias(safe_unicode, 'sn', 'social-network', 'socialNetwork') +Tracker.alias(safe_unicode, 'st', 'social-target', 'socialTarget') + +# Exceptions +Tracker.alias(safe_unicode, 'exd', 'exception-description', 'exceptionDescription', 'exDescription') +Tracker.alias(int, 'exf', 'exception-fatal', 'exceptionFatal', 'exFatal') + +# User Timing +Tracker.alias(safe_unicode, 'utc', 'timingCategory', 'timing-category') +Tracker.alias(safe_unicode, 'utv', 'timingVariable', 'timing-variable') +Tracker.alias(int, 'utt', 'time', 'timingTime', 'timing-time') +Tracker.alias(safe_unicode, 'utl', 'timingLabel', 'timing-label') +Tracker.alias(float, 'dns', 'timingDNS', 'timing-dns') +Tracker.alias(float, 'pdt', 'timingPageLoad', 'timing-page-load') +Tracker.alias(float, 'rrt', 'timingRedirect', 'timing-redirect') +Tracker.alias(safe_unicode, 'tcp', 'timingTCPConnect', 'timing-tcp-connect') +Tracker.alias(safe_unicode, 'srt', 'timingServerResponse', 'timing-server-response') + +# Custom dimensions and metrics +for i in range(0,200): + Tracker.alias(safe_unicode, 'cd{0}'.format(i), 'dimension{0}'.format(i)) + Tracker.alias(int, 'cm{0}'.format(i), 'metric{0}'.format(i)) + +# Enhanced Ecommerce +Tracker.alias(str, 'pa') # Product action +Tracker.alias(str, 'tcc') # Coupon code +Tracker.alias(unicode, 'pal') # Product action list +Tracker.alias(int, 'cos') # Checkout step +Tracker.alias(str, 'col') # Checkout step option + +Tracker.alias(str, 'promoa') # Promotion action + +for product_index in range(1, MAX_EC_PRODUCTS): + Tracker.alias(str, 'pr{0}id'.format(product_index)) # Product SKU + Tracker.alias(unicode, 'pr{0}nm'.format(product_index)) # Product name + Tracker.alias(unicode, 'pr{0}br'.format(product_index)) # Product brand + Tracker.alias(unicode, 'pr{0}ca'.format(product_index)) # Product category + Tracker.alias(unicode, 'pr{0}va'.format(product_index)) # Product variant + Tracker.alias(str, 'pr{0}pr'.format(product_index)) # Product price + Tracker.alias(int, 'pr{0}qt'.format(product_index)) # Product quantity + Tracker.alias(str, 'pr{0}cc'.format(product_index)) # Product coupon code + Tracker.alias(int, 'pr{0}ps'.format(product_index)) # Product position + + for custom_index in range(MAX_CUSTOM_DEFINITIONS): + Tracker.alias(str, 'pr{0}cd{1}'.format(product_index, custom_index)) # Product custom dimension + Tracker.alias(int, 'pr{0}cm{1}'.format(product_index, custom_index)) # Product custom metric + + for list_index in range(1, MAX_EC_LISTS): + Tracker.alias(str, 'il{0}pi{1}id'.format(list_index, product_index)) # Product impression SKU + Tracker.alias(unicode, 'il{0}pi{1}nm'.format(list_index, product_index)) # Product impression name + Tracker.alias(unicode, 'il{0}pi{1}br'.format(list_index, product_index)) # Product impression brand + Tracker.alias(unicode, 'il{0}pi{1}ca'.format(list_index, product_index)) # Product impression category + Tracker.alias(unicode, 'il{0}pi{1}va'.format(list_index, product_index)) # Product impression variant + Tracker.alias(int, 'il{0}pi{1}ps'.format(list_index, product_index)) # Product impression position + Tracker.alias(int, 'il{0}pi{1}pr'.format(list_index, product_index)) # Product impression price + + for custom_index in range(MAX_CUSTOM_DEFINITIONS): + Tracker.alias(str, 'il{0}pi{1}cd{2}'.format(list_index, product_index, custom_index)) # Product impression custom dimension + Tracker.alias(int, 'il{0}pi{1}cm{2}'.format(list_index, product_index, custom_index)) # Product impression custom metric + +for list_index in range(1, MAX_EC_LISTS): + Tracker.alias(unicode, 'il{0}nm'.format(list_index)) # Product impression list name + +for promotion_index in range(1, MAX_EC_PROMOTIONS): + Tracker.alias(str, 'promo{0}id'.format(promotion_index)) # Promotion ID + Tracker.alias(unicode, 'promo{0}nm'.format(promotion_index)) # Promotion name + Tracker.alias(str, 'promo{0}cr'.format(promotion_index)) # Promotion creative + Tracker.alias(str, 'promo{0}ps'.format(promotion_index)) # Promotion position + + +# Shortcut for creating trackers +def create(account, *args, **kwargs): + return Tracker(account, *args, **kwargs) + +# vim: set nowrap tabstop=4 shiftwidth=4 softtabstop=0 expandtab textwidth=0 filetype=python foldmethod=indent foldcolumn=4 diff --git a/resources/lib/UniversalAnalytics/__init__.py b/resources/lib/UniversalAnalytics/__init__.py new file mode 100644 index 0000000..9c0c110 --- /dev/null +++ b/resources/lib/UniversalAnalytics/__init__.py @@ -0,0 +1 @@ +import Tracker \ No newline at end of file diff --git a/resources/settings.xml b/resources/settings.xml index b504851..8fdc9b4 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -11,6 +11,8 @@ + + -- 2.30.2