|
6 | 6 | import pickle |
7 | 7 | import pprint |
8 | 8 | import urllib |
| 9 | +import optparse |
9 | 10 | import requests |
10 | 11 | import subprocess |
11 | 12 | import webbrowser |
|
14 | 15 | from pprint import pprint |
15 | 16 | from datetime import datetime |
16 | 17 |
|
17 | | -def get_client_auth(): |
18 | | - if os.path.isfile("consumer_oauth.json"): |
19 | | - with open("consumer_oauth.json", 'rb') as oauth_file: |
20 | | - consumer = json.load(oauth_file) |
21 | | - ckey = consumer['consumer_key'] |
22 | | - csecret = consumer['consumer_secret'] |
23 | | - else: |
24 | | - with open("consumer_oauth.json", 'wb') as oauth_file: |
25 | | - json.dump({'consumer_key':'YOUR KEY HERE', |
26 | | - 'consumer_secret':'YOUR SECRET HERE'}, oauth_file) |
27 | | - exit("go to https://secure.splitwise.com/oauth_clients to obtain your keys."+ |
28 | | - "place them in consumer_oauth.json") |
29 | | - return ckey, csecret |
30 | | - |
31 | | -def get_client(): |
32 | | - ckey, csecret = get_client_auth() |
33 | | - client = oauthlib.oauth1.Client(ckey, client_secret=csecret) |
34 | | - uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_request_token", |
35 | | - http_method='POST') |
36 | | - r = requests.post(uri, headers=headers, data=body) |
37 | | - resp = r.text.split('&') |
38 | | - oauth_token = resp[0].split('=')[1] |
39 | | - oauth_secret = resp[1].split('=')[1] |
40 | | - uri = "https://secure.splitwise.com/authorize?oauth_token=%s" % oauth_token |
41 | | - |
42 | | - webbrowser.open_new(uri) |
43 | | - |
44 | | - proc = subprocess.Popen(['python', 'server.py'], stdout=subprocess.PIPE, shell=True) |
45 | | - stdout, stderr = proc.communicate() |
46 | | - if stderr: |
47 | | - exit(stderr) |
48 | | - client = oauthlib.oauth1.Client(ckey, client_secret=csecret, |
49 | | - resource_owner_key=oauth_token, |
50 | | - resource_owner_secret=oauth_secret, |
51 | | - verifier=stdout.strip()) |
52 | | - |
53 | | - uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_access_token", |
54 | | - http_method='POST') |
55 | | - resp = requests.post(uri, headers=headers, data=body) |
56 | | - tokens = resp.text.split('&') |
57 | | - oauth_token = tokens[0].split('=')[1] |
58 | | - oauth_secret = tokens[1].split('=')[1] |
59 | | - client = oauthlib.oauth1.Client(ckey, client_secret=csecret, |
60 | | - resource_owner_key=oauth_token, |
61 | | - resource_owner_secret=oauth_secret, |
62 | | - verifier=stdout.strip()) |
63 | | - with open('oauth_client.pkl', 'wb') as pkl: |
64 | | - pickle.dump(client, pkl) |
65 | | - return client |
66 | | - |
67 | 18 | def split(total, num_people): |
68 | 19 | base = total * 100 // num_people / 100 |
69 | 20 | extra = total - num_people * base |
70 | 21 | assert base * num_people + extra == total, "InternalError:" + \ |
71 | 22 | " something doesnt add up here: %d * %d + %d != %d" %(base, num_people, extra, total) |
72 | 23 | return base, extra |
73 | 24 |
|
74 | | -def api_call(url, http_method, client): |
75 | | - uri, headers, body = client.sign(url, http_method=http_method) |
76 | | - resp = requests.request(http_method, uri, headers=headers, data=body) |
77 | | - return resp.json() |
78 | | - |
79 | | -def main(): |
80 | | - if os.path.isfile("oauth_client.pkl"): |
81 | | - with open('oauth_client.pkl', 'rb') as oauth_pkl: |
82 | | - client = pickle.load(oauth_pkl) |
83 | | - else: |
84 | | - client = get_client() |
85 | | - |
86 | | - resp = api_call("https://secure.splitwise.com/api/v3.0/get_current_user", 'GET', client) |
87 | | - my_id = resp['user']['id'] |
88 | | - |
89 | | - resp = api_call("https://secure.splitwise.com/api/v3.0/get_groups", 'GET', client) |
90 | | - num_found = 0 |
91 | | - gid = '' |
92 | | - members = {} |
93 | | - |
94 | | - for group in resp['groups']: |
95 | | - if group['name'].lower() == sys.argv[2].lower(): |
96 | | - gid = group['id'] |
97 | | - members = [m['id'] for m in group['members'] if m['id'] != my_id] |
98 | | - num_found += 1 |
99 | | - |
100 | | - if num_found > 1: |
101 | | - exit("More than 1 group found") |
102 | | - elif num_found < 1: |
103 | | - exit("No matching group") |
104 | | - elif len(members) < 1: |
105 | | - exit("No members in group") |
106 | | - |
107 | | - with open(sys.argv[1], 'rb') as csvfile: |
108 | | - reader = csv.reader(csvfile) |
109 | | - rows = [x for x in reader] |
110 | | - |
111 | | - |
112 | | - if os.path.isfile("csv_settings.pkl"): |
113 | | - with open('csv_settings.pkl', 'rb') as f: |
114 | | - date_col, amount_col, desc_col, has_title_row, local_currency, remember = pickle.load(f) |
115 | | - |
116 | | - else: |
117 | | - print "These are the first two rows of your csv" |
118 | | - print '\n'.join([str(t) for t in rows[0:2]]) |
119 | | - print 'Colnum numbers start at 0' |
120 | | - date_col = input("Which column has the date?") |
121 | | - amount_col = input("Which column has the amount?") |
122 | | - desc_col = input("Which column has the description?") |
123 | | - has_title_row = raw_input("Does first row have titles? [Y/n]").lower() != 'n' |
124 | | - local_currency = raw_input("What currency were these transactions made in?") |
125 | | - test = Money("1.00", local_currency) #pylint: disable=W0612 |
126 | | - remember = raw_input("Remember these settings? [Y/n]").lower() != 'n' |
127 | | - |
128 | | - if remember: |
129 | | - with open("csv_settings.pkl", "wb") as pkl: |
130 | | - csv_settings = (date_col, amount_col, desc_col, has_title_row, local_currency, remember) |
131 | | - pickle.dump(csv_settings, pkl) |
132 | | - |
133 | | - if has_title_row: |
134 | | - rows = rows[1:] |
135 | | - transactions = [{"date": datetime.strftime(datetime.strptime(r[date_col], "%m/%d/%y"), "%Y-%m-%dT%H:%M:%SZ"), |
136 | | - "amount": -1 * Money(r[amount_col], local_currency), |
137 | | - "desc": re.sub('\s+',' ', r[desc_col])} |
138 | | - for r in rows if float(r[amount_col]) < 0] |
139 | | - splits = [] |
140 | | - for t in transactions: |
141 | | - if raw_input("%s at %s $%s. Split? [y/N]" % (t['date'], t['desc'], t['amount'])).lower() == 'y': |
142 | | - splits.append(t) |
143 | | - |
| 25 | +class Splitwise: |
| 26 | + def __init__(self): |
| 27 | + if os.path.isfile("oauth_client.pkl"): |
| 28 | + with open('oauth_client.pkl', 'rb') as oauth_pkl: |
| 29 | + self.client = pickle.load(oauth_pkl) |
| 30 | + else: |
| 31 | + self.get_client() |
| 32 | + |
| 33 | + def get_client_auth(self): |
| 34 | + if os.path.isfile("consumer_oauth.json"): |
| 35 | + with open("consumer_oauth.json", 'rb') as oauth_file: |
| 36 | + consumer = json.load(oauth_file) |
| 37 | + ckey = consumer['consumer_key'] |
| 38 | + csecret = consumer['consumer_secret'] |
| 39 | + else: |
| 40 | + with open("consumer_oauth.json", 'wb') as oauth_file: |
| 41 | + json.dump({'consumer_key':'YOUR KEY HERE', |
| 42 | + 'consumer_secret':'YOUR SECRET HERE'}, oauth_file) |
| 43 | + exit("go to https://secure.splitwise.com/oauth_clients to obtain your keys."+ |
| 44 | + "place them in consumer_oauth.json") |
| 45 | + self.ckey = ckey |
| 46 | + self.csecret = csecret |
| 47 | + |
| 48 | + def get_client(self): |
| 49 | + self.get_client_auth() |
| 50 | + client = oauthlib.oauth1.Client(self.ckey, client_secret=self.csecret) |
| 51 | + uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_request_token", |
| 52 | + http_method='POST') |
| 53 | + r = requests.post(uri, headers=headers, data=body) |
| 54 | + resp = r.text.split('&') |
| 55 | + oauth_token = resp[0].split('=')[1] |
| 56 | + oauth_secret = resp[1].split('=')[1] |
| 57 | + uri = "https://secure.splitwise.com/authorize?oauth_token=%s" % oauth_token |
| 58 | + |
| 59 | + webbrowser.open_new(uri) |
| 60 | + |
| 61 | + proc = subprocess.Popen(['python', 'server.py'], stdout=subprocess.PIPE) |
| 62 | + stdout, stderr = proc.communicate() |
| 63 | + if stderr: |
| 64 | + exit(stderr) |
| 65 | + client = oauthlib.oauth1.Client(self.ckey, client_secret=self.csecret, |
| 66 | + resource_owner_key=oauth_token, |
| 67 | + resource_owner_secret=oauth_secret, |
| 68 | + verifier=stdout.strip()) |
| 69 | + |
| 70 | + uri, headers, body = client.sign("https://secure.splitwise.com/api/v3.0/get_access_token", |
| 71 | + http_method='POST') |
| 72 | + resp = requests.post(uri, headers=headers, data=body) |
| 73 | + tokens = resp.text.split('&') |
| 74 | + oauth_token = tokens[0].split('=')[1] |
| 75 | + oauth_secret = tokens[1].split('=')[1] |
| 76 | + client = oauthlib.oauth1.Client(self.ckey, client_secret=self.csecret, |
| 77 | + resource_owner_key=oauth_token, |
| 78 | + resource_owner_secret=oauth_secret, |
| 79 | + verifier=stdout.strip()) |
| 80 | + with open('oauth_client.pkl', 'wb') as pkl: |
| 81 | + pickle.dump(client, pkl) |
| 82 | + self.client = client |
| 83 | + |
| 84 | + def api_call(self, url, http_method): |
| 85 | + uri, headers, body = self.client.sign(url, http_method=http_method) |
| 86 | + resp = requests.request(http_method, uri, headers=headers, data=body) |
| 87 | + return resp.json() |
| 88 | + |
| 89 | + def get_id(self): |
| 90 | + if not hasattr(self, "my_id"): |
| 91 | + resp = self.api_call("https://secure.splitwise.com/api/v3.0/get_current_user", 'GET') |
| 92 | + self.my_id = resp['user']['id'] |
| 93 | + return self.my_id |
| 94 | + |
| 95 | + def get_groups(self): |
| 96 | + resp = self.api_call("https://secure.splitwise.com/api/v3.0/get_groups", 'GET') |
| 97 | + return resp['groups'] |
| 98 | + |
| 99 | + def post_expense(self, uri): |
| 100 | + resp = self.api_call(uri, 'POST') |
| 101 | + if resp["errors"]: |
| 102 | + print "URI:" |
| 103 | + print uri |
| 104 | + pprint(resp) |
| 105 | + else: |
| 106 | + sys.stdout.write(".") |
| 107 | + sys.stdout.flush() |
144 | 108 |
|
145 | | - print "Uploading %d splits" % len(splits) |
146 | | - one_cent = Money("0.01", local_currency) |
147 | | - for s in splits: |
148 | | - num_people = len(members) + 1 |
| 109 | +class SplitGenerator(): |
| 110 | + def __init__(self, options, args, api): |
| 111 | + self.api = api |
| 112 | + csv_file = args[0] |
| 113 | + group_name = args[1] |
| 114 | + with open(csv_file, 'rb') as csvfile: |
| 115 | + reader = csv.reader(csvfile) |
| 116 | + self.rows = [x for x in reader] |
| 117 | + |
| 118 | + |
| 119 | + if os.path.isfile("csv_settings.pkl"): |
| 120 | + with open('csv_settings.pkl', 'rb') as f: |
| 121 | + self = pickle.load(f) |
| 122 | + |
| 123 | + else: |
| 124 | + print "These are the first two rows of your csv" |
| 125 | + print '\n'.join([str(t) for t in self.rows[0:2]]) |
| 126 | + print 'Colnum numbers start at 0' |
| 127 | + self.date_col = input("Which column has the date?") |
| 128 | + self.amount_col = input("Which column has the amount?") |
| 129 | + self.desc_col = input("Which column has the description?") |
| 130 | + self.has_title_row = raw_input("Does first row have titles? [Y/n]").lower() != 'n' |
| 131 | + self.local_currency = raw_input("What currency were these transactions made in?").upper() |
| 132 | + self.test = Money("1.00", self.local_currency) #pylint: disable=W0612 |
| 133 | + self.remember = raw_input("Remember these settings? [Y/n]").lower() != 'n' |
| 134 | + |
| 135 | + if self.remember: |
| 136 | + with open("csv_settings.pkl", "wb") as pkl: |
| 137 | + pickle.dump(self, pkl) |
| 138 | + |
| 139 | + if self.has_title_row: |
| 140 | + self.rows = self.rows[1:] |
| 141 | + |
| 142 | + self.make_transactions() |
| 143 | + self.get_group(group_name) |
| 144 | + self.splits = [] |
| 145 | + self.ask_for_splits() |
| 146 | + |
| 147 | + def make_transactions(self): |
| 148 | + self.transactions = [{"date": datetime.strftime(datetime.strptime(r[self.date_col], "%m/%d/%y"), "%Y-%m-%dT%H:%M:%SZ"), |
| 149 | + "amount": -1 * Money(r[self.amount_col], self.local_currency), |
| 150 | + "desc": re.sub('\s+',' ', r[self.desc_col])} |
| 151 | + for r in self.rows if float(r[self.amount_col]) < 0] |
| 152 | + |
| 153 | + def get_group(self, name): |
| 154 | + num_found = 0 |
| 155 | + gid = '' |
| 156 | + members = {} |
| 157 | + groups = self.api.get_groups() |
| 158 | + for group in groups: |
| 159 | + if group['name'].lower() == name.lower(): |
| 160 | + gid = group['id'] |
| 161 | + members = [m['id'] for m in group['members'] if m['id'] != self.api.get_id()] |
| 162 | + num_found += 1 |
| 163 | + |
| 164 | + if num_found > 1: |
| 165 | + exit("More than 1 group found") |
| 166 | + elif num_found < 1: |
| 167 | + exit("No matching group") |
| 168 | + elif len(members) < 1: |
| 169 | + exit("No members in group") |
| 170 | + |
| 171 | + self.members = members |
| 172 | + self.gid = gid |
| 173 | + |
| 174 | + def ask_for_splits(self): |
| 175 | + for t in self.transactions: |
| 176 | + if raw_input("%s at %s $%s. Split? [y/N]" % (t['date'], t['desc'], t['amount'])).lower() == 'y': |
| 177 | + self.splits.append(t) |
| 178 | + |
| 179 | + def __getitem__(self, index): |
| 180 | + s = self.splits[index] |
| 181 | + one_cent = Money("0.01", self.local_currency) |
| 182 | + num_people = len(self.members) + 1 |
149 | 183 | base, extra = split(s['amount'], num_people) |
150 | 184 | params = { |
151 | 185 | "payment": 'false', |
152 | 186 | "cost": s["amount"].amount, |
153 | 187 | "description": s["desc"], |
154 | 188 | "date": s["date"], |
155 | | - "group_id": gid, |
156 | | - "currency_code": local_currency, |
157 | | - "users__0__user_id": my_id, |
| 189 | + "group_id": self.gid, |
| 190 | + "currency_code": self.local_currency, |
| 191 | + "users__0__user_id": self.api.get_id(), |
158 | 192 | "users__0__paid_share": s["amount"].amount, |
159 | 193 | "users__0__owed_share": base.amount, |
160 | 194 | } |
161 | | - for i in range(len(members)): |
162 | | - params['users__%s__user_id' % (i+1)] = members[i] |
| 195 | + for i in range(len(self.members)): |
| 196 | + params['users__%s__user_id' % (i+1)] = sef.members[i] |
163 | 197 | params['users__%s__paid_share' % (i+1)] = 0 |
164 | 198 | params['users__%s__owed_share' % (i+1)] = (base + one_cent).amount if extra else base.amount |
165 | 199 | extra -= one_cent |
166 | 200 | paramsStr = urllib.urlencode(params) |
167 | | - uri = "https://secure.splitwise.com/api/v3.0/create_expense?%s" % (paramsStr) |
168 | | - resp = api_call(uri, 'POST', client) |
169 | | - if resp["errors"]: |
170 | | - print "URI:" |
171 | | - print uri |
172 | | - pprint(resp) |
173 | | - else: |
174 | | - sys.stdout.write(".") |
175 | | - sys.stdout.flush() |
| 201 | + return "https://secure.splitwise.com/api/v3.0/create_expense?%s" % (paramsStr) |
| 202 | + |
| 203 | + |
| 204 | +def main(): |
| 205 | + parser = optparse.OptionParser() |
| 206 | + parser.add_option('-v', '--verbose', default=False, dest='verbose') |
| 207 | + options, args = parser.parse_args() |
| 208 | + splitwise = Splitwise() |
| 209 | + split_gen = SplitGenerator(options, args, splitwise) |
| 210 | + print "Uploading splits" |
| 211 | + for uri in split_gen: |
| 212 | + splitwise.post_expense(uri) |
176 | 213 | sys.stdout.write("\n") |
177 | 214 | sys.stdout.flush() |
178 | 215 |
|
|
0 commit comments