Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

- v3.0.5 #74

Merged
merged 1 commit into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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