Scripts

Reporting For Duty

Who likes getting a daily report on the number of downloads they’ve had from iTunes? Me too! Thats why I recently wrote a python script to download those numbers and email me a copy of the results, every day! Woot woot! This tutorial is going to show you how to do it.

iTunesConnect and Reporter

If you go into your iTunesConnect account, and tap on the Sales and Trends icon, you will see the sales your applications have had over the last week or so. You can also see how many downloads were done on a given day. While this is all good, I don’t like to need to log into iTunesConnect every day to view these numbers.  I would really just like them to be sent to me… (Lazy! right?).  Luckily for me (and us, if you are still reading this post), Apple has provided a way for us to automate this process!

The first thing we need to do is to download their Reporter files (here). This will download a zip file, containing a folder called Reporter.  This folder contains two files, Reporter.jar, and Reporter.properties.  These files allow us to access and download a report from iTunesConnect.  So we need to move this folder to wherever you would like these files to be saved. I simply put the folder in my documents folder… Seemed logical…

Great! Now, the Reporter.jar file requires you to have Java 1.6 or later installed.  So make sure that you are up to date on that front. (If you need to download Java, you can do so here). With that bit of house keeping done, we can start having fun!

If you open up your Reporter.properties it should look something like this:

AccessToken=

Mode=Normal

SalesUrl=https://reportingitc-reporter.apple.com/reportservice/sales/v1
FinanceUrl=https://reportingitc-reporter.apple.com/reportservice/finance/v1

So we see that we need to provide an AccessToken… The rest doesn’t really concern us. But we do need to find that access token.

There are two ways to do this. Perhaps the easiest is via iTunesConnect. To do so, follow these steps:

1) From the homepage, click Sales and Trends.

2) In the upper-left corner, click the pop-up menu and choose Reports.

3) In the upper-right corner, click on the tooltip next to About Reports.

4) Click Generate Access Token.

5) Copy the access token to your clipboard.

6) Paste the access token to the RHS of the equals:
       AccessToken=#YourTokenHere#

Much of the above instructions comes form Apples help document on using reporter. You can read it here for more information (like how to get your token via command line).

At this point, you should be able to start using Reporter. The first thing we need to do is to get the account number your want to download your numbers from.  To do so, open your terminal app of choice, navigate to the Reporter folder, and run the following command:

java -jar Reporter.jar p=Reporter.properties Sales.getAccounts

This should print out a list of companies, or iTunesConnect accounts your have access to, followed by a number:

Company1 LLC, 123456
Company2 LLC, 234567
...

That account number is what we need!

Next we need to get the vendor number we need. Run the following command in terminal:

java -jar Reporter.jar p=Reporter.properties a=YOUR_ACCOUNT_NUMBER Sales.getVendors

This should print out a list of vender numbers. Now, for me it only showed one number, so I’m not really sure what the difference each vendor number makes… So, if you had more than one vendor number, you may need alter the script to iterate over the vendor numbers… But now we have everything we need to write our python script!

Python Script

Open up your favorite python IDE and create a new file called Reporter.py.  Copy the following code into this new file and change out the base_file_path, account_number, and vendor_number values to the values you collected above:

# set current directory to
import os
base_file_path = "Path To your Reporter file"
os.system("cd " + base_file_path)

# get a string version of yesterdays date
from datetime import date, timedelta

yesterday = date.today() - timedelta(1)
yesterday_str = yesterday.strftime("%Y%m%d")

# request report from itc
import subprocess
account_number = "Your Account Number"
vendor_number = "Your Vender Number"
output = subprocess.check_output("java -jar Reporter.jar p=Reporter.properties a=" + account_number +
                                 " Sales.getReport " + vendor_number + ", Sales, Summary, Daily, " + yesterday_str,
                                 shell=True)

These steps are all pretty clear, we set the current directory to the directory containing the Reporter.jar and Reporter.properties files. Next, we calculate yesterdays date and transform that date to a string. Finally we request from iTunesConnect a report containing the sales information for yesterday.

If you run this code, you should get a .gz file downloaded into your Reporter folder.  Double click this and you will get a .txt file.  This .txt file contains all the sales information from yesterday. Looking over this information, you might notice multiple lines for the same app. Not quite what we wanted (though I do like summing things up in my head…). So our next step is to parse this document and count the different types if sales each application had. You see, the totals listed are divided by sales type (download, redownload, update, etc) as well as the country it was sold in. As I was only interested in the type of sale, not the country, the following script combines by country, but separates by type. Lets have a look, shall we? Copy the following code below your last line in your Reporter.py file:

# open downloaded itc report file and unzip it.
import gzip
zip_filename = str(output.decode('UTF-8').split()[-1])
zip_ref = gzip.open(base_file_path + zip_filename, 'r')
file_contents = zip_ref.read().decode("UTF-8").split('\n')
zip_ref.close()

# parse contents
# get header row, find desired indices
headers = file_contents[0].split('\t')
title_index = headers.index('Title')
units_index = headers.index('Units')
download_type_index = headers.index('Product Type Identifier')

# iterate through data rows, getting desired data.
new_download_types = ["1", "1E", "1EP", "1EU", "1F", "1T", "F1"]
redownload_types = ["3", "3F", "3T", "F3"]
update_types = ["7", "7F", "F7"]
iap_types = ["IA1-M", "IA1", "IA9", "IA9-M", "IAC", "IAC-M", "IAY", "IAY-M", "FI1"]

# drop header row
file_contents = file_contents[1:]

So, the first thing we do, is unzip the file and open it.  Next, we find the indices for the columns we are interested in.  In our case we are only interested in the Title (product name), Units (number of sales), and Product Type Identifier. The Product Type Identifier is an identification code that indicates what type of sale happened.  As the next section shows, for example, 1, 1E, 1EP, 1EU, 1F, 1T, and F1 all indicate a new download.  Each number indicates a different type of new download, such as Mac, iPad, iPhone, Universal, etc. But again, I’m only interested in a total, so I am throwing them into the same basket. Note that iap_types stands for in-app-purchas types.  Next, we drop the header file, since we are done with it.

Awesome! Now paste the following code below the above code (Warning! this should be simplified… it was late and I wanted to get this over with, so I was a little too… copy-paste happy…), :

new_downloads = dict()
redownloads = dict()
updates = dict()
iaps = dict()
for row in file_contents:
    row_array = row.split("\t")
    if len(row_array) < max(download_type_index, title_index, units_index):
        break

    product_title = row_array[title_index]
    units = int(row_array[units_index])

    download_type = row_array[download_type_index]

    # Downloads
    if download_type in new_download_types:
        if product_title in new_downloads:
            previous_total = new_downloads[product_title]
            new_downloads[product_title] = previous_total + units
        else:
            new_downloads[product_title] = units

    # redownloads
    if download_type in redownload_types:
        if product_title in redownloads:
            previous_total = redownloads[product_title]
            redownloads[product_title] = previous_total + units
        else:
            redownloads[product_title] = units

    # Downloads
    if download_type in update_types:
        if product_title in new_downloads:
            previous_total = updates[product_title]
            updates[product_title] = previous_total + units
        else:
            updates[product_title] = units

    # Downloads
    if download_type in new_download_types:

        if product_title in iap_types:
            previous_total = iaps[product_title]
            iaps[product_title] = previous_total + units
        else:
            iaps[product_title] = units

Basically, we are iterating over each row of the downloaded report and checking its type and adding the unit to the total being stored in the dictionaries created at the top of the code snippet… Thus we will have a dictionary containing the ProductName as the keys, and the total sales for that type (new downloads, updates, etc) as the value.

Tubular, now we have all the information condensed in the format we want.  Next we need to email it to ourselves. First we create the message text in HTML from the dictionaries we just created.  Again, I was lazy and simply copy and pasted my way through this:

# create email
message = ''  # "<strong>New Downloads</strong><b />"
message += '<p><table style="width:100%"><caption>New Downloads</caption><tr><th>Name</th><th>Units</th></tr>'
for key, value in new_downloads.items():
    message += '<tr><td>' + key + "</td><td>" + str(value) + '</td></tr>'

message += "</table></p><b /><b />"

# message += "<strong>New In App Purchases</strong><b />"
message += '<p><table style="width:100%"><caption>New In App Purchases</caption><tr><th>Name</th><th>Units</th></tr>'
for key, value in iaps.items():
    message += '<tr><td>' + key + "</td><td>" + str(value) + '</td></tr>'

message += "</table></p><b /><b />"

# message += "<strong>Updates</strong><b />"
message += '<p><table style="width:100%"><caption>Updates</caption><tr><th>Name</th><th>Units</th></tr>'
for key, value in updates.items():
    message += '<tr><td>' + key + "</td><td>" + str(value) + '</td></tr>'

message += "</table></p><b /><b />"

# message += "<strong>Redownloads</strong><b />"
message += '<p><table style="width:100%"><caption>Redownloads</caption><tr><th>Name</th><th>Units</th></tr>'
for key, value in redownloads.items():
    message += '<tr><td>' + key + "</td><td>" + str(value) + '</td></tr>'

message += "</table></p><b /><b />"

Now that we have the message text created, lets create the email:

# skipped your comments for readability
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

me = "YOUR EMAIL"
my_password = r"YOUR EMAIL PASSWORD"
you = "YOUR EMAIL... AGAIN"

msg = MIMEMultipart('alternative')
msg['Subject'] = "Yesterday's Downloads!"
msg['From'] = me
msg['To'] = you

html = '<html><head><style>table { font-family: arial, sans-serif; border-collapse: collapse; width: 100%;}' \
       'td, th {border: 1px solid #dddddd;text-align: left;padding: 8px;}' \
       'tr:nth-child(even) {background-color: #dddddd;}' \
       '</style></head><body>' + message + '</body></html>'
part2 = MIMEText(html, 'html')

msg.attach(part2)

# Send the message via gmail's regular server, over SSL - passwords are being sent, afterall
s = smtplib.SMTP_SSL('smtp.gmail.com', 465)
# uncomment if interested in the actual smtp conversation
# s.set_debuglevel(1)
# do the smtp auth; sends ehlo if it hasn't been sent already
s.login(me, my_password)

s.sendmail(me, you, msg.as_string())
s.quit()

First, this is configured to send an email via a gmail account.  It may differ depending on your client.  Next, I got this after exploring several different SO answers on sending emails from python, but I can’t seem to find it again. (If you know where this is, comment and I’ll link it…).

OK, now that I’ve said that, if you add the info that you need to (email and password info) and run the code you will probably get an error indicating that google will not allow the request. You will probably also get an email from google stating that they blocked a log-in.  To get it to work, you need to click on the link in the email to allow less-secure apps to log into your account.

Once you’ve done that, you should be able to run the code and get an email with all your numbers in a pretty HTML table! Woot woot!

Fun, huh? The only thing left to do, is to automate this script so that it runs every day. I used an app called Plisterine. Simply download and install this application.  Then, to run our python script, set it to the file selected in the Application/script to launch section, and configure the rest of the options to your liking.  I have my launching on a schedule, every day at 11:00 a.m. Hit continue, follow the prompts and, you have got daily download emails coming your way!

TL;DR
  1. Download and install reporter.
  2. Download Reporter.py file, and move into your Reporter folder.
  3. Fill out the missing information in the Reporter.py file.
  4. (Optional) Automate the running of the Reporter.py file using Plisterine.
Sources

http://help.apple.com/itc/contentreporterguide/en.lproj/static.html#apda86f89da5

http://www.launchd.info

Frameworks

First Contact

Soooooo…. September went quick…. Sorry about that…. But todays post is going to be great! Will it be as great as the titles namesake, perhaps not. But great none the less.

A number of years ago I wrote a contact app for a client.  While the app has come and gone, the lessons it taught burn brightly in my memory! You see, at the time, there was no nifty Contacts Framework.  No, there was the good o’l ABAddressBook framework; a low level framework with no ARC.  So when the contacts framework was release, in 2015, I did a little jig to celebrate the improvement!

Using the Framework

Getting Contacts

First off, to use the framework, you will need to update your app’s info.plist by adding the Privacy – Contacts Usage Description key-value par.  You can do this programmatically (below) or via the UI.

<key>NSContactsUsageDescription</key>
<string>Why You Want To Access the Contacts!</string>

The message is what is displayed when a user is alerted that your app would like to access their contacts. With that done, your app will be allowed to interact with the Contacts framework!

Next, lets get the contacts.  This is done via a query on the contacts store:

// 1) Get Store instance
let store = CNContactStore()
// 2) Create list of what information we would like to access from each contact
let keys:[CNKeyDescriptor] = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactThumbnailImageDataKey as CNKeyDescriptor]

// 3) Get all the containers
var allContainers: [CNContainer] = []
let storePredicate:NSPredicate? = nil /// DOES NOT SUPPORT GENERAL PREDICATES!!!
do {
    allContainers = try store.containers(matching: storePredicate)
} catch {
    print("Error fetching containers")
}

// 4) Itterate over containers, fetching the contacts in each container
var results: [CNContact] = []
for container in allContainers {
    
    let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier) /// DOES NOT SUPPORT GENERAL PREDICATES!!!
    do {
        let containerResults = try store.unifiedContacts(matching: fetchPredicate, keysToFetch: keys)
        results.append(contentsOf: containerResults)
    } catch {
        print("Error fetching results for container")
    }
}
// Outline from: https://stackoverflow.com/a/34095632/877032

Most of the steps should be pretty clear. However, lets highlight a few things.  First, note step 2: creating the list of attributes we plan to access on the contact. This is a critical step. If we do not tell the store that we want to access some portion of a contacts information, that information will be withheld.  The result is that if you try an access that information, not only is it nil, but you get an error thrown! So be sure to ask for what you need, before you try to access it.  Alternatively, there are checks you can perform before accessing a contacts attribute to see if its available (see the isKeyAvailable function).  So you can also use those to provide safety.

Step 3 is necessary only if you want ALL the contacts.  Otherwise, if you simply skip to step 4, you will only get the contacts in the default contact container. (Note: there is a contact container for each source of contacts on your phone. EG iCloud, facebook, gmail, etc)

Another important thing to note here is that general predicates are not permissible.  For instance, you can not set the predicate to filter out everyone whose name doesn’t start with the letter b. In fact we are limited to just 4 predicates:

CNContact.predicateForContacts(matchingName:<#String#>)
CNContact.predicateForContacts(withIdentifiers:[<#String#>])
CNContact.predicateForContactsInGroup(withIdentifier:<#String#>)
CNContact.predicateForContactsInContainer(withIdentifier:<#String#>)

It should be noted that the matchingNames predicate only returns whole word matches: EG: if you had a contact named Jonny Appleseed, searching for ‘Apple’ would not return anything, but ‘Appleseed’ would.

A final note to the above code is that the fetches are done synchronously! To keep your UI responsive it is recommended that you perform these actions on a background thread and update the UI on the main thread once they have completed.

Updating a Contact

Updating a contact is relatively straightforward:

// Create mutable copy
let updatedContact = contact.mutableCopy() as! CNMutableContact

// Update
let newEmail = CNLabeledValue(label: CNLabelHome,
                              value: "john@example.com" as NSString)
updatedContact.emailAddresses.append(newEmail)

// Save
let saveRequest = CNSaveRequest()
saveRequest.update(updatedContact)

let store = CNContactStore()
do {
    try store.execute(saveRequest)
} catch {
    print("store save failed!")
}

The only thing to note is that this should be done on a background thread.

Creating a New Contact

This step is really quite easy! Instead of creating a mutable copy of a contact, you just create a CNMutableContact directly! After that you are good to go! Woot woot! The only thing different in the save is that you need to designate what container (remember those) you would like the object to be saved in.

// Create new contact
let mike = CNMutableContact()
// Set contacts info
mike.givenName = "Mike"
mike.familyName = "Wazowski"
// ...

// Save the contact
let saveRequest = CNSaveRequest()
// Set the container
saveRequest.add(mike, toContainerWithIdentifier: nil)
do{
    let store = CNContactStore()
    try store.execute(saveRequest)
} catch{
    print("Dang! Saving new contact failed!")
}

As shown above, if the toContainerWithIdentifier is left nil, the contact will be saved in the default container.

TL;DR

First, don’t forget to add the Permission Request message to the info.plist file.

// Get all contacts:
DispatchQueue.global(qos: .default).async {
    // 1) Get Store instance
    let store = CNContactStore()
    // 2) Create list of what information we would like to access from each contact
    let keys:[CNKeyDescriptor] = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactThumbnailImageDataKey as CNKeyDescriptor]
    
    // 3) Get all the containers
    var allContainers: [CNContainer] = []
    let storePredicate:NSPredicate? = nil /// DOES NOT SUPPORT GENERAL PREDICATES!!!
    do {
        allContainers = try store.containers(matching: storePredicate)
    } catch {
        print("Error fetching containers")
    }
    
    // 4) Itterate over containers, fetching the contacts in each container
    var results: [CNContact] = []
    for container in allContainers {
        let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier) /// DOES NOT SUPPORT GENERAL PREDICATES!!!
        do {
            let containerResults = try store.unifiedContacts(matching: fetchPredicate, keysToFetch: keys)
            results.append(contentsOf: containerResults)
        } catch {
            print("Error fetching results for container")
        }
    }    
}

// Update Contact
// Create mutable copy
let updatedContact = contact.mutableCopy() as! CNMutableContact
// let newContact = CNMutableContact() // Use this to create new contact
// Update info
let newEmail = CNLabeledValue(label: CNLabelHome,
                              value: "john@example.com" as NSString)
updatedContact.emailAddresses.append(newEmail)

// Save Updated Contact
let saveRequest = CNSaveRequest()
// Set the container (for new contacts
// saveRequest.add(newContact, toContainerWithIdentifier: nil)
do{
    let store = CNContactStore()
    try store.execute(saveRequest)
} catch{
    print("Dang! Saving contact failed!")
}

 

References

https://developer.apple.com/documentation/contacts

https://developer.apple.com/videos/play/wwdc2015/223/

 https://useyourloaf.com/blog/privacy-settings-in-ios-10/

 

Swift

Generally Speaking…

So… I’ve been reading this great book, Introduction to Algorithms, to brush up on my mad (OK as a non CS major, not so mad, hence the book) algorithm skills.  As I’ve been reading and implementing various sorting algorithms it struck me that it would be great to be able to sort more than just the type I’ve written the code for.  For example, lets do quick sort for Ints (drum roll please):

/// From the sudo code in the Introduction To Algorithms

func quickSort(array:inout [Int], p:Int, r:Int){
    
    func partition(array:inout [Int], p:Int, r:Int)->Int{
        let x = array[r]
        var i = p - 1
        for j in p...r-1{
            if array[j] <= x{
                i = i + 1
                (array[i], array[j]) = (array[j], array[i])
            }
        }
        (array[i+1], array[r]) = (array[r], array[i+1])
        return i+1
    }
    
    if p < r {
        let q = partition(array: &array, p: p, r: r)
        quickSort(array: &array, p: p, r: q-1)
        quickSort(array: &array, p: q+1, r: r)
    }
}

var array = [-1,4,6,-2,6,3]
quickSort(array: &array, p: 0, r: array.count-1)
print(array)// Prints [-2,-1,3,4,6,6]

Great! But if I simply change the -1 to -1.0 (making it a double) this code doesn’t work.  Now I could change the function method to sort Doubles instead of Ints, or copy and past this code to sort Doubles and Ints, but both of those are terrible ideas.  Solution? Lets make our function a bit more generic, with Generics (Yay)!

So, using generics in Swift is a neat (and usually easy) way of expanding your code and making it more flexible. To do this we place a <T> after our function name, and replace any [Int] with [T]. What the brackets (‘<‘ ‘>’) do, is tell the Swift compiler that T is a type placeholder, and not to look for an actual type called T. Here is the implementation:

func quickSort<T>(array:inout [T], p:Int, r:Int){
    
    func partition(array:inout [T], p:Int, r:Int)->Int{
....
}

Just a few character changes and poof! This code works! Except it doesn’t… If you’ve been coding along with this post, you’ll notice that you get an error at this point, something about binary operands not being able to be applied to T.

Why not… Oh, I got it, we are comparing this (array[j] <= array[r] ) but T can be ANYTHING! If T is an Int, Double, even String or Char we are golden, but what if it is a dictionary… How do we know if one dictionary is less than another?  Hmmm… OK, so we need to restrict T so that its not anything, but just things that fit within our problem scope.  Thinking over our options, we realize that if we restrict T to types conforming to the Comparable protocol we keep what we want, and toss what we don’t! Our new definition becomes:

func quickSort<T: Comparable>(array:inout [T], p:Int, r:Int){
   ...
}

Thats it! Now you can sort anything that conforms to Comparable!  While we’ve restricted T to conform to a protocol, you can also restrict generic parameters to be of certain class types as well. So thats cool.

The above example is just one of many ways for using generics in Swift. Examples on how to use others, (such as associated types, generic where clauses, etc) can be found in the Swift documentation, which, as always, is best to read!

TL;DR

Use generics to increase the usability and flexibility of your code!

// For functions:
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}
Sources

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html

 

Swift Generics Tutorial: Getting Started