quill.api-test
Version:
a twitter bot api for node.js this os one option to bypass the pay wall for twiters api
578 lines (443 loc) • 22.8 kB
JavaScript
"use strict";
const {By, Builder, Capabilities, Key, until, promise} = require('selenium-webdriver'); //<-- Selenium Webdriver
const { QuillJsTypeError, ErrorCodes, QuillJsError } = require('../errors/index.js'); //<-- Import Error System --
const BaseClient = require('./BaseClient.js'); //<-- import the bace driver creation --
const Options = require('../util/options.js'); //<-- Not used right now --
const fs = require('fs'); // <-- File System for cookies --
const { DraftManager } = require('../managers/DraftManager.js'); //<-- Import Draft Manager --
const proxy = require('selenium-webdriver/proxy'); //<-- Selenium Webdriver Proxy Support!
const validDrivers = require('../util/validDrivers.json'); //<-- Import All ValidDriver types --
const Events = require('../util/Events.js'); //<-- Import Events --
const CookieManeger = require('../managers/CookiesManeger.js'); //<-- Import Cookie Manager --
const postConstructor = require('../managers/postManeger.js');
// Import all supported Selenium drivers
const chrome = require('selenium-webdriver/chrome');
const firefox = require('selenium-webdriver/firefox');
const edge = require('selenium-webdriver/edge');
const safari = require('selenium-webdriver/safari');
const ie = require('selenium-webdriver/ie');
const { text } = require('stream/consumers');
/**
* The base class for all clients. v
* @extends BaseClient
*/
class Client extends BaseClient {
/**
* @param {ClientOptions} options Options for the client
*/
constructor(options) {
super(options);
// const defaults = Options.createDefault(); < -- Not used
this.emit(Events.Debug, `[Client] Validationg options`);
this._validateOptions(options);
let DRoptions;
switch (options.driver) { //<-- Create options for the rite driver
case "chrome":
DRoptions = new chrome.Options();
break;
case "firefox":
DRoptions = new firefox.Options();
break;
case "edge":
DRoptions = new edge.Options();
break;
case "safari":
DRoptions = new safari.Options();
break;
case "ie":
DRoptions = new ie.Options();
break;
default:
throw new QuillJsTypeError(ErrorCodes.clientInvalidDriver, options.driver, validDrivers);
}
try
{
this.emit(Events.Debug, `[Client] Adding driver arguments`);
// alwayes added
DRoptions.addArguments('--lang=en');
// checks
if (options.ProxyClnt)
{
this.emit(Events.Debug, `[Client] initilising Proxy Client`);
// this.emit(Events.Debug, `Using Proxy Client with: ${options.ProxyClnt}`);
// DRoptions.setProxy(proxy.manual(options.ProxyClnt));
// /**
// * Networking Manager of the client
// * @type {NetworkManager}
// */
// this.network = new Network({ Client: this, cltOptions: options});
}
if(options.Headless)
{
this.emit(Events.Debug, `[!] The Driver will be opend in hedless mode`);
DRoptions.addArguments('--headless');
}
} finally
{
this.emit(Events.Debug, `[Client] Opening Driver Window`);
switch (options.driver) {
case "chrome":
this.driver = new Builder().forBrowser(options.driver).setChromeOptions(DRoptions).build();
break;
case "firefox":
this.driver = new Builder().forBrowser(options.driver).setFirefoxOptions(DRoptions).build();
break;
case "edge":
this.driver = new Builder().forBrowser(options.driver).setEdgeOptions(DRoptions).build();
break;
case "safari":
this.driver = new Builder().forBrowser(options.driver).setSafariOptions(DRoptions).build();
break;
case "ie":
this.driver = new Builder().forBrowser(options.driver).setIeOptions(DRoptions).build();
break;
default:
throw new QuillJsTypeError(ErrorCodes.clientInvalidDriver, options.driver, validDrivers);
}
/**
* Options for the driver return
*/
this.driverOptions = DRoptions;
}
/**
* Draft Manager Of the client
* @type {DraftManager}
*/
this.emit(Events.Debug, `[Client] Init Draft Manager`);
this.draft = new DraftManager(this);
this.emit(Events.Debug, `[!] Warning The Draft Maneger Has ben depricated`);
/**
* init the post manager with created class for refrensing
*/
this.emit(Events.Debug, `[Client] Init Post Manager`);
const init = new postConstructor.post({ driver: this.driver, client: this });
/**
* global little guy to store id's of sent messages.
*/
this.posts = [];
}
/**
* Logs the client in, establishing a conection To twitter.
* @param {string} [token=this.token] Login info of the account to log in with
* @returns {Promise<string>} Token of the account used
* @example
* client.login(username:"Display Name", password:"Password", cookies: JSONpath);
*/
login(options = {}) {
// this.emit(Events.Debug, `Provided Info: Uname: ${options.username} | Pwrd: ${"*".repeat(options.password.length)}`);
if (typeof options.username !== 'string')
{
throw new QuillJsTypeError(ErrorCodes.InvalidType, options.username, "String");
}
else if (typeof options.password !== 'string')
{
throw new QuillJsTypeError(ErrorCodes.InvalidType, options.password, "String");
}
else
{
this.emit(Events.Debug, `[Client] Attempting login`);
this.driver.get('https://x.com/i/flow/login', (err) => {
if (err)
{
this.emit(Events.Debug, ` Driver Get Error: ${err}`);
}
else
{
this.emit(Events.Debug, ` Nav Sucsess`);
}
});
this.driver.get('https://x.com/home', (err) => {
if (err) {
this.emit(Events.Debug, ` Driver Get Error: ${err}`);
} else {
this.emit(Events.Debug, ` Nav Sucsess`);
}
});
if(!options.cookies) { // Load without cookies
this.driver.wait(until.elementLocated(By.xpath("(//input[@name='text'])[1]"))).then((element) => {
this.emit(Events.Debug, `[Client] Going to Twitter home`);
this.driver.get("https://x.com", (err) => {
if (err) {
this.emit(Events.Debug, `Driver Get Error: ${err}`);
} else {
this.emit(Events.Debug, ` Nav Sucsess`);
}
}).then(() => {
this.emit(Events.Debug, `[client] Refreshing`);
this.driver.get("https://x.com/home").then(() => {
this.driver.get("https://x.com/home").then(() => {
this.driver.get("https://x.com/home").then(() => {
this.emit(Events.ClientReady)
});
});
});
});
});
} else if(CookieManeger.objectExists(options.cookies, options.username)){
this.emit(Events.Debug, `[Client] Using Cookies`);
CookieManeger.Load(options.cookies, options.username, this.driver).then((out) => {
this.emit(Events.Debug, `[Client] Validationg Cookies`);
if (out == 7) {
this.emit(Events.Debug, `[DEV!!] X idk why I put this here but its debug thats for shure`);
//wait for isername popup to appear
this.driver.wait(until.elementLocated(By.xpath("/html[1]/body[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[2]/div[2]/div[1]/div[1]/div[2]/div[2]/div[1]/div[1]/div[2]/label[1]/div[1]/div[2]/div[1]/input[1]"))).then((element) => {
throw new QuillJsError(ErrorCodes.LoginGatwayDetected, "Try again Using cookies or create the driver without --headless when constructing the client and manuly follow the steps!");
});
//check it account is valid
this.driver.wait(until.elementLocated(By.xpath("//span[normalize-space()='Sorry, we could not find your account.']"))).then((element) => {
throw new QuillJsError(ErrorCodes.LoginWrongUsername, options.username, "144");
});
//type in the password when it apperes
this.driver.wait(until.elementLocated(By.xpath("(//input[@name='password'])[1]"))).then((element) => {
this.emit(Events.Debug, `Sending Password! ${"*".repeat(options.password.length)}`);
element.sendKeys(options.password).then(() => {
element.sendKeys(Key.ENTER);
this.emit(Events.Debug, `NEXT!`);
for(let i = 0; i < this.options.loop; i++) {
element.sendKeys(Key.ENTER).then(() => {
});
}
});
});
//check for wrong password popup
this.driver.wait(until.elementLocated(By.xpath("//span[normalize-space()='Wrong password!']"))).then((element) => {
throw new QuillJsError(ErrorCodes.LoginWrongPassword, "*".repeat(options.password.length), "156");
});
//check for login gate way
this.driver.wait(until.elementLocated(By.xpath("(//span[@class='css-1qaijid r-bcqeeo r-qvutc0 r-poiln3'])[3]"))).then((element) => {
return new QuillJsError(ErrorCodes.LoginGatwayDetected, "loging in with cookies or login on difrent device");
});
//wait for login to be completed
this.driver.wait(until.urlIs("https://x.com/home")).then(() => {
this.emit(Events.Debug, `Saving Cookies...`);
this.driver.manage().getCookies().then((cookies) => {
CookieManeger.Save(cookies, options.cookies, options.username).then(() => {
this.emit(Events.ClientReady);
});
});
});
} else {
this.emit(Events.Debug, `[Client] Reloading Page`);
this.driver.get("https://x.com/home").then(() => {
this.driver.get("https://x.com/home").then(() => {
this.driver.get("https://x.com/home").then(() => {
this.driver.get("https://x.com/home").then(() => {
this.emit(Events.Debug, `[Client] Client Sucsefully Loaded with cookies!`);
this.emit(Events.ClientReady);
});
});
});
});
}
});
} else if(!CookieManeger.objectExists(options.cookies, options.username) && options.cookies) { // check to see if the username is not in the file but the cookies peramiter is used.
// Login to twitter and save cookies
this.driver.wait(until.elementLocated(By.xpath("(//input[@name='text'])[1]"))).then((element) => {
this.emit(Events.Debug, `Sending Username! ${options.username}`);
element.sendKeys(options.username).then(() => {
this.emit(Events.Debug, `NEXT!`);
element.sendKeys(Key.ENTER) // sends enter key to go to next page.
});
});
//wait for isername popup to appear
this.driver.wait(until.elementLocated(By.xpath("/html[1]/body[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[2]/div[2]/div[1]/div[1]/div[2]/div[2]/div[1]/div[1]/div[2]/label[1]/div[1]/div[2]/div[1]/input[1]"))).then((element) => {
throw new QuillJsError(ErrorCodes.LoginGatwayDetected, "Try again Using cookies or create the driver without --headless when constructing the client and manuly follow the steps!");
});
//check it account is valid
this.driver.wait(until.elementLocated(By.xpath("//span[normalize-space()='Sorry, we could not find your account.']"))).then((element) => {
throw new QuillJsError(ErrorCodes.LoginWrongUsername, options.username, "144");
});
//type in the password when it apperes
this.driver.wait(until.elementLocated(By.xpath("(//input[@name='password'])[1]"))).then((element) => {
this.emit(Events.Debug, `Sending Password! ${"*".repeat(options.password.length)}`);
element.sendKeys(options.password).then(() => {
element.sendKeys(Key.ENTER);
this.emit(Events.Debug, `NEXT!`);
for(let i = 0; i < this.options.loop; i++) {
element.sendKeys(Key.ENTER).then(() => {
});
}
});
});
//check for wrong password popup
this.driver.wait(until.elementLocated(By.xpath("//span[normalize-space()='Wrong password!']"))).then((element) => {
throw new QuillJsError(ErrorCodes.LoginWrongPassword, "*".repeat(options.password.length), "156");
});
//check for login gate way
this.driver.wait(until.elementLocated(By.xpath("(//span[@class='css-1qaijid r-bcqeeo r-qvutc0 r-poiln3'])[3]"))).then((element) => {
return new QuillJsError(ErrorCodes.LoginGatwayDetected, "loging in with cookies or login on difrent device");
});
//wait for login to be completed
this.driver.wait(until.urlIs("https://x.com/home")).then(() => {
this.emit(Events.Debug, `Saving Cookies...`);
this.driver.manage().getCookies().then((cookies) => {
CookieManeger.Save(cookies, options.cookies, options.username).then(() => {
this.emit(Events.ClientReady);
});
});
});
}
}
}
/**
* Validates the options passed to the client.
* @param {Object} options Options to validate
* @returns {Promise<void>} Valid options
* Used only For by the program. no need to use this.
*/
_validateOptions(options = this.options) {
return new Promise((resolve, reject) => {
this.emit(Events.Debug, `Validating Options...`);
if (!validDrivers.includes(options.driver)) {
throw new QuillJsTypeError(ErrorCodes.clientInvalidDriver, options.driver, validDrivers);
} else {
resolve();
}
});
}
/**
* Exits the client and destroys it.
* @param {fullwipe} [fulwipe = options.fullwipe] - to Destroy evrything including cookies.json. ! DANGER ZONE !
* @param {path} [path = options.path] - the path to the cookies.json file. ! only used for a full wipe !
* @example client.Exit(options = { fullwipe: false });
*/
Destroy(options)
{
if (options.fullwipe)
{
CookieManeger.wipeJSON(options.path);
}
this.driver.quit();
super.destroy();
}
Send(Data = []) {
this.emit(Events.Debug, `Composing New Tweet...`);
this.driver.get("https://x.com/compose/tweet").then(() => {
for(let i = 0; i <= Data.length; i++){
if(i > 0){
// add to thread
this.driver.findElement(By.xpath("//div[@aria-label='Add post']")).click();
}
if(Data[i].txt){
this.driver.findElement(By.xpath(`(//div[@aria-label='Post text'])[${i + 1}]`)).then((element) => {
if(this.driver.getType() == "chrome"){ // <-- chrome dose not support emojies
var regex = /[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2580-\u27BF]|\uD83E[\uDD10-\uDDFF]/g; // <-- serches for emojies and removes them
var result = data.content.replace(regex, ' ');
element.sendKeys(result);
}
else{
element.sendKeys(Data[i].txt);
}
});
}
if(Data[i].media){
this.driver.findElement(By.xpath(`(//input[@aria-label='Add photos or video'])[${i + 1}]`))
.then((element) => {
element.sendKeys(Data[i].media);
});
}
if(i == Data.length){
return new Promise((resolve, reject) => {
this.driver.wait(until.elementLocated(By.xpath("//span[normalize-space()='Post']")))
.then((element) => {
this.emit(Events.Debug, `Waiting for the tweet to be ready...`);
for(let i = 0; i <= this.options.MaxloopTterations; i++) {
setInterval(() => {
element.getAttribute("aria-disabled").then(() => {
this.emit(Events.Debug, `Sending Tweet...`);
element.click();
resolve();
});
}, this.options.loopDelayLength);
}
});
// Alredy Sent!
CL.driver.wait(until.elementLocated(By.xpath("//span[normalize-space()='Whoops! You already said that.']"))).then((element) => {
CL.emit(Events.Warn, "You already said that")
});
});
}
}
});
}
// \/ Depricated functions Might be removed in the future \/
draft_Tweet() {
return new Promise((resolve, reject) => {
this.emit(Events.Debug, `Composing New Tweet...`);
this.driver.get("https://x.com/compose/tweet").then(() => {
resolve();
});
});
}
/**
* Adds a bit of text to the draft you just created.
* - Make shure you make a new draft before you do this othorwise thigs will mess up
* @param {string} [token=this.token] Login info of the account to log in with
* @returns {Promise<string>} Token of the account used
* @example
* client.login(username:"Display Name", numbmail:"Email / Phone Numb", password:"Password");
*/
addText(text) {
return new Promise((resolve, reject) => {
this.driver
.wait(
until.elementLocated(By.xpath("(//div[@aria-label='Post text'])[1]"))
)
.then((element) => {
this.emit(Events.Debug, `Adding Text: ${text}`);
element.sendKeys(text).then(() => {
resolve();
});
});
});
}
/**
* Adds an imige to the draft you created
* - Make shure you make a new draft before you do this othorwise thigs will mess up
* @param {string} [token=this.token] Login info of the account to log in with
* @returns {Promise<string>} Token of the account used
* @example
* client.login(username:"Display Name", numbmail:"Email / Phone Numb", password:"Password");
*/
addMedia(Path) {
return new Promise((resolve, reject) => {
this.driver.wait( until.elementLocated(By.xpath("//input[@type='file']")) ).then((element) => {
this.emit(Events.Debug, `Adding Media: ${Path}`);
element.sendKeys(Path).then(() => {
resolve();
});
});
});
}
/**
* Sends the draft you just created.
*
*/
Send() {
return new Promise((resolve, reject) => {
this.driver.wait(until.elementLocated(By.xpath("//span[normalize-space()='Post']")))
.then((element) => {
this.emit(Events.Debug, `Waiting for the tweet to be ready...`);
for(let i = 0; i <= this.options.MaxloopTterations; i++) {
setInterval(() => {
element.getAttribute("aria-disabled").then(() => {
this.emit(Events.Debug, `Sending Tweet...`);
element.click();
resolve();
});
}, this.options.loopDelayLength);
}
});
// Alredy Sent!
this.driver.wait(until.elementLocated(By.xpath("//span[normalize-space()='Whoops! You already said that.']"))).then((element) => {
this.emit(Events.Warn, "You already said that")
});
});
}
}
module.exports = {
Client,
};
////////////////////////////////////////////////////
// © 2023 DogeProductions. All rights reserved. //
////////////////////////////////////////////////////