money-lover-app-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nexo-Technology
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # money-lover-client
2
+ An unofficial, community-maintained connector for the Money Lover app that enables programmatic access to user financial data for analytics, automation, and integrations. Provides tools to import and normalise accounts and transactions. Not affiliated with or endorsed by Money Lover App.
package/dist/bundle.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -0,0 +1,16 @@
1
+ import { MoneyLoverCategory } from "./interfaces/category";
2
+ import { MoneyLoverTransaction } from "./interfaces/transaction";
3
+ import { MoneyLoverWallet } from "./interfaces/wallet";
4
+ declare class MoneyLoverClient {
5
+ private _jwtToken;
6
+ constructor(jwtToken?: string | null);
7
+ private _postRequest;
8
+ static getToken(email: string, password: string): Promise<any>;
9
+ isTokenValid(): boolean;
10
+ getUserInfo(): Promise<any>;
11
+ getWallets(): Promise<MoneyLoverWallet[]>;
12
+ getCategories(walletId: string): Promise<MoneyLoverCategory[]>;
13
+ getTransactions(walletId: string, startDate: Date, endDate: Date): Promise<any>;
14
+ addTransaction(transaction: MoneyLoverTransaction): Promise<any>;
15
+ }
16
+ export default MoneyLoverClient;
package/dist/client.js ADDED
@@ -0,0 +1,95 @@
1
+ import jwt from "jsonwebtoken";
2
+ import { formatDate } from "./utils";
3
+ const moneyLoverUrl = "https://web.moneylover.me";
4
+ const moneyLoverTokenUrl = "https://oauth.moneylover.me";
5
+ class MoneyLoverClient {
6
+ constructor(jwtToken = null) {
7
+ this._jwtToken = null;
8
+ this._jwtToken = jwtToken;
9
+ }
10
+ async _postRequest(path, body = null, headers = {}) {
11
+ const res = await fetch(`${moneyLoverUrl}/api${path}`, {
12
+ method: "POST",
13
+ headers: {
14
+ authorization: `AuthJWT ${this._jwtToken}`,
15
+ "cache-Control": "no-cache, max-age=0, no-store, no-transform, must-revalidate",
16
+ "Content-Type": "application/json",
17
+ ...headers,
18
+ },
19
+ body: JSON.stringify(body),
20
+ });
21
+ const data = await res.json();
22
+ if (data.error != null && data.error !== 0) {
23
+ const error = new Error(`Error ${data.error}, ${data.msg}`);
24
+ error.name = "MoneyLoverError";
25
+ error.message = data.msg;
26
+ throw error;
27
+ }
28
+ else if (data.e != null) {
29
+ const error = new Error(`Error ${data.e}, ${data.message}`);
30
+ error.name = "MoneyLoverError";
31
+ error.message = data.message;
32
+ throw error;
33
+ }
34
+ else {
35
+ return data.data;
36
+ }
37
+ }
38
+ static async getToken(email, password) {
39
+ const loginUrlRes = await fetch(`${moneyLoverUrl}/api/user/login-url`, {
40
+ method: "POST",
41
+ });
42
+ const loginUrlData = await loginUrlRes.json();
43
+ const res = await fetch(`${moneyLoverTokenUrl}/token`, {
44
+ method: "POST",
45
+ headers: {
46
+ authorization: `Bearer ${loginUrlData.data.request_token}`,
47
+ client: loginUrlData.data.login_url.match("client=(.+?)&")[1],
48
+ "Content-Type": "application/json",
49
+ },
50
+ body: JSON.stringify({ email, password }),
51
+ });
52
+ const tokenData = await res.json();
53
+ return tokenData.access_token;
54
+ }
55
+ isTokenValid() {
56
+ if (this._jwtToken != null) {
57
+ // Check if token is expired
58
+ const jwtToken = jwt.decode(this._jwtToken);
59
+ if (!jwtToken.exp) {
60
+ return false;
61
+ }
62
+ return jwtToken.exp * 1000 > Date.now();
63
+ }
64
+ return false;
65
+ }
66
+ getUserInfo() {
67
+ return this._postRequest("/user/info");
68
+ }
69
+ getWallets() {
70
+ return this._postRequest("/wallet/list");
71
+ }
72
+ getCategories(walletId) {
73
+ return this._postRequest("/category/list-all", { walletId: walletId });
74
+ }
75
+ getTransactions(walletId, startDate, endDate) {
76
+ return this._postRequest("/transaction/list", {
77
+ startDate: startDate.toISOString().substr(0, 10),
78
+ endDate: endDate.toISOString().substr(0, 10),
79
+ walletId, // "all" to get the transactions of all wallets
80
+ });
81
+ }
82
+ addTransaction(transaction) {
83
+ return this._postRequest("/transaction/add", JSON.stringify({
84
+ with: [],
85
+ account: transaction.account,
86
+ category: transaction.category,
87
+ amount: transaction.amount,
88
+ note: transaction.note,
89
+ displayDate: formatDate(transaction.date),
90
+ }), {
91
+ "Content-Type": "application/json",
92
+ });
93
+ }
94
+ }
95
+ export default MoneyLoverClient;
@@ -0,0 +1,4 @@
1
+ import MoneyLoverClient from "../client";
2
+ import { MoneyLoverCategory } from "../interfaces/category";
3
+ declare const getCategories: (client: MoneyLoverClient, walletId: string) => Promise<MoneyLoverCategory[]>;
4
+ export default getCategories;
@@ -0,0 +1,13 @@
1
+ const getCategories = async (client, walletId) => {
2
+ if (!client) {
3
+ throw new Error("Not logged in");
4
+ }
5
+ if (!client.isTokenValid()) {
6
+ throw new Error("Token has expired");
7
+ }
8
+ const allCategories = await client.getCategories(walletId);
9
+ // Api may return all categories even when the walletId is specified,
10
+ // so we filter them here to return only the categories that belong to the specified walletId.
11
+ return allCategories.filter((category) => category.walletId === walletId);
12
+ };
13
+ export default getCategories;
@@ -0,0 +1,3 @@
1
+ import { UserProfile } from "../interfaces/user-profile";
2
+ declare const login: (username: string, password: string) => Promise<UserProfile | null>;
3
+ export default login;
@@ -0,0 +1,24 @@
1
+ import MoneyLoverClient from "../client";
2
+ import jwt from "jsonwebtoken";
3
+ const debug = process.env.DEBUG === "true";
4
+ const login = async (username, password) => {
5
+ let token;
6
+ token = await MoneyLoverClient.getToken(username, password);
7
+ try {
8
+ const jwtToken = jwt.decode(token);
9
+ const ml = new MoneyLoverClient(token);
10
+ const userInfo = await ml.getUserInfo();
11
+ if (debug) {
12
+ if (!jwtToken.exp) {
13
+ throw new Error("Invalid JWT token: missing exp field");
14
+ }
15
+ console.log(`MoneyLoverClient: Logged in as ${userInfo.email} until ${new Date(jwtToken.exp * 1000)}`);
16
+ }
17
+ return userInfo;
18
+ }
19
+ catch (e) {
20
+ console.error("MoneyLoverClient: Login failed", e);
21
+ return null;
22
+ }
23
+ };
24
+ export default login;
@@ -0,0 +1,2 @@
1
+ declare const logout: (deviceId?: string | null) => Promise<void>;
2
+ export default logout;
@@ -0,0 +1,6 @@
1
+ const logout = async (deviceId = null) => {
2
+ if (deviceId != null) {
3
+ // Delete device from Money Lover servers
4
+ }
5
+ };
6
+ export default logout;
@@ -0,0 +1,5 @@
1
+ import MoneyLoverClient from "../client";
2
+ import { MoneyLoverTransaction } from "../interfaces/transaction";
3
+ declare const getTransactions: (client: MoneyLoverClient, walletId: string, startDate: Date, endDate: Date) => Promise<any>;
4
+ declare const addTransaction: (client: MoneyLoverClient, transaction: MoneyLoverTransaction) => Promise<any>;
5
+ export { getTransactions, addTransaction };
@@ -0,0 +1,26 @@
1
+ const getTransactions = async (client, walletId, startDate, endDate) => {
2
+ if (!client) {
3
+ throw new Error("MoneyLoverClient: Not logged in");
4
+ }
5
+ if (!client.isTokenValid()) {
6
+ throw new Error("MoneyLoverClient: Token has expired");
7
+ }
8
+ if (!walletId) {
9
+ // Default to all
10
+ walletId = "all";
11
+ }
12
+ return await client.getTransactions(walletId, startDate, endDate);
13
+ };
14
+ const addTransaction = async (client, transaction) => {
15
+ if (!client) {
16
+ throw new Error("MoneyLoverClient: Not logged in");
17
+ }
18
+ if (!client.isTokenValid()) {
19
+ throw new Error("MoneyLoverClient: Token has expired");
20
+ }
21
+ if (!transaction.account || !transaction.category) {
22
+ throw new Error("MoneyLoverClient: Transaction must have account and category");
23
+ }
24
+ return await client.addTransaction(transaction);
25
+ };
26
+ export { getTransactions, addTransaction };
@@ -0,0 +1,3 @@
1
+ import MoneyLoverClient from "../client";
2
+ declare const getWallets: (client: MoneyLoverClient) => Promise<import("../interfaces/wallet").MoneyLoverWallet[]>;
3
+ export default getWallets;
@@ -0,0 +1,15 @@
1
+ const debug = process.env.DEBUG === "true";
2
+ const getWallets = async (client) => {
3
+ if (!client) {
4
+ throw new Error("MoneyLoverClient: Not logged in");
5
+ }
6
+ if (!client.isTokenValid()) {
7
+ throw new Error("MoneyLoverClient: Token has expired");
8
+ }
9
+ const wallets = await client.getWallets();
10
+ if (debug) {
11
+ console.log(`MoneyLoverClient: Retrieved ${wallets.length} wallets`);
12
+ }
13
+ return wallets;
14
+ };
15
+ export default getWallets;
File without changes
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,4 @@
1
+ declare enum CategoryType {
2
+ INCOME = 1,
3
+ EXPENSE = 2
4
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ var CategoryType;
3
+ (function (CategoryType) {
4
+ CategoryType[CategoryType["INCOME"] = 1] = "INCOME";
5
+ CategoryType[CategoryType["EXPENSE"] = 2] = "EXPENSE";
6
+ })(CategoryType || (CategoryType = {}));
@@ -0,0 +1,25 @@
1
+ export interface MoneyLoverCategory {
2
+ /** Internal unique identifier for the specific item */
3
+ _id: string;
4
+ /** Display name (e.g., "Tejas") */
5
+ name: string;
6
+ /** Icon identifier used in the UI */
7
+ icon: string;
8
+ /** * Reference ID to the parent wallet/account.
9
+ * Matches the structure of your previously defined Wallet _id.
10
+ */
11
+ account: string;
12
+ /** * Numeric type identifier.
13
+ * Likely represents a specific category or sub-account type.
14
+ */
15
+ type: number;
16
+ /** Metadata for additional info (currently an empty string) */
17
+ metadata: string;
18
+ /** Grouping identifier for organizational purposes */
19
+ group: number;
20
+ /** Duplicate of _id, often used for legacy or specific API compatibility */
21
+ id: string;
22
+ /** Optional parent category ID for hierarchical categorization */
23
+ parent?: string;
24
+ exclude_accounts: string[];
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export interface ClientState {
2
+ jwtToken: string | null;
3
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export interface MoneyLoverTransaction {
2
+ account: string;
3
+ category: string;
4
+ amount: string;
5
+ note: string;
6
+ date: Date;
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ export interface ClientSettings {
2
+ is_done_intro_home: boolean;
3
+ segment_user_ui_type: number;
4
+ ob_step_add_budget: boolean;
5
+ ob_step_get_it: boolean;
6
+ ob_step_add_transaction: boolean;
7
+ ob_budget_suggest_show: boolean;
8
+ is_show_step_view_for_user: boolean;
9
+ ob_step_create_wallet: boolean;
10
+ ob_step_closed: boolean;
11
+ main_currency: string;
12
+ su: boolean;
13
+ nps__last_ask: number;
14
+ ls: number;
15
+ om: number;
16
+ sb: number;
17
+ er: boolean;
18
+ fmy: number;
19
+ fdw: number;
20
+ pl: number;
21
+ av: number;
22
+ dr: number;
23
+ fd: number;
24
+ df: number;
25
+ l: string;
26
+ ps: number;
27
+ ds: number;
28
+ sa: boolean;
29
+ sc: boolean;
30
+ sd: boolean;
31
+ show_advance_add_transaction: boolean;
32
+ future_period: number;
33
+ ol: boolean;
34
+ sl: boolean;
35
+ is_done_intro_money_insider_home: boolean;
36
+ is_done_intro_money_insider_detail: boolean;
37
+ update_password_fb: string;
38
+ is_done_tooltip_txn_ai_add_trans: boolean;
39
+ is_done_tooltip_txn_ai_home: boolean;
40
+ }
41
+ export interface UserProfile {
42
+ _id: string;
43
+ email: string;
44
+ icon_package: any[];
45
+ limitDevice: number;
46
+ tags: string[];
47
+ client_setting: ClientSettings;
48
+ purchased: boolean;
49
+ subscribeProduct: string;
50
+ deviceId: string;
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ export interface MoneyLoverWallet {
2
+ _id: string;
3
+ name: string;
4
+ currency_id: number;
5
+ owner: string;
6
+ sortIndex: number;
7
+ transaction_notification: boolean;
8
+ archived: boolean;
9
+ account_type: number;
10
+ exclude_total: boolean;
11
+ icon: string;
12
+ listUser: MoneyLoverObjectUser[];
13
+ createdAt: string;
14
+ updateAt: string;
15
+ isDelete: boolean;
16
+ balance: MoneyLoverBankTransaction[];
17
+ }
18
+ export interface MoneyLoverObjectUser {
19
+ _id: string;
20
+ email: string;
21
+ }
22
+ export interface MoneyLoverBankTransaction {
23
+ date: string;
24
+ valueDate: string;
25
+ transaction: string;
26
+ debit: string;
27
+ credit: string;
28
+ balance: string;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare function getState(key: string): any;
2
+ export declare function setState(key: string, value: any): void;
3
+ export declare function clearState(): void;
package/dist/state.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getState = getState;
4
+ exports.setState = setState;
5
+ exports.clearState = clearState;
6
+ let state = {
7
+ jwtToken: null,
8
+ };
9
+ function getState(key) {
10
+ return state[key];
11
+ }
12
+ function setState(key, value) {
13
+ state[key] = value;
14
+ }
15
+ function clearState() {
16
+ state = {
17
+ jwtToken: null,
18
+ };
19
+ }
@@ -0,0 +1,2 @@
1
+ declare function formatDate(date: string | Date): string;
2
+ export { formatDate };
package/dist/utils.js ADDED
@@ -0,0 +1,12 @@
1
+ function formatDate(date) {
2
+ const d = new Date(date);
3
+ let month = "" + (d.getMonth() + 1);
4
+ let day = "" + d.getDate();
5
+ const year = d.getFullYear();
6
+ if (month.length < 2)
7
+ month = "0" + month;
8
+ if (day.length < 2)
9
+ day = "0" + day;
10
+ return [year, month, day].join("-");
11
+ }
12
+ export { formatDate };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "money-lover-app-client",
3
+ "version": "1.0.0",
4
+ "main": "dist/bundle.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "rollup -c",
8
+ "prepublishOnly": "npm run build"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Nexo-Technology/money-lover-client.git"
13
+ },
14
+ "keywords": [],
15
+ "author": "",
16
+ "license": "ISC",
17
+ "type": "commonjs",
18
+ "bugs": {
19
+ "url": "https://github.com/Nexo-Technology/money-lover-client/issues"
20
+ },
21
+ "homepage": "https://github.com/Nexo-Technology/money-lover-client#readme",
22
+ "devDependencies": {
23
+ "@rollup/plugin-commonjs": "^29.0.0",
24
+ "@rollup/plugin-node-resolve": "^16.0.3",
25
+ "@rollup/plugin-typescript": "^12.3.0",
26
+ "@types/jsonwebtoken": "^9.0.10",
27
+ "@types/node": "^25.0.3",
28
+ "rollup": "^4.54.0",
29
+ "ts-node": "^10.9.2",
30
+ "typescript": "^5.9.3"
31
+ },
32
+ "dependencies": {
33
+ "jsonwebtoken": "^9.0.3",
34
+ "tslib": "^2.8.1"
35
+ }
36
+ }
@@ -0,0 +1,13 @@
1
+ import resolve from '@rollup/plugin-node-resolve';
2
+ import commonjs from '@rollup/plugin-commonjs';
3
+ import typescript from '@rollup/plugin-typescript';
4
+
5
+ export default {
6
+ input: 'src/index.ts',
7
+ output: {
8
+ file: 'dist/bundle.js',
9
+ format: 'cjs',
10
+ sourcemap: true
11
+ },
12
+ plugins: [resolve(), commonjs(), typescript({ tsconfig: './tsconfig.json' })]
13
+ };