Skip to content

Commit ce251da

Browse files
committed
Update and fix feature
1 parent e60be06 commit ce251da

6 files changed

+205
-3091
lines changed

SHPBOT-addon.bat

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@echo off
2+
cd /d "%~dp0"
3+
4+
call npm install
5+
call node SHPBOT.js addon
6+
pause
7+
exit /b 1

SHPBOT-addon.sh

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
npm install
3+
node SHPBOT.js addon
4+

SHPBOT.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

library/file_helper.cjs

+1-22
Original file line numberDiff line numberDiff line change
@@ -1,22 +1 @@
1-
const fs = require("fs").promises;
2-
3-
const isFileExist = async (filePath) => {
4-
const fileExists = await fs
5-
.access(filePath)
6-
.then(() => true)
7-
.catch(() => false);
8-
9-
if (fileExists) {
10-
console.log("File saved successfully:", filePath);
11-
return true;
12-
} else {
13-
console.error("Error: File not saved.");
14-
return false;
15-
}
16-
}
17-
18-
const getRandomDelay = (min=3000, max=30000) => {
19-
return Math.floor(Math.random() * (max - min + 1) + min);
20-
}
21-
22-
module.exports = {getRandomDelay, isFileExist}
1+
const fs=require("fs").promises,isFileExist=async e=>await fs.access(e).then((()=>!0)).catch((()=>!1))?(console.log("File saved successfully:",e),!0):(console.error("Error: File not saved."),!1),getRandomDelay=(e=3e3,s=3e4)=>Math.floor(Math.random()*(s-e+1)+e);module.exports={getRandomDelay:getRandomDelay,isFileExist:isFileExist};

library/streamer.cjs

+1-313
Original file line numberDiff line numberDiff line change
@@ -1,313 +1 @@
1-
const {mkdirSync} = require("fs");
2-
const chalk = require("chalk");
3-
const path = require('path')
4-
5-
let ffmpegGlobalProcess = null;
6-
7-
function decodeUnicodeEscape(url) {
8-
return url.replace(/\\u([\d\w]{4})/gi, (match, grp) => {
9-
return String.fromCharCode(parseInt(grp, 16));
10-
});
11-
}
12-
const videoProcessing = async ({liveUrl, rtmpServer = null, rtmpKey = null, isInfiniteMode = false, streamDuration = null, baseFilePath = "", mode = "stream"}) =>{
13-
console.log(chalk.blue("Starting the streaming process. Please wait..."));
14-
const childProcess = require("child_process");
15-
const {existsSync} = require("fs");
16-
let outputDestination;
17-
let secondaryOutput = null;
18-
// Dynamically import boxen due to ES Module requirement
19-
if (mode === "restream") {
20-
if (rtmpServer && rtmpKey) {
21-
outputDestination = `${rtmpServer}${rtmpKey}`;
22-
secondaryOutput = `${baseFilePath}.flv`;
23-
} else {
24-
outputDestination = `${baseFilePath}.flv`;
25-
}
26-
} else {
27-
outputDestination = `${rtmpServer}${rtmpKey}`;
28-
}
29-
30-
// Function to generate a unique file name to avoid conflicts
31-
const generateUniqueFileName = (baseName, extension) => {
32-
let counter = 0;
33-
let uniqueName = `${baseName}${counter > 0 ? `-${counter}` : ''}.${extension}`;
34-
while (existsSync(uniqueName)) {
35-
counter++;
36-
uniqueName = `${baseName}${counter > 0 ? `-${counter}` : ''}.${extension}`;
37-
}
38-
return uniqueName;
39-
};
40-
41-
// Adjusting file names to be unique if necessary
42-
if (secondaryOutput && existsSync(secondaryOutput)) {
43-
secondaryOutput = generateUniqueFileName(baseFilePath, 'flv');
44-
}
45-
if (existsSync(outputDestination)) {
46-
const extension = outputDestination.split('.').pop();
47-
outputDestination = generateUniqueFileName(baseFilePath, extension);
48-
}
49-
50-
process.stdout.clearLine(0);
51-
process.stdout.cursorTo(0);
52-
const messageComponents = [
53-
chalk.white('Live Url : ')+chalk.red(`${liveUrl}`),
54-
chalk.white('Output Destination : ')+chalk.yellow(`${outputDestination}`),
55-
chalk.white('Secondary Output : ')+chalk.green(`${secondaryOutput || 'Not Applicable'}`)
56-
];
57-
const message = messageComponents.join('\n');
58-
// Ensuring borderLength is not negative
59-
const borderLength = Math.max(0, message.length - chalk.reset(message).length + 20);
60-
const border = chalk.blue('='.repeat(borderLength));
61-
process.stdout.write(`${border}\n${message}\n${border}`);
62-
const ffmpegArgs = [
63-
"-re",
64-
"-stream_loop", "-1",
65-
"-i", liveUrl,
66-
"-r", "30",
67-
"-b:v", "2000k",
68-
"-c:v", "libx264",
69-
"-preset", "veryfast",
70-
"-c:a", "aac",
71-
"-f", "flv",
72-
"-loglevel", "info",
73-
"-hide_banner",
74-
outputDestination
75-
];
76-
77-
if (secondaryOutput) {
78-
ffmpegArgs.push(secondaryOutput);
79-
}
80-
81-
if (streamDuration) {
82-
ffmpegArgs.push("-t", streamDuration.toString());
83-
}
84-
85-
ffmpegGlobalProcess = childProcess.spawn("ffmpeg", ffmpegArgs);
86-
let errorCount = 0;
87-
let totalSeconds = 0, currentTime = 0;
88-
89-
ffmpegGlobalProcess.stderr.on("data", data => {
90-
const message = data.toString();
91-
const durationRegex = /Duration: (\d{2}):(\d{2}):(\d{2})\.\d{2},/;
92-
const timeRegex = /time=(\d{2}):(\d{2}):(\d{2})\.\d{2}/;
93-
const durationMatch = message.match(durationRegex);
94-
const timeMatch = message.match(timeRegex);
95-
96-
if (durationMatch) {
97-
totalSeconds = parseInt(durationMatch[1]) * 3600 + parseInt(durationMatch[2]) * 60 + parseInt(durationMatch[3]);
98-
}
99-
100-
if (timeMatch) {
101-
currentTime = parseInt(timeMatch[1]) * 3600 + parseInt(timeMatch[2]) * 60 + parseInt(timeMatch[3]);
102-
const progress = (currentTime / totalSeconds * 100).toFixed(2);
103-
if(streamDuration && currentTime >= streamDuration){
104-
ffmpegGlobalProcess.kill('SIGINT');
105-
}
106-
process.stdout.clearLine(0);
107-
process.stdout.cursorTo(0);
108-
process.stdout.write(chalk.green(`Streaming Progress: [${progress}%] | Time Elapsed: ${currentTime} seconds`));
109-
}
110-
if (message.includes("error")) {
111-
errorCount += 1;
112-
if (errorCount > 5) {
113-
console.log(chalk.red("We encountered a problem and need to stop. Please try again."));
114-
ffmpegGlobalProcess.kill('SIGINT');
115-
}
116-
}
117-
});
118-
ffmpegGlobalProcess.on("close", (code, signal) => {
119-
const exitMessage = `Streaming process exited with code: ${code} and signal: ${signal}`;
120-
const restartMessage = "Restarting in 3 seconds...";
121-
const restartProcess = () => {
122-
setTimeout(() => {
123-
try {
124-
videoProcessing({liveUrl: secondaryOutput, rtmpServer, rtmpKey, isInfiniteMode, streamDuration, baseFilePath, mode});
125-
} catch (error) {
126-
console.error(chalk.red(`Error restarting streaming process: ${error.message}`));
127-
}
128-
}, 3000);
129-
};
130-
131-
if (code !== 0) {
132-
console.log(chalk.magenta(exitMessage));
133-
console.log(chalk.yellow("The streaming process has ended unexpectedly. Attempting to restart..."));
134-
} else {
135-
console.log(chalk.green("Streaming completed successfully."));
136-
}
137-
138-
if (isInfiniteMode || (errorCount <= 5 && mode === "restream" && rtmpServer && rtmpKey)) {
139-
console.log(chalk.blue(restartMessage));
140-
restartProcess();
141-
}
142-
});
143-
144-
process.on("SIGINT", () => {
145-
console.log(chalk.magenta("Streaming has been stopped. Thank you for using our service."));
146-
if (ffmpegGlobalProcess) {
147-
ffmpegGlobalProcess.kill('SIGINT');
148-
}
149-
process.exit();
150-
});
151-
}
152-
const reStreamShopee = async ({videoUrl, rtmpServer = null, rtmpKey = null, isInfiniteMode = false, streamDuration = null}) => {
153-
return new Promise(async (resolve, reject) => {
154-
try {
155-
let streamData = null;
156-
let liveUrl = `${__dirname}/../${videoUrl}`;
157-
let mode = "stream";
158-
if(!videoUrl.includes("mp4") && !videoUrl.includes("flv")){
159-
streamData = await getShopeeStreamDetails(videoUrl);
160-
if (!streamData) {
161-
throw new Error("Stream details could not be retrieved.");
162-
}
163-
liveUrl = decodeURIComponent(streamData.play_url);
164-
mode = "restream";
165-
}
166-
const baseFilePath = `${__dirname}/../stream_output/${streamData?.room_id}-${streamData?.username}`;
167-
168-
console.info(`\nCtrl+C to stop downloading and exit`)
169-
await videoProcessing({liveUrl, rtmpServer, rtmpKey, isInfiniteMode, streamDuration, baseFilePath, mode});
170-
171-
} catch (error) {
172-
reject(error);
173-
console.error("Error in reStreamShopee function: ", error);
174-
}});
175-
};
176-
177-
178-
const streamDownloader = async ({videoUrl, durasiVideo = null, rtmpServer = null, rtmpKey = null, isInfinite = false}) => {
179-
try {
180-
const streamProvider = getStreamProvider(videoUrl);
181-
let result = null;
182-
switch (streamProvider) {
183-
case "shopee":
184-
case "filestream":
185-
result = await reStreamShopee({videoUrl, streamDuration:durasiVideo?durasiVideo*60:null, rtmpServer, rtmpKey, isInfiniteMode:isInfinite});
186-
break;
187-
188-
case "tiktok":
189-
result = await tiktokDownload({videoUrl:videoUrl, duration:durasiVideo?parseInt(durasiVideo)*60:null, rtmpServer, rtmpKey, isInfiniteMode:isInfinite})
190-
break;
191-
192-
default:
193-
throw Error(`Platform "${streamProvider}" Not Supported Yet!`);
194-
}
195-
return result;
196-
} catch(err) {
197-
throw Error(err);
198-
}
199-
200-
}
201-
202-
const getStreamProvider = (videoUrl) => {
203-
let hostname = "";
204-
if(videoUrl.includes("http")){
205-
const url = new URL(videoUrl);
206-
hostname = url.hostname;
207-
}else {
208-
hostname = "filestream";
209-
}
210-
if (hostname.includes("tiktok.com")) {
211-
return "tiktok";
212-
} else if (hostname.includes("youtube.com")) {
213-
return "youtube";
214-
} else if (hostname.includes("twitch.tv")) {
215-
return "twitch";
216-
} else if (hostname.includes("facebook.com")) {
217-
return "facebook";
218-
} else if (hostname.includes("tokopedia.com")) {
219-
return "tokopedia";
220-
} else if (hostname.includes("shopee.co.id")) {
221-
return "shopee";
222-
} else if (hostname.includes("file")) {
223-
return "filestream";
224-
} else {
225-
return hostname;
226-
}
227-
}
228-
229-
const getShopeeStreamDetails = async (videoUrl) => {
230-
const url = new URL(videoUrl);
231-
const session_id = url.searchParams.get("session");
232-
const response = fetch(
233-
`https://live.shopee.co.id/api/v1/session/${session_id}`,
234-
{
235-
headers: {
236-
Host: "live.shopee.co.id",
237-
"Sec-Ch-Ua":
238-
'"Brave";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
239-
"Sec-Ch-Ua-Mobile": "?0",
240-
"User-Agent":
241-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
242-
"X-Api-Source": "pc",
243-
"Content-Type": "application/json",
244-
Accept: "application/json",
245-
"X-Shopee-Language": "id",
246-
"X-Requested-With": "XMLHttpRequest",
247-
"Sec-Ch-Ua-Platform": '"macOS"',
248-
"Sec-Gpc": "1",
249-
"Accept-Language": "id-ID,id;q=0.6",
250-
"Sec-Fetch-Site": "same-origin",
251-
"Sec-Fetch-Mode": "cors",
252-
"Sec-Fetch-Dest": "empty",
253-
Referer: "https://shopee.co.id/?is_from_login=true&is_from_login=true",
254-
"Accept-Encoding": "gzip, deflate, br",
255-
},
256-
}
257-
).then(async (res) => {
258-
const jsonRes = await res.json();
259-
if (jsonRes.err_code == 0) {
260-
return jsonRes?.data?.session;
261-
}
262-
return jsonRes;
263-
});
264-
return response;
265-
};
266-
267-
const tiktokStreamData = async (username) => {
268-
const tiktokUrl = `https://www.tiktok.com/@${username}/live`
269-
const textHtml = await fetch(tiktokUrl, {
270-
headers: {
271-
'User-Agent':
272-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
273-
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
274-
},
275-
}).then((res) => res.text())
276-
const matchRoomId = textHtml.match(/room_id=(\d+)/)
277-
if (!matchRoomId) {
278-
throw new Error('No live stream found')
279-
}
280-
const roomId = matchRoomId[1]
281-
console.info(`\nFound live stream with room id ${roomId}!`)
282-
const api = `https://www.tiktok.com/api/live/detail/?aid=1988&roomID=${roomId}`
283-
const {LiveRoomInfo} = await fetch(api).then((res) => res.json())
284-
const {title, liveUrl} = LiveRoomInfo;
285-
286-
return {title, liveUrl};
287-
}
288-
289-
const tiktokDownload = async ({videoUrl, output="stream_output", format="mp4", duration=0, rtmpKey=null, rtmpServer=null, isInfiniteMode=false}) => {
290-
return new Promise(async (resolve, reject) => {
291-
try {
292-
const splitUsername = videoUrl.split('/');
293-
const tt_username = splitUsername[splitUsername.length - 2].replace('@', '');
294-
const {title, liveUrl} = await tiktokStreamData(tt_username);
295-
const fileName = output.endsWith(format)
296-
? output
297-
: `${output.replace(
298-
/\/$/,
299-
''
300-
)}/${tt_username}-${Date.now()}.${format}`
301-
mkdirSync(path.dirname(fileName), { recursive: true })
302-
console.info(`\nCtrl+C to stop downloading and exit`)
303-
await videoProcessing({liveUrl, rtmpServer, rtmpKey, isInfiniteMode, streamDuration: duration, baseFilePath:fileName, mode:"restream"});
304-
}catch (error) {
305-
reject(error);
306-
}
307-
});
308-
}
309-
310-
311-
312-
module.exports = { ffmpegGlobalProcess ,reStreamShopee, getShopeeStreamDetails, decodeUnicodeEscape, streamDownloader };
313-
1+
const{mkdirSync:mkdirSync}=require("fs"),chalk=require("chalk"),path=require("path");let ffmpegGlobalProcess=null;function decodeUnicodeEscape(e){return e.replace(/\\u([\d\w]{4})/gi,((e,t)=>String.fromCharCode(parseInt(t,16))))}const videoProcessing=async({liveUrl:e,rtmpServer:t=null,rtmpKey:o=null,isInfiniteMode:r=!1,streamDuration:s=null,baseFilePath:a="",mode:i="stream"})=>{console.log(chalk.blue("Starting the streaming process. Please wait..."));const n=require("child_process"),{existsSync:l}=require("fs");let c,d=null;"restream"===i?t&&o?(c=`${t}${o}`,d=`${a}.flv`):c=`${a}.flv`:c=`${t}${o}`;const generateUniqueFileName=(e,t)=>{let o=0,r=`${e}${o>0?`-${o}`:""}.${t}`;for(;l(r);)o++,r=`${e}${o>0?`-${o}`:""}.${t}`;return r};if(d&&l(d)&&(d=generateUniqueFileName(a,"flv")),l(c)){const e=c.split(".").pop();c=generateUniqueFileName(a,e)}process.stdout.clearLine(0),process.stdout.cursorTo(0);const p=[chalk.white("Live Url : ")+chalk.red(`${e}`),chalk.white("Output Destination : ")+chalk.yellow(`${c}`),chalk.white("Secondary Output : ")+chalk.green(`${d||"Not Applicable"}`)].join("\n"),m=Math.max(0,p.length-chalk.reset(p).length+20),u=chalk.blue("=".repeat(m));process.stdout.write(`${u}\n${p}\n${u}`);const h=["-re","-stream_loop","-1","-i",e,"-r","30","-b:v","2000k","-c:v","libx264","-preset","veryfast","-c:a","aac","-f","flv","-loglevel","info","-hide_banner",c];d&&h.push(d),s&&h.push("-t",s.toString()),ffmpegGlobalProcess=n.spawn("ffmpeg",h);let f=0,g=0,S=0;ffmpegGlobalProcess.stderr.on("data",(e=>{const t=e.toString(),o=t.match(/Duration: (\d{2}):(\d{2}):(\d{2})\.\d{2},/),r=t.match(/time=(\d{2}):(\d{2}):(\d{2})\.\d{2}/);if(o&&(g=3600*parseInt(o[1])+60*parseInt(o[2])+parseInt(o[3])),r){S=3600*parseInt(r[1])+60*parseInt(r[2])+parseInt(r[3]);const e=(S/g*100).toFixed(2);s&&S>=s&&ffmpegGlobalProcess.kill("SIGINT"),process.stdout.clearLine(0),process.stdout.cursorTo(0),process.stdout.write(chalk.green(`Streaming Progress: [${e}%] | Time Elapsed: ${S} seconds`))}t.includes("error")&&(f+=1,f>5&&(console.log(chalk.red("We encountered a problem and need to stop. Please try again.")),ffmpegGlobalProcess.kill("SIGINT")))})),ffmpegGlobalProcess.on("close",((e,n)=>{const l=`Streaming process exited with code: ${e} and signal: ${n}`;0!==e?(console.log(chalk.magenta(l)),console.log(chalk.yellow("The streaming process has ended unexpectedly. Attempting to restart..."))):console.log(chalk.green("Streaming completed successfully.")),(r||f<=5&&"restream"===i&&t&&o)&&(console.log(chalk.blue("Restarting in 3 seconds...")),setTimeout((()=>{try{videoProcessing({liveUrl:d,rtmpServer:t,rtmpKey:o,isInfiniteMode:r,streamDuration:s,baseFilePath:a,mode:i})}catch(e){console.error(chalk.red(`Error restarting streaming process: ${e.message}`))}}),3e3))})),process.on("SIGINT",(()=>{console.log(chalk.magenta("Streaming has been stopped. Thank you for using our service.")),ffmpegGlobalProcess&&ffmpegGlobalProcess.kill("SIGINT"),process.exit()}))},reStreamShopee=async({videoUrl:e,rtmpServer:t=null,rtmpKey:o=null,isInfiniteMode:r=!1,streamDuration:s=null})=>new Promise((async(a,i)=>{try{let a=null,i=`${__dirname}/../${e}`,n="stream";if(!e.includes("mp4")&&!e.includes("flv")){if(a=await getShopeeStreamDetails(e),!a)throw new Error("Stream details could not be retrieved.");i=decodeURIComponent(a.play_url),n="restream"}const l=`${__dirname}/../stream_output/${a?.room_id}-${a?.username}`;console.info("\nCtrl+C to stop downloading and exit"),await videoProcessing({liveUrl:i,rtmpServer:t,rtmpKey:o,isInfiniteMode:r,streamDuration:s,baseFilePath:l,mode:n})}catch(e){i(e),console.error("Error in reStreamShopee function: ",e)}})),streamDownloader=async({videoUrl:e,durasiVideo:t=null,rtmpServer:o=null,rtmpKey:r=null,isInfinite:s=!1})=>{try{const a=getStreamProvider(e);let i=null;switch(a){case"shopee":case"filestream":i=await reStreamShopee({videoUrl:e,streamDuration:t?60*t:null,rtmpServer:o,rtmpKey:r,isInfiniteMode:s});break;case"tiktok":i=await tiktokDownload({videoUrl:e,duration:t?60*parseInt(t):null,rtmpServer:o,rtmpKey:r,isInfiniteMode:s});break;default:throw Error(`Platform "${a}" Not Supported Yet!`)}return i}catch(e){throw Error(e)}},getStreamProvider=e=>{let t="";if(e.includes("http")){t=new URL(e).hostname}else t="filestream";return t.includes("tiktok.com")?"tiktok":t.includes("youtube.com")?"youtube":t.includes("twitch.tv")?"twitch":t.includes("facebook.com")?"facebook":t.includes("tokopedia.com")?"tokopedia":t.includes("shopee.co.id")?"shopee":t.includes("file")?"filestream":t},getShopeeStreamDetails=async e=>{const t=new URL(e).searchParams.get("session");return fetch(`https://live.shopee.co.id/api/v1/session/${t}`,{headers:{Host:"live.shopee.co.id","Sec-Ch-Ua":'"Brave";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',"Sec-Ch-Ua-Mobile":"?0","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36","X-Api-Source":"pc","Content-Type":"application/json",Accept:"application/json","X-Shopee-Language":"id","X-Requested-With":"XMLHttpRequest","Sec-Ch-Ua-Platform":'"macOS"',"Sec-Gpc":"1","Accept-Language":"id-ID,id;q=0.6","Sec-Fetch-Site":"same-origin","Sec-Fetch-Mode":"cors","Sec-Fetch-Dest":"empty",Referer:"https://shopee.co.id/?is_from_login=true&is_from_login=true","Accept-Encoding":"gzip, deflate, br"}}).then((async e=>{const t=await e.json();return 0==t.err_code?t?.data?.session:t}))},tiktokStreamData=async e=>{const t=`https://www.tiktok.com/@${e}/live`,o=(await fetch(t,{headers:{"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"}}).then((e=>e.text()))).match(/room_id=(\d+)/);if(!o)throw new Error("No live stream found");const r=o[1];console.info(`\nFound live stream with room id ${r}!`);const s=`https://www.tiktok.com/api/live/detail/?aid=1988&roomID=${r}`,{LiveRoomInfo:a}=await fetch(s).then((e=>e.json())),{title:i,liveUrl:n}=a;return{title:i,liveUrl:n}},tiktokDownload=async({videoUrl:e,output:t="stream_output",format:o="mp4",duration:r=0,rtmpKey:s=null,rtmpServer:a=null,isInfiniteMode:i=!1})=>new Promise((async(n,l)=>{try{const n=e.split("/"),l=n[n.length-2].replace("@",""),{title:c,liveUrl:d}=await tiktokStreamData(l),p=t.endsWith(o)?t:`${t.replace(/\/$/,"")}/${l}-${Date.now()}.${o}`;mkdirSync(path.dirname(p),{recursive:!0}),console.info("\nCtrl+C to stop downloading and exit"),await videoProcessing({liveUrl:d,rtmpServer:a,rtmpKey:s,isInfiniteMode:i,streamDuration:r,baseFilePath:p,mode:"restream"})}catch(e){l(e)}}));module.exports={ffmpegGlobalProcess:ffmpegGlobalProcess,reStreamShopee:reStreamShopee,getShopeeStreamDetails:getShopeeStreamDetails,decodeUnicodeEscape:decodeUnicodeEscape,streamDownloader:streamDownloader};

0 commit comments

Comments
 (0)