-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
153 lines (139 loc) · 6.85 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
'use strict';
let bluebird = require('bluebird');
let request = bluebird.promisify(require('request'));
request = request.defaults({ jar: true });
let _ = require('lodash');
let utils = require('./lib/utils.js');
const RV_TOKEN_REGEX = new RegExp('value="([a-zA-Z0-9/+=]+)"');
const USER_AGENT = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36';
function MenigaClient() {
this.baseUrl = 'https://www.meniga.is';
this.requestVerificationToken = null;
}
MenigaClient.prototype.auth = function* (username, password) {
var that = this; // Goddamn it
var options = {
method: 'POST',
url: `${this.baseUrl}/User/LogOn`,
headers: { 'user-agent': USER_AGENT },
form: { culture: 'is-IS', email: username, password: password },
json: true
};
const logonResponse = yield request(options).get(0);
// Make the follow-up request to fetch the request verification token.
// I actually have no idea if this is needed or not.
options = {
method: 'GET',
url: this.baseUrl + logonResponse.headers['location'],
headers: { 'user-agent': USER_AGENT },
json: true
};
// Where art thou destructuring?!
const resAndBody = yield request(options);
const res = resAndBody[0];
const body = resAndBody[1];
// Parse the "__RequestVerificationToken" from the HTML body. Sawry.
_.forEach(body.split('\n'), function (line) {
if (line.indexOf('__RequestVerificationToken') >= 0) {
let match = line.match(RV_TOKEN_REGEX);
that.requestVerificationToken = match[1];
}
});
return true;
}
var endpoints = [
{
identifier: 'createUserCategory',
path: '/Api/User/CreateUserCategory',
params: [
{ name: 'categoryType', type: 'string', description: 'expenses ("0")' },
{ name: 'isFixedExpenses', type: 'boolean', description: 'whether this is fixed or variable expenses' },
{ name: 'name', type: 'string', description: 'the name of the new category' },
{ name: 'parentId', type: 'string', description: 'the parent category of the new category' }
]
},
{
identifier: 'getBudgetEquationWidget',
path: '/Api/Widgets/GetBudgetEquationWidget',
params: [
{ name: 'period', type: 'integer', description: 'no idea...' }
]
}, {
identifier: 'getTransactionsPage',
path: '/Api/Transactions/GetTransactionsPage',
description: 'Fetches data on transactions and their categories over timespans',
params: [
{ name: 'page', type: 'integer', description: 'the page number to fetch' },
{ name: 'transactionsPerPage', type: 'integer', description: 'the number of transactions per page' },
{ name: 'filter', type: 'object', description: 'the filters to apply', subProperties: [
{ name: 'PeriodFrom', type: 'datetime', description: 'lower bound timestamp' },
{ name: 'PeriodTo', type: 'datetime', description: 'upper bound timestamp' },
] }
]
}, {
identifier: 'getUserCategories',
path: '/Api/User/GetUserCategories',
description: 'Fetches data on all public categories and the ones created by the currently logged in user.',
params: []
}, {
identifier: 'getTrendsReport',
path: '/Api/Planning/GetTrendsReport',
description: 'An analytics endpoint allowing you to analyze your expenses by categories and over timespans',
params: [
{ name: 'filter', type: 'object', description: 'the filters to apply', subProperties: [
{ name: 'View', type: 'integer', defaults: 1, description: '1 = sum over all months, 2 = group by month' },
{ name: 'Type', type: 'integer', defaults: 1, description: '1 = im not sure, just use that' },
{ name: 'Tags', type: 'array[string]', defaults: null, description: 'the tags to analyze, null to ignore.'},
{ name: 'Period', type: 'string', defaults: '0', description: '0=this month, 1=last month, 3=last 3 months, 6=last 6 months, 12=last 12 months, -1=this year, -2=last year'},
{ name: 'PeriodFrom', type: 'datetime', defaults: null, description: 'lower bound timestamp, overrides "Period".' },
{ name: 'PeriodTo', type: 'datetime', defaults: null, description: 'upper bound timestamp, overrides "Period".' },
{ name: 'Merchants', type: 'string', defaults: null, description: 'im not sure, null is default.' },
{ name: 'Group', type: 'integer', defaults: 1, description: 'im not sure' },
{ name: 'CategoryIds', type: 'array[integer]', description: 'IDs of the categories to analyze' },
{ name: 'AccountIdentifiers', type: '?', defaults: null, description: '?' },
{ name: 'AccountIds', type: '?', defaults: null, description: '?' },
{ name: 'ComparisonPeriod', type: '?', defaults: null, description: '?' },
{ name: 'Options', type: 'object', description: 'additional options to apply', subProperties: [
{ name: 'AccumulateCategoryExpenses', type: 'boolean', defaults: false, description: '?' },
{ name: 'DateFormat', type: '?', defaults: null, description: '?' },
{ name: 'DisableSliceGrouping', type: '?', defaults: false, description: '?' },
{ name: 'ExcludeNonMappedMerchants', type: '?', defaults: false, description: '?' },
{ name: 'FutureMonths', type: 'integer', description: '?' },
{ name: 'GetAverage', type: 'boolean', defaults: false, description: '?' },
{ name: 'GetFuture', type: 'boolean', description: '?' },
{ name: 'IncludeSavingsInNetIncome', type: 'boolean', defaults: true, description: '?' },
{ name: 'IsParent', type: 'boolean', defaults: true, description: '?' },
{ name: 'MaxSlicesInPie', type: 'integer', defaults: 10, description: '?' },
{ name: 'MaxTopMerchants', type: 'integer', defaults: 10, description: '?' },
{ name: 'MinPieSliceValue', type: 'integer', defaults: 1, description: '?' },
{ name: 'MinSlicesInPie', type: 'integer', defaults: 5, description: '?' },
{ name: 'SkipInvertedCategories', type: 'boolean', defaults: false, description: '?' },
{ name: 'UseAndSearchForTags', type: 'boolean', defaults: false, description: '?' }
] }
] }
]
}
];
_.forEach(endpoints, function (endpoint) {
MenigaClient.prototype[endpoint.identifier] = function* (options) {
var options = {
method: 'POST',
url: this.baseUrl + endpoint.path,
body: utils.toAspNetDates(options || {}),
headers: {
'user-agent': USER_AGENT,
'__RequestVerificationToken': this.requestVerificationToken
},
json: true,
}
let resAndBody = yield request(options);
let res = resAndBody[0];
let body = resAndBody[1];
if (res.statusCode >= 400) {
throw new Error('Non-200 response from the Meniga API: ' + res.statusCode + ' body: ', body);
}
return utils.fromAspNetDates(body, false);
}
});
// Exports.
module.exports = MenigaClient;