Skip to content

Instantly share code, notes, and snippets.

@frosty
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save frosty/b6d1615dab5544fc22b0 to your computer and use it in GitHub Desktop.
Save frosty/b6d1615dab5544fc22b0 to your computer and use it in GitHub Desktop.
iTunes Receipt Email Parser
class ReceiptParser
attr_reader :counts, :prices
def initialize(currency_symbol)
if (currency_symbol == "") then currency_symbol = "£" end
@currency_symbol = currency_symbol
@all_items = []
@counts = { :ios_apps => 0, :iaps => 0 }
@prices = { :ios_apps => 0, :iaps => 0 }
end
def parse_email(file)
lines = slice_out_relevant_section(file)
if (lines) then
lines.map! { |line|
line.gsub!(/\Fr=\r\n/, "Free")
line.gsub!(/\=\r\n/, "")
line.gsub!(/\=C2=A3/, @currency_symbol)
line.gsub!(/\=A3=\r\n/, @currency_symbol)
line.gsub!(/\=A3/, @currency_symbol)
line
}
new_lines = []
lines.each_with_index do |line, index|
if (line.end_with? @currency_symbol ) then
new_lines << (line + lines[index+1])
end
if (line =~ / #{@currency_symbol}(\.|\d)+\r\n/) then
new_lines << (lines[index-1] + line)
end
if (line.end_with? "Free") then
new_lines << line
end
end
new_lines.select! { |line|
(line.include?("Seller:") || line.include?("App"))
}
new_lines.reject! { |line|
line.include?("Mac App") # Filter out all Mac Apps
}
# Clean up the data
new_lines.map! { |line|
line.gsub!(/^\d{1,2}\s+/, "")
line.gsub!(/^Q\d{4}\s+/, "")
line.gsub!(/\ {3,}/, "\t")
line.gsub!(/, Seller: /, "\t")
if (line.count("\t") == 2) then
line.gsub!(/\tFree$/, "\t\tFree")
line.gsub!(/\t#{@currency_symbol}/, "\t\t#{@currency_symbol}")
end
line
}
new_lines
end
end
def slice_out_relevant_section(file)
lines = File.readlines(file)
item_index = lines.index { |line| line =~ /^Item/ }
if (!item_index) then return end
lines.slice!(0, item_index+2)
line_index = lines.index { |line| line !~ /^-----------------------------------------------/ }
lines.slice!(0, line_index+1)
next_line_index = lines.index { |line| line =~ /^-----------------------------------------------/ }
lines.slice!(next_line_index, lines.size - next_line_index)
return lines
end
def price_for_part(part)
return 0 if (part == "")
return part.gsub(/#{@currency_symbol}/, "").to_f
end
def parse
Dir.glob("**/*.eml") do |file|
email_items = parse_email(file)
@all_items += email_items || []
end
@all_items.each do |item|
parts = item.split("\t")
case parts[2]
when ""
@counts[:ios_apps] += 1
@prices[:ios_apps] += price_for_part(parts[3])
when "In-App Pu"
@counts[:iaps] += 1
@prices[:iaps] += price_for_part(parts[3])
when "In App Pu"
@counts[:iaps] += 1
@prices[:iaps] += price_for_part(parts[3])
when "App"
@counts[:ios_apps] += 1
@prices[:ios_apps] += price_for_part(parts[3])
when "iOS App"
@counts[:ios_apps] += 1
@prices[:ios_apps] += price_for_part(parts[3])
end
end
File.open("App.tsv", "w") do |file|
@all_items.each do |item|
file.write item
end
end
end
def price_for_type(item_type)
format_price(@prices[item_type])
end
def format_price(price)
"#{@currency_symbol}#{price.round(2)}"
end
end
currency_symbol = [(print 'What currency symbol do you use? (default £) '), gets.rstrip][1]
parser = ReceiptParser.new(currency_symbol)
parser.parse
puts
puts "iOS Apps: #{parser.counts[:ios_apps]} (#{parser.price_for_type(:ios_apps)})"
puts "In-App Purchases: #{parser.counts[:iaps]} (#{parser.price_for_type(:iaps)})"
puts
puts "Total Purchases: #{parser.counts.values.inject(:+)} (#{parser.format_price(parser.prices.values.inject(:+))})"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment