lib/ruby_mint.rb in ruby_mint-0.1.1 vs lib/ruby_mint.rb in ruby_mint-0.2.0

- old
+ new

@@ -30,11 +30,15 @@ @token = nil end # Login retrieves a user token from Mint.com - def login + # + # @param use_token [String] Use an existing token + def login(use_token = nil) + @token = use_token and return if use_token + response = agent.get("https://wwws.mint.com/login.event?task=L") raise RubyMintError.new("Unable to GET Mint login page.") if response.code != "200" response = agent.post("https://wwws.mint.com/getUserPod.xevent", { "username" => @username }, JSON_HEADERS) raise RubyMintError.new("Unable to POST to getUserPod.") if response.code != "200" @@ -70,13 +74,13 @@ # Request that Mint.com refresh its account and transaction data # # @param sleep_time [Integer] Num of seconds to wait between calls to refreshing? when block is passed # @param block [Block] Code to execute upon completion of of refreshing - def initiate_account_refresh(sleep_time = 3, &block) + def initiate_account_refresh(sleep_time = 3) agent.post("https://wwws.mint.com/refreshFILogins.xevent", { "token" => @token }, JSON_HEADERS) - if block + if block_given? loop{ sleep sleep_time; break if !refreshing? } yield end end @@ -110,10 +114,12 @@ # Use token to get list of accounts results = agent.post("https://wwws.mint.com/bundledServiceController.xevent?legacy=false&token=#{@token}", account_query, JSON_HEADERS) raise RubyMintError.new("Unable to obtain account information. Response code: #{results.code}") if results.code != "200" + raise RubyMintError.new("Not logged in.") if results.body.include?('Session has expired.') + account_body = JSON.load(results.body) if !account_body || !account_body["response"] || !account_body["response"][@request_id.to_s] || !account_body["response"][@request_id.to_s]["response"] raise RubyMintError.new("Unable to obtain account information (no account information in response).") end @@ -122,22 +128,108 @@ # Get transactions from mint. They are returned as CSV and include ALL # the transactions available # # @return [String] CSV of all transactions - def transactions + def transactions_csv results = agent.get("https://wwws.mint.com/transactionDownload.event", JSON_HEADERS) - raise RubyMintError.new("Unable to obtain transations.") if results.code != "200" + raise RubyMintError.new("Unable to obtain transactions.") if results.code != "200" raise RubyMintError.new("Non-CSV content returned.") if !results.header["content-type"].include?("text/csv") results.body end + # Get transactions from mint. Returned as JSON. Paginate + # + # Options: + # include_pending [Boolean] default false + # search_term [String] default "" + # + # @param start_date [Time] get all transactions on or after this date + # @param end_date [Time] get all transactions up to and including this date + # @param options [Hash] options hash + # @returns [Array<Hash>] array of transactions + def transactions(start_date, end_date = Time.now, options = {}) + include_pending = options.fetch('include_pending', false) + search_term = options.fetch('search_term', '') + offset = 0 + results = [] + # Convert start and end dates + start_date = Time.local(start_date.year, start_date.month, start_date.day) + end_date = Time.local(end_date.year, end_date.month, end_date.day) + + loop do + next_page = transaction_page(offset, search_term) + break if next_page.empty? + + # Filter out pending transactions + if !include_pending + next_page.reject!{ |t| t['isPending'] } + end + + results.concat next_page + break if earliest_mint_date(next_page) < start_date + + offset += next_page.count + end + + # Filter by date + results.select do |t| + t['date'] >= start_date && t['date'] <= end_date + end + end + + private def agent @agent ||= Mechanize.new { |agent| agent.user_agent_alias = 'Linux Firefox' } + end + + # Get a single page of transaction data. Mint always returns 50 transactions per page. + def transaction_page(offset, search_term) + # Example query: https://wwws.mint.com/app/getJsonData.xevent?queryNew=&offset=0&filterType=cash&acctChanged=T&task=transactions&rnd=1436026512488 + base_url = "https://wwws.mint.com/app/getJsonData.xevent" + search_query = "?queryNew=#{URI.encode(search_term)}" + offset_query = "&offset=#{offset}" + transaction_query = "&filterType=cash&comparableType=8&task=transactions&rnd=#{random_number}" + + json_results = agent.get("#{base_url}#{search_query}#{offset_query}#{transaction_query}", JSON_HEADERS) + raise RubyMintError.new("Unable to obtain transactions.") if json_results.code != "200" + raise RubyMintError.new("Non-JSON content returned.") if !json_results.header["content-type"].include?("text/json") + + transform_transaction_times JSON.parse(json_results.body)['set'][0]['data'] + end + + # Mint returns transactions in two formats: "Mar 8" for this year, "10/08/14" for previous years (month/day/year). + # Transform dates into ruby time objects. + # + # NOTE: Mint only returns the date of the transaction, there is no time + # + # @param transactions [Array<Hash>] array of transactions + def transform_transaction_times(transactions) + transactions.map do |t| + t['date'] = (t['date'] =~ /\d+\/\d+\/\d+/ ? Time.strptime(t['date'], "%D") : Time.parse(t['date'])) + t + end + end + + # Get the earliest date from this set of transactions. Assumes they are in reverse order. + # + # @params transactions [Array<Hash>] + # @returns [Time] + def earliest_mint_date(transactions) + transactions.last['date'] + end + + # Queries require a 12-digit random number + def random_number + # They are starting with 14, so I won't break the mold. Get 10 more. + nums = (0..9).to_a + result = "14" + 10.times{ result << nums.sample.to_s } + result end end