Skip to content

Instantly share code, notes, and snippets.

@Bizarrus
Last active January 28, 2025 08:47
Show Gist options
  • Save Bizarrus/f05f3b2b930855087957131c5030f468 to your computer and use it in GitHub Desktop.
Save Bizarrus/f05f3b2b930855087957131c5030f468 to your computer and use it in GitHub Desktop.
K3-Register
import FileSystem from 'node:fs';
import HTTPS from 'node:https';
import { parse as HTMLParser } from 'node-html-parser';
export default (new class ClientProperties {
File = './client.json';
BaseURL = 'https://app.knuddels.de/';
Properties = {
build: null,
version: null,
platform: 'Web',
type: 'K3GraphQl',
browser: {
name: 'Firefox',
version: 132,
useragent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
},
urls: {
login: 'https://www.knuddels.de/logincheck.html',
graph: null,
subscription: null,
origin: 'https://app.knuddels.de',
referer: 'https://app.knuddels.de',
photo: {
login: {
page: 'https://photo.knuddels.de/photos-login.html?d=knuddels.de',
action: 'https://photo.knuddels.de/photos-login_submit.html'
},
upload: {
page: 'https://photo.knuddels.de/photos-settings.html?mode=uploadprofilephoto',
action: 'https://upload.knuddels.de'
},
delete: 'https://photo.knuddels.de/photos-delete.html?id='
},
register: {
check: 'https://www.knuddels.de/registration/registration_submit.html',
origin: 'https://www.knuddels.de',
}
}
};
constructor() {
if(this.exists()) {
try {
this.Properties = Object.assign({}, this.Properties, JSON.parse(FileSystem.readFileSync(this.File, 'utf8')));
//console.log('Loaded ClientProperties:', this.Properties);
} catch(error) {
console.warn('Can\'t parse', this.File, ':', error);
}
}
}
exists() {
return FileSystem.existsSync(this.File);
}
resolve() {
console.log('Trying to resolve ClientProperties from ' + this.BaseURL + '...');
return new Promise((success, failure) => {
this.requesting(this.BaseURL, (data) => {
const document = HTMLParser(data);
const scripts = document.getElementsByTagName('script');
let found = null;
scripts.forEach((script) => {
let src = script.getAttribute('src');
if(src.match(/index\-([a-zA-Z0-9]+)\.js/g)) {
found = src;
}
});
if(found === null) {
failure('Can\'t found main-script on ' + this.BaseURL);
return;
}
this.requesting(this.BaseURL + found, (data) => {
let build = null;
let version = null;
let url_graph = null;
let url_subscr = null;
if(data.match(/(gitRevision):"([^"]+)",(version):"([^"]+)"/gi)) {
const matches = /(gitRevision):"([^"]+)",(version):"([^"]+)"/gi.exec(data);
build = matches[2];
version = matches[4];
} else {
failure('Can\'t found Revision or Version on main-script');
return;
}
if(data.match(/(graphQl):"([^"]+)"/gi)) {
const matches = /(graphQl):"([^"]+)"/gi.exec(data);
url_graph = matches[2];
} else {
failure('Can\'t found graphQl on main-script');
return;
}
if(data.match(/(graphQlSubscription):"([^"]+)"/gi)) {
const matches = /(graphQlSubscription):"([^"]+)"/gi.exec(data);
url_subscr = matches[2];
} else {
failure('Can\'t found graphQlSubscription on main-script');
return;
}
if(build !== null) {
this.Properties.build = build;
}
if(version !== null) {
this.Properties.version = version;
}
if(url_graph !== null) {
this.Properties.urls.graph = url_graph;
}
if(url_subscr !== null) {
this.Properties.urls.subscription = url_subscr;
}
//console.log('Updated ClientProperties:', this.Properties);
FileSystem.writeFile(this.File, JSON.stringify(this.Properties, null, 4), (error) => {
if(error) {
console.error('Can\'t save', this.File, ':', error);
return;
}
console.warn(this.File, 'was saved on root-directory!');
});
success();
});
});
});
}
requesting(file, callback) {
HTTPS.get(file, (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
callback(data);
});
}).on('error', (error) => {
console.log('HTTP-Error:', error.message);
})
}
get(key) {
if(key.indexOf('.') !== -1) {
return key.split('.').reduce((current, key) => current && current[key], this.Properties);
}
if(typeof(this.Properties[key]) !== 'undefined') {
return this.Properties[key];
}
return null;
}
}());
npm install --save puppeteer-extra puppeteer puppeteer-extra-plugin-stealth node-html-parser
import HTTPS from 'node:https';
import ClientProperties from '../ClientProperties.class.js';
import puppeteer from 'puppeteer-extra';
import { executablePath } from 'puppeteer';
import pluginStealth from 'puppeteer-extra-plugin-stealth';
import FileSystem from 'node:fs';
import Readline from 'node:readline';
import Process from 'node:process';
export const Register = (new class Register {
RecaptchaKey = '6LcTjIoUAAAAAD0Xd19UnkbArq-qb2nQWdFiG6IX';
checkNick(nickname) {
return this.request({
submitElement: 'nick',
nick: nickname,
resultAsJSON: true,
isAjax: true
});
}
registerNick(nickname, password, age, gender) {
return new Promise((success, failure) => {
let data = {
submitElement: 'fullRegV1',
nick: nickname,
password: password,
age: age,
dsgvoQuestion: true,
category: 'chat',
platform: 'web',
flags: 'RegistrationEroticContentDecision_Hide',
gender: gender,
uids: '',
resultAsJSON: true,
isAjax: true
};
this.startRecaptchaInjector(this.RecaptchaKey).then((token) => {
data.recaptchaToken = token;
this.request(data).then((response) => {
console.log("REG", response);
//success(); @ToDo?
}).catch((error) => {
switch(error.code) {
case 'VERIFICATION_REQUIRED':
this.createVerify(data);
break;
default:
failure(error);
break;
}
});
}).catch(failure);
});
}
createVerify(regdata, number) {
if(typeof(number) !== 'undefined') {
this.receiveCode(regdata, number);
return;
}
this.CLI.question('Knuddels need a verified Telephone-Number.\nPlease enter your Telephone Number (Sample: +491234567890):', telephone => {
this.receiveCode(regdata, telephone);
});
}
receiveCode(regdata, telephone) {
this.request({
submitElement: 'phone-verify',
phoneNumber: telephone,
regionCode: 'DE',
resultAsJSON: true,
isAjax: true
}).then((phoneid) => {
this.validateVerify(regdata, phoneid);
}).catch((error) => {
switch(error.code) {
case 'PHONE_NUMBER_INVALID':
console.error('The entered Telephone Number is invalid!');
this.createVerify(regdata);
break;
case 'PHONE_NUMBER_QUOTA_EXCEEDED':
console.error('You have entered the wrong phone number too often!\nTry again later!');
this.CLI.close();
break;
case 'PHONE_NUMBER_COUNTRY_BLOCKED':
console.error('The Country of entered Telephone Number is BLOCKED!');
this.createVerify(regdata);
break;
case 'PHONE_NUMBER_OF_BLOCKED_USER_SOFT':
console.error('The entered Telephone Number has already an User registred and these is temporary BLOCKED!');
this.createVerify(regdata);
break;
case 'PHONE_NUMBER_OF_BLOCKED_USER_HARD':
console.error('The entered Telephone Number has already an User registred and these is permanently BLOCKED!');
this.createVerify(regdata);
break;
case 'VERIFICATION_CODE_WRONG':
console.error('The Code is Wrong!');
this.validateVerify(regdata, error.phoneid);
break;
case 'VERIFICATION_CODE_EXPIRED':
console.error('The Code is Expired!');
this.createVerify(regdata, telephone);
break;
case 'VERIFICATION_TOO_MANY_RETRIES':
console.error('You have tried the verificationtoo often!\nTry again later!');
this.CLI.close();
break;
default:
console.log(error);
break;
}
});
}
validateVerify(regdata, phoneid) {
this.CLI.question('Enter Verification-Code:', code => {
this.startRecaptchaInjector(this.RecaptchaKey).then((token) => {
regdata.recaptchaToken = token;
regdata.phoneVerificationRegId = phoneid
regdata.phoneVerificationCode = code;
this.request(regdata).then((nickname) => {
console.log("REG Finished:", nickname);
}).catch((error) => {
switch(error.code) {
case 'VERIFICATION_REQUIRED':
this.createVerify(regdata);
break;
default:
console.log("Error: ", error);
break;
}
});
}).catch((error) => {
console.log("Error: ", error);
});
});
}
request(data) {
return new Promise((success, failure) => {
let body = new URLSearchParams(data).toString();
let request = HTTPS.request(ClientProperties.get('urls.register.check'), {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Content-Length': body.length,
'Accept': 'application/json',
'Origin': ClientProperties.get('urls.register.origin'),
'Referer': ClientProperties.get('urls.referer'),
'User-Agent': ClientProperties.get('browser.useragent'),
'X-Requested-With': 'XMLHttpRequest'
},
}, (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
console.log(data);
try {
let json = JSON.parse(data);
if(typeof(json.errors) !== 'undefined') {
failure({
code: json.errors[0].errorType,
text: null
});
} else if(typeof(json.JSONData) !== 'undefined') {
success(json.JSONData.nick);
} else if(typeof(json.FormData) !== 'undefined') {
let form = json.FormData;
if(typeof(form.errors) === 'undefined') {
success(json.nickContext);
return;
}
failure({
code: form.errors.nick.error,
text: form.errors.nick.text.trim()
});
} else if(typeof(json.regId) !== 'undefined') {
switch(json.status) {
case 'PHONE_NUMBER_INVALID':
case 'PHONE_NUMBER_QUOTA_EXCEEDED':
case 'PHONE_NUMBER_COUNTRY_BLOCKED':
case 'PHONE_NUMBER_OF_BLOCKED_USER_SOFT':
case 'PHONE_NUMBER_OF_BLOCKED_USER_HARD':
case 'VERIFICATION_CODE_WRONG':
case 'VERIFICATION_CODE_EXPIRED':
case 'VERIFICATION_TOO_MANY_RETRIES':
failure({
code: json.status,
text: null,
phoneid: json.regId
});
break;
default:
success(json.regId);
break;
}
}
} catch(error) {
failure({
code: 'BAD_RESPONSE',
text: error
});
}
});
});
request.on('error', (error) => {
failure('BAD_RESPONSE', error.message);
});
request.write(body);
request.end();
});
}
startRecaptchaInjector(key) {
return new Promise((success, failure) => {
puppeteer.use(pluginStealth());
puppeteer.launch({
executablePath: executablePath(),
headless: true,
ignoreHTTPSErrors: true
}).then(async (browser) => {
try {
browser.newPage().then(async (page) => {
await page.setUserAgent(ClientProperties.get('browser.useragent'));
await page.setViewport({
width: 1920 + Math.floor(Math.random() * 100),
height: 3000 + Math.floor(Math.random() * 100),
deviceScaleFactor: 1,
hasTouch: false,
isLandscape: false,
isMobile: false
});
await page.setJavaScriptEnabled(true);
await page.setDefaultNavigationTimeout(0)
await page.evaluateOnNewDocument(() => {
window.chrome = {
runtime: {}
};
});
await page.setRequestInterception(true);
page.on('request', request => {
request.continue();
});
await page.setContent(`<script src="https://www.google.com/recaptcha/api.js?render=${key}"></script>
<script>
window.CAPTCHA = null;
let watcher = setInterval(() => {
if(window.CAPTCHA !== null) {
clearInterval(watcher);
return;
}
window.grecaptcha.ready(() => {
window.grecaptcha.execute('${key}', {
action: 'submit'
}).then(token => {
window.CAPTCHA = {
token: token,
error: null
};
}).catch(error => {
window.CAPTCHA = {
token: null,
error: error
};
})
});
}, 1500);
</script>`);
const waitForVariable = (page, variableName) => {
return page.evaluate((name) => {
return new Promise((resolve) => {
const checkVariable = () => {
if (window[name] !== undefined) {
resolve(window[name]);
} else {
setTimeout(checkVariable, 50);
}
};
checkVariable();
});
}, variableName);
};
let watch = setInterval(async () => {
const CAPTCHA = await waitForVariable(page, 'CAPTCHA');
if(CAPTCHA == null) {
return;
}
clearInterval(watch);
if(CAPTCHA.token !== null) {
success(CAPTCHA.token);
} else if(CAPTCHA.error !== null) {
failure(CAPTCHA.error);
}
await browser.close();
}, 1000);
});
} catch (e) {
failure(e);
}
});
});
}
}());
export default Register;
import Register from './Register.class.js';
Register.checkNick('Testnickname 1').then((nickname) => {
Register.registerNick(nickname, 'Passwort1234', 20, 'female').then(() => {
console.log('Nickname Successfully registred! :-)');
}).catch((error) => {
console.error('[FEHLER]', error);
});
}).catch((error) => {
console.error('[FEHLER]', error);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment