├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── appveyor.yml ├── docs ├── ffmpeg.md └── index.md ├── readme.md └── src ├── .gitignore ├── appicon.icns ├── appicon.ico ├── appicon.png ├── build ├── appicon.icns ├── appicon.ico └── appicon.png ├── custom_modules ├── DataManager │ └── index.js ├── DownloadManager │ └── index.js └── Favorites │ └── index.js ├── dist └── empty.txt ├── index.js ├── lmt ├── appicon.png ├── chat.html ├── fans.html ├── favorites-list.html ├── following.html ├── fonts │ ├── roboto │ │ ├── Roboto-Bold.woff │ │ ├── Roboto-Bold.woff2 │ │ ├── Roboto-Light.woff │ │ ├── Roboto-Light.woff2 │ │ ├── Roboto-Medium.woff │ │ ├── Roboto-Medium.woff2 │ │ ├── Roboto-Regular.woff │ │ ├── Roboto-Regular.woff2 │ │ ├── Roboto-Thin.woff │ │ └── Roboto-Thin.woff2 │ └── sourcesans │ │ ├── sourcecodepro-regular-webfont.eot │ │ ├── sourcecodepro-regular-webfont.svg │ │ ├── sourcecodepro-regular-webfont.ttf │ │ ├── sourcecodepro-regular-webfont.woff │ │ ├── sourcesanspro-light-webfont.eot │ │ ├── sourcesanspro-light-webfont.svg │ │ ├── sourcesanspro-light-webfont.ttf │ │ ├── sourcesanspro-light-webfont.woff │ │ ├── sourcesanspro-regular-webfont.eot │ │ ├── sourcesanspro-regular-webfont.svg │ │ ├── sourcesanspro-regular-webfont.ttf │ │ ├── sourcesanspro-regular-webfont.woff │ │ ├── sourcesanspro-semibold-webfont.eot │ │ ├── sourcesanspro-semibold-webfont.svg │ │ ├── sourcesanspro-semibold-webfont.ttf │ │ ├── sourcesanspro-semibold-webfont.woff │ │ └── stylesheet.css ├── images │ ├── blank.png │ ├── drop-down-triangle-dark.png │ ├── ic_add_circle_outline_white_24px.svg │ ├── ic_arrow_drop_down_white_18px.svg │ ├── ic_book_white_24px.svg │ ├── ic_cancel_white_24px.svg │ ├── ic_chat_white_24px.svg │ ├── ic_check_white_24px.svg │ ├── ic_close_white_24px.svg │ ├── ic_content_copy_white_18px.svg │ ├── ic_content_copy_white_24px.svg │ ├── ic_favorite_border_white_24px.svg │ ├── ic_favorite_white_24px.svg │ ├── ic_file_download_white_24px.svg │ ├── ic_file_upload_white_24px.svg │ ├── ic_fullscreen_white_18px.svg │ ├── ic_menu_white_24px.svg │ ├── ic_pause_circle_outline_white_24px.svg │ ├── ic_pause_white_18px.svg │ ├── ic_play_arrow_white_18px.svg │ ├── ic_play_circle_outline_white_24px.svg │ ├── ic_search_white_18px.svg │ ├── ic_settings_white_24px.svg │ ├── liveme.tools.icon.svg │ ├── search.svg │ └── wait.gif ├── index.html ├── javascript │ ├── cclist.js │ ├── favorites.js │ ├── hls.js │ ├── jquery-3.2.1.min.js │ ├── livemetools.js │ └── settings.js ├── livemeomg.html ├── player.html ├── queue.html ├── settings.html ├── splash.html ├── style │ └── style.css ├── update.html └── upgrade.html └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | .DS_Store 61 | .DS_Store 62 | src/.DS_Store 63 | 64 | # Specific to our code 65 | *.sh 66 | *.php 67 | 68 | dist/* 69 | 70 | *.lock 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode8.3 2 | 3 | dist: trusty 4 | sudo: false 5 | 6 | language: node_js 7 | node_js: "8" 8 | 9 | env: 10 | global: 11 | - ELECTRON_CACHE=$HOME/.cache/electron 12 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 13 | 14 | os: 15 | - linux 16 | - osx 17 | 18 | cache: 19 | directories: 20 | - node_modules 21 | - $HOME/.cache/electron 22 | - $HOME/.cache/electron-builder 23 | - $HOME/.npm/_prebuilds 24 | 25 | before_install: 26 | - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v2.2.0/git-lfs-$([ "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-2.2.0.tar.gz | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull 27 | - export PATH="$HOME/.yarn/bin:$PATH" 28 | - cd src 29 | 30 | install: 31 | - npm install -g yarn 32 | - yarn install 33 | 34 | script: 35 | - yarn dist-travis 36 | 37 | before_cache: 38 | - rm -rf $HOME/.cache/electron-builder/wine 39 | 40 | branches: 41 | except: 42 | - "/^v\\d+\\.\\d+\\.\\d+$/" 43 | 44 | notifications: 45 | email: false 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | ### 7.x.x Releases 4 | 5 | **No more releases are scheduled at this time, please use the stable 6.x.x branch as the 7.x.x branch is buggy still.** 6 | 7 | #### 2017-11-25 - v7.0.8 (EOL) 8 | **Fixed:** 9 | - Addressed multiple crash and other bug issues 10 | - Clean up of database commit code to reduce disk writes 11 | 12 | #### 2017-11-24 - v7.0.7 13 | **Fixed:** 14 | - Addressed bug issues 15 | 16 | #### 2017-11-24 - v7.0.6 17 | **Fixed:** 18 | - Addressed bug issues 19 | 20 | #### 2017-11-23 - v7.0.5 21 | **Fixed:** 22 | - Addressed bug issues 23 | 24 | 25 | #### 2017-11-23 - v7.0.4 26 | 27 | #### 2017-11-21 - v7.0.2 28 | **Fixed:** 29 | - Fixed download issue #111 and issue #107 30 | 31 | #### 2017-11-19 - v7.0.1 32 | **Added:** 33 | - Added settings option to change visited profile timeout with custom options 34 | - Added commands to DataManager module for download tracking (Need to update Downloads Module) 35 | 36 | **Fixed:** 37 | - Updated DataManager module for better handling of lookups and storage formats 38 | - Fixed crash when running first time after upgrading to 7.0.x release. 39 | 40 | #### 2017-11-18 - v7.0.0 41 | **Added:** 42 | - Changed data storage system to using lowDB for easier management and stability 43 | - Added ability to track previously visited UserIDs 44 | - Speed improvements for Favorites window and functions 45 | 46 | ### 6.x.x Releases 47 | 48 | #### 2016-11-17 - v6.3.2 49 | **Fixed:** 50 | - Fixed some minor code bugs 51 | - Fixed autobuild process bugs 52 | 53 | #### 2016-11-17 - v6.3.1 54 | **Fixed:** 55 | - Fixed file name issue in download module causing some playlists to download as `playlist.mp4` or `playlist_eof.mp4` 56 | 57 | #### 2017-11-17 - v6.3.0 58 | **Added:** 59 | - Added new download manager with chunk support speeding update downloads 60 | - Added ability to search Fans and Followings by UserID 61 | 62 | #### 2017-11-06 - v6.2.1 63 | **Fixed:** 64 | - Fixed bug that was causing the main window to not show. 65 | 66 | #### 2017-11-06 - v6.2.0 67 | **Added:** 68 | - Added support for saving and restoring size and position of the main, queue, player and favorite windows. 69 | 70 | **Fixed:** 71 | - Fixed rendering bug when displaying replays that would occasionally occur. 72 | 73 | #### 2017-10-28 - v6.1.4 74 | **Fixed:** 75 | - Thanks to @zp for fixing the duration bug in the video player 76 | 77 | #### 2017-10-28 - v6.1.3 78 | **Fixed:** 79 | - Rebuilt using updated LiveMe-API with fix for VideoID search and Message History issues. 80 | 81 | #### 2017-10-26 - v6.1.2 82 | **Fixed:** 83 | - Updated auto load on scroll to now trigger near the end to avoid issues experienced by some users on Windows 84 | 85 | #### 2017-10-25 - v6.1.1 86 | **Fixed:** 87 | - Fixed broken hashtag search. 88 | 89 | #### 2017-10-24 - v6.1.0 90 | **Added:** 91 | - Added search option to Fans, Followings and Favorites windows 92 | - Added countryCode list to source for future implementation or use 93 | 94 | **Fixed:** 95 | - Cleanup of some of the visual stylings code 96 | 97 | #### 2017-10-20 - v6.0.12 98 | **Added:** 99 | - Updated the player skin to support displaying total video length 100 | - Updated the player progress bar to show a better buffer state and position indicator 101 | 102 | #### 2017-10-17 - v6.0.11 103 | **Fixed:** 104 | - Fixed Video ID List import bug 105 | - Fixed Replay entry for searched video missing playback button 106 | - Fixed overlay status message of main window 107 | 108 | #### 2017-10-15 - v6.0.10 109 | **Fixed:** 110 | - Fixed showing empty fields for Video URL when it wasn't available 111 | - Fixed rendering issue when replays were unavailable when a search was performed. 112 | - Fixed VideoID List import bugs. 113 | 114 | #### 2017-10-15 - v6.0.9 115 | **Fixed:** 116 | - Fixed video search bug 117 | 118 | #### 2017-10-11 - v6.0.8 119 | **Fixed:** 120 | - Fixed layout issue when a full length date is present 121 | - Fixed Favorites Export bug 122 | 123 | #### 2017-10-09 - v6.0.7 124 | **Fixed:** 125 | - Fixed broken Favorites Export function. 126 | 127 | #### 2017-10-01 - v6.0.6 128 | **Added:** 129 | - Updated video progress bar styling for better visibility 130 | - Ffmpeg checks to see if it's available and alert you if it can't be found on startup. 131 | - Added settings: 132 | - You can manually choose a version of ffmpeg for LMT to use. 133 | - A button to check if ffmpeg is valid and can be used. 134 | - Added a basic check to see if the user is currently live. If they aren't, re-enable the download on an alternate link. (If the first one linked to a live url but they weren't live - it wouldn't work) 135 | 136 | **Fixed:** 137 | - Fixed broken time in message history. 138 | - Fixed time jumping from message history when time was clicked on. 139 | - Fixed settings resetting if you changed download directory but didn't save. 140 | - Fixed download directory window not showing. 141 | - Fixed crash if you use File -> Quit. 142 | 143 | **Changed:** 144 | - Bundling Windows versions as a portable version. No extraction required. 145 | - Merged x64 and x86 into one portable version - it will use the x64 version if you're running 64-bit and the x86 version if you're running on 32-bit. 146 | - Color of downloaded video highlight is now a green, instead of a very faint white. 147 | 148 | #### 2017-09-26 - v6.0.5 149 | **Fixed:** 150 | - Fixed broken VideoID search 151 | 152 | #### 2017-09-26 - v6.0.4 153 | **Added:** 154 | - Will show the details of the video that was directly search by VideoID 155 | 156 | #### 2017-09-26 - v6.0.3 157 | **Added:** 158 | - Restored the context (right-click) menu to text fields. 159 | 160 | **Fixed:** 161 | - Removed FFMPEG prompt 162 | - Fixed Username search not returning results at times. 163 | - Fixed replays not showing at times due to all being invisible and not downloaded. 164 | - Fixed closing issue experienced by some users. 165 | 166 | #### 2017-09-25 - v6.0.2 167 | **Fixed:** 168 | - Fixed issue where LiveMeOMG page was sending the VideoID instead of the URL to the player. 169 | 170 | #### 2017-09-25 - v6.0.1 171 | **Fixed:** 172 | - Removed bundled FFMPEG executable due to issues with macOS and Windows systems. 173 | 174 | #### 2017-09-24 - v6.0.0 175 | **Added:** 176 | - Migrated to using LiveMe API module 177 | - Whole new UI styling added 178 | - Added custom video player UI 179 | - Added ability to jump to video time index from message history by click on the time 180 | - Added ability to trigger a search by clicking on the username in the message history 181 | - Added autoload of content when you scroll to the bottom with loading of 10 entries at a time 182 | - Added LiveMe OMG window with ability to watch videos from it and search the users (All, Only Girls, Only Boys) 183 | 184 | **Fixed:** 185 | - Moved List Import and Export functions to main thread 186 | - Major code cleanups 187 | - Upgraded jQuery to 3.2.1 from 2.2.4 188 | - Improved network data speeds by moving all web requests to Node modules from jQuery 189 | 190 | ### 5.x.x Releases 191 | 192 | #### 2017-09-17 - v5.0.9 193 | **Added:** 194 | - Can now delete active downloads as well. 195 | 196 | **Fixed:** 197 | - See commits for details. 198 | 199 | #### 2017-09-13 - v5.0.6 200 | **Added:** 201 | - Automatically will download FFMPEG if its not found on the computer. 202 | 203 | **Fixed:** 204 | - Updated to use only FFMPEG for downloading of playlists. 205 | - Issue #54 206 | - Issue #56 207 | - Issue #57 208 | 209 | #### 2017-09-11 - v5.0.3 210 | **Fixed:** 211 | - Search loop issue 212 | - Minor favorites cleanup 213 | 214 | 215 | #### 2017-09-11 - v5.0.2 216 | **Added:** 217 | - Will show queue window when first download is clicked now. 218 | 219 | **Fixed:** 220 | - Issue #59 221 | - Issue #56 222 | 223 | 224 | #### 2017-09-08 - v5.0.1 225 | **Fixed:** 226 | - Fixed no replays when set to unlimited replays. 227 | 228 | 229 | #### 2017-09-08 - v5.0.0 230 | **Added:** 231 | - Option to empty download queue now available in the queue window 232 | - Ability to add a single URL to download queue 233 | - Ability to limit number of replay results 234 | - Import of a list of VideoIDs 235 | - Export a list of Favorites (UserID) list 236 | - Shows VideoID of each replay next to its URL 237 | 238 | **Fixed:** 239 | - Disabled Live Video download so it doesn't cause a hangup of the queue 240 | - Moved custom modules to main thread to fix multiple instance issues and lost data 241 | - Adding a URL to downloader igored Pause state (See #47) 242 | - Fixed critical download and queue bugs (See #47) 243 | - Fixed critical FFMPEG bug (See #47) 244 | - Fixed URL removal bug (See #47) 245 | 246 | 247 | ### 4.x.x Releases 248 | 249 | #### 2017-09-05 - v4.6.1 250 | **Fixed:** 251 | - Removed download from being re-added back to queue upon failure. 252 | - Added input cleanup when importing a list of URLs to download. 253 | 254 | #### 2017-08-31 - v4.6.0 255 | **Added:** 256 | - Added ability to cancel getting user's replays. 257 | - Minor code improvements. 258 | 259 | **Fixed:** 260 | - Cleanup of code. 261 | - Removed debug code found. 262 | 263 | #### 2017-08-30 - v4.5.0 264 | **Added:** 265 | - Updated favorites list to now show extended info when available. 266 | - Added status text when doing lookups and searches. 267 | - Added bad UID detectors and handlers for lookups. 268 | 269 | **Fixed:** 270 | - Issue #39 271 | - Issue #40 272 | - Typos fixed. 273 | 274 | #### 2017-08-26 - v4.4.2 275 | **Added:** 276 | - Added save_queue to purge_queue function 277 | - Added null detector when closing the app that happens occasionally. 278 | 279 | **Fixed:** 280 | - Removed debug code. 281 | - Minor code cleanup and optimization 282 | 283 | #### 2017-08-25 - v4.4.0 284 | **Added:** 285 | - Issue #37 286 | 287 | #### 2017-08-23 - v4.3.0 288 | **Added:** 289 | - Ability to flush all queue'd download entries from Settings page. 290 | - Ability to refresh the User Avatar and Nickname list in Favorites window 291 | - Some null detectors to help avoid error popping up. 292 | 293 | **Fixed:** 294 | - Issue #31 295 | - Issue #33 296 | - Issue #34 297 | 298 | #### 2017-08-21 - v4.2.0 299 | Minor coding fixes and cleanup. Also fixed the detector bug in the Update notice. 300 | 301 | #### 2017-08-20 - v4.1.5 302 | **Added:** 303 | - Now checks for updated versions availability 304 | 305 | **Fixed:** 306 | - Issue #29 307 | - Issue #31 308 | 309 | #### 2017-08-16 - v4.1.3 310 | **Fixed:** 311 | - Issue #28 312 | 313 | #### 2017-08-16 - v4.1.2 314 | **Fixed:** 315 | - Issue #27 316 | 317 | #### 2017-08-15 - v4.1.1 318 | **Added:** 319 | - Variable height resizing of queue window 320 | 321 | **Fixed:** 322 | - Issue #25 323 | - Issue #26 324 | 325 | #### 2017-08-15 - v4.1.0 326 | **Added:** 327 | - Added button to hide download queue 328 | - Added ability to remove entries from download queue (Issue #24) 329 | 330 | **Fixed:** 331 | - Issue #23 332 | 333 | #### 2017-08-14 - v4.0.0 334 | **Added:** 335 | - New download queue and handler, now supports using FFMPEG 336 | - Ability to enable custom filenaming of downloaded playlists 337 | - Improved list renderings 338 | - Improved user interfaces 339 | - Ability to disable/enable download history tracking 340 | - Auto-recovery of download queue if crashed or closed before they are finished 341 | - and much much more! 342 | 343 | **Fixed:** 344 | - Issue #15 345 | - Issue #16 346 | - Issue #18 347 | - Iusse #20 348 | 349 | 350 | ### 3.x.x Releases 351 | 352 | #### 2017-08-10 - v3.6.0 353 | **Added:** 354 | - polydragon's chat history code 355 | 356 | **Fixed:** 357 | - Couple minor code bugs 358 | 359 | #### 2017-08-10 - v3.5.6 360 | **Added:** 361 | - Button to show the queue window 362 | 363 | **Fixed:** 364 | - An issue where the queue download list would sometimes have a stale entry that would be ignored 365 | 366 | #### 2017-08-10 - v3.5.4 367 | **Fixed:** 368 | - Issue #14 369 | - Minor bug in queue causing entries to be stalled and remain 370 | 371 | #### 2017-08-09 - v3.5.2 372 | **Fixed:** 373 | - Couple minor bugs with favorites 374 | - Fix slowdown of Fans and Following lists not fully loading. 375 | 376 | #### 2017-08-09 - v3.5.1 377 | **Fixed:** 378 | - Bug in favorites storage 379 | 380 | #### 2017-08-09 - v3.5.0 381 | **Added:** 382 | - Now keeps track of what's been downloaded to avoid multiple downloads. 383 | - Queue recovery if the app crashes. 384 | 385 | **Fixed:** 386 | - Issue #10 387 | - Issue #11 388 | - Issue #12 389 | - Issue #13 390 | 391 | ### Prior Releases 392 | **See [Releases](https://github.com/thecoder75/liveme-tools/releases) for details on prior versions.** 393 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: unstable 2 | cache: 3 | - node_modules 4 | environment: 5 | GH_TOKEN: 6 | secure: ruXayOUgryDqMg8KBrkEVzK3WZCvh+nJq1ZVA8NRylHdeQQDQO44wfyBf0Bi9ZUt 7 | matrix: 8 | - nodejs_version: 8 9 | install: 10 | - ps: Install-Product node $env:nodejs_version 11 | - set CI=true 12 | - cd src 13 | - yarn install 14 | matrix: 15 | fast_finish: true 16 | build: off 17 | version: '{branch}-{build}' 18 | shallow_clone: true 19 | clone_depth: 1 20 | test_script: 21 | - yarn dist-appveyor -------------------------------------------------------------------------------- /docs/ffmpeg.md: -------------------------------------------------------------------------------- 1 | # Setting up ffmpeg 2 | 3 | You **must** have ffmpeg installed on your computer to enable the merging of the download chunks. 4 | 5 | ### Linux Users 6 | 7 | You can obtain the latest version from the repository for your distribution. 8 | 9 | ### macOS Users 10 | 11 | Download the prebuilt binaries and copy them to `/usr/local/bin` directory using the Terminal app. Be sure to also set their permissions 12 | using `chmod +x /usr/local/bin/ff*` 13 | 14 | ### Windows Users 15 | 16 | 1. Download the [ffmpeg binaries from here](http://ffmpeg.zeranoe.com/builds/). 17 | 2. Extract the .zip to any folder. 18 | 3. In LiveMe Tools, click `File` then `Preferences`. 19 | 4. Click on the `...` button for `FFMPEG` and browse into the newly created directory from the ffmpeg .zip. 20 | 5. Browse into the `bin` directory and select `ffmpeg.exe`. 21 | 6. Repeat steps 4 and 5 but for `FFPROBE`. 22 | 7. Click on `Test FFMPEG`, it should say that the test was successful. 23 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # LiveMe Tools 2 | 3 | *This project for the most part has been abandoned and the code is being left here for reference and use in future apps. A new fork/version of this is available at [https://github.com/polydragon/liveme-toolkit/](LiveMe Toolkit Repo).* 4 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # LiveMe Tools 2 | 3 | **This project has been replaced by the [LiveMe Pro Tools](https://github.com/thecoder75/liveme-pro-tools).** 4 | 5 | **The 6.x.x branch is considered the last stable branch.** 6 | **THe 7.x.x branch is considered to be in the beta stage.** 7 | 8 | ### Build Status 9 | **Windows:** [![Build status](https://ci.appveyor.com/api/projects/status/al0lo5cr41ssqd74/branch/master?svg=true)](https://ci.appveyor.com/project/thecoder75/liveme-tools/branch/master) **macOS/Linux:** [![Build Status](https://travis-ci.org/thecoder75/liveme-tools.svg?branch=master)](https://travis-ci.org/thecoder75/liveme-tools) 10 | 11 | This is an Electron-based desktop app for Windows, macOS and Ubuntu Linux designed to: 12 | - Allow viewing a list of live videos with filter options 13 | - Search for users or videos tagged with hashtags 14 | - View details on users and their replays 15 | - Track previously viewed users 16 | - Watch and download replay videos 17 | - Create local Favorites lists without an account 18 | - Import and Export Favorites lists 19 | - Import a list of Replay URLs or VideoIDs for downloading 20 | - Ability to add a single URL 21 | - Uses a custom chunk downloader and FFMPEG to download replays 22 | - and much more! 23 | 24 | ## Getting Started 25 | 26 | ### Downloading and Installing 27 | 28 | [![All Downloads](https://img.shields.io/github/downloads/thecoder75/liveme-tools/total.svg)](https://github.com/thecoder75/liveme-tools/releases) 29 | 30 | *Click the button above to go to the downloads.* 31 | 32 | ### Building from Scratch 33 | 34 | You will need to download and install `yarn` package manager if you wish to build executables for Windows. This also relies on the [LiveMe API](https://github.com/thecoder75/liveme-api) module for the main communications with the Live.me servers. 35 | 36 | Extract to a folder and execute either `yarn install` or `npm install` to install all of the required modules. 37 | 38 | To execute in developer mode, run `yarn dev` or `npm run dev`. To build executables for your OS, run `yarn dist` or `npm run dist`. 39 | 40 | ## Built With 41 | * [Electron](http://electron.atom.io) 42 | * [NodeJS](http://nodejs.org) 43 | 44 | ## Contributors 45 | * [thecoder75](https://github.com/thecoder75) 46 | * [polydragon](https://github.com/polydragon) 47 | * [zp](https://github.com/zp) 48 | 49 | ## Bug Hunters and Beta Testers 50 | * [slyfox99](https://github.com/slyfox99) 51 | * [thegeezer9999](https://github.com/thegeezer9999) 52 | * [jaylittt](https://github.com/jaylittt) 53 | * [ushall](https://github.com/ushall) 54 | * [destruck51](https://github.com/destruck51) 55 | * [mmind99](https://github.com/mmind99) 56 | 57 | ## License 58 | This project is licensed under the GPL-3 License - see the [LICENSE](LICENSE) 59 | file for details 60 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .DS_Store 60 | .php 61 | 62 | dist/* 63 | 64 | -------------------------------------------------------------------------------- /src/appicon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/appicon.icns -------------------------------------------------------------------------------- /src/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/appicon.ico -------------------------------------------------------------------------------- /src/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/appicon.png -------------------------------------------------------------------------------- /src/build/appicon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/build/appicon.icns -------------------------------------------------------------------------------- /src/build/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/build/appicon.ico -------------------------------------------------------------------------------- /src/build/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/build/appicon.png -------------------------------------------------------------------------------- /src/custom_modules/DataManager/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | DataManager Module 4 | 5 | */ 6 | 7 | const low = require('lowdb'), 8 | FileSync = require('lowdb/adapters/FileSync'), 9 | fs = require('fs-extra'), 10 | path = require('path'), 11 | events = require('events'), 12 | { app } = require('electron'), 13 | LiveMe = require('liveme-api'); 14 | 15 | var index = 0, adapter, db; 16 | 17 | class DataManager { 18 | 19 | constructor() { 20 | this._favorites = []; 21 | this._visited = []; 22 | this.events = new (events.EventEmitter)(); 23 | 24 | fs.ensureDirSync(path.join(app.getPath('appData'), app.getName())); 25 | adapter = new FileSync(path.join(app.getPath('appData'), app.getName(), 'livemetools_db.json')); 26 | db = low(adapter); 27 | } 28 | 29 | ResetDB() { 30 | db.defaults({ 31 | favorites: [], 32 | visited: [], 33 | downloaded: [] 34 | }).write(); 35 | } 36 | 37 | commitDatabases() { 38 | db.write(); 39 | } 40 | 41 | 42 | 43 | /* 44 | Favorites 45 | */ 46 | addFavorite(e) { 47 | 48 | LiveMe.getUserInfo(e.uid) 49 | .then(user => { 50 | 51 | db.get('favorites').push({ 52 | id : user.user_info.uid, 53 | face: user.user_info.face, 54 | nickname: user.user_info.uname, 55 | sex: user.user_info.sex > -1 ? ( user.user_info.sex > 0 ? 'male' : 'female') : '', 56 | level: user.user_info.level, 57 | video_count: user.count_info.video_count, 58 | usign: user.user_info.usign, 59 | stars: user.user_info.stars 60 | }); 61 | 62 | var list = db.get('favorites').cloneDeep().value(); 63 | this.events.emit('refresh_favorites', list); 64 | 65 | }); 66 | } 67 | 68 | loadFavorites() { 69 | var list = db.get('favorites').cloneDeep().value(); 70 | this.events.emit('refresh_favorites', list); 71 | } 72 | 73 | removeFavorite(u) { 74 | db.get('favorites').remove({ id: u }).write(); 75 | this.loadFavorites(); 76 | } 77 | 78 | 79 | updateFavorites() { 80 | var count = db.get('favorites').size().value(); 81 | if (count == index) { 82 | index = 0; 83 | } 84 | 85 | index = 0; 86 | this._updateFavorites(); 87 | 88 | } 89 | _updateFavorites() { 90 | LiveMe.getUserInfo(db.get('favorites['+index+'].id').value()) 91 | .then(user => { 92 | 93 | db.get('favorites').find({ id: user.user_info.uid }) 94 | .assign({ face: user.user_info.face }) 95 | .assign({ nickname: user.user_info.nickname }) 96 | .assign({ usign: user.user_info.usign }) 97 | .assign({ level: user.user_info.level }) 98 | .assign({ stars: user.user_info.stars }) 99 | .assign({ sex: user.user_info.sex > -1 ? ( user.user_info.sex > 0 ? 'male' : 'female') : '' }) 100 | .assign({ video_count: user.count_info.video_count }); 101 | 102 | }); 103 | 104 | index++; 105 | 106 | var count = db.get('favorites').size().value(); 107 | if (count == index) { 108 | index = 0; 109 | var list = db.get('favorites').cloneDeep().value(); 110 | this.events.emit('refresh_favorites', list); 111 | } else { 112 | this._updateFavorites(); 113 | } 114 | 115 | } 116 | 117 | isInFavorites(e) { 118 | var t = db.get('favorites').find({ id: e }).value(); 119 | return (t == 'undefined' || typeof t == 'undefined' || t == null) ? false : true; 120 | } 121 | 122 | 123 | importFavorites(e) { 124 | fs.readFile(e, 'utf8', (err, data) => { 125 | if (err) { 126 | dialog.showErrorBox('Import Error', 'There was an error when attempting to import your favorites'); 127 | console.error(err); 128 | } else { 129 | for (let id of data.split("\n")) { 130 | id = id.trim(); 131 | 132 | if (id.startsWith('#') || id.length == 0) { 133 | continue; 134 | } 135 | 136 | if (this.isInFavorites(id)) { 137 | continue; 138 | } else { 139 | 140 | db.get('favorites').push({ 141 | id : id, 142 | face: '', 143 | nickname: '', 144 | sex: '', 145 | level: 0, 146 | video_count: 0, 147 | usign: '', 148 | stars: 0 149 | }); 150 | }; 151 | } 152 | this.updateFavorites(); 153 | } 154 | }); 155 | } 156 | 157 | exportFavorites(e) { 158 | var ids = "# Generated by LiveMe-Tools", list = db.get('favorites').cloneDeep().value(); 159 | 160 | for (let o of list) { 161 | ids += "\n" + o.id; 162 | } 163 | 164 | fs.writeFile(e, ids, 'utf8', function(err) { 165 | if (err) { 166 | dialog.showErrorBox('Export Error', 'There was an error when attempting to export your favorites'); 167 | console.error(err); 168 | } 169 | }); 170 | 171 | } 172 | 173 | 174 | 175 | /* 176 | Tracking of Visited UserIds 177 | */ 178 | addTrackedVisited(e) { 179 | var t = db.get('visited').find({ id: e.id }).value(); 180 | 181 | if (t == 'undefined' || typeof t == 'undefined' || t == null) { 182 | db.get('visited').push({ 183 | id: e.id, 184 | dt: e.dt 185 | }); 186 | } 187 | 188 | } 189 | dropTrackedVisited(e) { 190 | db.get('visited').remove({ id: e.id }).write(); 191 | } 192 | wasVisited(e) { 193 | var dt = Math.floor(new Date().getTime() / 1000), 194 | t = db.get('visited').find({ id: e }).value(); 195 | 196 | if (t == 'undefined' || typeof t == 'undefined' || t == null) return false; 197 | 198 | if ((dt - t.dt) > 0) { 199 | db.get('visited').remove({ id: e.id }); 200 | return false; 201 | } else { 202 | return true; 203 | } 204 | } 205 | 206 | 207 | 208 | /* 209 | Tracking of Downloaded Replays 210 | */ 211 | addDownloaded(e) { 212 | var t = db.get('downloaded').find({ id: e }).value(); 213 | if (t == 'undefined' || typeof t == 'undefined' || t == null) { 214 | db.get('downloaded').push({ 215 | id: e, 216 | dt: Math.floor(new Date().getTime() / 1000) 217 | }).write(); 218 | } 219 | } 220 | wasDownloaded(e) { 221 | var t = db.get('downloaded').find({ id: e }).value(); 222 | return (t == 'undefined' || typeof t == 'undefined' || t == null) ? false : true; 223 | } 224 | 225 | 226 | } 227 | 228 | exports.DataManager = DataManager; 229 | -------------------------------------------------------------------------------- /src/custom_modules/DownloadManager/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const wget = require('wget-improved'); 12 | const pMap = require('p-map'); 13 | const pProgress = require('p-progress'); 14 | const axios = require('axios'); 15 | const path = require("path"); 16 | const fs = require("fs-extra"); 17 | const events = require("events"); 18 | const { app, ipcMain, dialog } = require('electron'); 19 | const { exec } = require('child_process'); 20 | function uuidv4() { 21 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 22 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 23 | return v.toString(16); 24 | }); 25 | } 26 | class DownloadManager { 27 | constructor() { 28 | this._queue = new Map(); 29 | this._history = []; 30 | this._paused = false; 31 | this._running = false; 32 | this._ffmpegPath = 'ffmpeg'; 33 | this.events = new (events.EventEmitter)(); 34 | } 35 | _emit(channel, obj) { 36 | if (this._eventCache && (this._eventCache.channel === channel && JSON.stringify(this._eventCache.obj) === JSON.stringify(obj))) { 37 | return; 38 | } 39 | else { 40 | this._eventCache = { 41 | channel: channel, 42 | obj: obj 43 | }; 44 | } 45 | this.events.emit(channel, obj); 46 | } 47 | _processChunk(url, dest) { 48 | return new pProgress((resolve, reject, progress) => { 49 | fs.ensureDirSync(path.dirname(dest)); 50 | let download = wget.download(url, dest); 51 | download.on('error', (err) => { 52 | return resolve({ success: false, error: err, local: dest }); 53 | }); 54 | download.on('start', (filesize) => { 55 | }); 56 | download.on('end', (output) => { 57 | return resolve({ success: true, local: dest }); 58 | }); 59 | download.on('progress', (p) => { 60 | progress({ chunk: path.basename(url, '.ts'), progress: p }); 61 | }); 62 | }); 63 | } 64 | _getTempFilename(url, uuid) { 65 | return path.join(this._appSettings.get('downloads.directory'), 'temp', uuid, path.basename(url)); 66 | } 67 | _getLocalFilename(playlist) { 68 | let defaultPath = path.join(this._appSettings.get('downloads.directory'), path.basename(playlist.video.url).replace("m3u8", "mp4")); 69 | let finalPath; 70 | if (this._appSettings.get('downloads.filemode') == 0) { 71 | finalPath = defaultPath; 72 | } 73 | else { 74 | let finalName = this._appSettings.get('downloads.filetemplate') 75 | .replace(/%%username%%/g, playlist.user.name) 76 | .replace(/%%userid%%/g, playlist.user.id) 77 | .replace(/%%videoid%%/g, playlist.video.id) 78 | .replace(/%%videotitle%%/g, playlist.video.title) 79 | .replace(/%%videotime%%/g, '' + playlist.video.time); 80 | if (!finalName || finalName == '') { 81 | finalPath = defaultPath; 82 | } 83 | else { 84 | finalPath = path.join(this._appSettings.get('downloads.directory'), finalName.replace(/[:*?""<>|]/g, '_') + ".mp4"); 85 | } 86 | } 87 | let basename = path.basename(finalPath); 88 | if (basename == 'playlist.mp4' || basename == 'playlist_eof.mp4') { 89 | let parentName = path.basename(path.dirname(playlist.video.url)); 90 | finalPath = finalPath.replace(basename, parentName + '.mp4'); 91 | } 92 | fs.ensureDirSync(path.dirname(finalPath)); 93 | return finalPath; 94 | } 95 | _getUrlsFromPlaylist(m3u8) { 96 | return new Promise((resolve, reject) => { 97 | return axios 98 | .get(m3u8) 99 | .then(response => { 100 | let playlist = [], baseURL = path.dirname(m3u8); 101 | for (let line of (response.data.split('\n'))) { 102 | line = line.trim(); 103 | if (line.length == 0 || line[0] == '#') { 104 | continue; 105 | } 106 | line = line.split('?').shift(); 107 | line = `${baseURL}/${line}`; 108 | if (playlist.indexOf(line) != -1) { 109 | continue; 110 | } 111 | playlist.push(line); 112 | } 113 | return resolve(playlist); 114 | }) 115 | .catch(err => { 116 | return reject(err); 117 | }); 118 | }); 119 | } 120 | _processItem(uuid, playlist) { 121 | return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { 122 | this._emit('download-started', { uuid: uuid }); 123 | let chunkProgress = new Map(); 124 | let downloadedChunks = 0; 125 | this._emit('download-status', { uuid: uuid, status: 'Getting chunks to download...' }); 126 | let urls = yield this._getUrlsFromPlaylist(playlist.video.url).catch(err => { 127 | return reject(`Couldn't get chunks to download: ${err}`); 128 | }); 129 | let mapper = el => this._processChunk(el, this._getTempFilename(el, uuid)).onProgress(p => { 130 | chunkProgress.set(p.chunk, p.progress); 131 | let total = 0; 132 | for (let val of chunkProgress.values()) { 133 | if (val) 134 | total += val; 135 | } 136 | total = Math.floor((total / urls.length) * 100); 137 | this._emit('download-progress', { uuid: uuid, percent: total }); 138 | }).then((result) => { 139 | downloadedChunks++; 140 | this._emit('download-status', { uuid: uuid, status: `Downloading chunks [${downloadedChunks}/${urls.length}]` }); 141 | return result; 142 | }); 143 | this._emit('download-status', { uuid: uuid, status: `Downloading chunks [0/${urls.length}]` }); 144 | pMap(urls, mapper, { concurrency: (this._appSettings.get('downloads.concurrency') || 4) }).then(result => { 145 | this._emit('download-status', { uuid: uuid, status: 'Merging chunks' }); 146 | if (result.length == 1) { 147 | let newPath = this._getLocalFilename(playlist); 148 | if (fs.existsSync(newPath)) { 149 | fs.removeSync(newPath); 150 | } 151 | fs.moveSync(result[0].local, newPath); 152 | return resolve(); 153 | } 154 | let concatStr = '# Generated by LiveMeTools'; 155 | let concatPath = this._getTempFilename('concat.txt', uuid); 156 | for (let res of result) { 157 | if (res.success) { 158 | concatStr += `\nfile '${res.local}'`; 159 | } 160 | else { 161 | return reject(`Failed to download at least one file`); 162 | } 163 | } 164 | fs.writeFileSync(concatPath, concatStr); 165 | exec(`${this._ffmpegPath} -y -f concat -safe 0 -i "${concatPath}" -c copy -bsf:a aac_adtstoasc -vsync 2 -movflags +faststart "${this._getLocalFilename(playlist)}"`, (error, stdout, stderr) => { 166 | if (error) { 167 | let log = path.join(this._appSettings.get('downloads.directory'), 'ffmpeg-error.log'); 168 | fs.writeFileSync(log, `${error}\n\n${stderr}\n\n${stdout}`); 169 | this._cleanupTempFiles(uuid); 170 | return reject(`FFMPEG Error, saved in: ${log}`); 171 | } 172 | this._cleanupTempFiles(uuid); 173 | return resolve(); 174 | }); 175 | }); 176 | })); 177 | } 178 | _cleanupTempFiles(uuid) { 179 | this._emit('download-status', { uuid: uuid, status: 'Cleaning temporary files' }); 180 | fs.removeSync(path.join(this._appSettings.get('downloads.directory'), 'temp', uuid)); 181 | } 182 | init(appSettings) { 183 | this._appSettings = appSettings; 184 | this._downloadQueuePath = path.join(app.getPath('appData'), app.getName(), 'download-queue-v2.json'); 185 | this._downloadHistoryPath = path.join(app.getPath('appData'), app.getName(), 'downloadHistory.json'); 186 | let mpeg = appSettings.get('downloads.ffmpeg'), probe = appSettings.get('downloads.ffprobe'); 187 | if (mpeg && mpeg != 'ffmpeg') { 188 | this._ffmpegPath = mpeg; 189 | } 190 | if (probe && probe != 'ffprobe') { 191 | } 192 | } 193 | add(playlist) { 194 | let uuid = uuidv4(); 195 | this._queue.set(uuid, playlist); 196 | this._emit('download-queued', { uuid: uuid, display: `${playlist.user.name}: ${playlist.video.id}` }); 197 | this.loop(); 198 | return uuid; 199 | } 200 | delete(uuid) { 201 | this._queue.delete(uuid); 202 | this._emit('download-deleted', { uuid: uuid }); 203 | } 204 | start(uuid) { 205 | let item = this._queue.get(uuid); 206 | this._queue.delete(uuid); 207 | return this._processItem(uuid, item) 208 | .then(result => { 209 | this._emit('download-completed', { uuid: uuid }); 210 | if (this._appSettings.get('downloads.history')) { 211 | this._history.push(item.video.id); 212 | } 213 | }) 214 | .catch(err => { 215 | this._emit('download-errored', { uuid: uuid, error: err }); 216 | }); 217 | } 218 | loop() { 219 | return __awaiter(this, void 0, void 0, function* () { 220 | if (this._running || this._paused) { 221 | return; 222 | } 223 | this._running = true; 224 | while (this._queue.size > 0 && !this._paused) { 225 | yield this.start(this._queue.keys().next().value); 226 | this.saveQueue(); 227 | } 228 | this._running = false; 229 | }); 230 | } 231 | isPaused() { 232 | return this._paused; 233 | } 234 | isRunning() { 235 | return this._running; 236 | } 237 | pause() { 238 | this._paused = true; 239 | this._emit('download-global-pause', null); 240 | } 241 | resume() { 242 | this._paused = false; 243 | this._emit('download-global-resume', null); 244 | this.loop(); 245 | } 246 | load() { 247 | this.loadQueue(); 248 | this.loadHistory(); 249 | } 250 | save() { 251 | this.saveQueue(); 252 | this.saveHistory(); 253 | } 254 | hasBeenDownloaded(videoid) { 255 | return this._history.indexOf(videoid) != -1; 256 | } 257 | purgeHistory() { 258 | fs.removeSync(this._downloadHistoryPath); 259 | this._history = []; 260 | } 261 | purgeQueue() { 262 | this._queue = new Map(); 263 | this.saveQueue(); 264 | this._emit('download-queue-clear', null); 265 | } 266 | setFfmpegPath(path) { 267 | this._ffmpegPath = path; 268 | } 269 | setFfprobePath(path) { 270 | } 271 | detectFFMPEG() { 272 | return new Promise((resolve, reject) => { 273 | exec(`${this._ffmpegPath} -codecs`, (error, stdout, stderr) => { 274 | if (error) { 275 | console.log('--------------------'); 276 | console.log('FFMPEG CHECK FAILED:'); 277 | console.log(error); 278 | console.log('--------------------'); 279 | return resolve(false); 280 | } 281 | return resolve(true); 282 | }); 283 | }); 284 | } 285 | saveQueue() { 286 | let spread = JSON.stringify([...this._queue]); 287 | fs.writeFile(this._downloadQueuePath, spread, 'utf8', (err) => { 288 | if (err) { 289 | console.error(err); 290 | } 291 | }); 292 | } 293 | saveHistory() { 294 | if (!this._appSettings.get('downloads.history')) { 295 | return; 296 | } 297 | fs.writeFile(this._downloadHistoryPath, JSON.stringify(this._history), 'utf8', (err) => { 298 | if (err) { 299 | console.log(err); 300 | } 301 | }); 302 | } 303 | loadQueue() { 304 | if (!fs.existsSync(this._downloadQueuePath)) { 305 | this._queue = new Map(); 306 | return; 307 | } 308 | fs.readFile(this._downloadQueuePath, 'utf8', (err, data) => { 309 | if (err) { 310 | console.error(err); 311 | } 312 | else { 313 | try { 314 | this._queue = new Map(JSON.parse(data)); 315 | } 316 | catch (err) { 317 | console.error(err); 318 | } 319 | } 320 | if (this._queue.size > 0) { 321 | for (let [key, playlist] of this._queue) { 322 | this._emit('download-queued', { uuid: key, display: `${playlist.user.name}: ${playlist.video.id}` }); 323 | } 324 | this.loop(); 325 | } 326 | }); 327 | } 328 | loadHistory() { 329 | if (!this._appSettings.get('downloads.history')) { 330 | return; 331 | } 332 | if (!fs.existsSync(this._downloadHistoryPath)) { 333 | this._history = []; 334 | return; 335 | } 336 | fs.readFile(this._downloadHistoryPath, 'utf8', (err, data) => { 337 | if (err) { 338 | console.log(err); 339 | this._history = []; 340 | } 341 | else { 342 | try { 343 | this._history = JSON.parse(data); 344 | } 345 | catch (err) { 346 | console.log(err); 347 | this._history = []; 348 | } 349 | } 350 | }); 351 | } 352 | } 353 | exports.DownloadManager = DownloadManager; 354 | -------------------------------------------------------------------------------- /src/custom_modules/Favorites/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Favorites Module 3 | */ 4 | "use strict"; 5 | 6 | const { app, dialog } = require('electron'), 7 | path = require('path'), axios = require('axios'), fs = require('fs'), 8 | eventEmitter = new(require('events').EventEmitter)(); 9 | 10 | var fav_list = [], last_change = 0, is_saved = false, index = 0, test_var = 0; 11 | 12 | 13 | module.exports = { 14 | 15 | events: eventEmitter, 16 | 17 | add : function(e) { 18 | fav_list.push(e); 19 | update_single_user(fav_list.length - 1); 20 | eventEmitter.emit('refresh', fav_list); 21 | }, 22 | 23 | refresh: function() { 24 | eventEmitter.emit('status', 'Loading list, please wait...'); 25 | eventEmitter.emit('refresh', fav_list); 26 | }, 27 | 28 | remove: function(e) { 29 | var idx = 0; 30 | for (var i = 0; i < fav_list.length; i++) { 31 | if (fav_list[i].uid == e) { 32 | fav_list.splice(i, 1); 33 | } 34 | } 35 | 36 | write_to_file(); 37 | eventEmitter.emit('removeSingle', { userid: e }); 38 | }, 39 | 40 | save: function() { 41 | write_to_file(); 42 | eventEmitter.emit('refresh', fav_list); 43 | }, 44 | 45 | load: function() { 46 | eventEmitter.emit('status', 'Loading list, please wait...'); 47 | fs.readFile(path.join(app.getPath('appData'), app.getName(), 'favorites.json'), 'utf8', function (err,data) { 48 | if (err) { 49 | fav_list = []; 50 | } else { 51 | var i, j = JSON.parse(data); 52 | for (i = 0; i < j.length; i++) { 53 | fav_list.push({ 54 | 'uid' : j[i].uid, 55 | 'face' : j[i].face, 56 | 'nickname' : j[i].nickname, 57 | 'sex' : j[i].sex, 58 | 'level' : j[i].level, 59 | 'video_count' : j[i].video_count, 60 | 'usign' : j[i].usign, 61 | 'stars' : j[i].stars 62 | }) 63 | } 64 | 65 | fav_list = JSON.parse(data); 66 | eventEmitter.emit('refresh', fav_list); 67 | } 68 | }); 69 | 70 | }, 71 | 72 | isOnList: function(e) { 73 | for (var i = 0; i < fav_list.length; i++) { 74 | if (fav_list[i].uid == e) return true; 75 | } 76 | return false; 77 | }, 78 | 79 | tick : function() { }, 80 | forceSave : function() { write_to_file(true); }, 81 | update: function() { 82 | eventEmitter.emit('status', 'Refreshing list, please wait...'); 83 | index = 0; 84 | update_favorites_list(); 85 | //eventEmitter.emit('refresh', fav_list); 86 | }, 87 | 88 | export: function(file) { 89 | let ids = "# Generated by LiveMe-Tools"; 90 | 91 | for (let o of fav_list) { 92 | ids += "\n" + o['uid']; // Just use \n even though windows uses \r\n. It will look stupid in Notepad, but it will still work if this file gets transferred between Windows and Linux/Mac. The importer will parse for \n, not \r\n. 93 | } 94 | 95 | fs.writeFile(file, ids, 'utf8', function(err) { 96 | if (err) { 97 | dialog.showErrorBox('Export Error', 'There was an error when attempting to export your favorites'); 98 | console.error(err); 99 | } 100 | }); 101 | }, 102 | 103 | import: function(file) { 104 | fs.readFile(file, 'utf8', (err, data) => { 105 | if (err) { 106 | dialog.showErrorBox('Import Error', 'There was an error when attempting to import your favorites'); 107 | console.error(err); 108 | } else { 109 | for (let id of data.split("\n")) { 110 | id = id.trim(); 111 | 112 | if (id.startsWith('#') || id.length == 0) { 113 | continue; 114 | } 115 | 116 | if (this.isOnList(id)) { 117 | continue; 118 | } 119 | 120 | fav_list.push({ uid: id }); 121 | } 122 | 123 | this.update(); 124 | } 125 | }); 126 | } 127 | } 128 | 129 | 130 | 131 | 132 | 133 | function write_to_file() { 134 | var ti = new Date().getTime() / 1000; 135 | last_change = ti; 136 | 137 | fs.writeFile(path.join(app.getPath('appData'), app.getName(), 'favorites.json'), JSON.stringify(fav_list, null, 2), function(){ }); 138 | } 139 | 140 | function read_from_file(cb) { 141 | fs.readFile(path.join(app.getPath('appData'), app.getName(), 'favorites.json'), 'utf8', function (err,data) { 142 | if (err) { 143 | fav_list = []; 144 | } else { 145 | fav_list = JSON.parse(data); 146 | last_change = new Date().getTime() / 1000; 147 | cb(fav_list); 148 | } 149 | }); 150 | 151 | } 152 | 153 | function update_single_user(index) { 154 | 155 | axios.get('http://live.ksmobile.net/user/getinfo',{ 156 | params: { 157 | userid: fav_list[index].uid 158 | } 159 | }).then(function(resp) { 160 | var j = resp.data.data.user; 161 | 162 | if (resp.data.status == 200) { 163 | fav_list[index].face = j.user_info.face; 164 | fav_list[index].nickname = j.user_info.nickname; 165 | fav_list[index].usign = j.user_info.usign; 166 | fav_list[index].level = j.user_info.level; 167 | fav_list[index].stars = j.user_info.star; 168 | fav_list[index].currency = j.user_info.currency; 169 | fav_list[index].stars = j.user_info.star; 170 | fav_list[index].stars = j.user_info.star; 171 | fav_list[index].stars = j.user_info.star; 172 | fav_list[index].sex = j.user_info.sex > -1 ? ( j.user_info.sex > 0 ? 'male' : 'female') : ''; 173 | fav_list[index].video_count = j.count_info.video_count; 174 | } 175 | 176 | fs.writeFile(path.join(app.getPath('appData'), app.getName(), 'favorites.json'), JSON.stringify(fav_list, null, 2), function(){ 177 | eventEmitter.emit('refresh', fav_list); 178 | }); 179 | 180 | }).catch(function(err){ 181 | console.log('Error during favorites list update: ' + err); 182 | }); 183 | } 184 | 185 | function update_favorites_list() { 186 | if (fav_list.length == 0) return; 187 | 188 | axios.get('http://live.ksmobile.net/user/getinfo',{ 189 | params: { 190 | userid: fav_list[index].uid 191 | } 192 | }).then(function(resp) { 193 | var j = resp.data.data.user; 194 | 195 | if (resp.data.status == 200) { 196 | fav_list[index].face = j.user_info.face; 197 | fav_list[index].nickname = j.user_info.nickname; 198 | fav_list[index].usign = j.user_info.usign; 199 | fav_list[index].level = j.user_info.level; 200 | fav_list[index].stars = j.user_info.star; 201 | fav_list[index].currency = j.user_info.currency; 202 | fav_list[index].stars = j.user_info.star; 203 | fav_list[index].stars = j.user_info.star; 204 | fav_list[index].stars = j.user_info.star; 205 | fav_list[index].sex = j.user_info.sex > -1 ? ( j.user_info.sex > 0 ? 'male' : 'female') : ''; 206 | fav_list[index].video_count = j.count_info.video_count; 207 | } 208 | index++; 209 | 210 | if (index == fav_list.length) { 211 | fs.writeFile(path.join(app.getPath('appData'), app.getName(), 'favorites.json'), JSON.stringify(fav_list, null, 2), function(){ 212 | eventEmitter.emit('refresh', fav_list); 213 | }); 214 | } 215 | 216 | eventEmitter.emit('status', 'Refreshing '+index+' of ' + fav_list.length + '...'); 217 | update_favorites_list(); 218 | 219 | }).catch(function(err){ 220 | console.log('Error during favorites list update: ' + err); 221 | }); 222 | } 223 | -------------------------------------------------------------------------------- /src/dist/empty.txt: -------------------------------------------------------------------------------- 1 | This is just an empty file. -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | _ _ __ __ _______ _ 4 | | | (_) | \/ | |__ __| | | 5 | | | ___ _____| \ / | ___ | | ___ ___ | |___ 6 | | | | \ \ / / _ \ |\/| |/ _ \ | |/ _ \ / _ \| / __| 7 | | |____| |\ V / __/ | | | __/ | | (_) | (_) | \__ \ 8 | |______|_| \_/ \___|_| |_|\___| |_|\___/ \___/|_|___/ 9 | 10 | 11 | Licensed under GPL3 now 12 | 13 | Developers: 14 | 15 | thecoder - https://github.com/thecoder75 16 | polydragon - https://github.com/polydragon 17 | 18 | */ 19 | const { app, BrowserWindow, ipcMain, Menu, shell, dialog } = require('electron'), 20 | os = require('os'), 21 | fs = require('fs'), 22 | isDev = require('electron-is-dev'), 23 | path = require('path'), 24 | request = require('request'), 25 | appSettings = require('electron-settings'), 26 | DataManager = new(require('./custom_modules/DataManager').DataManager)(), 27 | DownloadManager = new (require('./custom_modules/DownloadManager').DownloadManager)(), 28 | LiveMe = require('liveme-api'); 29 | 30 | let mainwin = null, 31 | queuewin = null, 32 | playerWindow = null, 33 | favoritesWindow = null, 34 | chatWindow = null, 35 | importwin = null, 36 | aboutwin = null, 37 | updatewin = null, 38 | livemeomg = null; 39 | 40 | function startApplication() { 41 | if (!appSettings.get('downloads.directory')) { 42 | appSettings.set('downloads', { 43 | directory: path.join(app.getPath('home'), 'Downloads'), 44 | filemode: 0, 45 | filetemplate: '', 46 | history: true, 47 | replaycount: 10, 48 | engine: 'internal' 49 | }); 50 | } 51 | 52 | var mainpos = { x: -1, y: -1 }, queuepos = { x: -1, y: -1 }, mainsize = [980, 540], queuesize = [640, 400]; 53 | 54 | if (!appSettings.get('windowpos.main')) { 55 | appSettings.set('windowpos', { 56 | main: JSON.stringify([ -1, -1]), 57 | queue: JSON.stringify([ -1, -1]), 58 | player: JSON.stringify([ -1, -1]), 59 | favorites: JSON.stringify([ -1, -1]), 60 | messages: JSON.stringify([ -1, -1]) 61 | }); 62 | appSettings.set('windowsize', { 63 | main: JSON.stringify([ 980, 560]), 64 | queue: JSON.stringify([ 640, 400]), 65 | player: JSON.stringify([ 368, process.platform == 'darwin' ? 640 : 664]), 66 | favorites: JSON.stringify([ 360, 720]), 67 | messages: JSON.stringify([ 360, 480]) 68 | }); 69 | } else { 70 | mainpos = JSON.parse(appSettings.get('windowpos.main')); 71 | queuepos = JSON.parse(appSettings.get('windowpos.queue')); 72 | mainsize = JSON.parse(appSettings.get('windowsize.main')); 73 | queuesize = JSON.parse(appSettings.get('windowsize.queue')); 74 | } 75 | 76 | 77 | mainwin = new BrowserWindow({ 78 | icon: __dirname + '/appicon.ico', 79 | x: mainpos[0] != -1 ? mainpos[0] : null, 80 | y: mainpos[1] != -1 ? mainpos[1] : null, 81 | width: mainsize[0], 82 | height: mainsize[1], 83 | minWidth: 980, 84 | minHeight: 560, 85 | darkTheme: true, 86 | autoHideMenuBar: false, 87 | disableAutoHideCursor: true, 88 | titleBarStyle: 'default', 89 | fullscreen: false, 90 | maximizable: true, 91 | closable: true, 92 | frame: true, 93 | backgroundColor: '#000000', 94 | show: false, 95 | webPreferences: { 96 | webSecurity: false, 97 | textAreasAreResizable: false, 98 | plugins: true 99 | } 100 | }); 101 | 102 | mainwin 103 | .on('ready-to-show', () => { 104 | // mainwin.show(); 105 | }); 106 | 107 | mainwin.on('close', () => { 108 | appSettings.set('windowpos.main', JSON.stringify(mainwin.getPosition()) ); 109 | appSettings.set('windowsize.main', JSON.stringify(mainwin.getSize()) ); 110 | }); 111 | mainwin.on('closed', () => { 112 | shutdownApp(); 113 | }); 114 | 115 | mainwin.loadURL(`file://${__dirname}/lmt/index.html`); 116 | 117 | queuewin = new BrowserWindow({ 118 | x: queuepos[0] != -1 ? queuepos[0] : null, 119 | y: queuepos[1] != -1 ? queuepos[1] : null, 120 | width: queuesize[0], 121 | height: queuesize[1], 122 | resizable: true, 123 | minWidth: 640, 124 | maxWidth: 640, 125 | minHeight: 200, 126 | maxHeight: 1600, 127 | darkTheme: true, 128 | autoHideMenuBar: true, 129 | show: false, 130 | skipTaskbar: false, 131 | disableAutoHideCursor: true, 132 | titleBarStyle: 'default', 133 | fullscreen: false, 134 | minimizable: true, 135 | maximizable: false, 136 | closable: false, 137 | frame: true, 138 | backgroundColor: '#000000', 139 | webPreferences: { 140 | webSecurity: false, 141 | plugins: true, 142 | devTools: true 143 | } 144 | }); 145 | 146 | queuewin.setMenu(null); 147 | queuewin 148 | .on('closed', () => { 149 | queuewin = null; 150 | }) 151 | .loadURL(`file://${__dirname}/lmt/queue.html`); 152 | queuewin.on('close', () => { 153 | appSettings.set('windowpos.queue', JSON.stringify(queuewin.getPosition()) ); 154 | appSettings.set('windowsize.queue', JSON.stringify(queuewin.getSize()) ); 155 | }); 156 | 157 | 158 | updatewin = new BrowserWindow({ 159 | width: 480, 160 | height: 80, 161 | resizable: false, 162 | darkTheme: true, 163 | autoHideMenuBar: true, 164 | show: false, 165 | skipTaskbar: true, 166 | disableAutoHideCursor: true, 167 | titleBarStyle: 'default', 168 | fullscreen: false, 169 | minimizable: false, 170 | maximizable: false, 171 | closable: true, 172 | frame: false, 173 | vibrancy: 'ultra-dark', 174 | backgroundColor: '#000000', 175 | webPreferences: { 176 | webSecurity: false, 177 | plugins: true, 178 | devTools: true 179 | } 180 | }); 181 | updatewin.setMenu(null); 182 | updatewin 183 | .on('closed', () => { 184 | updatewin = null; 185 | }) 186 | .loadURL(`file://${__dirname}/lmt/update.html`); 187 | 188 | // Build our custom menubar 189 | Menu.setApplicationMenu(Menu.buildFromTemplate(getMenuTemplate())); 190 | 191 | setInterval(function(){ 192 | // We want to commit the database every 300 seconds 193 | // to avoid too many system writes. 194 | DataManager.commitDatabases(); 195 | }, (300 * 1000)); 196 | 197 | setTimeout(function () { 198 | CheckForUpgrade(); 199 | }, 10000); 200 | 201 | setTimeout(function() { 202 | queuewin.minimize(); 203 | }, 50); 204 | 205 | setTimeout(() => { 206 | showSplash(); 207 | }, 500); 208 | 209 | //DataManager.load(); 210 | DownloadManager.init(appSettings); 211 | global.DataManager = DataManager; 212 | global.DownloadManager = DownloadManager; 213 | 214 | DownloadManager.events.on('show-queue', () => { 215 | if (queuewin) { 216 | queuewin.showInactive(); 217 | } 218 | }); 219 | 220 | setTimeout(function() { 221 | if (!fs.existsSync(path.join(app.getPath('appData'), app.getName(), 'favorites.json'))) { 222 | aboutwin.on('close', () => { 223 | mainwin.show(); 224 | }); 225 | } else { 226 | updatewin.on('close', () => { 227 | mainwin.show(); 228 | }); 229 | 230 | aboutwin.on('close', () => { 231 | updatewin.show(); 232 | }); 233 | } 234 | }, 500); 235 | 236 | 237 | } 238 | 239 | var shouldQuit = app.makeSingleInstance(function (commandLine, workingDirectory) { 240 | }); 241 | 242 | if (shouldQuit) { 243 | shutdownApp(); 244 | return; 245 | } 246 | 247 | app 248 | .on('ready', startApplication) 249 | .on('activate', () => { 250 | if (mainwin === null) { 251 | startApplication(); 252 | } 253 | }); 254 | 255 | 256 | 257 | /* 258 | Splash/About Window 259 | */ 260 | function showSplash() { 261 | aboutwin = new BrowserWindow({ 262 | width: 640, 263 | height: 224, 264 | resizable: false, 265 | darkTheme: true, 266 | autoHideMenuBar: true, 267 | show: false, 268 | skipTaskbar: true, 269 | disableAutoHideCursor: true, 270 | titleBarStyle: 'default', 271 | fullscreen: false, 272 | backgroundColor: '#000000', 273 | maximizable: false, 274 | frame: false, 275 | movable: false, 276 | transparent: true, 277 | parent: mainwin 278 | }); 279 | aboutwin.setMenu(null); 280 | aboutwin 281 | .on('ready-to-show', () => { 282 | aboutwin.show(); 283 | }) 284 | .loadURL(`file://${__dirname}/lmt/splash.html`); 285 | 286 | } 287 | 288 | function showSettings() { 289 | let settingsWindow = new BrowserWindow({ 290 | width: 900, 291 | height: 420, 292 | resizable: false, 293 | darkTheme: true, 294 | autoHideMenuBar: true, 295 | show: false, 296 | skipTaskbar: false, 297 | center: true, 298 | backgroundColor: '#000000', 299 | disableAutoHideCursor: true, 300 | titleBarStyle: 'default', 301 | fullscreen: false, 302 | maximizable: false, 303 | closable: true, 304 | frame: true, 305 | parent: mainwin, 306 | modal: false 307 | }); 308 | 309 | settingsWindow.setMenu(null); 310 | 311 | settingsWindow 312 | .on('ready-to-show', () => { 313 | settingsWindow.show(); 314 | }) 315 | .loadURL(`file://${__dirname}/lmt/settings.html`); 316 | }; 317 | 318 | 319 | function showLiveMeOMG() { 320 | let livemeomg = new BrowserWindow({ 321 | width: 640, 322 | height: 540, 323 | resizable: true, 324 | darkTheme: true, 325 | autoHideMenuBar: true, 326 | show: false, 327 | skipTaskbar: false, 328 | center: true, 329 | backgroundColor: '#000000', 330 | disableAutoHideCursor: true, 331 | titleBarStyle: 'default', 332 | fullscreen: false, 333 | maximizable: true, 334 | closable: true, 335 | frame: true, 336 | modal: false 337 | }); 338 | 339 | livemeomg.setMenu(null); 340 | 341 | livemeomg 342 | .on('ready-to-show', () => { 343 | livemeomg.show(); 344 | }) 345 | .loadURL(`file://${__dirname}/lmt/livemeomg.html`); 346 | }; 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | /* 359 | Favorites Related 360 | */ 361 | function openFavoritesWindow() { 362 | if (favoritesWindow == null) { 363 | 364 | var favpos = JSON.parse(appSettings.get('windowpos.favorites')), 365 | favsize = JSON.parse(appSettings.get('windowsize.favorites')); 366 | 367 | favoritesWindow = new BrowserWindow({ 368 | x: favpos[0] != -1 ? favpos[0] : null, 369 | y: favpos[1] != -1 ? favpos[1] : null, 370 | width: favsize[0], 371 | height: favsize[1], 372 | resizable: false, 373 | darkTheme: true, 374 | autoHideMenuBar: true, 375 | show: false, 376 | skipTaskbar: false, 377 | backgroundColor: '#000000', 378 | disableAutoHideCursor: true, 379 | titleBarStyle: 'default', 380 | fullscreen: false, 381 | maximizable: false, 382 | closable: true, 383 | frame: true, 384 | webPreferences: { 385 | webSecurity: false, 386 | plugins: true, 387 | devTools: true 388 | } 389 | }); 390 | favoritesWindow.setMenu(null); 391 | favoritesWindow 392 | .on('ready-to-show', () => { 393 | favoritesWindow.show(); 394 | }) 395 | .on('closed', () => { 396 | favoritesWindow = null; 397 | }) 398 | .on('close', () => { 399 | appSettings.set('windowpos.favorites', JSON.stringify(favoritesWindow.getPosition()) ); 400 | appSettings.set('windowsize.favorites', JSON.stringify(favoritesWindow.getSize()) ); 401 | }) 402 | .loadURL(`file://${__dirname}/lmt/favorites-list.html`); 403 | 404 | } 405 | }; 406 | 407 | ipcMain.on('show-settings', (event, arg) => { 408 | showSettings(); 409 | }); 410 | 411 | 412 | 413 | /* 414 | Search Related 415 | ?? - Called from Following/Fans/Favorites windows when an entry is clicked. Will 416 | need to clean up and standardize as a single command 417 | */ 418 | 419 | ipcMain.on('submit-search', (event, arg) => { 420 | mainwin.webContents.send('do-search', { userid: arg.userid }); 421 | }); 422 | 423 | ipcMain.on('livemesearch', (event, arg) => { 424 | if (arg.type == 'search') { 425 | lmt.searchkeyword(arg.query, function (e) { 426 | mainwin.webContents.send('render_results', { data: e, type: 'search' }); 427 | }) 428 | } else { 429 | lmt.getuservideos(arg.query, function (e) { 430 | mainwin.webContents.send('render_results', { data: e, type: 'userlookup' }); 431 | }) 432 | } 433 | }); 434 | 435 | 436 | 437 | /* 438 | Popup Windows (Followings/Fans) 439 | */ 440 | ipcMain.on('open-window', (event, arg) => { 441 | 442 | let win = new BrowserWindow({ 443 | width: 320, 444 | height: 720, 445 | resizable: false, 446 | darkTheme: true, 447 | autoHideMenuBar: false, 448 | skipTaskbar: false, 449 | backgroundColor: '#000000', 450 | disableAutoHideCursor: true, 451 | titleBarStyle: 'default', 452 | fullscreen: false, 453 | maximizable: false, 454 | closable: true, 455 | frame: true, 456 | show: false 457 | }); 458 | win.setMenu(null); 459 | 460 | win.on('ready-to-show', () => { 461 | win.show(); 462 | }).loadURL(`file://${__dirname}/lmt/` + arg.url); 463 | }); 464 | 465 | 466 | 467 | /* 468 | Video Player Related 469 | 470 | macOS allows us to still have window controls but no titlebar 471 | and overlay the controls on the content of the page. 472 | 473 | This allows us to have a window just like QuickTime Player 474 | does. 475 | */ 476 | ipcMain.on('play-video', (event, arg) => { 477 | if (playerWindow == null) { 478 | 479 | var playerpos = JSON.parse(appSettings.get('windowpos.player')), 480 | playersize = JSON.parse(appSettings.get('windowsize.player')); 481 | 482 | playerWindow = new BrowserWindow({ 483 | x: playerpos[0] != -1 ? playerpos[0] : null, 484 | y: playerpos[1] != -1 ? playerpos[1] : null, 485 | width: playersize[0], 486 | height: playersize[1], 487 | minWidth: 368, 488 | minHeight: process.platform == 'darwin' ? 640 : 664, 489 | resizable: true, 490 | darkTheme: true, 491 | autoHideMenuBar: false, 492 | show: false, 493 | skipTaskbar: false, 494 | backgroundColor: '#000000', 495 | disableAutoHideCursor: true, 496 | titleBarStyle: 'hidden', 497 | fullscreen: false, 498 | maximizable: true, 499 | closable: true, 500 | frame: process.platform == 'darwin' ? false : true 501 | }); 502 | playerWindow.setMenu(null); 503 | 504 | /* 505 | if (process.platform == 'darwin') { 506 | playerWindow.setAspectRatio(9 / 16); 507 | } 508 | */ 509 | 510 | playerWindow 511 | .on('ready-to-show', () => { 512 | playerWindow.show(); 513 | }) 514 | .on('close', () => { 515 | appSettings.set('windowpos.player', JSON.stringify(playerWindow.getPosition()) ); 516 | appSettings.set('windowsize.player', JSON.stringify(playerWindow.getSize()) ); 517 | }) 518 | .on('closed', () => { 519 | playerWindow = null; 520 | }); 521 | } 522 | 523 | playerWindow.loadURL(`file://${__dirname}/lmt/player.html#` + arg.url); 524 | }); 525 | ipcMain.on('video-set-time', (event, arg) => { 526 | playerWindow.webContents.send('jump-to-time', { time: arg.time, label: arg.label }); 527 | }); 528 | 529 | /* 530 | Chat Window 531 | */ 532 | ipcMain.on('open-chat', (event, arg) => { 533 | 534 | var msgpos = JSON.parse(appSettings.get('windowpos.messages')), 535 | msgsize = JSON.parse(appSettings.get('windowsize.messages')); 536 | 537 | 538 | chatWindow = new BrowserWindow({ 539 | x: msgpos[0] != -1 ? msgpos[0] : null, 540 | y: msgpos[1] != -1 ? msgpos[1] : null, 541 | width: msgsize[0], 542 | height: msgsize[1], 543 | width: 360, 544 | height: 480, 545 | minWidth: 360, 546 | maxWidth: 360, 547 | minHeight: 240, 548 | maxHeight: 1600, 549 | resizable: true, 550 | darkTheme: true, 551 | autoHideMenuBar: false, 552 | show: false, 553 | skipTaskbar: false, 554 | backgroundColor: '#000000', 555 | disableAutoHideCursor: true, 556 | titleBarStyle: 'default', 557 | fullscreen: false, 558 | minimizable: false, 559 | maximizable: false, 560 | closable: true, 561 | frame: true 562 | }); 563 | chatWindow.setMenu(null); 564 | 565 | chatWindow 566 | .on('close', () => { 567 | appSettings.set('windowpos.messages', JSON.stringify(chatWindow.getPosition()) ); 568 | appSettings.set('windowsize.messages', JSON.stringify(chatWindow.getSize()) ); 569 | }) 570 | .on('closed', () => { 571 | chatWindow = null; 572 | }) 573 | .loadURL(`file://${__dirname}/lmt/chat.html?${arg.videoid}`); 574 | 575 | chatWindow.showInactive(); 576 | }); 577 | 578 | 579 | 580 | /* 581 | 582 | Queue Window 583 | 584 | show-queue is issued when only the first download is added to the queue 585 | */ 586 | ipcMain.on('show-queue', () => { 587 | if (queuewin.isMinimized()) { 588 | queuewin.restore(); 589 | } 590 | }); 591 | function toggleQueueWindow() { 592 | if (queuewin.isMinimized()) { 593 | queuewin.restore(); 594 | } else { 595 | queuewin.minimize(); 596 | } 597 | }; 598 | 599 | 600 | 601 | /* 602 | History Window 603 | 604 | */ 605 | 606 | ipcMain.on('history-delete', (event, arg) => { 607 | mainwin.send('history-delete', {}); 608 | }); 609 | 610 | 611 | 612 | 613 | /* 614 | 615 | Misc. Functions 616 | 617 | Here are the Import and Export functions and other functions we 618 | use. 619 | 620 | */ 621 | function importUrlList() { 622 | var d = dialog.showOpenDialog( 623 | { 624 | properties: [ 625 | 'openFile', 626 | ], 627 | buttonLabel : 'Import', 628 | filters : [ 629 | { name : 'Plain Text File', extensions: [ 'txt' ]} 630 | ] 631 | }, 632 | (filePaths) => { 633 | if (filePaths == null) return; 634 | // We have a selection... 635 | mainwin.send('show-status', { message : 'Importing file, please wait...' }); 636 | fs.readFile(filePaths[0], 'utf8', function (err,data) { 637 | if (err) { 638 | mainwin.send('update-status', { message : 'Import error while accessing the file.' }); 639 | setTimeout(function(){ 640 | mainwin.send('hide-status'); 641 | }, 2000); 642 | } else { 643 | var filelist = data.split('\n'); 644 | 645 | for (i = 0; i < filelist.length; i++) { 646 | if (filelist[i].indexOf('http') > -1) { 647 | Downloads.add({ 648 | user: { 649 | id: null, 650 | name: null 651 | }, 652 | video: { 653 | id: null, 654 | title: null, 655 | time: 0, 656 | url: filelist[i].trim() 657 | } 658 | }); 659 | 660 | } 661 | } 662 | } 663 | }); 664 | } 665 | );} 666 | 667 | function importVideoIdList() { 668 | var d = dialog.showOpenDialog( 669 | { 670 | properties: [ 671 | 'openFile', 672 | ], 673 | buttonLabel : 'Import', 674 | filters : [ 675 | { name : 'Plain Text File', extensions: [ 'txt' ]} 676 | ] 677 | }, 678 | (filePaths) => { 679 | // We have a selection... 680 | if (filePaths == null) return; 681 | mainwin.send('show-status', { message : 'Importing file, please wait...' }); 682 | 683 | fs.readFile(filePaths[0], 'utf8', function (err,data) { 684 | if (err) { 685 | mainwin.send('update-status', { message : 'Import error while accessing the file.' }); 686 | setTimeout(function(){ 687 | mainwin.send('hide-status'); 688 | }, 2500); 689 | } else { 690 | var tt = data.replace('\r', ''); 691 | var t = tt.split('\n'), i = 0, idlist = []; 692 | 693 | for (i = 0; i < t.length; i++) { 694 | if (t[i].length > 16) 695 | idlist.push(t[i].trim()); 696 | } 697 | 698 | mainwin.send('update-status', { message : 'Found '+idlist.length+' entries to import.' }); 699 | _importVideoIdList(idlist); 700 | 701 | setTimeout(function(){ 702 | mainwin.send('hide-status'); 703 | }, 2500); 704 | } 705 | }); 706 | } 707 | ); 708 | } 709 | 710 | function _importVideoIdList(list) { 711 | var entry = list.shift(); 712 | LiveMe.getVideoInfo(entry) 713 | .then(video => { 714 | 715 | if (video.vid.length > 16) { 716 | DownloadManager.add({ 717 | user: { 718 | id: video.userid, 719 | name: video.uname 720 | }, 721 | video: { 722 | id: video.vid, 723 | title: video.title, 724 | time: video.vtime, 725 | url: video.hlsvideosource 726 | } 727 | }); 728 | } 729 | 730 | if (list.length > 0) 731 | _importVideoIdList(list); 732 | else { 733 | mainwin.send('update-status', { message : 'Import complete.' }); 734 | setTimeout(function(){ 735 | mainwin.send('hide-status'); 736 | }, 1000); 737 | } 738 | }); 739 | } 740 | 741 | function exportFavorites() { 742 | let d = dialog.showSaveDialog( 743 | { 744 | filters: [ { name: "Text File", extensions: ["txt"] }, { name: 'All Files', extensions: ['*'] } ], 745 | defaultPath: 'Exported Favorites UserID List.txt' 746 | }, 747 | (filePath) => { 748 | 749 | if (filePath != null) 750 | DataManager.exportFavorites(filePath); 751 | } 752 | ); 753 | } 754 | 755 | function importFavorites() { 756 | let d = dialog.showOpenDialog( 757 | { 758 | filters: [ { name: "Text File", extensions: ["txt"] }, { name: 'All Files', extensions: ['*'] } ], 759 | defaultPath: 'Exported Favorites UserID List.txt' 760 | }, 761 | (filePath) => { 762 | if (filePath != null) 763 | DataManager.importFavorites(filePath[0]); 764 | } 765 | ); 766 | } 767 | 768 | function shutdownApp() { 769 | if (queuewin != null) { 770 | queuewin.setClosable(true); 771 | queuewin.close(); 772 | } 773 | 774 | if (updatewin != null) { 775 | updatewin.setClosable(true); 776 | updatewin.close(); 777 | } 778 | 779 | DataManager.commitDatabases(); 780 | DownloadManager.save(); 781 | //Downloader.killActiveDownload(); 782 | 783 | setTimeout(function(){ 784 | app.quit(); 785 | }, 500); 786 | } 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | /* 802 | 803 | Main Window/macOS Menubar menu template 804 | 805 | */ 806 | function getMenuTemplate() { 807 | var template = [ 808 | { 809 | label: 'Edit', 810 | submenu: [ 811 | { role: 'undo' }, 812 | { role: 'redo' }, 813 | { type: 'separator' }, 814 | { role: 'cut' }, 815 | { role: 'copy' }, 816 | { role: 'paste' }, 817 | { role: 'delete' }, 818 | { role: 'selectall' } 819 | ] 820 | }, 821 | { 822 | label : 'Favorites', 823 | submenu : [ 824 | { 825 | label: 'Open Favorites', 826 | accelerator: 'CommandOrControl+D', 827 | click: () => openFavoritesWindow() 828 | }, 829 | { 830 | type: 'separator' 831 | }, 832 | { 833 | label: 'Refresh Entries', 834 | click: () => DataManager.updateFavorites() 835 | }, 836 | { 837 | label: 'Export Favorites List', 838 | click: () => exportFavorites() 839 | }, 840 | { 841 | label: 'Import Favorites List', 842 | click: () => importFavorites() 843 | } 844 | ] 845 | }, 846 | { 847 | role: 'window', 848 | submenu: [ 849 | { role: 'minimize' }, 850 | { role: 'close' }, 851 | { type: 'separator' }, 852 | { 853 | label: 'Developer Tools', 854 | submenu: [ 855 | { role: 'reload' }, 856 | { role: 'forcereload' }, 857 | { role: 'toggledevtools' } 858 | ] 859 | } 860 | ] 861 | }, 862 | { 863 | role: 'help', 864 | submenu: [ 865 | { 866 | label: 'LiveMe Tools Page', 867 | click: () => shell.openExternal('https://thecoder75.github.io/liveme-tools/') 868 | }, 869 | { 870 | label: 'Help with FFMPEG Installation', 871 | click: () => shell.openExternal('https://github.com/thecoder75/liveme-tools/blob/master/docs/ffmpeg.md') 872 | }, 873 | { 874 | label: 'Report an Issue', 875 | click: () => shell.openExternal('https://github.com/thecoder75/liveme-tools/issues') 876 | } 877 | ] 878 | } 879 | ]; 880 | 881 | if (process.platform === 'darwin') { 882 | template.unshift({ 883 | label: 'File', 884 | submenu: [ 885 | { 886 | label: 'Import', 887 | submenu : [ 888 | { 889 | label: 'Import URL List', 890 | accelerator: 'CommandOrControl+I', 891 | click: () => importUrlList() 892 | }, 893 | { 894 | label: 'Import VideoID List', 895 | accelerator: 'CommandOrControl+Shift+I', 896 | click: () => importVideoIdList() 897 | } 898 | ] 899 | }, 900 | { type: 'separator' }, 901 | { 902 | label: 'Show LiveMe-OMG', 903 | click: () => showLiveMeOMG() 904 | } 905 | ] 906 | }); 907 | template.unshift({ 908 | label: app.getName(), 909 | submenu: [ 910 | { 911 | label: 'About ' + app.getName(), 912 | click: () => showSplash() 913 | }, 914 | { type: 'separator' }, 915 | { 916 | label : 'Preferences', 917 | accelerator: 'CommandOrControl+,', 918 | click: () => showSettings() 919 | }, 920 | { type: 'separator' }, 921 | { role: 'services', submenu: [] }, 922 | { type: 'separator' }, 923 | { role: 'hide' }, 924 | { role: 'hideothers' }, 925 | { role: 'unhide' }, 926 | { type: 'separator' }, 927 | { 928 | label: 'Quit ' + app.getName(), 929 | accelerator: 'CommandOrControl+Q', 930 | click: () => shutdownApp() 931 | } 932 | ] 933 | }); 934 | } else { 935 | template.unshift({ 936 | label: 'File', 937 | submenu: [ 938 | { 939 | label: 'Import', 940 | submenu : [ 941 | { 942 | label: 'Import URL List', 943 | accelerator: 'CommandOrControl+I', 944 | click: () => importUrlList() 945 | }, 946 | { 947 | label: 'Import VideoID List', 948 | accelerator: 'CommandOrControl+Shift+I', 949 | click: () => importVideoIdList() 950 | } 951 | ] 952 | }, 953 | { type: 'separator' }, 954 | { 955 | label: 'Show LiveMe-OMG', 956 | click: () => showLiveMeOMG() 957 | }, 958 | { type: 'separator' }, 959 | { 960 | label : 'Preferences', 961 | click: () => showSettings() 962 | }, 963 | { type: 'separator' }, 964 | { 965 | label: 'Quit', 966 | accelerator: 'CommandOrControl+F4', 967 | click: () => shutdownApp() 968 | } 969 | ] 970 | }); 971 | } 972 | 973 | return template; 974 | } 975 | 976 | 977 | function CheckForUpgrade() { 978 | var r = new Date().getTime(); 979 | 980 | request({ url: 'https://raw.githubusercontent.com/thecoder75/liveme-tools/master/src/package.json?random=' + r, timeout: 15000 }, function (err, response, body) { 981 | var js = JSON.parse(body), nv = parseFloat(js.version.replace('.', '')), ov = parseFloat(app.getVersion().replace('.', '')), isCurrent = nv > ov; 982 | 983 | if (nv > ov) { 984 | let win = new BrowserWindow({ 985 | width: 480, 986 | height: 280, 987 | resizable: false, 988 | darkTheme: true, 989 | autoHideMenuBar: false, 990 | skipTaskbar: false, 991 | vibrancy: 'ultra-dark', 992 | backgroundColor: process.platform == 'darwin' ? null : '#000000', // We utilize the macOS Vibrancy mode 993 | disableAutoHideCursor: true, 994 | titleBarStyle: 'default', 995 | fullscreen: false, 996 | maximizable: false, 997 | closable: true, 998 | frame: true, 999 | show: false 1000 | }); 1001 | 1002 | win.on('ready-to-show', () => { 1003 | win.show(); 1004 | }).loadURL(`file://${__dirname}/lmt/upgrade.html`); 1005 | } 1006 | }); 1007 | } 1008 | -------------------------------------------------------------------------------- /src/lmt/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/appicon.png -------------------------------------------------------------------------------- /src/lmt/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Message History 6 | 9 | 10 | 11 | 12 | 92 | 93 | 94 | 95 | 96 |
Loading...
97 |
98 |
99 |
100 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /src/lmt/fans.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fans 5 | 6 | 9 | 10 | 11 | 12 | 13 | 86 | 87 | 88 | 89 | 90 |
91 | 92 | 93 |
94 |
95 |
96 |
97 |
98 | 99 | -------------------------------------------------------------------------------- /src/lmt/favorites-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Favorites 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 |
22 |
23 |
24 |
25 |
Loading, please wait...
26 |
27 |
28 | 29 | -------------------------------------------------------------------------------- /src/lmt/following.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Following 5 | 6 | 9 | 10 | 11 | 12 | 13 | 86 | 87 | 88 | 89 | 90 |
91 | 92 | 93 |
94 |
95 |
96 |
97 |
98 | 99 | -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /src/lmt/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcecodepro-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcecodepro-regular-webfont.eot -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcecodepro-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcecodepro-regular-webfont.ttf -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcecodepro-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcecodepro-regular-webfont.woff -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-light-webfont.eot -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-light-webfont.ttf -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-light-webfont.woff -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-regular-webfont.eot -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-regular-webfont.ttf -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-regular-webfont.woff -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-semibold-webfont.eot -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-semibold-webfont.ttf -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/sourcesanspro-semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/fonts/sourcesans/sourcesanspro-semibold-webfont.woff -------------------------------------------------------------------------------- /src/lmt/fonts/sourcesans/stylesheet.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: 'source-sans-pro'; 4 | src: url('sourcesanspro-light-webfont.eot'); 5 | src: url('sourcesanspro-light-webfont.eot?#iefix') format('embedded-opentype'), 6 | url('sourcesanspro-light-webfont.woff') format('woff'), 7 | url('sourcesanspro-light-webfont.ttf') format('truetype'), 8 | url('sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg'); 9 | font-weight: 300; 10 | font-style: normal; 11 | 12 | } 13 | 14 | 15 | 16 | 17 | @font-face { 18 | font-family: 'source-sans-pro'; 19 | src: url('sourcesanspro-regular-webfont.eot'); 20 | src: url('sourcesanspro-regular-webfont.eot?#iefix') format('embedded-opentype'), 21 | url('sourcesanspro-regular-webfont.woff') format('woff'), 22 | url('sourcesanspro-regular-webfont.ttf') format('truetype'), 23 | url('sourcesanspro-regular-webfont.svg#source_sans_proregular') format('svg'); 24 | font-weight: 400; 25 | font-style: normal; 26 | 27 | } 28 | 29 | 30 | 31 | 32 | @font-face { 33 | font-family: 'source-sans-pro'; 34 | src: url('sourcesanspro-semibold-webfont.eot'); 35 | src: url('sourcesanspro-semibold-webfont.eot?#iefix') format('embedded-opentype'), 36 | url('sourcesanspro-semibold-webfont.woff') format('woff'), 37 | url('sourcesanspro-semibold-webfont.ttf') format('truetype'), 38 | url('sourcesanspro-semibold-webfont.svg#source_sans_prosemibold') format('svg'); 39 | font-weight: 600; 40 | font-style: normal; 41 | 42 | } 43 | 44 | 45 | 46 | 47 | @font-face { 48 | font-family: 'source-code-pro'; 49 | src: url('sourcecodepro-regular-webfont.eot'); 50 | src: url('sourcecodepro-regular-webfont.eot?#iefix') format('embedded-opentype'), 51 | url('sourcecodepro-regular-webfont.woff') format('woff'), 52 | url('sourcecodepro-regular-webfont.ttf') format('truetype'), 53 | url('sourcecodepro-regular-webfont.svg#source_code_proregular') format('svg'); 54 | font-weight: normal; 55 | font-style: normal; 56 | 57 | } -------------------------------------------------------------------------------- /src/lmt/images/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/images/blank.png -------------------------------------------------------------------------------- /src/lmt/images/drop-down-triangle-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/images/drop-down-triangle-dark.png -------------------------------------------------------------------------------- /src/lmt/images/ic_add_circle_outline_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_arrow_drop_down_white_18px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_book_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_cancel_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_chat_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_check_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_close_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_content_copy_white_18px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_content_copy_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_favorite_border_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_favorite_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_file_download_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_file_upload_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_fullscreen_white_18px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_menu_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_pause_circle_outline_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_pause_white_18px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_play_arrow_white_18px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_play_circle_outline_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_search_white_18px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/ic_settings_white_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lmt/images/liveme.tools.icon.svg: -------------------------------------------------------------------------------- 1 | 2 | liveme.tools.icon 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/lmt/images/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Slice 1 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lmt/images/wait.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecoder75/liveme-tools/c7220600cb7a8d193bbfcebfb5138d7aa9891084/src/lmt/images/wait.gif -------------------------------------------------------------------------------- /src/lmt/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveMe Tools 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 28 |
29 | 30 | 35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 | FFMPEG was not detected, you will be unable to download broadcasts. If you are sure you have FFMPEG installed correctly, open preferences to set its location. 43 | Click here to see how to set up ffmpeg 44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/lmt/javascript/cclist.js: -------------------------------------------------------------------------------- 1 | var cclist = [ 2 | [ "All Countries", "" ], [ "Afghanistan", "AF" ], [ "Albania", "AL" ], [ "Algeria", "DZ" ], [ "American Samoa", "AS" ], [ "Andorra", "AD" ], 3 | [ "Angola", "AO" ], [ "Anguilla", "AI" ], [ "Antarctica", "AQ" ], [ "Antigua and Barbuda", "AG" ], [ "Argentina", "AR" ], [ "Armenia", "AM" ], 4 | [ "Aruba", "AW" ], [ "Australia", "AU" ], [ "Austria", "AT" ], [ "Azerbaijan", "AZ" ], [ "Bahamas", "BS" ], [ "Bahrain", "BH" ], 5 | [ "Bangladesh", "BD" ], [ "Barbados", "BB" ], [ "Belarus", "BY" ], [ "Belgium", "BE" ], [ "Belize", "BZ" ], [ "Benin", "BJ" ], 6 | [ "Bermuda", "BM" ], [ "Bhutan", "BT" ], [ "Bolivia", "BO" ], [ "Bosnia-Herzegovina", "BA" ], [ "Botswana", "BW" ], [ "Bouvet Island", "BV" ], 7 | [ "Brazil", "BR" ], [ "British Indian Ocean Territory", "IO" ], [ "Brunei Darussalam", "BN" ], [ "Bulgaria", "BG" ], [ "Burkina Faso", "BF" ], [ "Burundi", "BI" ], 8 | [ "Cambodia", "KH" ], [ "Cameroon", "CM" ], [ "Canada", "CA" ], [ "Cape Verde", "CV" ], [ "Cayman Islands", "KY" ], [ "Central African Republic", "CF" ], 9 | [ "Chad", "TD" ], [ "Chile", "CL" ], [ "China", "CN" ], [ "Christmas Island", "CX" ], [ "Cocos (Keeling) Islands", "CC" ], [ "Colombia", "CO" ], 10 | [ "Comoros", "KM" ], [ "Congo", "CG" ], [ "Congo, Dem. Republic", "CD" ], [ "Cook Islands", "CK" ], [ "Costa Rica", "CR" ], [ "Croatia", "HR" ], 11 | [ "Cuba", "CU" ], [ "Cyprus", "CY" ], [ "Czech Rep.", "CZ" ], [ "Denmark", "DK" ], [ "Djibouti", "DJ" ], [ "Dominica", "DM" ], 12 | [ "Dominican Republic", "DO" ], [ "Ecuador", "EC" ], [ "Egypt", "EG" ], [ "El Salvador", "SV" ], [ "Equatorial Guinea", "GQ" ], [ "Eritrea", "ER" ], 13 | [ "Estonia", "EE" ], [ "Ethiopia", "ET" ], [ "European Union", "EU.INT" ], [ "Falkland Islands (Malvinas)", "FK" ], [ "Faroe Islands", "FO" ], [ "Fiji", "FJ" ], 14 | [ "Finland", "FI" ], [ "France", "FR" ], [ "French Guiana", "GF" ], [ "French Southern Territories", "TF" ], [ "Gabon", "GA" ], [ "Gambia", "GM" ], 15 | [ "Georgia", "GE" ], [ "Germany", "DE" ], [ "Ghana", "GH" ], [ "Gibraltar", "GI" ], [ "Great Britain", "GB" ], [ "Greece", "GR" ], 16 | [ "Greenland", "GL" ], [ "Grenada", "GD" ], [ "Guadeloupe (French)", "GP" ], [ "Guam (USA)", "GU" ], [ "Guatemala", "GT" ], [ "Guernsey", "GG" ], 17 | [ "Guinea", "GN" ], [ "Guinea Bissau", "GW" ], [ "Guyana", "GY" ], [ "Haiti", "HT" ], [ "Heard Island and McDonald Islands", "HM" ], [ "Honduras", "HN" ], 18 | [ "Hong Kong", "HK" ], [ "Hungary", "HU" ], [ "Iceland", "IS" ], [ "India", "IN" ], [ "Indonesia", "ID" ], [ "International", "INT" ], 19 | [ "Iran", "IR" ], [ "Iraq", "IQ" ], [ "Ireland", "IE" ], [ "Isle of Man", "IM" ], [ "Israel", "IL" ], [ "Italy", "IT" ], 20 | [ "Ivory Coast", "CI" ], [ "Jamaica", "JM" ], [ "Japan", "JP" ], [ "Jersey", "JE" ], [ "Jordan", "JO" ], [ "Kazakhstan", "KZ" ], 21 | [ "Kenya", "KE" ], [ "Kiribati", "KI" ], [ "Korea-North", "KP" ], [ "Korea-South", "KR" ], [ "Kuwait", "KW" ], [ "Kyrgyzstan", "KG" ], 22 | [ "Laos", "LA" ], [ "Latvia", "LV" ], [ "Lebanon", "LB" ], [ "Lesotho", "LS" ], [ "Liberia", "LR" ], [ "Libya", "LY" ], 23 | [ "Liechtenstein", "LI" ], [ "Lithuania", "LT" ], [ "Luxembourg", "LU" ], [ "Macau", "MO" ], [ "Macedonia", "MK" ], [ "Madagascar", "MG" ], 24 | [ "Malawi", "MW" ], [ "Malaysia", "MY" ], [ "Maldives", "MV" ], [ "Mali", "ML" ], [ "Malta", "MT" ], [ "Marshall Islands", "MH" ], 25 | [ "Martinique (French)", "MQ" ], [ "Mauritania", "MR" ], [ "Mauritius", "MU" ], [ "Mayotte", "YT" ], [ "Mexico", "MX" ], [ "Micronesia", "FM" ], 26 | [ "Moldova", "MD" ], [ "Monaco", "MC" ], [ "Mongolia", "MN" ], [ "Montenegro", "ME" ], [ "Montserrat", "MS" ], [ "Morocco", "MA" ], 27 | [ "Mozambique", "MZ" ], [ "Myanmar", "MM" ], [ "Namibia", "NA" ], [ "Nauru", "NR" ], [ "Nepal", "NP" ], [ "Netherlands", "NL" ], 28 | [ "Netherlands Antilles", "AN" ], [ "New Caledonia (French)", "NC" ], [ "New Zealand", "NZ" ], [ "Nicaragua", "NI" ], [ "Niger", "NE" ], [ "Nigeria", "NG" ], 29 | [ "Niue", "NU" ], [ "Norfolk Island", "NF" ], [ "Northern Mariana Islands", "MP" ], [ "Norway", "NO" ], [ "Oman", "OM" ], [ "Pakistan", "PK" ], 30 | [ "Palau", "PW" ], [ "Panama", "PA" ], [ "Papua New Guinea", "PG" ], [ "Paraguay", "PY" ], [ "Peru", "PE" ], [ "Philippines", "PH" ], 31 | [ "Pitcairn Island", "PN" ], [ "Poland", "PL" ], [ "Polynesia (French)", "PF" ], [ "Portugal", "PT" ], [ "Puerto Rico", "PR" ], [ "Qatar", "QA" ], 32 | [ "Reunion (French)", "RE" ], [ "Romania", "RO" ], [ "Russia", "RU" ], [ "Rwanda", "RW" ], [ "Saint Helena", "SH" ], [ "Saint Kitts and Nevis Anguilla", "KN" ], 33 | [ "Saint Lucia", "LC" ], [ "Saint Pierre and Miquelon", "PM" ], [ "Saint Vincent and Grenadines", "VC" ], [ "Samoa", "WS" ], [ "San Marino", "SM" ], [ "Sao Tome and Principe", "ST" ], 34 | [ "Saudi Arabia", "SA" ], [ "Senegal", "SN" ], [ "Serbia", "RS" ], [ "Seychelles", "SC" ], [ "Sierra Leone", "SL" ], [ "Singapore", "SG" ], 35 | [ "Slovakia", "SK" ], [ "Slovenia", "SI" ], [ "Solomon Islands", "SB" ], [ "Somalia", "SO" ], [ "South Africa", "ZA" ], [ "South Georgia and South Sandwich Islands", "GS" ], 36 | [ "South Sudan", "SS" ], [ "Spain", "ES" ], [ "Sri Lanka", "LK" ], [ "Sudan", "SD" ], [ "Suriname", "SR" ], [ "Svalbard and Jan Mayen Islands", "SJ" ], 37 | [ "Swaziland", "SZ" ], [ "Sweden", "SE" ], [ "Switzerland", "CH" ], [ "Syria", "SY" ], [ "Taiwan", "TW" ], [ "Tajikistan", "TJ" ], 38 | [ "Tanzania", "TZ" ], [ "Thailand", "TH" ], [ "Togo", "TG" ], [ "Tokelau", "TK" ], [ "Tonga", "TO" ], [ "Trinidad and Tobago", "TT" ], 39 | [ "Tunisia", "TN" ], [ "Turkey", "TR" ], [ "Turkmenistan", "TM" ], [ "Turks and Caicos Islands", "TC" ], [ "Tuvalu", "TV" ], [ "U.K.", "UK" ], 40 | [ "USA", "US" ], [ "USA Minor Outlying Islands", "UM" ], [ "Uganda", "UG" ], [ "Ukraine", "UA" ], [ "United Arab Emirates", "AE" ], [ "Uruguay", "UY" ], 41 | [ "Uzbekistan", "UZ" ], [ "Vanuatu", "VU" ], [ "Vatican", "VA" ], [ "Venezuela", "VE" ], [ "Vietnam", "VN" ], [ "Virgin Islands (British)", "VG" ], 42 | [ "Virgin Islands (USA)", "VI" ], [ "Wallis and Futuna Islands", "WF" ], [ "Western Sahara", "EH" ], [ "Yemen", "YE" ], [ "Zambia", "ZM" ], [ "Zimbabwe", "ZW" ] 43 | ]; -------------------------------------------------------------------------------- /src/lmt/javascript/favorites.js: -------------------------------------------------------------------------------- 1 | const { remote, ipcRenderer } = require('electron'), DataManager = remote.getGlobal('DataManager'); 2 | 3 | $(function(){ 4 | 5 | setTimeout(function(){ 6 | DataManager.loadFavorites(); 7 | }, 50); 8 | 9 | DataManager.events.on('refresh_favorites', (data) => { 10 | 11 | $('#small_user_list').empty(); 12 | 13 | for (i = 0; i < data.length; i++) { 14 | $("#small_user_list").append(` 15 |
16 |
17 | 18 |
19 |
20 |
${data[i].nickname}
21 |
${data[i].usign}
22 |
23 |
${data[i].video_count} videos
24 |
25 |
26 |
27 | `); 28 | } 29 | }); 30 | 31 | 32 | }); 33 | 34 | function getVideos(e) { 35 | ipcRenderer.send('submit-search', { userid: e }); 36 | } 37 | function enterOnSearch(e) { if (e.keyCode == 13) beginSearch(); } 38 | function beginSearch() { 39 | var q = $('#query').val(); 40 | 41 | $('.item').removeClass('highlight'); 42 | $('.item:contains(\''+q+'\')').addClass('highlight'); 43 | 44 | var f = $('.item:contains(\''+q+'\')'); 45 | if (f != null) { 46 | $('main').animate({ 47 | scrollTop: f.offset().top - f.height() 48 | }, 400); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/lmt/javascript/livemetools.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | _ _ __ __ _______ _ 4 | | | (_) | \/ | |__ __| | | 5 | | | ___ _____| \ / | ___ | | ___ ___ | |___ 6 | | | | \ \ / / _ \ |\/| |/ _ \ | |/ _ \ / _ \| / __| 7 | | |____| |\ V / __/ | | | __/ | | (_) | (_) | \__ \ 8 | |______|_| \_/ \___|_| |_|\___| |_|\___/ \___/|_|___/ 9 | 10 | */ 11 | 12 | const { electron, BrowserWindow, remote, ipcRenderer, shell, clipboard } = require('electron'), 13 | fs = require('fs'), path = require('path'), fmtDuration = require('format-duration'), 14 | appSettings = remote.require('electron-settings'), 15 | DataManager = remote.getGlobal('DataManager'), 16 | DownloadManager = remote.getGlobal('DownloadManager'), 17 | LiveMe = require('liveme-api'); 18 | 19 | var debounced = false, current_user = {}, current_page = 1, has_more = false, 20 | current_search = '', scroll_busy = false; 21 | 22 | $(function(){ 23 | 24 | document.title = remote.app.getName() + ' v' + remote.app.getVersion(); 25 | 26 | onTypeChange(); 27 | 28 | // Remote search calls 29 | ipcRenderer.on('do-search' , function(event , data) { 30 | $('#query').val(data.userid); 31 | $('#type').val('user-lookup'); 32 | beginSearch2(); 33 | }); 34 | 35 | ipcRenderer.on('do-shutdown' , function(event , data) { 36 | DownloadManager.save(); 37 | }); 38 | 39 | ipcRenderer.on('show-status' , function(event , data) { 40 | $('.status').html(data.message); 41 | $('overlay .status').show(); 42 | $('overlay .ffmpeg-error').hide(); 43 | $('overlay').show(); 44 | }); 45 | ipcRenderer.on('update-status' , function(event , data) { 46 | $('.status').html(data.message); 47 | }); 48 | ipcRenderer.on('hide-status' , function(event , data) { 49 | $('overlay').hide(); 50 | }); 51 | 52 | DownloadManager.load(); 53 | 54 | DownloadManager.detectFFMPEG().then(result => { 55 | if (!result) { 56 | $('overlay').show(); 57 | $('overlay .status').hide(); 58 | $('overlay .ffmpeg-error').show(); 59 | } 60 | }); 61 | 62 | $('main').scroll(function() { 63 | if (($(this).scrollTop() + $(this).height()) > ($('.list').height() - 80)) { 64 | if (has_more == false) return; 65 | if (scroll_busy == true) return; 66 | 67 | current_page++; 68 | if (current_search == 'getUsersReplays') { 69 | getUsersReplays(); 70 | } else if (current_search == 'performUsernameSearch') { 71 | performUsernameSearch(); 72 | } else if (current_search == 'performHashtagSearch') { 73 | performHashtagSearch(); 74 | } 75 | } 76 | }); 77 | 78 | 79 | const InputMenu = remote.Menu.buildFromTemplate([{ 80 | label: 'Undo', 81 | role: 'undo', 82 | }, { 83 | label: 'Redo', 84 | role: 'redo', 85 | }, { 86 | type: 'separator', 87 | }, { 88 | label: 'Cut', 89 | role: 'cut', 90 | }, { 91 | label: 'Copy', 92 | role: 'copy', 93 | }, { 94 | label: 'Paste', 95 | role: 'paste', 96 | }, { 97 | type: 'separator', 98 | }, { 99 | label: 'Select all', 100 | role: 'selectall', 101 | }, 102 | ]); 103 | 104 | document.body.addEventListener('contextmenu', (e) => { 105 | e.preventDefault(); 106 | e.stopPropagation(); 107 | 108 | let node = e.target; 109 | 110 | while (node) { 111 | if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) { 112 | InputMenu.popup(remote.getCurrentWindow()); 113 | break; 114 | } 115 | node = node.parentNode; 116 | } 117 | }); 118 | 119 | /* 120 | 121 | countryCode search is not working even when directly tested with LiveMe API URLs and calls. 122 | Don't know why it quit working now so we're removing until further testing can be done. 123 | 124 | 125 | setTimeout(function(){ 126 | for (var i = 0; i < cclist.length; i++) { 127 | $('#cclist').append(''); 128 | } 129 | }, 100); 130 | */ 131 | }); 132 | 133 | function copyToClipboard(i) { clipboard.writeText(i); } 134 | function cancelAction() { cancelLMTweb = true; } 135 | function enterOnSearch(e) { if (e.keyCode == 13) beginSearch(); } 136 | 137 | function onTypeChange() { 138 | var t=$('#type').val(); 139 | switch (t) { 140 | case 'user-lookup': 141 | $('#query').attr('placeholder', 'Short or Long UserID'); 142 | $('div.toolbar').removeClass('has-cc-list'); 143 | $('div.cclist').hide(); 144 | break; 145 | case 'video-lookup': 146 | $('#query').attr('placeholder', 'Enter VideoID'); 147 | $('div.toolbar').removeClass('has-cc-list'); 148 | $('div.cclist').hide(); 149 | break; 150 | case 'url-lookup': 151 | $('#query').attr('placeholder', 'Enter URL'); 152 | $('div.toolbar').removeClass('has-cc-list'); 153 | $('div.cclist').hide(); 154 | break; 155 | case 'search': 156 | $('#query').attr('placeholder', 'Enter Partial or Full Username'); 157 | $('div.toolbar').addClass('has-cc-list'); 158 | $('div.cclist').show(); 159 | break; 160 | case 'hashtag': 161 | $('#query').attr('placeholder', 'Enter a hashtag without any #\'s'); 162 | $('div.toolbar').addClass('has-cc-list'); 163 | $('div.cclist').show(); 164 | break; 165 | } 166 | } 167 | 168 | function toggleFavorite() { 169 | if (DataManager.isInFavorites(current_user.uid) == true) { 170 | DataManager.removeFavorite(current_user.uid); 171 | $('#favorites_button').removeClass('active'); 172 | } else { 173 | DataManager.addFavorite(current_user); 174 | $('#favorites_button').addClass('active'); 175 | } 176 | } 177 | 178 | function beginSearch() { 179 | 180 | var u=$('#query').val(), isnum = /^\d+$/.test(u); 181 | 182 | if ((u.length==20) && (isnum)) { 183 | if ($('#type').val() != 'video-lookup') { 184 | $('#type').val('video-lookup'); 185 | onTypeChange(); 186 | } 187 | } else if ((u.length == 18) && (isnum)) { 188 | if ($('#type').val() != 'user-lookup') { 189 | $('#type').val('user-lookup'); 190 | onTypeChange(); 191 | } 192 | } else if (u.indexOf('http') > -1) { 193 | if ($('#type').val() != 'url-lookup') { 194 | $('#type').val('url-lookup'); 195 | onTypeChange(); 196 | } 197 | } else if (u.indexOf('#') > -1) { 198 | if ($('#type').val() != 'hashtag') { 199 | $('#type').val('hashtag'); 200 | $('#query').val($('#query').val().replace('#', '')); 201 | onTypeChange(); 202 | } 203 | /* 204 | } else { 205 | if (($('#type').val() != 'search') || ($('#type').val() != 'hashtag')) { 206 | $('#type').val('search'); 207 | onTypeChange(); 208 | } 209 | */ 210 | } 211 | beginSearch2(); 212 | } 213 | 214 | function beginSearch2() { 215 | 216 | if (debounced) return; 217 | debounced = true; 218 | setTimeout(function(){ debounced = false; }, 250); 219 | 220 | current_page = 1; 221 | 222 | var videoid = '', userid = ''; 223 | 224 | $('overlay').show(); 225 | $('main').html(''); 226 | 227 | if ($('#type').val() == 'url-lookup') { 228 | var q = '', u=$('#query').val(), t=u.split('/'); 229 | 230 | if (u.indexOf('/live/') > -1) { 231 | $('#type').val('video-lookup'); 232 | $('#query').val(u[3]); 233 | videoid = u[3]; 234 | 235 | } else if (t[t.length-1].indexOf('yolo') > -1) { 236 | var a=t[t.length - 1].split('-'); 237 | $('#type').val('video-lookup'); 238 | $('#query').val(a[1]); 239 | videoid = a[1]; 240 | 241 | } else if (u.indexOf('videoid') > -1) { 242 | var a=t[t.length - 1].split('?'),b=a[1].split('&'); 243 | 244 | for (i = 0; i < b.length; i++) { 245 | if (b[i].indexOf('videoid') > -1) { 246 | var c=b[i].split('='); 247 | 248 | $('#type').val('video-lookup'); 249 | $('#query').val(c[1]); 250 | videoid = c[1]; 251 | 252 | } 253 | } 254 | } else if (u.indexOf('userid') > -1) { 255 | var a=t[t.length - 1].split('?'),b=a[1].split('&'); 256 | 257 | for (i = 0; i < b.length; i++) { 258 | if (b[i].indexOf('userid') > -1) { 259 | var c=b[i].split('='); 260 | 261 | $('#type').val('user-lookup'); 262 | $('#query').val(c[1]); 263 | videoid = c[1]; 264 | 265 | } 266 | } 267 | } else { 268 | $('main').html('
Unsupported URL was specified.
'); 269 | } 270 | $('overlay').hide(); 271 | } else if ($('#type').val() == 'video-lookup') { 272 | videoid = $('#query').val(); 273 | } else if ($('#type').val() == 'user-lookup') { 274 | $('panel').hide(); 275 | $('main').removeClass('with-panel'); 276 | userid = $('#query').val(); 277 | } 278 | 279 | $('overlay').hide(); 280 | if (videoid.length > 0) { 281 | 282 | LiveMe.getVideoInfo(videoid) 283 | .then(video => { 284 | 285 | $('panel').show(); 286 | $('main').addClass('with-panel').html('
'); 287 | 288 | if (video.videosource.length < 1) { 289 | $('panel').hide(); 290 | $('main').removeClass('with-panel').html('
Search returned no data, account may be closed.
'); 291 | } else { 292 | let dt = new Date(video.vtime * 1000); 293 | var ds = (dt.getMonth() + 1) + '-' + dt.getDate() + '-' + dt.getFullYear() + ' ' + (dt.getHours() < 10 ? '0' : '') + dt.getHours() + ':' + (dt.getMinutes() < 10 ? '0' : '') + dt.getMinutes(); 294 | var hi1 = $('#type').val() == 'url-lookup' ? ($('#query').val() == video.hlsvideosource ? true : false) : false; 295 | var hi2 = $('#type').val() == 'video-lookup' ? ($('#query').val() == video.vid ? true : false) : false; 296 | 297 | var deleted = '[SEARCHED] ', highlight = hi1 || hi2 ? 'highlight' : ''; 298 | var downloaded = DownloadManager.hasBeenDownloaded(video.vid) ? 'downloaded' : ''; 299 | 300 | let isLive = video.hlsvideosource.endsWith('flv') || video.hlsvideosource.indexOf('liveplay') > 0, videoUrl = video.hlsvideosource; 301 | 302 | if (!isLive && video.hlsvideosource.indexOf('hlslive') > 0) { 303 | videoUrl = video.videosource; 304 | } 305 | if (videoUrl.length < 1) isLive = true; 306 | 307 | var h = ` 308 |
309 |
${deleted}${video.title} 
310 |
311 |
312 |
313 | Posted on: 314 | ${ds} 315 |
316 |
317 | Length: 318 | ${fmtDuration(+video.videolength * 1000)} 319 |
320 |
321 | Views: 322 | ${video.playnumber} 323 |
324 |
325 | Likes: 326 | ${video.likenum} 327 |
328 |
329 | Shares: 330 | ${video.sharenum} 331 |
332 |
333 | Country 334 | ${video.countryCode} 335 |
336 |
337 | `; 338 | if (!isLive) { 339 | h += ` 340 | 341 | 342 | 343 | `; 344 | } 345 | h+=` 346 |
347 |
348 |
349 | 372 |
373 | `; 374 | $('.list').append(h); 375 | performUserLookup(video.userid); 376 | } 377 | }); 378 | 379 | } else if (userid.length > 0) { 380 | $('panel').hide(); 381 | $('main').addClass('with-panel').html('
'); 382 | performUserLookup(userid); 383 | } else { 384 | if ($('#type').val() == 'search') { 385 | $('main').html('
'); 386 | performUsernameSearch(); 387 | } else if ($('#type').val() == 'hashtag') { 388 | $('main').html('
'); 389 | performHashtagSearch(); 390 | } 391 | } 392 | } 393 | 394 | function performUserLookup(uid) { 395 | $('overlay').hide(); 396 | $('panel').show(); 397 | 398 | LiveMe.getUserInfo(uid) 399 | .then(user => { 400 | 401 | var sex = user.user_info.sex < 0 ? '' : (user.user_info.sex == 0 ? 'female' : 'male'), 402 | dt = Math.floor(new Date().getTime() / 1000) + parseInt(appSettings.get('profiles.visitedtimeout')); 403 | 404 | DataManager.addTrackedVisited({ id: user.user_info.uid, dt: dt }); 405 | 406 | $('.user-panel').html(` 407 | 408 |
409 |
410 | Username: 411 | ${user.user_info.uname} 412 |
413 |
414 | User ID: 415 |
416 | 417 | 418 |
419 |
420 |
421 | Level: 422 | ${user.user_info.level} 423 |
424 |
425 |

426 | 427 |


428 |
429 |
430 | Following: 431 | 432 |
433 |
434 | Fans: 435 | 436 |
437 |
438 | 439 | `); 440 | 441 | 442 | scroll_busy = false; 443 | current_page = 1; 444 | 445 | current_user = { 446 | uid: user.user_info.uid, 447 | sex: sex, 448 | face: user.user_info.face, 449 | nickname: user.user_info.uname 450 | }; 451 | getUsersReplays(); 452 | 453 | setTimeout(function(){ 454 | if (DataManager.isInFavorites(current_user.uid) == true) { 455 | $('#favorites_button').addClass('active'); 456 | } 457 | }, 250); 458 | 459 | }) 460 | .catch(err => { 461 | isSearching = false; 462 | $('main').html('
Search returned no data, account may be closed.
'); 463 | }); 464 | 465 | } 466 | 467 | function getUsersReplays() { 468 | 469 | LiveMe.getUserReplays(current_user.uid, current_page, 10) 470 | .then(replays => { 471 | 472 | if ((typeof replays == 'undefined') || (replays == null)) return; 473 | 474 | if (replays.length > 0) { 475 | 476 | $('.empty').remove(); 477 | 478 | for (var i = 0; i < replays.length; i++) { 479 | 480 | if (current_user.uid == replays[i].userid) { 481 | 482 | let dt = new Date(replays[i].vtime * 1000); 483 | var ds = (dt.getMonth() + 1) + '-' + dt.getDate() + '-' + dt.getFullYear() + ' ' + (dt.getHours() < 10 ? '0' : '') + dt.getHours() + ':' + (dt.getMinutes() < 10 ? '0' : '') + dt.getMinutes(); 484 | var hi1 = $('#type').val() == 'url-lookup' ? ($('#query').val() == replays[i].hlsvideosource ? true : false) : false; 485 | var hi2 = $('#type').val() == 'video-lookup' ? ($('#query').val() == replays[i].vid ? true : false) : false; 486 | 487 | var deleted = replays[i].private == true ? '[PRIVATE] ' : '', highlight = hi1 || hi2 ? 'highlight' : ''; 488 | var downloaded = DownloadManager.hasBeenDownloaded(replays[i].vid) ? 'downloaded' : ''; 489 | 490 | let isLive = replays[i].hlsvideosource.endsWith('flv') || replays[i].hlsvideosource.indexOf('liveplay') > 0, videoUrl = replays[i].hlsvideosource; 491 | 492 | if (!isLive && replays[i].hlsvideosource.indexOf('hlslive') > 0) { 493 | videoUrl = replays[i].videosource; 494 | } 495 | 496 | var h = ` 497 |
498 |
${deleted}${replays[i].title} 
499 |
500 |
501 |
502 | Posted on: 503 | ${ds} 504 |
505 |
506 | Length: 507 | ${fmtDuration(+replays[i].videolength * 1000)} 508 |
509 |
510 | Views: 511 | ${replays[i].playnumber} 512 |
513 |
514 | Likes: 515 | ${replays[i].likenum} 516 |
517 |
518 | Shares: 519 | ${replays[i].sharenum} 520 |
521 |
522 | Country 523 | ${replays[i].countryCode} 524 |
525 |
526 | 527 | `; 528 | 529 | if (!isLive) { 530 | h += ` 531 | 532 | 533 | `; 534 | } 535 | 536 | h += ` 537 |
538 |
539 |
540 | 563 |
564 | `; 565 | $('.list').append(h); 566 | } 567 | } 568 | } 569 | 570 | current_search = 'getUsersReplays'; 571 | scroll_busy = false; 572 | 573 | if (replays.length == 10) { 574 | has_more = true; 575 | } else if (replays.length < 10) { 576 | has_more = false; 577 | } 578 | 579 | setTimeout(function(){ 580 | if ($('.item').length < 1) { 581 | $('.list').html('
No visible replays available for this account.
'); 582 | } 583 | }, 1000); 584 | 585 | }); 586 | 587 | } 588 | 589 | function performUsernameSearch() { 590 | LiveMe.performSearch($('#query').val(), current_page, 10, 1) 591 | .then(results => { 592 | for(var i = 0; i < results.length; i++) { 593 | $('.list').append(` 594 |
595 |
596 | 597 |
598 |
599 |
${results[i].nickname} 
600 |
601 |
602 | Level: 603 | ${results[i].level} 604 |
605 |
606 | User ID: 607 | 608 |
609 |
610 | Fans: 611 | 612 |
613 |
614 |
615 |
616 | `); 617 | } 618 | 619 | current_search = 'performUsernameSearch'; 620 | scroll_busy = false; 621 | 622 | if (results.length == 10) { 623 | has_more = true; 624 | } else if (results.length < 10) { 625 | has_more = false; 626 | } 627 | 628 | if (results.length == 0 && current_page == 1) { 629 | $('.list').html('
No accounts were found matching your search.
'); 630 | } 631 | 632 | }); 633 | } 634 | 635 | function performHashtagSearch() { 636 | LiveMe.performSearch($('#query').val(), current_page, 10, 2) 637 | .then(results => { 638 | for(var i = 0; i < results.length; i++) { 639 | 640 | var dt = new Date(results[i].vtime * 1000); 641 | var ds = (dt.getMonth() + 1) + '-' + dt.getDate() + '-' + dt.getFullYear() + ' ' + (dt.getHours() < 10 ? '0' : '') + dt.getHours() + ':' + (dt.getMinutes() < 10 ? '0' : '') + dt.getMinutes(); 642 | var hi1 = $('#type').val() == 'url-lookup' ? ($('#query').val() == results[i].hlsvideosource ? true : false) : false; 643 | var hi2 = $('#type').val() == 'video-lookup' ? ($('#query').val() == results[i].vid ? true : false) : false; 644 | 645 | var downloaded = DownloadManager.hasBeenDownloaded(results[i].vid) ? 'downloaded' : ''; 646 | 647 | let isLive = results[i].hlsvideosource.endsWith('flv') || results[i].hlsvideosource.indexOf('liveplay') > 0, videoUrl = results[i].hlsvideosource; 648 | 649 | if (!isLive && results[i].hlsvideosource.indexOf('hlslive') > 0) { 650 | videoUrl = results[i].videosource; 651 | } 652 | 653 | var h = ` 654 | 655 |
656 |
${results[i].title} 
657 |
658 |
659 |
660 | Posted on: 661 | ${ds} 662 |
663 |
664 | Length: 665 | ${fmtDuration(+results[i].videolength * 1000)} 666 |
667 |
668 | Views: 669 | ${results[i].playnumber} 670 |
671 |
672 | Likes: 673 | ${results[i].likenum} 674 |
675 |
676 | Shares: 677 | ${results[i].sharenum} 678 |
679 |
680 | Country 681 | ${results[i].countryCode} 682 |
683 |
684 | 685 | `; 686 | if (!isLive) { 687 | h += ` 688 | 689 | 690 | `; 691 | } 692 | 693 | h += ` 694 |
695 |
696 |
697 | 714 |
715 | `; 716 | 717 | $('.list').append(h); 718 | 719 | } 720 | 721 | current_search = 'performHashtagSearch'; 722 | scroll_busy = false; 723 | 724 | if (results.length == 10) { 725 | has_more = true; 726 | } else if (results.length < 10) { 727 | has_more = false; 728 | } 729 | 730 | if (results.length == 0 && current_page == 1) { 731 | $('.list').html('
No videos were found on LiveMe matching the specified hashtag.
'); 732 | } 733 | 734 | }); 735 | } 736 | 737 | function showSettings() { 738 | ipcRenderer.send('show-settings'); 739 | } 740 | 741 | 742 | function showUser(u) { 743 | $('#type').val('user-lookup'); 744 | $('#query').val(u); 745 | beginSearch2(); 746 | } 747 | 748 | function showFollowing(u,m,n) { 749 | if (debounced) return; 750 | debounced = true; 751 | setTimeout(function(){ debounced = false; }, 500); 752 | 753 | ipcRenderer.send('open-window', { url: 'following.html?'+u+'#'+m+'#'+n }); 754 | } 755 | 756 | function showFans(u,m,n) { 757 | if (debounced) return; 758 | debounced = true; 759 | setTimeout(function(){ debounced = false; }, 500); 760 | 761 | ipcRenderer.send('open-window', { url: 'fans.html?'+u+'#'+m+'#'+n }); 762 | } 763 | 764 | function playVideo(u) { 765 | if (debounced) return; 766 | debounced = true; 767 | setTimeout(function(){ debounced = false; }, 500); 768 | 769 | ipcRenderer.send('play-video', { url: u }); 770 | } 771 | 772 | function downloadVideo(userid, username, videoid, videotitle, videotime, videourl) { 773 | if (debounced) return; 774 | debounced = true; 775 | setTimeout(function(){ debounced = false; }, 500); 776 | 777 | DownloadManager.add({ 778 | user: { 779 | id: userid, 780 | name: username 781 | }, 782 | video: { 783 | id: videoid, 784 | title: videotitle, 785 | time: videotime, 786 | url: videourl 787 | } 788 | }); 789 | } 790 | 791 | function openChat(id) { 792 | if (debounced) return; 793 | debounced = true; 794 | setTimeout(function(){ debounced = false; }, 500); 795 | ipcRenderer.send('open-chat', { videoid: id }); 796 | } 797 | -------------------------------------------------------------------------------- /src/lmt/javascript/settings.js: -------------------------------------------------------------------------------- 1 | const {electron, remote, ipcRenderer} = require('electron'), 2 | appSettings = remote.require('electron-settings'), 3 | DownloadManager = remote.getGlobal('DownloadManager'), 4 | DataManager = remote.getGlobal('DataManager'); 5 | 6 | const path = require('path'); 7 | 8 | $(function() { 9 | 10 | if (appSettings.has('downloads.directory') == false) { 11 | appSettings.set('downloads', { 12 | directory : path.join(remote.app.getPath('home'), 'Downloads'), 13 | filemode: 0, 14 | filetemplate: '', 15 | history: true, 16 | ffmpeg: 'ffmpeg', 17 | ffprobe: 'ffprobe' 18 | }); 19 | appSettings.set('profiles', { 20 | visitedtimeout: 86400 21 | }); 22 | } 23 | 24 | // 7.0.1 upgrade 25 | if (appSettings.has('profiles.visitedtimeout') == false) { 26 | appSettings.set('profiles', { 27 | visitedtimeout: 86400 28 | }); 29 | } 30 | 31 | // Upgrading from < 6.0.5 to >= 6.0.6 32 | if (appSettings.has('downloads.directory') != false && appSettings.has('downloads.ffmpeg') == false) { 33 | appSettings.set('downloads', { 34 | directory: appSettings.get('downloads.directory'), 35 | filemode: appSettings.get('downloads.filemode'), 36 | filetemplate: appSettings.get('downloads.filetemplate'), 37 | history: appSettings.get('downloads.history'), 38 | ffmpeg: 'ffmpeg', 39 | ffprobe: 'ffprobe' 40 | }); 41 | appSettings.set('profiles', { 42 | visitedtimeout: 86400 43 | }); 44 | } 45 | 46 | setTimeout(function(){ 47 | $('#download_folder').val(appSettings.get('downloads.directory')); 48 | $('#filemode').prop('checked', appSettings.get('downloads.filemode')); 49 | $('#filetemplate').val(appSettings.get('downloads.filetemplate')); 50 | $('#history').prop('checked', appSettings.get('downloads.history')); 51 | $('#ffmpegpath').val(appSettings.get('downloads.ffmpeg')); 52 | $('#ffprobepath').val(appSettings.get('downloads.ffprobe')); 53 | $('#ptimeout').val(appSettings.get('profiles.visitedtimeout')); 54 | checkType(); 55 | }, 1); 56 | }); 57 | 58 | function closeWindow() { 59 | window.close(); 60 | } 61 | 62 | function saveSettings(close=true) { 63 | let oldHistory = appSettings.get('downloads.history'); 64 | 65 | appSettings.set('downloads', { 66 | directory: $('#download_folder').val(), 67 | filemode: $('#filemode').is(':checked') ? 1 : 0, 68 | filetemplate: $('#filetemplate').val(), 69 | history: $('#history').is(':checked') ? 1 : 0, 70 | ffmpeg: $('#ffmpegpath').val(), 71 | ffprobe: $('#ffprobepath').val() 72 | }); 73 | appSettings.set('profiles', { 74 | visitedtimeout: $('#ptimeout').val() 75 | }); 76 | 77 | DownloadManager.setFfmpegPath(appSettings.get('downloads.ffmpeg')); 78 | DownloadManager.setFfprobePath(appSettings.get('downloads.ffprobe')); 79 | 80 | if (oldHistory && !appSettings.get('downloads.history')) { 81 | ipcRenderer.send('history-delete'); 82 | } 83 | 84 | if (close) { 85 | closeWindow(); 86 | } 87 | } 88 | 89 | function checkType() { 90 | if ($('#filemode').is(':checked') == 0) { 91 | $('#ftblock-yes').hide(); 92 | $('#ftblock-no').show(); 93 | } else { 94 | $('#ftblock-no').hide(); 95 | $('#ftblock-yes').show(); 96 | } 97 | 98 | if ($('#ffmpegautodetect').is(':checked') == 0) { 99 | $('#ffmpegPathBox').show(); 100 | } else { 101 | $('#ffmpegPathBox').hide(); 102 | } 103 | } 104 | 105 | function SetDownloadPath() { 106 | var dir_path = remote.dialog.showOpenDialog({ 107 | properties: ['openDirectory'] 108 | }); 109 | if (typeof (dir_path) != 'undefined') { 110 | $('#download_folder').val(dir_path); 111 | } 112 | } 113 | 114 | function setFfmpegPath() { 115 | var path = remote.dialog.showOpenDialog({ 116 | properties: ['openFile'] 117 | }); 118 | if (typeof (path) != 'undefined') { 119 | $('#ffmpegpath').val(path); 120 | } 121 | } 122 | 123 | function setFfprobePath() { 124 | var path = remote.dialog.showOpenDialog({ 125 | properties: ['openFile'] 126 | }); 127 | if (typeof (path) != 'undefined') { 128 | $('#ffprobepath').val(path); 129 | } 130 | } 131 | 132 | function checkFfmpeg() { 133 | saveSettings(false); 134 | 135 | DownloadManager.detectFFMPEG().then(result => { 136 | if (result) { 137 | remote.dialog.showMessageBox(null, { type: "info", buttons: [ "OK" ], title: "LiveMe Tools", message: "FFMPEG check passed" }); 138 | } else { 139 | remote.dialog.showErrorBox('LiveMe Tools', 'FFMPEG check failed'); 140 | } 141 | }); 142 | } 143 | -------------------------------------------------------------------------------- /src/lmt/livemeomg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveMe OMG 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 190 | 191 | 192 | 193 |
194 |
195 |
196 |
197 |
198 | 199 | 200 | -------------------------------------------------------------------------------- /src/lmt/player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Player 5 | 6 | 9 | 10 | 11 | 12 | 13 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 |
127 | 128 |
129 |
130 |
131 |
132 |
133 |
134 | 135 | -------------------------------------------------------------------------------- /src/lmt/queue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Download Queue 5 | 6 | 7 | 10 | 11 | 12 | 152 | 153 | 154 | 155 | 156 | 157 |
158 |
159 |
160 | 161 | 166 | 167 | 179 | 180 | 181 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/lmt/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Settings 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 49 | 114 | 115 | 165 | 166 | 167 |
50 | 51 |

Downloads

52 | 53 |
54 | Folder to store downloads in: 55 |
56 | 57 | 58 |
59 |
60 | 61 |
62 |
63 |
64 |

65 | Enable Download History 66 |

67 |
68 |
69 | 70 | 71 | 72 | 73 |
74 |
75 |

76 | When enabled, highlights any videos if you previously downloaded them. 77 |

78 |
79 | 80 |
81 |
82 |
83 |

84 | FFMPEG Path 85 |

86 |
87 |
88 |

Required for downloads

89 |
90 |
91 |
92 | 93 | 94 |
95 |
96 | 97 |
98 |
99 |
100 |

101 | FFPROBE Path 102 |

103 |
104 |
105 |

Required for progress %

106 |
107 |
108 |
109 | 110 | 111 |
112 |
113 |
116 | 117 |

Viewing History

118 |
119 | Keep visited profiles marked for: 120 |
121 | 128 |
129 |
130 | 131 |

Filenames

132 |
133 |
134 |

135 | Use Custom Filenames 136 |

137 |
138 |
139 | 140 | 141 | 142 | 143 |
144 |
145 |
146 | Filenames will be the playlist name in the URL. 147 |
148 |
149 | Filenames will be created using the template below: 150 | 151 |
    152 |
  • %%userid%% User ID
  • 153 |
  • %%username%% Username
  • 154 |
  • %%videoid%% Video ID
  • 155 |
  • %%videotitle%% Video Title
  • 156 |
  • %%videotime%% Video Time
  • 157 |
158 |

159 | The filename of the playlist will be used if the custom title ends up being empty. 160 |

161 |
162 | 163 | 164 |
168 | 169 |
170 | 171 | 172 | 173 |
174 | 175 |
176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/lmt/splash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 21 | 22 | 23 | 74 | 75 | 76 |

LiveMe Tools

77 |

-

78 |
79 | 85 |
86 | 87 | -------------------------------------------------------------------------------- /src/lmt/style/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | _ _ __ __ _______ _ 4 | | | (_) | \/ | |__ __| | | 5 | | | ___ _____| \ / | ___ | | ___ ___ | |___ 6 | | | | \ \ / / _ \ |\/| |/ _ \ | |/ _ \ / _ \| / __| 7 | | |____| |\ V / __/ | | | __/ | | (_) | (_) | \__ \ 8 | |______|_| \_/ \___|_| |_|\___| |_|\___/ \___/|_|___/ 9 | 10 | Custom UI CSS 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Regular Stylings are grouped based on type or usage. 19 | 20 | Colors and Icon URLs are at the bottom grouped together 21 | 22 | 23 | 24 | 25 | */ 26 | 27 | @font-face { font-family: "Roboto"; src: local(Roboto Thin), url("../fonts/roboto/Roboto-Thin.woff2") format("woff2"), url("../fonts/roboto/Roboto-Thin.woff") format("woff"); font-weight: 100; } 28 | @font-face { font-family: "Roboto"; src: local(Roboto Light), url("../fonts/roboto/Roboto-Light.woff2") format("woff2"), url("../fonts/roboto/Roboto-Light.woff") format("woff"); font-weight: 300; } 29 | @font-face { font-family: "Roboto"; src: local(Roboto Regular), url("../fonts/roboto/Roboto-Regular.woff2") format("woff2"), url("../fonts/roboto/Roboto-Regular.woff") format("woff"); font-weight: 400; } 30 | @font-face { font-family: "Roboto"; src: local(Roboto Medium), url("../fonts/roboto/Roboto-Medium.woff2") format("woff2"), url("../fonts/roboto/Roboto-Medium.woff") format("woff"); font-weight: 500; } 31 | @font-face { font-family: "Roboto"; src: local(Roboto Bold), url("../fonts/roboto/Roboto-Bold.woff2") format("woff2"), url("../fonts/roboto/Roboto-Bold.woff") format("woff"); font-weight: 700; } 32 | 33 | * { 34 | -webkit-box-sizing: border-box; 35 | box-sizing: border-box; 36 | -webkit-transition: 0.4s all; 37 | font-family: 'Roboto',Arial,Helvetica,sans-serif; 38 | vertical-align: top; 39 | } 40 | 41 | ::-webkit-input-placeholder { 42 | color: rgba(255,255,255,0.20); 43 | } 44 | 45 | body { 46 | background: transparent; 47 | margin: 0; 48 | padding: 0; 49 | -webkit-user-select: none; 50 | cursor: default; 51 | overflow: hidden; 52 | } 53 | 54 | h1,h2,h3,h4,h5,h6 { margin: 0; padding: 0; } 55 | 56 | p { 57 | font-weight: 300; 58 | font-size: 10pt; 59 | } 60 | 61 | 62 | 63 | 64 | /* 65 | 66 | Helper Stylings 67 | 68 | */ 69 | .align-left { text-align: left; } 70 | .align-center { text-align: center; } 71 | .align-right { text-align: right; } 72 | .text-ellipsis { display: inline-block; text-overflow: ellipsis; } 73 | .float-left { float: left; } 74 | .float-right { float: right; } 75 | 76 | .spacer { width: 16px; } 77 | .width24 { width: 24px; } 78 | .width32 { width: 32px; } 79 | .width48 { width: 48px; } 80 | .width75 { width: 75px; } 81 | .width100 { width: 100px; } 82 | .width150 { width: 150px; } 83 | .width180 { width: 180px; } 84 | .width200 { width: 200px; } 85 | .width300 { width: 300px; } 86 | .width400 { width: 400px; } 87 | .width500 { width: 500px; } 88 | .width520 { width: 520px; } 89 | .width544 { width: 544px; } 90 | .width600 { width: 600px; } 91 | .width700 { width: 700px; } 92 | 93 | .male, .female { border: solid 3px; } 94 | .marked { 95 | background-image: url(../images/ic_check_white_24px.svg); 96 | background-position: right 24px center; 97 | background-repeat: no-repeat; 98 | background-size: 24px 24px; 99 | -webkit-transition: 0s; 100 | } 101 | 102 | .slow-fade-in { 103 | opacity: 1.0; 104 | -webkit-transition: 0.8s; 105 | } 106 | .slow-fade-out { 107 | opacity: 1.0; 108 | -webkit-transition: 0.8s; 109 | } 110 | 111 | 112 | /* 113 | 114 | Main Data Area 115 | 116 | */ 117 | main { 118 | position: absolute; 119 | top: 0; 120 | left: 0; 121 | right: 0; 122 | bottom: 0; 123 | display: block; 124 | overflow-x: hidden; 125 | overflow-y: auto; 126 | -webkit-transition: 0s; 127 | 128 | -webkit-user-drag: none; 129 | -webkit-user-select: none; 130 | } 131 | 132 | main.offset48 { 133 | top: 48px; 134 | } 135 | 136 | main.with-panel { 137 | left: 240px; 138 | } 139 | 140 | main.with-footer { 141 | bottom: 32px; 142 | } 143 | 144 | 145 | 146 | /* 147 | 148 | Side User Info Panel 149 | 150 | */ 151 | panel { 152 | display: none; 153 | position: absolute; 154 | left: 0; 155 | top: 48px; 156 | bottom: 0; 157 | width: 239px; 158 | border-right: solid 1px; 159 | } 160 | 161 | panel .user-panel { 162 | display: block; 163 | width: 239px; 164 | } 165 | 166 | panel .user-panel img.avatar { 167 | display: block; 168 | width: 128px; 169 | height: 128px; 170 | border-radius: 64px; 171 | margin: 16px auto 0; 172 | } 173 | 174 | panel .user-panel .meta { 175 | display: block; 176 | font-size: 10pt; 177 | font-weight: 300; 178 | padding: 10px; 179 | text-align: center; 180 | } 181 | 182 | panel .user-panel .meta > div span { 183 | display: block; 184 | font-size: 9pt; 185 | font-weight: 500; 186 | padding-bottom: 4px; 187 | text-align: left; 188 | } 189 | 190 | 191 | 192 | /* 193 | 194 | Page Overlay - Also used for progress feedback of searches 195 | 196 | */ 197 | overlay { 198 | display: none; 199 | position: absolute; 200 | top: 0; 201 | left: 0; 202 | right: 0; 203 | bottom: 0; 204 | z-index: 999; 205 | } 206 | overlay .status { 207 | display: block; 208 | width: 480px; 209 | position: absolute; 210 | top: 50%; 211 | left: 50%; 212 | transform: translate(-50%, -50%); 213 | z-index: 1000; 214 | font-size: 10pt; 215 | font-weight: 300; 216 | text-align: center; 217 | color: rgba(255,255,255,0.7); 218 | } 219 | overlay .status progress { 220 | width: 440px; 221 | } 222 | overlay .ffmpeg-error { 223 | display: none; 224 | text-align: center; 225 | color: rgba(255,255,255,0.7); 226 | position: absolute; 227 | top: 50%; 228 | left: 50%; 229 | transform: translate(-50%, -50%); 230 | } 231 | overlay .ffmpeg-error span { 232 | display: block; 233 | width: 100%; 234 | padding-bottom: 20px; 235 | } 236 | 237 | /* 238 | 239 | Toolbar 240 | 241 | */ 242 | toolbar { 243 | display: block; 244 | position: absolute; 245 | top: 0; 246 | left: 0; 247 | right: 0; 248 | height: 48px; 249 | padding: 8px; 250 | } 251 | .section { 252 | display: inline-block; 253 | vertical-align: top; 254 | line-height: 32px; 255 | height: 32px; 256 | margin-top: 4px; 257 | } 258 | 259 | .section.with-progress-bar { 260 | line-height: normal; 261 | overflow: hidden; 262 | } 263 | .section.with-progress-bar > label { 264 | display: block; 265 | width: 500px; 266 | line-height: 24px; 267 | font-size: 8.5pt; 268 | font-weight: 300; 269 | text-overflow: ellipsis; 270 | overflow: hidden; 271 | } 272 | .section.with-progress-bar .progress { 273 | display: block; 274 | width: 100%; 275 | height: 4px; 276 | border-radius: 1px; 277 | } 278 | .section.with-progress-bar > .progress .bar { 279 | display: block; 280 | height: 100%; 281 | width: 0px; 282 | border-radius: 1px; 283 | -webkit-transition: 0s; 284 | } 285 | 286 | 287 | 288 | /* 289 | 290 | Footer 291 | 292 | */ 293 | footer { 294 | display: block; 295 | position: absolute; 296 | bottom: 0; 297 | left: 0; 298 | right: 0; 299 | height: 32px; 300 | line-height: 32px; 301 | padding: 4px 8px; 302 | } 303 | 304 | 305 | 306 | /* 307 | 308 | Video Player and Control Stylings 309 | 310 | */ 311 | #videoplayer { 312 | position: absolute; 313 | top: 0; 314 | left: 0; 315 | right: 0; 316 | bottom: 0; 317 | display: block; 318 | width: 100%; 319 | height: 100%; 320 | z-index: 1; 321 | -webkit-app-region: drag; 322 | } 323 | 324 | .video_controls { 325 | z-index: 2; 326 | display: block; 327 | position: absolute; 328 | bottom: 0; 329 | left: 0; 330 | right: 0; 331 | line-height: 40px; 332 | height: 40px; 333 | padding: 4px 8px; 334 | background-image: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 80%); 335 | opacity: 0; 336 | -webkit-transition: 0; 337 | -webkit-app-region: no-drag; 338 | } 339 | 340 | .video_controls:hover { 341 | opacity: 1.0; 342 | } 343 | 344 | .video_controls a.button { 345 | width: 32px !important; 346 | height: 32px !important; 347 | opacity: 1 !important; 348 | cursor: pointer; 349 | position: absolute; 350 | left: 12px; 351 | } 352 | 353 | .video_controls .progress { 354 | display: block; 355 | z-index: 3; 356 | position: absolute; 357 | left: 64px; 358 | right: 128px; 359 | bottom: 16px; 360 | height: 8px; 361 | background-color: rgba(255,255,255,0.20); 362 | padding: 0px; 363 | border-radius: 4px; 364 | cursor: pointer; 365 | z-index: 3; 366 | } 367 | .video_controls .progress > .buffer-bar { 368 | position: absolute; 369 | top: 0; 370 | left: 0; 371 | display: block; 372 | background-color: rgba(255,255,255,0.50); 373 | height: 100%; 374 | border-radius: 4px; 375 | -webkit-transition: 0.25s; 376 | z-index: 4; 377 | } 378 | 379 | .video_controls .progress > .position { 380 | position: absolute; 381 | top: 50%; 382 | left: 0px; 383 | transform: translate(-50%, -50%); 384 | 385 | display: block; 386 | background-color: rgb(32,32,224); 387 | border: solid 3px rgb(128,128,255); 388 | height: 16px; 389 | width: 16px; 390 | border-radius: 8px; 391 | -webkit-transition: 0s; 392 | z-index: 5; 393 | } 394 | 395 | .video_controls .current_time { 396 | position: absolute; 397 | display: block; 398 | width: 120px; 399 | height: 24px; 400 | line-height: 25px; 401 | right: 4px; 402 | bottom: 8px; 403 | font-size: 9pt; 404 | font-weight: 300; 405 | text-align: center; 406 | color: rgb(255,255,255); 407 | letter-spacing: 0.04em; 408 | } 409 | 410 | /* 411 | 412 | Form Controls/Buttons/Etc... 413 | 414 | */ 415 | a.button, input[type=button], button { 416 | display: inline-block; 417 | height: 32px; 418 | line-height: 32px; 419 | padding: 0 8px; 420 | font-size: 10pt; 421 | font-weight: 300; 422 | border: none 0px; 423 | border-radius: 4px; 424 | letter-spacing: 0.04em; 425 | cursor: pointer; 426 | -webkit-transition: 0.2s all; 427 | outline: none; 428 | } 429 | a.button.small, input[type=button].small, button.small { 430 | line-height: 28px; 431 | height: 28px; 432 | font-size: 9.5pt; 433 | } 434 | 435 | a.button.tiny, input[type=button].tiny, button.tiny { 436 | line-height: 24px; 437 | height: 24px; 438 | font-size: 8.5pt; 439 | letter-spacing: 0.06em; 440 | } 441 | 442 | a.button.tiny-60, input[type=button].tiny-60, button.tiny-60 { 443 | width: 60px; 444 | } 445 | a.button.tiny-100, input[type=button].tiny-100, button.tiny-100 { 446 | width: 100px; 447 | } 448 | a.button.tiny-160, input[type=button].tiny-160, button.tiny-160 { 449 | width: 160px; 450 | } 451 | a.button.toggle, input[type=button].toggle, button.toggle { 452 | -webkit-transition: 0.4s all; 453 | } 454 | a.button.toggle.active, input[type=button].toggle.active, button.toggle.active { 455 | font-weight: 500; 456 | } 457 | 458 | input[type=button].icon { 459 | background-position: center; 460 | background-repeat: no-repeat; 461 | background-size: 16px 16px; 462 | } 463 | 464 | a.button { 465 | text-decoration: none; 466 | } 467 | 468 | a.button.icon { 469 | width: 24px; 470 | height: 24px; 471 | border-radius: 12px; 472 | margin-right: 8px; 473 | opacity: 0.6; 474 | background-position: center; 475 | background-repeat: no-repeat; 476 | background-size: 24px 24px; 477 | } 478 | a.button.icon:hover { 479 | opacity: 1.0; 480 | } 481 | 482 | input[type=text],input[type=password] { 483 | display: inline-block; 484 | height: 28px; 485 | line-height: 28px; 486 | padding: 0 5px; 487 | border: solid 1px; 488 | font-size: 10pt; 489 | font-weight: 300; 490 | letter-spacing: 0.02em; 491 | border-radius: 4px; 492 | margin: 0 4px; 493 | outline: none; 494 | -webkit-user-drag: force; 495 | -webkit-user-select: force; 496 | } 497 | 498 | input[type=text].disabled { 499 | -webkit-transition: 0s; 500 | cursor: not-allowed; 501 | } 502 | 503 | .select { 504 | display: inline-block; 505 | overflow: hidden; 506 | height: 28px; 507 | line-height: 28px; 508 | border: 1px solid; 509 | border-radius: 5px; 510 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAFCAYAAABM6GxJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGQTdGMTE3NDA3MjA2ODExODIyQUFFN0UwNDc4QjEzNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyNTgwRUJBODc3RjMxMUUyOTZERjg4QTAyMThGQkVCMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyNTgwRUJBNzc3RjMxMUUyOTZERjg4QTAyMThGQkVCMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RkE3RjExNzQwNzIwNjgxMTgyMkFBRTdFMDQ3OEIxMzciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RkE3RjExNzQwNzIwNjgxMTgyMkFBRTdFMDQ3OEIxMzciLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz78dZPNAAAAcUlEQVR42mI8cuQIIwMDwzIgjmDABEuBONba2vo/Aw7A+P//f4ajR49yAdn7gNgcSQ7E9wRq/sWAB4ANAAGgIaJA6gwQywHxZSC2BWr+yEAAMMEYQMWvgZQPEF8FYi9iNIMByAXIGBgmLOhi+DBAgAEAQvBLybSnPkQAAAAASUVORK5CYII='); 511 | background-position: right; 512 | background-repeat: no-repeat; 513 | 514 | } 515 | 516 | .select > select { 517 | display: block; 518 | position: relative; 519 | width: 112%; 520 | height: 27px; 521 | line-height: 27px; 522 | margin: 0; 523 | padding: 0 4px; 524 | padding-left: 9px; 525 | background: transparent; 526 | border: 0; 527 | font-size: 9.5pt; 528 | font-weight: 400; 529 | border: none; 530 | -webkit-transition: 0s; 531 | -webkit-appearance: none; 532 | } 533 | 534 | .select > select:focus { 535 | z-index: 6; 536 | width: 100%; 537 | outline: none; 538 | -webkit-appearance: none; 539 | } 540 | 541 | div.input { 542 | display: block; 543 | position: relative; 544 | text-align: left; 545 | } 546 | 547 | div.input > input[type=text], input[type=password] { 548 | display: block; 549 | width: 100%; 550 | margin: 0; 551 | } 552 | 553 | div.input.has-left-select > input[type=text], input[type=password] { 554 | padding-left: 120px; 555 | } 556 | 557 | div.input.has-right-button > input[type=text], input[type=password] { 558 | padding-right: 32px; 559 | } 560 | 561 | div.input.has-cc-list > input[type=text], input[type=password] { 562 | padding-right: 352px; 563 | } 564 | 565 | div.input.has-right-button > input[type=button] { 566 | position: absolute; 567 | z-index: 5; 568 | right: 0; 569 | top: 0; 570 | height: 28px; 571 | line-height: 28px; 572 | font-size: 18pt; 573 | padding: 0; 574 | margin: 0; 575 | width: 32px; 576 | border-radius: 0 4px 4px 0; 577 | } 578 | 579 | div.input.has-left-select > .select.left { 580 | position: absolute; 581 | z-index: 6; 582 | border-radius: 4px 0 0 4px; 583 | width: 112px; 584 | height: 28px; 585 | } 586 | 587 | div.input > .select select { 588 | height: 26px; 589 | line-height: 26px; 590 | } 591 | 592 | div.input.has-cc-list > .select.right { 593 | position: absolute; 594 | border-radius: 0px; 595 | top: 0; 596 | right: 32px; 597 | display: block; 598 | width: 320px; 599 | 600 | } 601 | 602 | div.input.has-cc-list > .select.right { 603 | position: absolute; 604 | } 605 | 606 | 607 | .switch { 608 | display: inline-block; 609 | position: relative; 610 | width: 48px; 611 | -webkit-user-select: none; 612 | user-select: none; 613 | vertical-align: middle; 614 | outline: none; 615 | } 616 | 617 | .switch>label { 618 | display: block; 619 | position: relative; 620 | height: 18px; 621 | line-height: 18px; 622 | font-size: 8pt; 623 | color: rgba(255,255,255,0.7); 624 | text-align: center; 625 | text-transform: uppercase; 626 | background: transparent; 627 | border: 1px solid rgba(255,255,255,0.1); 628 | border-radius: 11px; 629 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.06); 630 | -webkit-transition: padding-left 0.15s ease-out; 631 | } 632 | 633 | .switch>label:before { 634 | content: ''; 635 | display: block; 636 | width: 16px; 637 | height: 16px; 638 | background: rgba(255,255,255,0.2); 639 | border-radius: 8px; 640 | -webkit-box-shadow: inset 0 0 0 1px rgba(255,255,255,0.4), 1px 0 1px rgba(0,0,0,0.3); 641 | } 642 | 643 | .switch>label:after { 644 | content: attr(data-off); 645 | position: absolute; 646 | top: 0; 647 | right: 0; 648 | width: 30px 649 | } 650 | 651 | .switch>input { 652 | position: absolute; 653 | z-index: 0; 654 | top: 1px; 655 | left: 1px; 656 | width: 48px; 657 | height: 16px; 658 | padding: 0; 659 | opacity: 0; 660 | -webkit-appearance: none; 661 | outline: none; 662 | } 663 | 664 | .switch>input:focus { 665 | opacity: 1; 666 | } 667 | 668 | .switch>input:checked+label { 669 | padding-left: 31px; 670 | color: rgb(224,224,224); 671 | background: rgb(96,96,96); 672 | } 673 | 674 | .switch>input:checked+label:after { 675 | content: attr(data-on); 676 | left: 0 677 | } 678 | 679 | 680 | 681 | 682 | 683 | /* 684 | 685 | Modal Dialog 686 | 687 | */ 688 | .modal { 689 | position: absolute; 690 | z-index: 9999; 691 | top: 50%; 692 | left: 50%; 693 | display: none; 694 | width: 480px; 695 | height: 200px; 696 | margin: -100px 0 0 -240px; 697 | border-radius: 10px; 698 | } 699 | 700 | .modal.small { 701 | height: 120px; 702 | margin: -60px 0 0 -240px; 703 | } 704 | 705 | .modal.tiny { 706 | height: 90px; 707 | margin: -45px 0 0 -240px; 708 | } 709 | 710 | .modal > div.content { 711 | display: block; 712 | width: 480px; 713 | height: 200px; 714 | position: relative; 715 | padding: 20px 10px 0; 716 | font-size: 10pt; 717 | } 718 | 719 | .modal.small > div.content { 720 | height: 120px; 721 | } 722 | 723 | .modal.tiny > div.content { 724 | text-align: center; 725 | height: 90px; 726 | } 727 | 728 | .modal > div.content > div.buttons { 729 | position: absolute; 730 | bottom: 10px; 731 | left: 0; 732 | right: 0; 733 | text-align: center; 734 | } 735 | 736 | 737 | 738 | /* 739 | 740 | Message Overlay 741 | 742 | */ 743 | .message_overlay { 744 | display: block; 745 | position: absolute; 746 | left: 40px; 747 | right: 40px; 748 | bottom: 40px; 749 | height: 24px; 750 | line-height: 24px; 751 | border-radius: 12px; 752 | font-size: 10pt; 753 | font-weight: 400; 754 | text-align: center; 755 | padding: 0 12px; 756 | z-index: 9999; 757 | } 758 | 759 | 760 | 761 | /* 762 | 763 | Results Lists 764 | 765 | */ 766 | .list { 767 | display: block; 768 | margin: 0; 769 | padding: 0; 770 | } 771 | 772 | .list .item { 773 | display: block; 774 | width: 100%; 775 | table-layout: fixed; 776 | border-top: solid 1px; 777 | padding: 8px; 778 | margin: 0; 779 | } 780 | 781 | .list .item:first-child { 782 | border-top: none; 783 | } 784 | 785 | .list .item .avatar { 786 | display: table-cell; 787 | height: 96px; 788 | max-width: 100%; 789 | position: relative; 790 | } 791 | 792 | .list .item .avatar img { 793 | display: block; 794 | border-radius: 1000px; 795 | width: 96px; 796 | height: 96px; 797 | margin-right: 16px; 798 | } 799 | 800 | .list .item .content { 801 | display: table-cell; 802 | width: 100%; 803 | font-size: 10pt; 804 | padding: 10px 0; 805 | } 806 | 807 | .list .item .content .header { 808 | font-size: 12pt; 809 | font-weight: 300; 810 | } 811 | 812 | .list .item .content .subheader { 813 | font-size: 10pt; 814 | font-weight: 300; 815 | } 816 | 817 | .list .item .content .meta { 818 | display: block; 819 | table-layout: fixed; 820 | width: 100%; 821 | } 822 | .list .item .content .meta div { 823 | display: table-cell; 824 | font-size: 10pt; 825 | font-weight: 300; 826 | text-align: center; 827 | padding: 2px 4px; 828 | margin: 0 4px; 829 | } 830 | 831 | .list .item .content .meta div span, .list .item .footer > div span { 832 | display: block; 833 | text-align: left; 834 | font-size: 8pt; 835 | font-weight: 500; 836 | } 837 | 838 | .list .item .footer { 839 | display: block; 840 | table-layout: fixed; 841 | } 842 | .list .item .footer > div { 843 | display: table-cell; 844 | vertical-align: top; 845 | } 846 | 847 | .list .item.clickable { cursor: pointer; } 848 | .list .item.small { margin-top: 0; } 849 | .list .item.small .avatar { height: 48px; } 850 | .list .item.small .avatar img { height: 48px; width: 48px; margin-top: 0; } 851 | .list .item.small .content { padding: 0; } 852 | .list .item.small .content .header { font-size: 10pt; font-weight: 400; } 853 | .list .item.small .content .subheader { font-size: 8.5pt; } 854 | .list .item.small .content .meta div { font-size: 9pt; } 855 | 856 | .list .empty { 857 | display: block; 858 | font-size: 10pt; 859 | font-weight: 300; 860 | font-style: italic; 861 | text-align: center; 862 | line-height: calc(100vh - 48px); 863 | height: calc(100vh - 48px); 864 | } 865 | 866 | .list .item.message { 867 | display: block; 868 | font-size: 10pt; 869 | font-weight: 300; 870 | } 871 | 872 | .list .item.message div { 873 | display: block; 874 | vertical-align: top; 875 | } 876 | 877 | .list .item.message div span { 878 | display: inline-block; 879 | } 880 | 881 | 882 | 883 | 884 | .list .item.tile { 885 | display: inline-block; 886 | width: 300px; 887 | height: 280px; 888 | border: none; 889 | margin: 5px; 890 | } 891 | 892 | .list .item.tile .avatar { 893 | display: block; 894 | text-align: center; 895 | } 896 | 897 | .list .item.tile .avatar img { 898 | display: block; 899 | margin: 0 auto; 900 | } 901 | 902 | .list .item.tile .header { 903 | display: block; 904 | font-size: 10pt; 905 | height: 40px; 906 | vertical-align: top; 907 | padding: 8px 0 0; 908 | } 909 | 910 | .list .item.tile .meta { 911 | display: table; 912 | width: 100%; 913 | margin: 4px 0; 914 | } 915 | 916 | .list .item.tile .meta div { 917 | display: table-cell; 918 | vertical-align: top; 919 | font-size: 9pt; 920 | padding: 4px 0; 921 | text-align: center; 922 | } 923 | .list .item.tile .meta div span { 924 | display: block; 925 | font-size: 8pt; 926 | margin: 0 0 4px; 927 | text-align: left; 928 | } 929 | 930 | 931 | 932 | 933 | /* 934 | 935 | Color Settings 936 | 937 | */ 938 | h1,h2,h3,h4,h5,h6,p,small,label { 939 | color: rgba(240,240,240,0.9); 940 | } 941 | 942 | toolbar { 943 | background-color: rgba(48,48,48,0.4); 944 | } 945 | 946 | toolbar .section.with-progress-bar > .progress { 947 | background-color: rgba(0,0,0,0.4); 948 | } 949 | 950 | toolbar .section.with-progress-bar > .progress .bar { 951 | background-color: rgb(255,255,255); 952 | } 953 | 954 | panel { 955 | border-right-color: rgba(128,128,128,0.5); 956 | } 957 | 958 | panel .user-panel .meta > div { 959 | color: rgb(212,212,212); 960 | } 961 | 962 | panel .user-panel .meta > div span { 963 | color: rgb(160,160,160); 964 | } 965 | 966 | overlay { 967 | background-color: rgba(0,0,0,0.85); 968 | } 969 | 970 | footer { 971 | background-color: rgba(0,0,0,0.2); 972 | } 973 | 974 | .modal { 975 | color: rgba(255,255,255,0.9); 976 | } 977 | 978 | input[type=text],input[type=password] { 979 | border-color: rgba(128,128,128,0.5); 980 | background-color: transparent; 981 | color: rgba(240,240,240,0.8); 982 | } 983 | 984 | input[type=text]:focus,input[type=password]:focus { 985 | background-color: transparent; 986 | color: rgba(240,240,240,0.8); 987 | } 988 | 989 | input[type=text].disabled { 990 | color: rgba(112,112,112,0.9); 991 | background-color: transparent; 992 | } 993 | 994 | 995 | .select { 996 | background-color: rgba(64,64,64,1); 997 | border-color: rgba(96,96,96,0.7); 998 | } 999 | 1000 | .select > select { 1001 | background-color: transparent; 1002 | color: rgba(240,240,240,0.8); 1003 | } 1004 | 1005 | .select option { 1006 | text-shadow: none; 1007 | background-color: rgb(64,64,64); color: rgb(224,224,224); 1008 | } 1009 | 1010 | a.button, input[type=button] { 1011 | background-color: rgba(96,96,96,0.8); 1012 | color: rgba(240,240,240,0.8); 1013 | } 1014 | 1015 | a.button:hover , input[type=button]:hover { 1016 | background-color: rgba(96,96,96,0.5); 1017 | color: rgb(255,255,255); 1018 | } 1019 | 1020 | a.button.toggle, input[type=button].toggle { 1021 | background-color: rgba(48,48,48,0.5); 1022 | color: rgba(192,192,192,0.8); 1023 | } 1024 | 1025 | a.button.toggle.active, input[type=button].toggle.active { 1026 | background-color: rgb(224,224,224); 1027 | color: rgb(24,24,24); 1028 | } 1029 | a.button.icon { 1030 | background-color: transparent; 1031 | } 1032 | 1033 | .list .item { 1034 | color: rgb(212,211,210); 1035 | border-bottom-color: rgb(66,66,66); 1036 | } 1037 | 1038 | .list .item.visited { 1039 | opacity: 0.4; 1040 | } 1041 | 1042 | .list .item.highlighted { 1043 | background-color: rgb(48,48,48); 1044 | } 1045 | 1046 | .list .item .avatar img { 1047 | background-color: rgb(32,32,32); 1048 | } 1049 | 1050 | .list .item .content .meta div, .list .item .footer div { 1051 | color: rgb(212,212,212); 1052 | } 1053 | 1054 | .list .item .content .meta div span, .list .item .footer div span { 1055 | color: rgb(128,128,128); 1056 | } 1057 | 1058 | .list .item.tile { 1059 | background-color: rgba(64,64,64,0.25); 1060 | } 1061 | 1062 | .list .empty { 1063 | color: rgba(224,224,224,0.3); 1064 | } 1065 | 1066 | 1067 | .downloaded { 1068 | background-color: rgba(96,255,96,0.2); 1069 | } 1070 | .highlight { 1071 | background-color: rgba(255,255,0,0.1); 1072 | } 1073 | .error { 1074 | background-color: rgba(150, 0, 0, 0.3); 1075 | } 1076 | 1077 | .message_overlay { 1078 | background-color: rgba(0,0,0,0.8); 1079 | color: rgb(255,255,255); 1080 | } 1081 | 1082 | 1083 | /* 1084 | 1085 | Male/Female indication styling 1086 | 1087 | */ 1088 | .male { border-color: #89cff0; } 1089 | .female { border-color: #f9ccca; } 1090 | 1091 | 1092 | 1093 | /* 1094 | 1095 | Icon Add-on Classes 1096 | 1097 | */ 1098 | .icon-cancel { background-image: url(../images/ic_cancel_white_24px.svg); } 1099 | .icon-chat { background-image: url(../images/ic_chat_white_24px.svg); } 1100 | .icon-copy { background-image: url(../images/ic_content_copy_white_18px.svg); } 1101 | .icon-download { background-image: url(../images/ic_file_download_white_24px.svg); } 1102 | .icon-media-pause { background-image: url(../images/ic_pause_white_18px.svg); } 1103 | .icon-media-play { background-image: url(../images/ic_play_arrow_white_18px.svg); } 1104 | .icon-pause { background-image: url(../images/ic_pause_circle_outline_white_24px.svg); } 1105 | .icon-play { background-image: url(../images/ic_play_circle_outline_white_24px.svg); } 1106 | .icon-remove { background-image: url(../images/ic_cancel_white_24px.svg); } 1107 | .icon-search { background-image: url(../images/search.svg); } 1108 | -------------------------------------------------------------------------------- /src/lmt/update.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Update Status 5 | 8 | 9 | 10 | 11 | 73 | 76 | 77 | 78 | 79 | 80 |
81 | Preparing to begin update... 82 |
83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /src/lmt/upgrade.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveMe Tools - Upgrade Notice 5 | 8 | 9 | 10 | 11 | 29 | 33 | 34 | 35 | 36 |
37 |

38 | Looks like version has been released!
39 | You're running version . 40 |

41 |
42 | 43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LiveMeTools", 3 | "title": "LiveMe Tools", 4 | "version": "7.0.9", 5 | "minversion": "6.2.1", 6 | "description": "A tool designed to help in searching and viewing of LiveMe broadcasts.", 7 | "main": "index.js", 8 | "author": { 9 | "name": "The Coder", 10 | "email": "thecoder1975@gmail.com" 11 | }, 12 | "homepage": "https://github.com/thecoder75/liveme-tools/", 13 | "dependencies": { 14 | "async": "^2.5.0", 15 | "axios": "^0.16.2", 16 | "electron-is-dev": "^0.3.0", 17 | "electron-settings": "^3.1.2", 18 | "fluent-ffmpeg": "^2.1.2", 19 | "format-duration": "^1.0.0", 20 | "fs-extra": "^4.0.2", 21 | "init": "^0.1.2", 22 | "liveme-api": "^1.2.5", 23 | "lowdb": "^1.0.0", 24 | "p-map": "^1.2.0", 25 | "p-progress": "^0.1.1", 26 | "request": "^2.83.0", 27 | "shelljs": "^0.7.8", 28 | "wget-improved": "^1.5.0" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/thecoder75/liveme-tools" 33 | }, 34 | "contributors": [ 35 | "thecoder75", 36 | "polydragon", 37 | "zp" 38 | ], 39 | "license": "GPL-3.0", 40 | "devDependencies": { 41 | "electron": "^1.8.1", 42 | "electron-builder": "^19.36.0" 43 | }, 44 | "keywords": [ 45 | "node", 46 | "electron", 47 | "video", 48 | "tools", 49 | "liveme" 50 | ], 51 | "build": { 52 | "appId": "com.thecoderstoolbox.livemetools", 53 | "productName": "LiveMeTools", 54 | "artifactName": "${productName}-${version}-${arch}.${ext}", 55 | "asar": true, 56 | "compression": "maximum", 57 | "mac": { 58 | "category": "public.app-category.video", 59 | "target": [ 60 | "dmg" 61 | ], 62 | "identity": null, 63 | "icon": "build/appicon.icns" 64 | }, 65 | "win": { 66 | "target": [ 67 | "portable" 68 | ], 69 | "icon": "build/appicon.ico" 70 | }, 71 | "linux": { 72 | "target": [ 73 | "tar.bz2" 74 | ], 75 | "maintainer": "thecoder1975@gmail.com" 76 | }, 77 | "publish": [ 78 | { 79 | "provider": "github", 80 | "vPrefixedTagName": false, 81 | "releaseType": "release" 82 | } 83 | ] 84 | }, 85 | "scripts": { 86 | "postinstall": "install-app-deps", 87 | "start": "./node_modules/.bin/electron . --enable-logging", 88 | "dev": "NODE_ENV='development' npm run start", 89 | "dist-appveyor": "build --publish always -w --ia32 --x64", 90 | "dist-travis": "build --publish always" 91 | } 92 | } 93 | --------------------------------------------------------------------------------