|
|
|
|
|
|
|
|
import Signer from "tiktok-signature"; |
|
|
import axios, { AxiosResponse } from "axios"; |
|
|
import querystring from "querystring"; |
|
|
import { Browser, Page, BrowserContext } from "playwright"; |
|
|
import { exec } from 'child_process'; |
|
|
import { promisify } from 'util'; |
|
|
|
|
|
|
|
|
const execPromise = promisify(exec); |
|
|
|
|
|
const TT_REQ_USER_AGENT = |
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56"; |
|
|
|
|
|
const queryParams = { |
|
|
WebIdLastTime: "1739192062", |
|
|
aid: "1988", |
|
|
app_language: "en", |
|
|
app_name: "tiktok_web", |
|
|
browser_language: "en-US", |
|
|
browser_name: "Mozilla", |
|
|
browser_online: "true", |
|
|
browser_platform: "Win32", |
|
|
|
|
|
browser_version: |
|
|
"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0", |
|
|
channel: "tiktok_web", |
|
|
cookie_enabled: "true", |
|
|
count: "35", |
|
|
coverFormat: "2", |
|
|
cursor: "0", |
|
|
device_id: "7469772981536687624", |
|
|
device_platform: "web_pc", |
|
|
focus_state: "true", |
|
|
from_page: "user", |
|
|
history_len: "8", |
|
|
is_fullscreen: "false", |
|
|
is_page_visible: "true", |
|
|
language: "en", |
|
|
os: "windows", |
|
|
priority_region: "RO", |
|
|
referer: "https://www.tiktok.com/business-suite/messages?from=homepage&lang=en", |
|
|
region: "RO", |
|
|
root_referer: "https://www.tiktok.com/business-suite/messages?from=homepage&lang=en", |
|
|
screen_height: "1080", |
|
|
screen_width: "1920", |
|
|
secUid: |
|
|
"MS4wLjABAAAAAbt3ELTXRAMA0RVzgFjvOPEhWIKyDmY9pxPKUj5nJGIatb9xH7lTeIsU_322Sc0p", |
|
|
tz_name: "Europe/Bucharest", |
|
|
verifyFp: "verify_m6z21ydc_1d2kIOvr_Re3f_4Fit_AmQz_HL2g9Rui8xZp", |
|
|
webcast_language: "en", |
|
|
msToken: |
|
|
"Gv-tA8UBoR9MrpShqSMLW5jqxKbjgqn1NJx3h2AWw3AYNZl3eKUG3855040l0RcW0Ap6uoknQRj5Iq7YuaSFeDqwAh0dubDNHQSaoYgziHNyXVdnDqIAz1WdoQx_mXEM860DcE64CXvr9nzcrBaiimxfwA==", |
|
|
"X-Bogus": "DFSzswVuXEvANePAtkyrUO0EsCa-", |
|
|
_signature: "_02B4Z6wo000019pWL7AAAIDCIaUS-UMYoj.aVisAAJFF2a" |
|
|
}; |
|
|
const TT_REQ_PERM_URL = |
|
|
`https://www.tiktok.com/api/post/item_list/?${new URLSearchParams(queryParams).toString()}`; |
|
|
|
|
|
const parsedUrl = new URL(TT_REQ_PERM_URL); |
|
|
const parsedQuery = querystring.parse(parsedUrl.search.slice(1)); |
|
|
|
|
|
interface SignerInterface { |
|
|
init(): Promise<void>; |
|
|
sign(url: string): Promise<{ "x-tt-params": string; signed_url: string }>; |
|
|
navigator(): Promise<{ user_agent: string }>; |
|
|
browser: Browser; |
|
|
context: BrowserContext; |
|
|
} |
|
|
|
|
|
|
|
|
const signer: SignerInterface = new Signer(null, TT_REQ_USER_AGENT); |
|
|
|
|
|
|
|
|
(async function () { |
|
|
await signer.init(); |
|
|
})(); |
|
|
|
|
|
async function main(username: string): Promise<void> { |
|
|
try { |
|
|
const options = { |
|
|
method: "GET", |
|
|
url: "https://www.tiktok.com/@" + username, |
|
|
}; |
|
|
|
|
|
const response = await axios(options); |
|
|
const data: string = response.data; |
|
|
const secUidMatch = data.match(/"secUid":"(.*?)"/); |
|
|
const SEC_UID = secUidMatch ? secUidMatch[1] : "No secUid found"; |
|
|
console.log(SEC_UID) |
|
|
const PARAMS = { |
|
|
count: 30, |
|
|
device_id: "7165118680723998211", |
|
|
secUid: SEC_UID, |
|
|
cursor: 0, |
|
|
}; |
|
|
|
|
|
|
|
|
const mergedParams = { ...parsedQuery, ...PARAMS }; |
|
|
|
|
|
const qsObject = new URLSearchParams(mergedParams as any); |
|
|
const qs = qsObject.toString(); |
|
|
|
|
|
const unsignedUrl = `https://www.tiktok.com/api/post/item_list/?${qs}`; |
|
|
const signature = await signer.sign(unsignedUrl); |
|
|
const navigatorData = await signer.navigator(); |
|
|
const { "x-tt-params": xTtParams, signed_url } = signature; |
|
|
const { user_agent: userAgent } = navigatorData; |
|
|
|
|
|
const res = await testApiReq({ userAgent, xTtParams, signed_url }); |
|
|
if(res.data.length == 0) { |
|
|
throw new Error("No data") |
|
|
} |
|
|
console.log(JSON.stringify({...res.data, error: false})); |
|
|
} catch (e) { |
|
|
console.log( |
|
|
JSON.stringify({ error: true, errorlog: String(e).trim() }) |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
async function twitch(fun: string): Promise<void> { |
|
|
try { |
|
|
if (!signer.context) { |
|
|
console.log( |
|
|
JSON.stringify({ |
|
|
error: true, |
|
|
online: false, |
|
|
errorlog: "Browser not initialized, please wait a moment", |
|
|
}) |
|
|
); |
|
|
return; |
|
|
} |
|
|
const p: Page = await signer.context.newPage(); |
|
|
await p.goto("https://www.twitch.tv/" + fun); |
|
|
await p.waitForLoadState('networkidle') |
|
|
const CheckUserStatus = await p.evaluate(() => { |
|
|
try { |
|
|
document.getElementsByClassName("home__lower-content")[0].remove() |
|
|
const OfflineCheck = document.getElementsByClassName( |
|
|
"channel-status-info channel-status-info--offline" |
|
|
)[0]?.getElementsByTagName("p")[0].textContent; |
|
|
|
|
|
const OnlineCheck = document.getElementsByClassName( |
|
|
"tw-channel-status-text-indicator" |
|
|
)[0]; |
|
|
|
|
|
console.log(OnlineCheck, OfflineCheck); |
|
|
|
|
|
if (!OfflineCheck && !OnlineCheck) { |
|
|
return { online: false, error: true, errorlog: "User not Found" }; |
|
|
} else if (OfflineCheck) { |
|
|
return { online: false, error: false }; |
|
|
} else if (OnlineCheck) { |
|
|
const getText = (selector: string): string => { |
|
|
const element = document.querySelector(selector); |
|
|
return element instanceof HTMLElement ? element.innerText : ""; |
|
|
}; |
|
|
|
|
|
const viewers = getText( |
|
|
"p[data-a-target='animated-channel-viewers-count']" |
|
|
); |
|
|
const streamTime = getText(".live-time").split("\n")[0]; |
|
|
const title = getText('[data-a-target="stream-title"]'); |
|
|
const game = getText("a[data-a-target=stream-game-link] span"); |
|
|
|
|
|
if (!viewers && !streamTime && !title && !game) { |
|
|
return { online: false, error: true }; |
|
|
} |
|
|
return { |
|
|
online: true, |
|
|
viewers, |
|
|
streamTime, |
|
|
title, |
|
|
game, |
|
|
error: false, |
|
|
}; |
|
|
} |
|
|
} catch (e) { |
|
|
return { online: false, error: true, errorlog: String(e) }; |
|
|
} |
|
|
}); |
|
|
|
|
|
await p.close(); |
|
|
console.log(JSON.stringify(CheckUserStatus)); |
|
|
} catch (e) { |
|
|
console.log( |
|
|
JSON.stringify({ error: true, online: false, errorlog: String(e).trim() }) |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function kick(fun: string): Promise<void> { |
|
|
try { |
|
|
const curlCommand = `curl_chrome116 'https://kick.com/api/v1/channels/${encodeURIComponent(fun)}'`; |
|
|
const v = await execPromise(curlCommand.trim()); |
|
|
let k = (JSON.parse(v.stdout)); |
|
|
let u = k.livestream |
|
|
if(!u) { |
|
|
console.log(JSON.stringify({error: false, online: false})) |
|
|
return |
|
|
} |
|
|
console.log(JSON.stringify({error: false, online: true, ...u})) |
|
|
return; |
|
|
} catch (e) { |
|
|
console.log( |
|
|
JSON.stringify({ error: true, online: false, errorlog: String(e).trim() }) |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
interface TestApiReqParams { |
|
|
userAgent: string; |
|
|
xTtParams: string; |
|
|
signed_url: string; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function testApiReq({ |
|
|
userAgent, |
|
|
xTtParams, |
|
|
signed_url, |
|
|
}: TestApiReqParams): Promise<AxiosResponse<any>> { |
|
|
const options = { |
|
|
method: "GET", |
|
|
headers: { |
|
|
"user-agent": userAgent, |
|
|
"x-tt-params": xTtParams, |
|
|
}, |
|
|
url: signed_url, |
|
|
}; |
|
|
return axios(options); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
process.stdin.on("data", (data: Buffer) => { |
|
|
|
|
|
try { |
|
|
const fun = data.toString().trim().split(" "); |
|
|
switch (fun[0]) { |
|
|
case "twitch": |
|
|
twitch(fun.slice(1).join(" ")); |
|
|
break; |
|
|
case "kick": |
|
|
kick(fun.slice(1).join(" ")); |
|
|
break; |
|
|
default: |
|
|
|
|
|
main(fun.join(" ")); |
|
|
|
|
|
} |
|
|
} catch (e) { |
|
|
console.log(JSON.stringify({ error: true, errorlog: String(e) })); |
|
|
} |
|
|
}); |
|
|
|