Skip to content

Commit

Permalink
Merge pull request #74 from suryavaddiraju/devv
Browse files Browse the repository at this point in the history
- v3.0.5
  • Loading branch information
suryavaddiraju authored Jan 4, 2025
2 parents 7419ca9 + e59ce71 commit 8afc132
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 13 deletions.
47 changes: 46 additions & 1 deletion docs/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,30 @@ console.log(response);
- viu
- the path to viu folder, if it doesnot work it will fall back to auto download
- if it not works it throws an error
- gcloud
- This params can be `JSON String || Javascript Object || JSON File Buffer`
- Google Cloud Service Account Credentials having permission of `serviceusage.services.use`
- if this parameter is present we won't use viu for that process
- `Note: This service is chargeable by Google and follow their terms and conditions`
- To try this use below steps
1. Create a Google Cloud account (Skip this step if you already have a Google Cloud account).
2. Create a new, separate project.
3. In the newly created project:
- Search for `IAM & Admin` in the Google Cloud Console.
- Go to `Roles` under IAM.
4. Create a new role:
- Set the title, description, and ID of your choice.
- Set the Role Launch Stage to `General Availability`.
5. Add the `serviceusage.services.use` permission to the role and click **Create**.
6. Create a A New Service Account within IAM & Admin Page In `Service account details` - Give a Name, id, description of your choice and then in
- `Service account details` - Give a Name, id, description of your choice
- `Grant this service account access to project` - Attach the role that you created in previous step by searching your given name
- `Grant users access to this service account (optional)` - Leave Empty
- Then Click **Done**
7. Go To service Accounts List of your project and Click on the email that you created in previous step download and Go to `Keys`and then `Create New Key - JSON`. You will get a json file in your browser downloads - `Keep this JSON`
8. Search for `Cloud Vision API` in the Google Cloud Console and `Enable` it (Ignore, if its not already enabled)



The example input is as follows

Expand All @@ -81,6 +105,19 @@ const irctc = new IRCTC(
"userID":"XXXXXX",
"password":"XXXXXXXXX",
"viu":"./some/loaction/to/file.exe | ./some/loaction/to/file" // Optional
"gcloud":{
"type": "service_account",
"project_id": "vision-api",
"private_key_id": "b0357d061ce2c96737d96c2",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqdyztLFNG\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "12345678901234567",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/default%40vision-api.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
});
```

Expand All @@ -89,6 +126,12 @@ const irctc = new IRCTC(

`book` function in IRCTC class takes input as a javascript object, where they are explained below

```
Note:
for book_input function, there are a set of mandatory keys and a set of optional keys. Optional Keys means they're not compulsory to be passed.
```

- `Mandatory Keys`
- payment
- for UPI payment
Expand Down Expand Up @@ -174,8 +217,10 @@ const irctc = new IRCTC(
- Must be a string and should match the with the list of existing station code names
- Must be short code of the station from where you are boarding
- The Train must pass by and have a stop at this station
-gst
- board is the station where the passenger will be actually catching the train. this should not be confused with the mandatory from parameter which is a param for defining the starting point for the ticket. for eg. the passenger may book a train ticket from DEL to MUM, but he may prefer to join the journey at any intermediate station like AGC.
- gst
- Must be a string and it must be the 17 digit GSTIN number
- This param is not necessary unless you are a business owner and want to claim the gst later.



Expand Down
22 changes: 13 additions & 9 deletions lib/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ import {viupath} from "./utils/viu.mjs"
import {book_validator} from './utils/form_validator.mjs';
import {stations} from "./utils/stations.mjs";
import {countries} from "./utils/countries.mjs";
import {vision_api} from "./utils/gcloud.mjs";
const __dirname = dirname(fileURLToPath(import.meta.url));

async function viu_captcha(params={}){
const rl = readline.createInterface({ input, output });
const answer = await rl.question(`${execFileSync(params.viu, [params.captcha_path,"-t"])} \nPlease type the above text and press enter\n`);
rl.close();
return answer;
async function answer_captcha(params={},captcha){
if(Object.prototype.hasOwnProperty.call(params, "gcloud")){
return await vision_api(params,captcha);
}else{
writeFileSync(params.captcha_path,Buffer.from(captcha, 'base64'));
const rl = readline.createInterface({ input, output });
const answer = await rl.question(`${execFileSync(params.viu, [params.captcha_path,"-t"])} \nPlease type the above text and press enter\n`);
rl.close();
return answer;
}
}

function normal_sleep(ms){
Expand Down Expand Up @@ -153,8 +159,7 @@ async function login(params={}){
options.headers.greq = params.csrf;
const {body} = await params.browse.request("https://www.irctc.co.in/eticketing/protected/mapps1/loginCaptcha?nlpCaptchaException=true",options);
params.status = body.status;
writeFileSync(params.captcha_path,Buffer.from(body.captchaQuestion, 'base64'));
const answer = await viu_captcha(params);
const answer = await answer_captcha(params,body.captchaQuestion);
await custom_sleep({
"slot":params.slot,
"callfrom":"login",
Expand Down Expand Up @@ -427,8 +432,7 @@ async function confirm_booking_form(book_params={},params={}){
headersa["Authorization"] = params.access_token;
headersa["bmiyek"] = params.user_hash;
while (book_params["captchaDto"]["captchastatus"] !== "SUCCESS"){
writeFileSync(params.captcha_path,Buffer.from(book_params["captchaDto"]["captchaQuestion"], 'base64'));
let answer = await viu_captcha(params);
let answer = await answer_captcha(params,book_params["captchaDto"]["captchaQuestion"]);
headersa['spa-csrf-token'] = params.csrf;
let response = await params.browse.request(
`https://www.irctc.co.in/eticketing/protected/mapps1/captchaverify/${book_params.tid}/BOOKINGWS/${answer}`,
Expand Down
177 changes: 177 additions & 0 deletions lib/utils/gcloud.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { createSign } from "node:crypto";
import { readFileSync } from "node:fs";
import { request } from "node:https";
import { URLSearchParams } from "node:url";

async function checker(service_account,params){
function isPlainObject(value) {
return (
typeof value === 'object' &&
value !== null &&
!Buffer.isBuffer(value) &&
!Array.isArray(value) &&
value.constructor === Object
);
}

function isgcloudservice(value){
return (
isPlainObject(value) &&
Object.prototype.hasOwnProperty.call(value,"type") &&
typeof value.type === "string" &&
value.type === "service_account" &&
Object.prototype.hasOwnProperty.call(value,"private_key_id") &&
Object.prototype.hasOwnProperty.call(value,"private_key") &&
typeof value.private_key === "string" &&
value.private_key.startsWith("-----BEGIN PRIVATE KEY-----") &&
(
value.private_key.endsWith("-----END PRIVATE KEY-----") ||
value.private_key.endsWith("-----END PRIVATE KEY-----\n")
) &&
Object.prototype.hasOwnProperty.call(value,"client_email") &&
typeof value.client_email === "string" &&
value.client_email.endsWith(".iam.gserviceaccount.com") &&
!Object.prototype.hasOwnProperty.call(value,"token")
);
}

if (typeof service_account === "string"){
service_account = service_account.trim();
if (service_account.startsWith('{') && service_account.endsWith('}')){
service_account = JSON.parse(service_account);
return await checker(service_account,params);
}else{
throw new Error(`Invalid Parameter: value for gcloud key must be an object or valid JSON file content provided by google`);
}
} else if (typeof service_account === "object" && Buffer.isBuffer(service_account)){
return await checker(service_account.toString(),params);
} else if (isgcloudservice(service_account)){
params.gcloud_project = service_account.project_id;
return await generate_token(service_account);
} else if (isPlainObject(service_account) && Object.prototype.hasOwnProperty.call(service_account,"token") && Object.prototype.hasOwnProperty.call(service_account,"project_id")){
params.gcloud_project = service_account.project_id;
return service_account.token;
}else{
throw new Error(`Invalid Parameter: value for gcloud key must be an object or valid JSON file content provided by google`);
}
}

async function generate_token(service_account) {
try {
const header = Buffer.from(
JSON.stringify({
alg: "RS256",
typ: "JWT",
kid: service_account.private_key_id,
})
).toString("base64url");
const iat = Math.floor(Date.now() / 1000);
const payload = Buffer.from(
JSON.stringify({
iss: service_account.client_email,
scope: "https://www.googleapis.com/auth/cloud-vision",
aud: "https://oauth2.googleapis.com/token",
exp: iat + 3600,
iat: iat,
})
).toString("base64url");
const signature = createSign("RSA-SHA256").update(`${header}.${payload}`).sign(service_account.private_key, "base64url");
const post_body = new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: `${header}.${payload}.${signature}`
});
return new Promise((resolve, reject) => {
const req = request(
"https://oauth2.googleapis.com/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
},
(res) => {
const chunks = [];
res.on("data", (chunk) => {
chunks.push(chunk);
});

res.on("end", () => {
const data = JSON.parse(Buffer.concat(chunks).toString());
if (Object.prototype.hasOwnProperty.call(data,"access_token") && typeof data.access_token === "string" && data.access_token.length > 10){
resolve(data.access_token);
}
else{
reject(data);
}
});
}
);
req.on("error", (e) => {
reject(e);
});
req.write(post_body.toString());
req.end();
});
} catch (e) {
throw new Error(`Error generating token: ${e.message}`);
}
}

async function vision_api(params={},captcha){
try{
if (!Object.prototype.hasOwnProperty.call(params,"gcloud_token")){
params.gcloud_token = await checker(params.gcloud,params);
return await vision_api(params,captcha);
}else{
return new Promise((resolve, reject) => {
const req = request("https://vision.googleapis.com/v1/images:annotate",{
"method":"POST",
"headers":
{
"Authorization":`Bearer ${params.gcloud_token}`,
"x-goog-user-project":params.gcloud_project,
"Content-Type":"application/json; charset=utf-8"
}
},(res) =>{
const chunks = [];
res.on("data", (chunk) => {
chunks.push(chunk);
});
res.on("end", () => {
const data = Buffer.concat(chunks).toString();
if (res.statusCode !== 200){
reject(data);
}else{
resolve((JSON.parse(data)).responses[0].fullTextAnnotation.text.replace(/[\s\n\r]/g,''));
}
});
});
req.on("error", (e) => {
console.error("Request error:", e);
reject(e);
});
req.write(JSON.stringify({
"requests": [
{
"image": {
"content": captcha
},
"features": [
{
"type": "TEXT_DETECTION"
}
]
}
]
}
));
req.end();
});
}
} catch(e){
throw new Error(`Error at Google Cloud Vision API:\n${e}`);
}
}

export default vision_api;
export {vision_api};
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "irctc-api",
"description": "An exclusive NodeJs only package built on top of IRCTC Website APIs to book train tickets, managing user profile faster and simpler from anywhere in the world",
"version": "3.0.4",
"version": "3.0.5",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand Down

0 comments on commit 8afc132

Please sign in to comment.