Skip to content

Commit

Permalink
Add pine permission manager
Browse files Browse the repository at this point in the history
  • Loading branch information
Mathieu2301 committed Jan 30, 2022
1 parent 70fa186 commit d1a1cfd
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 33 deletions.
57 changes: 57 additions & 0 deletions examples/PinePermManage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const { PinePermManager } = require('../main');

/*
This example creates a pine
permission manager and tests
all the available functions
*/

const sessionid = process.argv[2];
const pineid = process.argv[3];

const manager = new PinePermManager(sessionid, pineid);

(async () => {
console.log('Users:', await manager.getUsers());

console.log('Adding user \'TradingView\'...');

switch (await manager.addUser('TradingView')) {
case 'ok':
console.log('Done !');
break;
case 'exists':
console.log('This user is already authorized');
break;
default:
console.error('Unknown error...');
break;
}

console.log('Users:', await manager.getUsers());

console.log('Modifying expiration date...');

const newDate = new Date(Date.now() + 24 * 3600000); // Add one day
if (await manager.modifyExpiration('TradingView', newDate) === 'ok') {
console.log('Done !');
} else console.error('Unknown error...');

console.log('Users:', await manager.getUsers());

console.log('Removing expiration date...');

if (await manager.modifyExpiration('TradingView') === 'ok') {
console.log('Done !');
} else console.error('Unknown error...');

console.log('Users:', await manager.getUsers());

console.log('Removing user \'TradingView\'...');

if (await manager.removeUser('TradingView') === 'ok') {
console.log('Done !');
} else console.error('Unknown error...');

console.log('Users:', await manager.getUsers());
})();
2 changes: 2 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ const miscRequests = require('./src/miscRequests');
const Client = require('./src/client');
const BuiltInIndicator = require('./src/classes/BuiltInIndicator');
const PineIndicator = require('./src/classes/PineIndicator');
const PinePermManager = require('./src/classes/PinePermManager');

module.exports = { ...miscRequests };
module.exports.Client = Client;
module.exports.BuiltInIndicator = BuiltInIndicator;
module.exports.PineIndicator = PineIndicator;
module.exports.PinePermManager = PinePermManager;
35 changes: 35 additions & 0 deletions src/FormData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class FormData {
#string = '';

#boundary = '';

get boundary() {
return this.#boundary;
}

constructor() {
const random = (Math.random() * 10 ** 20).toString(36);
this.#boundary = `${random}`;
this.#string = `--${this.boundary}`;
}

/**
* Adds a property to the FormData object
* @param {string} key Property key
* @param {string} value Property value
*/
append(key, value) {
this.#string += `\r\nContent-Disposition: form-data; name="${key}"`;
this.#string += `\r\n\r\n${value}`;
this.#string += `\r\n--${this.boundary}`;
}

/**
* @returns {string}
*/
toString() {
return `${this.#string}--`;
}
}

module.exports = FormData;
142 changes: 142 additions & 0 deletions src/classes/PinePermManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
const request = require('../request');
const FormData = require('../FormData');

/**
* @typedef {Object} AuthorizationUser
* @prop {id} id User id
* @prop {string} username User's username
* @prop {string} userpic User's profile picture URL
* @prop {string} expiration Authorization expiration date
* @prop {string} created Authorization creation date
*/

class PinePermManager {
sessionId;

pineId;

/**
* Creates a PinePermManager instance
* @param {string} sessionId Token from sessionid cookie
* @param {string} pineId Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
*/
constructor(sessionId, pineId) {
if (!sessionId) throw new Error('Please provide a SessionID');
if (!pineId) throw new Error('Please provide a PineID');
this.pineId = pineId;
this.sessionId = sessionId;
}

/**
* Get list of authorized users
* @param {number} limit Fetching limit
* @param {'user__username'
* | '-user__username'
* | 'created' | 'created'
* | 'expiration,user__username'
* | '-expiration,user__username'
* } order Fetching order
* @returns {AuthorizationUser[]}
*/
async getUsers(limit = 10, order = '-created') {
const data = await request({
method: 'POST',
host: 'www.tradingview.com',
path: `/pine_perm/list_users/?limit=${limit}&order_by=${order}`,
headers: {
origin: 'https://www.tradingview.com',
'Content-Type': 'application/x-www-form-urlencoded',
cookie: `sessionid=${this.sessionId}`,
},
}, false, `pine_id=${this.pineId.replace(/;/g, '%3B')}`);

if (!data.results) throw new Error('Wrong sessionId or pineId');

return data.results;
}

/**
* Adds an user to the authorized list
* @param {string} username User's username
* @param {Date} [expiration] Expiration date
* @returns {'ok' | 'exists' | null}
*/
async addUser(username, expiration = null) {
const formData = new FormData();
formData.append('pine_id', this.pineId);
formData.append('username_recip', username);
if (expiration && expiration instanceof Date) {
formData.append('expiration', expiration.toString());
}

const data = await request({
method: 'POST',
host: 'www.tradingview.com',
path: '/pine_perm/add/',
headers: {
origin: 'https://www.tradingview.com',
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
cookie: `sessionid=${this.sessionId}`,
},
}, false, formData.toString());

if (!data.status) throw new Error('Wrong sessionId or pineId');
return data.status;
}

/**
* Modify an authorization expiration date
* @param {string} username User's username
* @param {Date} [expiration] New expiration date
* @returns {'ok' | null}
*/
async modifyExpiration(username, expiration = null) {
const formData = new FormData();
formData.append('pine_id', this.pineId);
formData.append('username_recip', username);
if (expiration && expiration instanceof Date) {
formData.append('expiration', expiration.toISOString());
}

const data = await request({
method: 'POST',
host: 'www.tradingview.com',
path: '/pine_perm/modify_user_expiration/',
headers: {
origin: 'https://www.tradingview.com',
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
cookie: `sessionid=${this.sessionId}`,
},
}, false, formData.toString());

if (!data.status) throw new Error('Wrong sessionId or pineId');
return data.status;
}

/**
* Removes an user to the authorized list
* @param {string} username User's username
* @returns {'ok' | null}
*/
async removeUser(username) {
const formData = new FormData();
formData.append('pine_id', this.pineId);
formData.append('username_recip', username);

const data = await request({
method: 'POST',
host: 'www.tradingview.com',
path: '/pine_perm/remove/',
headers: {
origin: 'https://www.tradingview.com',
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
cookie: `sessionid=${this.sessionId}`,
},
}, false, formData.toString());

if (!data.status) throw new Error('Wrong sessionId or pineId');
return data.status;
}
}

module.exports = PinePermManager;
38 changes: 5 additions & 33 deletions src/miscRequests.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,12 @@
const https = require('https');
const request = require('./request');

const PinePermManager = require('./classes/PinePermManager');
const PineIndicator = require('./classes/PineIndicator');

const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
const builtInIndicList = [];

/**
* @param {https.RequestOptions} options HTTPS Request options
* @param {boolean} [raw] Get raw or JSON data
* @param {string} [content] Request body content
* @returns {Promise<string | object | array>} Result
*/
function request(options = {}, raw = false, content = '') {
return new Promise((cb, err) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', (c) => { data += c; });
res.on('end', () => {
if (raw) {
cb(data);
return;
}

try {
data = JSON.parse(data);
} catch (error) {
err(new Error('Can\'t parse server response'));
return;
}

cb(data);
});
});

req.on('error', err);
req.end(content);
});
}

async function fetchScanData(tickers = [], type = '', columns = []) {
let data = await request({
method: 'POST',
Expand Down Expand Up @@ -437,6 +406,9 @@ module.exports = {
get() {
return module.exports.getIndicator(ind.scriptIdPart, ind.version);
},
getManager() {
return new PinePermManager(ind.scriptIdPart);
},
})));
});

Expand Down
37 changes: 37 additions & 0 deletions src/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const https = require('https');

/**
* @param {https.RequestOptions} options HTTPS Request options
* @param {boolean} [raw] Get raw or JSON data
* @param {string} [content] Request body content
* @returns {Promise<string | object | array>} Result
*/
function request(options = {}, raw = false, content = '') {
return new Promise((cb, err) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', (c) => { data += c; });
res.on('end', () => {
if (raw) {
cb(data);
return;
}

try {
data = JSON.parse(data);
} catch (error) {
console.log(data);
err(new Error('Can\'t parse server response'));
return;
}

cb(data);
});
});

req.on('error', err);
req.end(content);
});
}

module.exports = request;

0 comments on commit d1a1cfd

Please sign in to comment.