lamd.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /*
  2. LiveMe Monitor CLI
  3. */
  4. const os = require('os'),
  5. fs = require('fs'),
  6. http = require('http'),
  7. LiveMe = require('liveme-api'),
  8. m3u8stream = require('./modules/m3u8stream'); // We use a custom variation of this module
  9. var config = {
  10. downloadPath: os.homedir() + '/Downloads',
  11. downloadConcurrent: 3,
  12. downloadTemplate: '%%replayid%%',
  13. loopCycle: 30,
  14. localPort: 8280
  15. },
  16. accounts = [],
  17. account_index = 0,
  18. download_list = [],
  19. activeDownloads = 0,
  20. minuteTick = 0,
  21. APIVERSION = '1.0';
  22. main();
  23. function main() {
  24. /*
  25. Load configuration file
  26. */
  27. if (fs.existsSync('config.json')) {
  28. fs.readFile('config.json', 'utf8', (err,data) => {
  29. if (!err) {
  30. config = JSON.parse(data);
  31. if (config.downloadChunks > 5) config.downloadChunks = 5;
  32. }
  33. });
  34. }
  35. /*
  36. Load Account List
  37. */
  38. if (fs.existsSync('accounts.json')) {
  39. fs.readFile('accounts.json', 'utf8', (err,data) => {
  40. if (!err) {
  41. accounts = JSON.parse(data);
  42. }
  43. });
  44. }
  45. /*
  46. Replay Check Interval - Runs every minute
  47. */
  48. setInterval(() => {
  49. minuteTick++;
  50. if (minuteTick == config.loopCycle) {
  51. minuteTick = 0;
  52. setImmediate(() => {
  53. account_index = 0;
  54. accountScanLoop();
  55. });
  56. }
  57. }, 60000);
  58. /*
  59. Download Check Interval - Runs every second
  60. */
  61. setInterval(() => {
  62. downloadFile();
  63. }, 1000);
  64. /*
  65. Internal Web Server - Used for command interface
  66. */
  67. http.createServer( (req, res) => {
  68. var chunks = req.url.substr(1).split('/'),
  69. response = {
  70. api_version: APIVERSION,
  71. message: '',
  72. data: null
  73. }
  74. switch (chunks[0]) {
  75. case 'add-account':
  76. var add_this = true, i = 0;
  77. for (i = 0; i < accounts.length; i++) {
  78. if (accounts[i] == chunks[1]) { add_this = false; }
  79. }
  80. if (add_this) {
  81. accounts.push({
  82. userid: chunks[1],
  83. scanned: Math.floor((new Date()).getTime() / 1000)
  84. });
  85. fs.writeFile(
  86. 'accounts.json',
  87. JSON.stringify(accounts),
  88. () => {}
  89. );
  90. response.message = 'Account added.';
  91. } else
  92. response.message = 'Account already in list.';
  93. break;
  94. case 'remove-account':
  95. response.message = 'Account not in the list.';
  96. for (var i = 0; i < accounts.length; i++) {
  97. if (accounts[i].userid == chunks[1]) {
  98. accounts.splice(i, 1);
  99. response.message = 'Account removed.';
  100. }
  101. }
  102. fs.writeFile(
  103. 'accounts.json',
  104. JSON.stringify(accounts),
  105. () => {}
  106. );
  107. break;
  108. case 'list-accounts':
  109. response.message = 'Accounts in list';
  110. response.data = [];
  111. for (var i = 0; i < accounts.length; i++) {
  112. response.data.push(accounts[i].userid);
  113. }
  114. break;
  115. case 'shutdown':
  116. fs.writeFile(
  117. 'config.json',
  118. JSON.stringify(config, null, 2),
  119. () => {}
  120. );
  121. setTimeout(() => {
  122. process.exit(0);
  123. }, 250);
  124. break;
  125. default:
  126. response.message = 'Invalid command.';
  127. break;
  128. }
  129. res.writeHead(200, { 'Content-Type': 'text/javascript'});
  130. res.write(JSON.stringify(response, null, 2));
  131. res.end();
  132. }).listen(config.localPort);
  133. }
  134. /*
  135. Account Scan Loop
  136. */
  137. function accountScanLoop() {
  138. if (account_index < accounts.length) {
  139. setTimeout(() => {
  140. accountScanLoop();
  141. }, 200);
  142. }
  143. setTimeout(function(){
  144. if (account_index < accounts.length) { account_index++; scanForNewReplays(account_index); }
  145. if (account_index < accounts.length) { account_index++; scanForNewReplays(account_index); }
  146. if (account_index < accounts.length) { account_index++; scanForNewReplays(account_index); }
  147. if (account_index < accounts.length) { account_index++; scanForNewReplays(account_index); }
  148. if (account_index < accounts.length) { account_index++; scanForNewReplays(account_index); }
  149. }, 200);
  150. }
  151. /*
  152. Replay Scan
  153. */
  154. function scanForNewReplays(i) {
  155. if (accounts[i] == undefined) return;
  156. LiveMe.getUserReplays(accounts[i].userid, 1, 5).then(replays => {
  157. if (replays == undefined) return;
  158. if (replays.length < 1) return;
  159. var ii = 0,
  160. count = 0,
  161. userid = replays[0].userid,
  162. last_scanned = null,
  163. dt = Math.floor((new Date()).getTime() / 1000);
  164. for (ii = 0; ii < accounts.length; ii++) {
  165. if (accounts[ii] == userid) {
  166. last_scanned = accounts[ii].scanned;
  167. accounts[ii].scanned = dt;
  168. }
  169. }
  170. for (ii = 0; ii < replays.length; ii++) {
  171. if (replays[ii].vtime - d > 0) {
  172. download_list.push(replays[ii].vid);
  173. }
  174. }
  175. });
  176. }
  177. /*
  178. Download Handler
  179. Checks first to see if there are any replays waiting to be downloaded
  180. then checks to see if there's any concurrent slots open then gets the
  181. info on the replay before sending to the stream download module.
  182. */
  183. function downloadFile() {
  184. if (download_list.length == 0) return;
  185. if (activeDownloads >= config.downloadConcurrent ) return;
  186. activeDownloads++;
  187. LiveMe.getVideoInfo(download_list[0]).then(video => {
  188. var filename = config.downloadTemplate
  189. .replace(/%%broadcaster%%/g, video.uname)
  190. .replace(/%%longid%%/g, video.userid)
  191. .replace(/%%replayid%%/g, video.vid)
  192. .replace(/%%replayviews%%/g, video.playnumber)
  193. .replace(/%%replaylikes%%/g, video.likenum)
  194. .replace(/%%replayshares%%/g, video.sharenum)
  195. .replace(/%%replaytitle%%/g, video.title ? video.title : 'untitled')
  196. .replace(/%%replayduration%%/g, video.videolength);
  197. filename += '.ts';
  198. download_list.shift();
  199. m3u8stream(video, {
  200. chunkReadahead: 5,
  201. on_progress: (e) => {
  202. /*
  203. e.index = current chunk
  204. e.total = total chunks
  205. */
  206. },
  207. on_complete: (e) => {
  208. activeDownloads--;
  209. setImmediate(() => { downloadFile(); });
  210. },
  211. on_error: (e) => {
  212. activeDownloads--;
  213. setImmediate(() => { downloadFile(); });
  214. }
  215. }).pipe(fs.createWriteStream(config.downloadPath + '/' + filename));
  216. });
  217. }