Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dineshdb committed Jul 3, 2024
0 parents commit ca411c3
Show file tree
Hide file tree
Showing 14 changed files with 341 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Publish
on:
push:
branches:
- main

jobs:
publish:
runs-on: ubuntu-latest

permissions:
contents: read
id-token: write

steps:
- uses: actions/checkout@v4
- name: Publish package
run: npx jsr publish

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
.env.*
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# README

CLI and API for accessing [MeroShare](https://meroshare.cdsc.com.np/)

## LICENSE

MIT
11 changes: 11 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@util/meroshare",
"version": "0.1.2",
"exports": "./mod.ts",
"tasks": {
"dev": "deno run --watch main.ts"
},
"imports": {
"@es-toolkit/es-toolkit": "jsr:@es-toolkit/es-toolkit@^1.7.0"
}
}
19 changes: 19 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { loadConfig } from "./src/config.ts";
import { getPortfolioCsv } from "./src/getPortfolioCsv.ts";
import { getPortfolio } from "./src/getPortfolio.ts";
import { login } from "./src/login.ts";
import { getOwnDetail } from "./src/getOwnDetail.ts";

type Args = {
command: string;
subcommand?: string;
};

const commands: Record<string, (args: Args) => void> = {
"portfolio csv": async () => {
const loginDetails = await loadConfig();
const { authorization } = await login(loginDetails);
loginDetails.authorization = authorization!;
const myPortfolio = await getPortfolioCsv(loginDetails);
console.log(myPortfolio);
},
"portfolio json": async () => {
const loginDetails = await loadConfig();
const { authorization } = await login(loginDetails);
loginDetails.authorization = authorization!;
const portfolio = await getPortfolio(loginDetails);
console.log(portfolio);
},
info: async () => {
const loginDetails = await loadConfig();
const { authorization } = await login(loginDetails);
loginDetails.authorization = authorization!;
const info = await getOwnDetail(loginDetails.authorization);
console.log(info);
},
};

export default function main() {
const [command, subcommand] = Deno.args;
const cmd = commands[`${command} ${subcommand}`] ?? commands[command];
const args = {
command,
subcommand,
};
if (cmd) {
return cmd(args);
} else {
if (Deno.args.length > 0) {
console.error("ERROR: command not found:", command, subcommand);
}
console.info("Available commands:");
Object.keys(commands).forEach((cmd) => {
console.info(` - ${cmd}`);
});
}
}

if (import.meta.main) {
main();
}
10 changes: 10 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from "./src/getPortfolioCsv.ts";
export * from "./src/getPortfolio.ts";
export * from "./src/login.ts";
export * from "./src/getOwnDetail.ts";
export * from "./src/config.ts";
export * from "./src/fetch.ts";

if (import.meta.main) {
import("./main.ts").then((main) => main.default());
}
43 changes: 43 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getCapitals } from "./getCapitals.ts";
import { isNotNil, keyBy } from "@es-toolkit/es-toolkit";

export type UserDetails = {
demat: string;
username: string;
clientId: number;
clientCode: string;
};

export type LoginDetails = UserDetails & {
password: string;
authorization?: string;
};

function getUsernamePassword(): { demat: string; password: string } {
const demat = Deno.env.get("DEMAT_ACCOUNT");
const password = Deno.env.get("DEMAT_PASSWORD");
if (demat?.length !== 16) {
throw new Error("valid demat account should be provided");
}
if (!isNotNil(password)) {
throw new Error("password is needed");
}
return { demat, password };
}

export async function loadConfig(): Promise<LoginDetails> {
const { demat, password } = getUsernamePassword();

const capitals = await getCapitals();
const capitalsMap = keyBy(capitals, (capital) => capital.code);

const clientCode = demat.substring(3, 8);
const username = demat.substring(8, demat.length);
return {
clientId: capitalsMap[clientCode].id,
password,
username,
clientCode,
demat,
};
}
36 changes: 36 additions & 0 deletions src/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const commonHeaders = {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:129.0) Gecko/20100101 Firefox/129.0",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en-US",
"Content-Type": "application/json",
"Sec-GPC": "1",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"Referer": "https://meroshare.cdsc.com.np/",
};

export async function fetchMeroshare(
path: string,
init: RequestInit = {},
): Promise<Response> {
const res = await fetch(
`https://webbackend.cdsc.com.np/api/meroShare${path}`,
{
credentials: "include",
mode: "cors",
...init,
headers: {
...commonHeaders,
...init.headers,
},
},
);

if (!res.ok) {
throw new Error(`Failed to fetch ${path}`);
}

return res;
}
11 changes: 11 additions & 0 deletions src/getCapitals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { fetchMeroshare } from "./fetch.ts";

type Capital = {
id: number;
code: string;
name: string;
};
export async function getCapitals(): Promise<Capital[]> {
const res = await fetchMeroshare("/capital/");
return await res.json();
}
40 changes: 40 additions & 0 deletions src/getOwnDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { fetchMeroshare } from "./fetch.ts";

export type OwnDetail = {
address: string;
boid: string;
clientCode: string;
contact: string;
createdApproveDate: string;
createdApproveDateStr: string;
customerTypeCode: string;
demat: string;
dematExpiryDate: string;
email: string;
expiredDate: string;
expiredDateStr: string;
gender: string;
id: number;
imagePath: string;
meroShareEmail: string;
name: string;
panNumber: string;
passwordChangeDate: string;
passwordChangedDateStr: string;
passwordExpiryDate: string;
passwordExpiryDateStr: string;
profileName: string;
renderDashboard: boolean;
renewedDate: string;
renewedDateStr: string;
username: string;
};

export async function getOwnDetail(authorization: string): Promise<OwnDetail> {
const res = await fetchMeroshare("/ownDetail/", {
"headers": {
"Authorization": authorization,
},
});
return await res.json();
}
41 changes: 41 additions & 0 deletions src/getPortfolio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { LoginDetails } from "./config.ts";
import { fetchMeroshare } from "./fetch.ts";

export type Portfolio = {
meroShareMyPortfolio: {
currentBalance: number;
lastTransactionPrice: string;
previousClosingPrice: string;
script: string;
scriptDesc: string;
valueAsOfLastTransactionPrice: string;
valueAsOfPreviousClosingPrice: string;
valueOfLastTransPrice: number;
valueOfPrevClosingPrice: number;
}[];
totalItems: number;
totalValueAsOfLastTransactionPrice: string;
totalValueAsOfPreviousClosingPrice: string;
totalValueOfLastTransPrice: number;
totalValueOfPrevClosingPrice: number;
};

export async function getPortfolio(
{ authorization, demat, clientCode }: LoginDetails,
): Promise<Portfolio> {
const res = await fetchMeroshare("View/myPortfolio/", {
headers: {
authorization: authorization!,
},
body: JSON.stringify({
sortBy: "script",
demat: [demat],
clientCode,
page: 1,
size: 200,
sortAsc: true,
}),
method: "POST",
});
return await res.json();
}
22 changes: 22 additions & 0 deletions src/getPortfolioCsv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { LoginDetails } from "./config.ts";
import { fetchMeroshare } from "./fetch.ts";

export async function getPortfolioCsv(
{ authorization, demat, clientCode }: LoginDetails,
): Promise<string> {
const res = await fetchMeroshare("View/report/myPortfolio/csv", {
"headers": {
"Authorization": authorization!,
},
body: JSON.stringify({
sortBy: "script",
demat: [demat],
clientCode,
page: 1,
size: 200,
sortAsc: true,
}),
method: "POST",
});
return await res.text();
}
22 changes: 22 additions & 0 deletions src/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { LoginDetails } from "./config.ts";
import { fetchMeroshare } from "./fetch.ts";

export async function login(
{ clientId, username, password }: LoginDetails,
): Promise<{ authorization: string }> {
const res = await fetchMeroshare("/auth/", {
method: "POST",
body: JSON.stringify({
clientId,
username,
password,
}),
});
const authorization = res.headers.get("authorization");
if (!authorization) {
throw new Error("Authorization token not found");
}
return {
authorization,
};
}

0 comments on commit ca411c3

Please sign in to comment.