├── .bowerrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── missing_content.md └── workflows │ └── build.yml ├── .gitignore ├── .jsbeautifyrc ├── .jshintrc ├── .vscode ├── config.json └── launch.json ├── CHANGELOG.md ├── Create-Desktop-Entry ├── LICENSE.txt ├── README.md ├── dist ├── linux │ ├── appimage │ │ ├── Popcorn-Time.desktop │ │ └── Popcorn-Time.png │ ├── copy-libatomic.sh │ └── deb-maker.sh ├── mac │ ├── background.png │ ├── casks │ │ └── popcorn-time.rb │ ├── codesign.sh │ ├── pkg-maker.sh │ ├── resources │ │ ├── app.popcorntime.desktop.plist │ │ ├── diagram.tiff │ │ ├── diagram@2x.tiff │ │ ├── distribution.xml │ │ ├── license.txt │ │ └── welcome.html │ └── yoursway-create-dmg │ │ ├── builder │ │ └── create-dmg.builder │ │ ├── create-dmg │ │ ├── sample │ │ └── support │ │ ├── dmg-license.py │ │ └── template.applescript └── windows │ ├── LICENSE.txt │ ├── installer-image.bmp │ ├── installer_makensis32.nsi │ ├── installer_makensis64.nsi │ ├── launcher.vbs │ └── uninstaller-image.bmp ├── docs ├── Build-Debug.md ├── Code-Standards.md ├── Contributing.md ├── Git-Workflow.md ├── json-rpc-api.md └── screenshots │ └── b1e1dc8c-7b32-11e5-9c25-d9fbd5b2f3bd.png ├── gulpfile.js ├── i18n-unused.config.js ├── make_popcorn.sh ├── package.json ├── patches ├── upnp-mediarenderer-client+1.4.0.patch └── webtorrent+1.9.7.patch ├── src └── app │ ├── .jshintrc │ ├── app.js │ ├── bootstrap.js │ ├── butter-provider │ ├── anime.js │ ├── generic.js │ ├── movie.js │ ├── tv.js │ └── yts.js │ ├── common.js │ ├── config.js │ ├── database.js │ ├── fileserver.js │ ├── fonts │ ├── OpenSans-Bold.svg │ ├── OpenSans-Bold.woff │ ├── OpenSans-Regular.svg │ ├── OpenSans-Regular.woff │ ├── OpenSans-Semibold.svg │ ├── OpenSans-Semibold.woff │ ├── vjs.eot │ ├── vjs.svg │ ├── vjs.ttf │ └── vjs.woff │ ├── global.js │ ├── httpapi.js │ ├── images │ ├── bg-header.jpg │ ├── butter-logo.svg │ ├── butter.icns │ ├── butter.ico │ ├── butter_uninstall.ico │ ├── icon.png │ ├── icons │ │ ├── Eztv.png │ │ ├── NnmClub.png │ │ ├── Rutor.png │ │ ├── Rutracker.png │ │ ├── T1337x.png │ │ ├── TorrentGalaxy.png │ │ ├── Yts.png │ │ ├── airplay-icon.png │ │ ├── airplay-xbmc-icon.png │ │ ├── chromecast-icon.png │ │ ├── dlna-icon.png │ │ ├── events │ │ │ ├── aprilsfool.png │ │ │ ├── halloween.png │ │ │ ├── newyear.png │ │ │ ├── pt_anniv.png │ │ │ ├── stpatrick.png │ │ │ ├── stvalentine.png │ │ │ └── xmas.png │ │ ├── external-bomi-icon.png │ │ ├── external-bsplayer-icon.png │ │ ├── external-fleex-player-icon.png │ │ ├── external-icon.png │ │ ├── external-iina-icon.png │ │ ├── external-mpc-be-icon.png │ │ ├── external-mpc-hc-icon.png │ │ ├── external-mplayer-icon.png │ │ ├── external-mpv-icon.png │ │ ├── external-mpvnet-icon.png │ │ ├── external-potplayer-icon.png │ │ ├── external-smplayer-icon.png │ │ ├── external-vlc-icon.png │ │ ├── flag-none.svg │ │ ├── flag-question.svg │ │ ├── icon-blog.png │ │ ├── icon-ci.png │ │ ├── icon-discourse.png │ │ ├── icon-github.png │ │ ├── icon-popcorn.png │ │ ├── icon-reddit.png │ │ ├── imdb.png │ │ ├── local-icon.png │ │ ├── nyaa.png │ │ ├── player │ │ │ ├── Sound0.png │ │ │ ├── Sound1.png │ │ │ ├── Sound2.png │ │ │ ├── Sound3.png │ │ │ └── Subtitles.png │ │ ├── rarbg.png │ │ ├── solidtorrents.png │ │ ├── topbar_sprite.png │ │ ├── topbar_sprite_win.png │ │ └── tpb.png │ ├── popcorn-time-logo.svg │ ├── popcorntime.icns │ ├── popcorntime.ico │ └── posterholder.png │ ├── index.html │ ├── jquery.plugins.js │ ├── language.js │ ├── language │ ├── .tx │ │ └── config │ ├── ar.json │ ├── bg.json │ ├── bn.json │ ├── br.json │ ├── ca.json │ ├── cs.json │ ├── da.json │ ├── de.json │ ├── el-GR.json │ ├── el.json │ ├── en.json │ ├── es-mx.json │ ├── es.json │ ├── et.json │ ├── eu.json │ ├── fa.json │ ├── fi.json │ ├── fr.json │ ├── gl.json │ ├── gu.json │ ├── he.json │ ├── hi.json │ ├── hr-HR.json │ ├── hr.json │ ├── hu.json │ ├── id.json │ ├── is.json │ ├── it.json │ ├── ja.json │ ├── ka.json │ ├── kn.json │ ├── ko.json │ ├── lt.json │ ├── lv.json │ ├── mk.json │ ├── ml.json │ ├── ms.json │ ├── nb.json │ ├── nl.json │ ├── nn.json │ ├── no.json │ ├── pl.json │ ├── pt-PT.json │ ├── pt-br.json │ ├── pt.json │ ├── ro.json │ ├── ru.json │ ├── sk.json │ ├── sl.json │ ├── sq-AL.json │ ├── sq.json │ ├── sr.json │ ├── sv-SE.json │ ├── sv.json │ ├── ta.json │ ├── te.json │ ├── th.json │ ├── tl.json │ ├── tr.json │ ├── uk.json │ ├── ur.json │ ├── vi.json │ ├── zh-cn.json │ └── zh-tw.json │ ├── lib │ ├── device │ │ ├── airplay.js │ │ ├── chromecast.js │ │ ├── dlna.js │ │ ├── ext_player.js │ │ ├── generic.js │ │ └── xbmc.js │ ├── models │ │ ├── anime_collection.js │ │ ├── content_item.js │ │ ├── favorite_collection.js │ │ ├── filter.js │ │ ├── generic_collection.js │ │ ├── lang.js │ │ ├── movie.js │ │ ├── movie_collection.js │ │ ├── notification.js │ │ ├── show.js │ │ ├── show_collection.js │ │ ├── stream_info.js │ │ └── watchlist_collection.js │ ├── providers │ │ ├── favorites.js │ │ ├── generic.js │ │ ├── icons.js │ │ ├── opensubtitles.js │ │ ├── torrent_cache.js │ │ ├── trakttv.js │ │ └── watchlist.js │ ├── subtitle │ │ ├── generic.js │ │ └── server.js │ └── views │ │ ├── about.js │ │ ├── browser │ │ ├── anime_browser.js │ │ ├── favorite_browser.js │ │ ├── generic_browser.js │ │ ├── movie_browser.js │ │ ├── show_browser.js │ │ └── watchlist_browser.js │ │ ├── disclaimer.js │ │ ├── file_selector.js │ │ ├── filter_bar.js │ │ ├── init_modal.js │ │ ├── item.js │ │ ├── keyboard.js │ │ ├── lang_dropdown.js │ │ ├── list.js │ │ ├── loading.js │ │ ├── main_window.js │ │ ├── movie_detail.js │ │ ├── notification.js │ │ ├── play_control.js │ │ ├── player.js │ │ ├── quality_selector.js │ │ ├── seedbox.js │ │ ├── settings_container.js │ │ ├── show_detail.js │ │ ├── title_bar.js │ │ ├── torrent_collection.js │ │ ├── torrent_list.js │ │ └── windows_title_bar.js │ ├── media_name.js │ ├── settings.js │ ├── streamer.js │ ├── styl │ ├── Dutchys_-_Dark_Orange_theme.styl │ ├── Official_-_Black_&_Yellow_theme.styl │ ├── Official_-_Dark_theme.styl │ ├── Official_-_FlaX_theme.styl │ ├── Official_-_Flat_UI_theme.styl │ ├── Official_-_Light_theme.styl │ ├── Sebastiaans_-_Black_&_Red_theme.styl │ └── views │ │ ├── about.styl │ │ ├── app.styl │ │ ├── bootstrap_theme.styl │ │ ├── button.styl │ │ ├── dropdowns.styl │ │ ├── file_selector.styl │ │ ├── filter_bar.styl │ │ ├── fonts.styl │ │ ├── initializing.styl │ │ ├── item.styl │ │ ├── keyboard.styl │ │ ├── list.styl │ │ ├── loading.styl │ │ ├── main_window.styl │ │ ├── misc.styl │ │ ├── movie_detail.styl │ │ ├── movie_error.styl │ │ ├── notification.styl │ │ ├── player.styl │ │ ├── seedbox.styl │ │ ├── settings_container.styl │ │ ├── show_detail.styl │ │ ├── title_bar.styl │ │ ├── torrent_collection.styl │ │ ├── torrent_list.styl │ │ ├── videojs.styl │ │ └── windows_titlebar.styl │ ├── templates │ ├── about.tpl │ ├── browser.tpl │ ├── changelog.tpl │ ├── disclaimer.tpl │ ├── file-selector.tpl │ ├── filter-bar.tpl │ ├── header.tpl │ ├── initializing.tpl │ ├── item.tpl │ ├── keyboard.tpl │ ├── lang-dropdown.tpl │ ├── list.tpl │ ├── loading.tpl │ ├── main-window.tpl │ ├── movie-detail.tpl │ ├── movie-error.tpl │ ├── notification.tpl │ ├── play-control.tpl │ ├── player-chooser.tpl │ ├── player.tpl │ ├── quality-selector.tpl │ ├── seedbox.tpl │ ├── settings-container.tpl │ ├── show-detail.tpl │ ├── torrent-collection.tpl │ ├── torrent-list.tpl │ └── windows-titlebar.tpl │ ├── updater.js │ └── vendor │ ├── videojshooks.js │ └── videojsplugins.js └── yarn.lock /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/app/vendor" 3 | } 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | 16 | #### Operating System Version: 17 | 18 | 19 | #### Popcorn Time Version: 20 | 21 | 22 | #### Download date: 23 | 24 | 25 | #### Download url: 26 | 27 | 28 | #### Expected Behaviour: 29 | 30 | ... 31 | 32 | #### Actual Behaviour: 33 | 34 | ... 35 | 36 | #### Steps to reproduce the behaviour: 37 | 38 | 39 | 1. ... 40 | 2. ... 41 | 3. ... 42 | 43 | #### Screenshot(s) of issue or error(s) logs of developer console (Windows/Linux: F12, MacOS: ⌘ + 0 ... then 'console' tab) (recommended): 44 | 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the idea you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/missing_content.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Missing or wrong movie / episode 3 | about: Do not open an issue 4 | title: Do not open an issue 5 | labels: wontfix 6 | assignees: '' 7 | 8 | --- 9 | 10 | The app doesn't manage the content, it uses third-party sources. 11 | 12 | Its not of much use opening an issue here for those kind of issues. 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Latest Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macOS-latest, windows-latest] 18 | nwjs: ['0.44.5', '0.86.0'] 19 | 20 | steps: 21 | - name: Context 22 | env: 23 | GITHUB_CONTEXT: ${{ toJson(github) }} 24 | run: echo "$GITHUB_CONTEXT" 25 | 26 | - uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | persist-credentials: false 30 | 31 | - name: Reconfigure git to use HTTP authentication 32 | run: > 33 | git config --global url."https://github.com/".insteadOf 34 | ssh://git@github.com/ 35 | 36 | - name: Use Node.js 20 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version: 20 40 | 41 | - name: yarn version 42 | run: | 43 | yarn version 44 | 45 | - name: node version 46 | run: | 47 | node --version 48 | 49 | - uses: kiriles90/variable-mapper@master 50 | with: 51 | key: "${{ matrix.os }}" 52 | map: | 53 | { 54 | "ubuntu-latest": { "platform": "linux64" }, 55 | "macOS-latest": { "platform": "osx64" }, 56 | "windows-latest": { "platform": "win64" } 57 | } 58 | 59 | - name: Build info 60 | run: echo Build ${{ env.platform }} on nw-v${{ matrix.nwjs }} 61 | 62 | - name: Build App 63 | run: | 64 | yarn 65 | yarn gulp dist --platforms=${{ env.platform == 'win' && '"' || '' }}${{ env.platform }}${{ env.platform == 'win' && '"' || '' }} --nwVersion=${{ matrix.nwjs }} 66 | - name: Upload artifacts 67 | uses: actions/upload-artifact@master 68 | with: 69 | name: ${{ env.platform }}-${{ matrix.nwjs }} 70 | path: build 71 | 72 | packs: 73 | needs: build 74 | name: Packs 75 | runs-on: ubuntu-latest 76 | 77 | strategy: 78 | matrix: 79 | nwjs: ['0.44.5', '0.86.0'] 80 | 81 | steps: 82 | - name: Context 83 | env: 84 | GITHUB_CONTEXT: ${{ toJson(github) }} 85 | run: echo "$GITHUB_CONTEXT" 86 | 87 | - uses: actions/checkout@v4 88 | with: 89 | path: repo 90 | persist-credentials: false 91 | 92 | - uses: actions/download-artifact@v4 93 | with: 94 | name: linux64-${{ matrix.nwjs }} 95 | path: . 96 | 97 | - name: Install packages and appimagetool 98 | run: | 99 | sudo apt update 100 | sudo apt install -y libfuse2 101 | wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage 102 | chmod +x appimagetool-x86_64.AppImage 103 | 104 | - name: Build AppImage 105 | run: | 106 | VER=$(ls *-linux64*.zip | sed 's/-linux64.*//') 107 | echo $VER 108 | unzip -q *-linux64*.zip -d $VER.AppDir 109 | cp repo/dist/linux/appimage/* $VER.AppDir/ 110 | ln -s Popcorn-Time $VER.AppDir/AppRun 111 | mkdir build 112 | ARCH=x86_64 ./appimagetool-x86_64.AppImage $VER.AppDir build/$VER-linux64${{ matrix.nwjs == '0.44.5' && '-0.44.5' || '' }}.AppImage 113 | 114 | - name: Upload artifacts 115 | uses: actions/upload-artifact@master 116 | with: 117 | name: linux64-${{ matrix.nwjs }}.AppImage 118 | path: build 119 | 120 | 121 | release: 122 | needs: packs 123 | name: Release 124 | runs-on: ubuntu-latest 125 | steps: 126 | - name: Context 127 | if: startsWith(github.ref, 'refs/tags/v') 128 | env: 129 | GITHUB_CONTEXT: ${{ toJson(github) }} 130 | run: echo "$GITHUB_CONTEXT" 131 | 132 | - uses: actions/download-artifact@v4 133 | with: 134 | path: artifacts 135 | merge-multiple: true 136 | 137 | - name: Display structure of downloaded files 138 | run: ls -R artifacts 139 | 140 | - uses: ncipollo/release-action@v1 141 | if: startsWith(github.ref, 'refs/tags/v') 142 | with: 143 | allowUpdates: true 144 | name: ${{ github.ref_name }} 145 | artifacts: "artifacts/*" 146 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build files 2 | node_modules/ 3 | src/app/themes 4 | build/ 5 | cache/ 6 | dist/windows/*-Setup.exe 7 | dist/linux/linux-installer 8 | dist/linux/*.deb 9 | src/app/css/app.css 10 | *git.json 11 | 12 | # temp files 13 | pids 14 | logs 15 | results 16 | npm-debug.log 17 | .sass-cache 18 | *-ck.js 19 | *.srt 20 | *.DS_Store 21 | lib-cov 22 | .idea/ 23 | *.swp 24 | *.sublime-* 25 | .vscode/ 26 | *.code-workspace 27 | *.bat 28 | *.db 29 | *.seed 30 | *.log 31 | *.csv 32 | *.dat 33 | *.out 34 | *.pid 35 | *.gz 36 | *.iml 37 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "js": { 3 | "brace_style": "collapse", 4 | "break_chained_methods": false, 5 | "e4x": false, 6 | "eval_code": false, 7 | "indent_level": 0, 8 | "indent_with_tabs": false, 9 | "indent_size": 4, 10 | "indent_char": " ", 11 | "jslint_happy": true, 12 | "keep_array_indentation": false, 13 | "keep_function_indentation": false, 14 | "max_preserve_newlines": 3, 15 | "preserve_newlines": true, 16 | "space_before_conditional": true, 17 | "space-after-anon-function": true, 18 | "space_in_paren": false, 19 | "unescape_strings": true, 20 | "wrap_line_length": 0, 21 | "allowed_file_extensions": ["js", "json", "jshintrc", "jsbeautifyrc"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Environments 3 | "browser" : true, 4 | "node" : true, 5 | "jquery" : true, 6 | "devel" : true, 7 | 8 | // Options 9 | "curly" : true, 10 | "bitwise" : false, 11 | "immed" : true, 12 | "latedef" : true, 13 | "nonbsp" : true, 14 | "nonew" : true, 15 | "validthis": true, 16 | "debug" : true, 17 | "boss" : true, 18 | "eqeqeq" : true, 19 | "expr" : true, 20 | "eqnull" : true, 21 | "quotmark" : "single", 22 | "trailing" : true, 23 | "sub" : true, 24 | "undef" : true, 25 | "laxbreak" : true, 26 | "loopfunc" : true, 27 | "indent" : 4, 28 | "newcap" : false, 29 | "esversion" : 11, 30 | 31 | // Globals 32 | "globals": { 33 | // App 34 | "nw": true, 35 | "App": true, 36 | "Settings": true, 37 | "AdvSettings": true, 38 | "ScreenResolution": true, 39 | "Database": true, 40 | "detectLanguage": true, 41 | "Common": true, 42 | "indexedDB": true, 43 | "startupTime": true, 44 | "deleteFolder": true, 45 | "deleteCookies": true, 46 | "isVideo": true, 47 | "minimizeToTray": true, 48 | "promisifyDb": true, 49 | "db": true, 50 | "Promise": true, 51 | 52 | // Global variables 53 | "_": true, 54 | "async": true, 55 | "inherits": true, 56 | "Q": true, 57 | "os": true, 58 | "dayjs": true, 59 | "crypt": true, 60 | "semver": true, 61 | "fs": true, 62 | "path": true, 63 | "mkdirp": true, 64 | "rimraf": true, 65 | "tar": true, 66 | "AdmZip": true, 67 | "zlib": true, 68 | "charsetDetect": true, 69 | "iconv": true, 70 | "gui": true, 71 | "win": true, 72 | "data_path": true, 73 | "i18n": true, 74 | "url": true, 75 | "tls": true, 76 | "http": true, 77 | "URI": true, 78 | "child": true, 79 | "WebTorrent": true, 80 | "torrentCollection": true, 81 | "Trakt": true, 82 | "pkJson": true, 83 | "curSetDefaultFilters": true, 84 | "extPlayerlst": true, 85 | "jsonFileEditor": true, 86 | 87 | // Third party 88 | "Backbone": true, 89 | "Marionette": true, 90 | "Mousetrap": true, 91 | "request": true, 92 | "videojs": true, 93 | "vjs": true, 94 | "VPNht": true 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /.vscode/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "GulpTester", 5 | "type": "node", 6 | "request": "launch", 7 | "program": "${workspaceRoot}/node_modules/gulp/bin/gulp.js", 8 | "stopOnEntry": false, 9 | "args": ["build"], 10 | "cwd": "${workspaceRoot}", 11 | "runtimeArgs": [ 12 | "--nolazy" 13 | ], 14 | "console": "internalConsole", 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Create-Desktop-Entry: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Adds a desktop entry for the current user 4 | 5 | create_desktop_entry() { 6 | NAME="Popcorn Time" 7 | COMMENT="Watch Movies and TV Shows instantly!" 8 | 9 | CURRENT_DIR=$(pwd) 10 | EXECUTABLE=$CURRENT_DIR/Popcorn-Time 11 | ICON_PATH=$CURRENT_DIR/src/app/images/icon.png 12 | 13 | DESTINATION_DIR=/home/$(whoami)/.local/share/applications 14 | DESKTOP_FILE=$DESTINATION_DIR/popcorn-time.desktop 15 | 16 | echo "[Desktop Entry]" > $DESKTOP_FILE 17 | echo "Version=1.0" >> $DESKTOP_FILE 18 | echo "Type=Application" >> $DESKTOP_FILE 19 | echo "Name=$NAME" >> $DESKTOP_FILE 20 | echo "Icon=$ICON_PATH" >> $DESKTOP_FILE 21 | echo "Exec=\"$EXECUTABLE\" %U" >> $DESKTOP_FILE 22 | echo "Comment=$COMMENT" >> $DESKTOP_FILE 23 | echo "Categories=Multimedia;" >> $DESKTOP_FILE 24 | echo "Terminal=false" >> $DESKTOP_FILE 25 | 26 | # Updates the desktop entries 27 | gtk-update-icon-cache /usr/share/icons/hicolor 28 | 29 | echo "Desktop entry added for $(whoami)" 30 | } 31 | 32 | printf "Create a desktop entry for Popcorn Time for the current user ? (Y)es/(N)o: " 33 | read -n1 input 34 | 35 | 36 | if [ "$input" == "Y" ] || [ "$input" == "y" ] || [ "$input" == "" ] 37 | then 38 | case "$(uname)" in 39 | Linux) 40 | create_desktop_entry ;; 41 | Darwin) 42 | echo "Create a desktop entry is only meant to be used on Linux" ;; 43 | esac 44 | else 45 | echo -e "Cancel. /nJust execute ./Create-Desktop-Entry when you are ready to create a desktop entry for Popcorn Time" 46 | fi 47 | -------------------------------------------------------------------------------- /dist/linux/appimage/Popcorn-Time.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Comment=Watch Movies and TV Shows instantly 3 | Name=Popcorn-Time 4 | Exec=Popcorn-Time %U 5 | Icon=Popcorn-Time 6 | MimeType=application/x-bittorrent;x-scheme-handler/magnet; 7 | StartupNotify=false 8 | Categories=AudioVideo 9 | Type=Application 10 | X-Desktop-File-Install-Version=0.26 11 | 12 | -------------------------------------------------------------------------------- /dist/linux/appimage/Popcorn-Time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/dist/linux/appimage/Popcorn-Time.png -------------------------------------------------------------------------------- /dist/linux/copy-libatomic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # launch 'copy-libatomic.sh ' 3 | 4 | builddir=$1 5 | projectName=$2 6 | arch=$3 7 | 8 | outDir="$1/$2/$3" 9 | 10 | sudo dpkg --add-architecture i386 11 | dpkg-query -s libatomic1 12 | if [ ! $? = 0 ]; then 13 | sudo apt update 14 | sudo apt install -y libatomic1 15 | fi 16 | dpkg-query -s libatomic1:i386 17 | if [ ! $? = 0 ]; then 18 | sudo apt update 19 | sudo apt install -y libatomic1:i386 20 | fi 21 | 22 | if [[ $arch == "linux64" ]] 23 | then 24 | read source <<< `readlink -f /usr/lib/x86_64*/libatomic.so.*` 25 | else 26 | read source <<< `readlink -f /usr/lib/i386*/libatomic.so.*` 27 | fi 28 | cp $source "$outDir/lib/libatomic.so.1" 29 | -------------------------------------------------------------------------------- /dist/mac/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/dist/mac/background.png -------------------------------------------------------------------------------- /dist/mac/casks/popcorn-time.rb: -------------------------------------------------------------------------------- 1 | cask "popcorn-time" do 2 | version "0.5.1" 3 | 4 | nwjs = "0.86.0" 5 | 6 | 7 | if Hardware::CPU.intel? 8 | sha256 "da6d993651e57cc88296f93f928ffedac3027313af0eb447ff8ca7a12a60e06a" 9 | url "https://github.com/popcorn-official/popcorn-desktop/releases/download/v#{version}/Popcorn-Time-#{version}-osx64.zip" 10 | arch = "x64" 11 | else 12 | sha256 "51f11fb0483983dd6c4baddf12938d8a85b8320e3499dbe12cbcf5e4146e7f74" 13 | url "https://github.com/popcorn-official/popcorn-desktop/releases/download/v#{version}/Popcorn-Time-#{version}-osxarm64.zip" 14 | arch = "arm64" 15 | end 16 | 17 | name token.gsub(/\b\w/, &:capitalize) 18 | desc "BitTorrent client that includes an integrated media player" 19 | homepage "https://github.com/popcorn-official/popcorn-desktop/releases/download/v0.5.1/Popcorn-Time-0.5.1-osx64.zip" 20 | 21 | repo = "popcorn-official/popcorn-desktop" 22 | zip = "#{name.first}-#{version}-osx64.zip" 23 | 24 | livecheck { url "https://github.com/#{repo}" } 25 | 26 | if (%w[-v --verbose -d --debug] & ARGV).any? 27 | v = "-v" 28 | quiet = silent = "verbose" 29 | else 30 | quiet = "quiet" 31 | silent = "silent" 32 | end 33 | 34 | 35 | auto_updates true 36 | depends_on arch: [:x86_64, :arm64] 37 | 38 | app "#{name.first}.app" 39 | 40 | app_support = "#{Dir.home}/Library/Application Support" 41 | 42 | uninstall quit: bundle_id = "com.nw-builder.#{token}" 43 | 44 | zap trash: %W[ 45 | #{app_support}/#{name.first} 46 | ~/Library/Preferences/#{bundle_id}.plist 47 | #{app_support}/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/#{bundle_id}.sfl* 48 | #{app_support}/configstore/#{token}.json 49 | ~/Library/Saved Application State/#{bundle_id}.savedState 50 | ~/Library/Caches/#{name.first} 51 | ] 52 | 53 | postflight do 54 | system_command "/usr/bin/xattr", 55 | args: ["-c", "/Applications/Popcorn-Time.app/"], 56 | sudo: true 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /dist/mac/codesign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case ${OSTYPE} in *darwin*) 4 | # if this fails please run `brew install coreutils` - homebrew also has this package 5 | alias readlink=greadlink 6 | ;; 7 | esac 8 | 9 | dir="$(dirname $(readlink -f ${0}))" 10 | build="${dir}/../../build/releases/Butter/mac" 11 | app="${build}/Butter.app" 12 | identity="2Z88DW977Y" 13 | 14 | if [ -z "$1" ]; then 15 | echo "You need to provide the version." 16 | exit 1 17 | fi 18 | 19 | echo "### remove previous dmg" 20 | rm -rf ${build}/*.dmg 21 | 22 | echo "### signing frameworks" 23 | codesign --force --verify --verbose --sign "${identity}" "${app}/Contents/Frameworks/nwjs Framework.framework/nwjs Framework" 24 | codesign --force --verify --verbose --sign "${identity}" "${app}/Contents/Frameworks/nwjs Helper EH.app/" 25 | codesign --force --verify --verbose --sign "${identity}" "${app}/Contents/Frameworks/nwjs Helper NP.app/" 26 | codesign --force --verify --verbose --sign "${identity}" "${app}/Contents/Frameworks/nwjs Helper.app/" 27 | codesign --force --verify --verbose --sign "${identity}" "${app}/Contents/Frameworks/crash_inspector" 28 | 29 | echo "### signing webkit" 30 | codesign --force --verify --verbose --sign "${identity}" "${app}/Contents/MacOS/nwjs" 31 | 32 | echo "### signing app" 33 | codesign --force --verify --verbose --sign "${identity}" "${app}" 34 | 35 | echo "### verifying signature" 36 | codesign -vvv -d "${app}" 37 | 38 | echo "### create dmg" 39 | dist/mac/yoursway-create-dmg/create-dmg --volname "Butter ${1}" --background ./dist/mac/background.png --window-size 480 540 --icon-size 128 --app-drop-link 240 370 --icon "Butter" 240 110 "${build}/Butter-${1}-Mac.dmg" "${build}" 40 | 41 | dmg="${build}/Butter-${1}-Mac.dmg" 42 | 43 | echo "### signing dmg" 44 | codesign --force --verify --verbose --sign "${identity}" "${dmg}" 45 | 46 | echo "### verifying signature" 47 | codesign -vvv -d "${dmg}" 48 | -------------------------------------------------------------------------------- /dist/mac/pkg-maker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | APP_NAME="$(cat package.json | grep '\"name\"' | cut -d '"' -f 4)" 3 | APP_VER="$(cat package.json | grep version | cut -d '"' -f 4)" 4 | BUILD_PATH="${APP_NAME}/osx64/${APP_NAME}.app" 5 | CURRENT_DIR="$( dirname "${BASH_SOURCE[0]}" )" 6 | 7 | cd $CURRENT_DIR/../../build 8 | 9 | rm -Rf *.pkg 10 | pkgbuild --root $BUILD_PATH --version $APP_VER --ownership recommended --install-location /Applications/${APP_NAME}.app Build.pkg 11 | productbuild --resources ../dist/mac/resources/ --distribution ../dist/mac/resources/distribution.xml --version $APP_VER $APP_NAME-$APP_VER.pkg 12 | rm -Rf Build.pkg 13 | -------------------------------------------------------------------------------- /dist/mac/resources/app.popcorntime.desktop.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | app.popcorntime.desktop 7 | Program 8 | /Applications/Popcorn-Time.app/Contents/MacOS/nwjs 9 | ProgramArguments 10 | 11 | /Applications/Popcorn-Time.app/Contents/MacOS/nwjs 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /dist/mac/resources/diagram.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/dist/mac/resources/diagram.tiff -------------------------------------------------------------------------------- /dist/mac/resources/diagram@2x.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/dist/mac/resources/diagram@2x.tiff -------------------------------------------------------------------------------- /dist/mac/resources/distribution.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Popcorn Time 4 | app.popcorntime 5 | 6 | 7 | 8 | 9 | 10 | Build.pkg 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /dist/mac/resources/license.txt: -------------------------------------------------------------------------------- 1 | our Acceptance 2 | By using the "POPCORN TIME" app you signify your agreement to (1) these terms and conditions (the “Terms of Service”). 3 | 4 | Privacy Policy. 5 | You understand that by using "POPCORN TIME" you may encounter material that you may deem to be offensive, indecent, or objectionable, and that such content may or may not be identified as having explicit material. "POPCORN TIME" will have no liability to you for such material – you agree that your use of "POPCORN TIME" is at your sole risk. 6 | 7 | DISCLAIMERS 8 | YOU EXPRESSLY AGREE THAT YOUR USE OF "POPCORN TIME" IS AT YOUR SOLE RISK. "POPCORN TIME" AND ALL PRODUCTS ARE PROVIDED TO YOU “AS IS” WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. "POPCORN TIME" MAKES ABSOLUTELY NO WARRANTIES WHATSOEVER, EXPRESS OR IMPLIED. TO THE FULLEST EXTENT POSSIBLE UNDER APPLICABLE LAWS, YIFY DISCLAIMS ALL WARRANTIES, EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR OTHER VIOLATIONS OF RIGHTS. 9 | 10 | LIMITATION OF LIABILITY 11 | "POPCORN TIME" IS NOT RESPONSIBLE FOR ANY PROBLEMS OR TECHNICAL MALFUNCTION OF ANY WEBSITE, NETWORK, COMPUTER SYSTEMS, SERVERS, PROVIDERS, COMPUTER EQUIPMENT, OR SOFTWARE, OR FOR ANY FAILURE DUE TO TECHNICAL PROBLEMS OR TRAFFIC CONGESTION ON THE INTERNET OR "POPCORN TIME" OR COMBINATION THEREOF, INCLUDING ANY INJURY OR DAMAGE TO USERS OR TO ANY COMPUTER OR OTHER DEVICE ON OR THROUGH WHICH "POPCORN TIME" IS PROVIDED. UNDER NO CIRCUMSTANCES WILL "POPCORN TIME" BE LIABLE FOR ANY LOSS OR DAMAGE, INCLUDING PERSONAL INJURY OR DEATH, RESULTING FROM YOUR USE OF "POPCORN TIME". 12 | 13 | SOURCE MATERIAL 14 | ALL MOVIES ARE NOT HOSTED IN ANY SERVER AND ARE STREAMED USING THE P2P BIT TORRENT PROTOCOL. ALL MOVIES ARE PULLED IN FROM THE YIFY MOVIE DATABASE. BY WATCHING A MOVIE WITH THIS APPLICATION YOU MIGHT BE COMMITING COPYRIGHT VIOLATIONS DEPENDING ON YOUR COUNTRY'S LAWS. WE DO NOT TAKE ANY RESPONSIBILITIES. 15 | 16 | Ability to Accept Terms of Service 17 | By using "POPCORN TIME" you affirm that you are either more than 18 years of age, or an emancipated minor, or possess legal parental or guardian consent, and are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations, and warranties set forth in these Terms of Service, and to abide by and comply with these Terms of Service. In any case, you affirm that you are over the age of 13, as the Service is not intended for children under 13. If you are under 13 years of age, then please do not use the Service. There are lots of other great web sites for you. Talk to your parents about what sites are appropriate for you. 18 | -------------------------------------------------------------------------------- /dist/mac/resources/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Popcorn Time Installer 5 | 6 | 7 |

Installation

8 |

9 | You will be guided through the steps necessary to install this software. 10 |

11 | 12 |

Uninstallation

13 |

14 | All files and services may be uninstalled by first closing the client app 15 | and dragging the Popcorn-Time app from your applications to the trash. A 16 | restart may be required to finish the removal process. 17 |

18 | 19 | 20 | -------------------------------------------------------------------------------- /dist/mac/yoursway-create-dmg/builder/create-dmg.builder: -------------------------------------------------------------------------------- 1 | SET app_name create-dmg 2 | 3 | VERSION create-dmg.cur create-dmg heads/master 4 | 5 | NEWDIR build.dir temp %-build - 6 | 7 | NEWFILE create-dmg.zip featured %.zip % 8 | 9 | 10 | COPYTO [build.dir] 11 | INTO create-dmg [create-dmg.cur]/create-dmg 12 | INTO sample [create-dmg.cur]/sample 13 | INTO support [create-dmg.cur]/support 14 | 15 | SUBSTVARS [build.dir]/create-dmg [[]] 16 | 17 | 18 | ZIP [create-dmg.zip] 19 | INTO [build-files-prefix] [build.dir] 20 | 21 | 22 | PUT megabox-builds create-dmg.zip 23 | PUT megabox-builds build.log 24 | 25 | PUT s3-builds create-dmg.zip 26 | PUT s3-builds build.log 27 | -------------------------------------------------------------------------------- /dist/mac/yoursway-create-dmg/sample: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | test -f test2.dmg && rm test2.dmg 3 | ./create-dmg --window-size 500 300 --background ~/Projects/eclipse-osx-repackager/build/background.gif --icon-size 96 --volname "Hyper Foo" --app-drop-link 380 205 --icon "Eclipse OS X Repackager" 110 205 test2.dmg /Users/andreyvit/Projects/eclipse-osx-repackager/temp/Eclipse\ OS\ X\ Repackager\ r10/ 4 | -------------------------------------------------------------------------------- /dist/mac/yoursway-create-dmg/support/template.applescript: -------------------------------------------------------------------------------- 1 | on run (volumeName) 2 | tell application "Finder" 3 | tell disk (volumeName as string) 4 | open 5 | 6 | set theXOrigin to WINX 7 | set theYOrigin to WINY 8 | set theWidth to WINW 9 | set theHeight to WINH 10 | 11 | set theBottomRightX to (theXOrigin + theWidth) 12 | set theBottomRightY to (theYOrigin + theHeight) 13 | set dsStore to "\"" & "/Volumes/" & volumeName & "/" & ".DS_STORE\"" 14 | 15 | tell container window 16 | set current view to icon view 17 | set toolbar visible to false 18 | set statusbar visible to false 19 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY} 20 | set statusbar visible to false 21 | end tell 22 | 23 | set opts to the icon view options of container window 24 | tell opts 25 | set icon size to ICON_SIZE 26 | set arrangement to not arranged 27 | end tell 28 | BACKGROUND_CLAUSE 29 | 30 | -- Positioning 31 | POSITION_CLAUSE 32 | 33 | -- Hiding 34 | HIDING_CLAUSE 35 | 36 | -- Application Link Clause 37 | APPLICATION_CLAUSE 38 | close 39 | open 40 | 41 | update without registering applications 42 | -- Force saving of the size 43 | delay 1 44 | 45 | tell container window 46 | set statusbar visible to false 47 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10} 48 | end tell 49 | 50 | update without registering applications 51 | end tell 52 | 53 | delay 1 54 | 55 | tell disk (volumeName as string) 56 | tell container window 57 | set statusbar visible to false 58 | set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY} 59 | end tell 60 | 61 | update without registering applications 62 | end tell 63 | 64 | --give the finder some time to write the .DS_Store file 65 | delay 3 66 | 67 | set waitTime to 0 68 | set ejectMe to false 69 | repeat while ejectMe is false 70 | delay 1 71 | set waitTime to waitTime + 1 72 | 73 | if (do shell script "[ -f " & dsStore & " ]; echo $?") = "0" then set ejectMe to true 74 | end repeat 75 | log "waited " & waitTime & " seconds for .DS_STORE to be created." 76 | end tell 77 | end run 78 | -------------------------------------------------------------------------------- /dist/windows/installer-image.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/dist/windows/installer-image.bmp -------------------------------------------------------------------------------- /dist/windows/launcher.vbs: -------------------------------------------------------------------------------- 1 | 'create shell' 2 | Set WshShell = CreateObject("WScript.Shell") 3 | 4 | 'define variables' 5 | Dim arg, command, path, package, executable 6 | 7 | 'set argument if exists' 8 | If WScript.Arguments.Count > 0 Then 9 | arg = chr(34) & WScript.Arguments(0) & chr(34) 10 | Else 11 | arg = "" 12 | End If 13 | 14 | 'set executable' 15 | executable = "\nw.exe" 16 | 17 | 'set app path' 18 | path = WshShell.RegRead("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall\Butter\InstallString") 19 | 20 | 'set command' 21 | command = chr(34) & path & executable & chr(34) 22 | 23 | 'launch the app' 24 | WshShell.Exec( command & " " & arg) 25 | 26 | 'close shell' 27 | Set WshShell = Nothing -------------------------------------------------------------------------------- /dist/windows/uninstaller-image.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/dist/windows/uninstaller-image.bmp -------------------------------------------------------------------------------- /docs/Build-Debug.md: -------------------------------------------------------------------------------- 1 | placeholder -------------------------------------------------------------------------------- /docs/Code-Standards.md: -------------------------------------------------------------------------------- 1 | placeholder -------------------------------------------------------------------------------- /docs/Git-Workflow.md: -------------------------------------------------------------------------------- 1 | # Git Workflow 2 | 3 | ## Commit Messages 4 | 5 | 1. The **first line should always be 50 characters or less** and that it should be followed by a blank line. 6 | 7 | 2. Never use the `-m ` / `--message=` flag to git commit. 8 | 9 | It gives you a poor mindset right off the bat as you will feel that you have to fit your commit message into the terminal command, and makes the commit feel more like a one-off argument than a page in history: 10 | 11 | `git commit -m "Fix login bug"` 12 | 13 | A more useful commit message might be: 14 | 15 | ``` 16 | Redirect user to the requested page after login 17 | 18 | https://trello.com/path/to/relevant/card 19 | 20 | Users were being redirected to the home page after login, which is less 21 | useful than redirecting to the page they had originally requested before 22 | being redirected to the login form. 23 | 24 | * Store requested path in a session variable 25 | * Redirect to the stored location after successfully logging in the user 26 | ``` 27 | 28 | 3. A git commit should answer the following questions: 29 | 30 | * **Why is this change necessary?** 31 | 32 | This question tells reviewers of your pull request what to expect in the commit, allowing them to more easily identify and point out unrelated changes. 33 | 34 | * **How does it address the issue?** 35 | 36 | Describe, at a high level, what was done to affect change. `Introduce a red/black tree to increase search speed` or `Remove , which was causing ` are good examples. 37 | 38 | * **What side effects does this change have?** 39 | 40 | This is the most important question to answer, as it can point out problems where you are making too many changes in one commit or branch. One or two bullet points for related changes may be okay, but five or six are likely indicators of a commit that is doing too many things. 41 | 42 | ## Clean Up History 43 | 44 | `TODO` 45 | 46 | ## Check It Passes The Tests 47 | 48 | `TODO` 49 | 50 | *** 51 | 52 | Sources: 53 | 54 | * https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message 55 | -------------------------------------------------------------------------------- /docs/screenshots/b1e1dc8c-7b32-11e5-9c25-d9fbd5b2f3bd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/docs/screenshots/b1e1dc8c-7b32-11e5-9c25-d9fbd5b2f3bd.png -------------------------------------------------------------------------------- /i18n-unused.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | localesPath: 'src/app/language', 3 | srcPath: 'src/app', 4 | srcExtensions: ['js', 'tpl'], 5 | flatTranslations: true 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Popcorn-Time", 3 | "companyName": "Popcorn Time", 4 | "installIcon": "./src/app/images/popcorntime.ico", 5 | "unInstallIcon": "./src/app/images/butter_uninstall.ico", 6 | "homepage": "https://popcorn-time.site/", 7 | "bugs": "https://github.com/popcorn-official/popcorn-desktop/issues", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/popcorn-official/popcorn-desktop.git" 11 | }, 12 | "license": "GPL-3.0", 13 | "main": "src/app/index.html", 14 | "version": "0.5.1", 15 | "releaseName": "Now.. Bring me that Horizon", 16 | "scripts": { 17 | "build": "gulp build", 18 | "clean": "gulp clean", 19 | "css": "gulp css", 20 | "dist": "gulp dist", 21 | "start": "gulp run", 22 | "test": "gulp test", 23 | "postinstall": "patch-package" 24 | }, 25 | "chromium-args": "--enable-node-worker --no-sandbox", 26 | "engines": { 27 | "yarn": ">= 1.0.0" 28 | }, 29 | "window": { 30 | "title": "Popcorn Time", 31 | "icon": "src/app/images/icon.png", 32 | "frame": false, 33 | "toolbar": false, 34 | "min_width": 960, 35 | "min_height": 520, 36 | "resizable": true, 37 | "show": false, 38 | "position": "center" 39 | }, 40 | "providers": [ 41 | "butter-provider/movie.js", 42 | "butter-provider/anime.js", 43 | "butter-provider/tv.js", 44 | "butter-provider/yts.js" 45 | ], 46 | "dependencies": { 47 | "@fortawesome/fontawesome-free": "^6.4", 48 | "bittorrent-dht-sodium": "1.2.0", 49 | "adm-zip": "0.4.16", 50 | "airplayer": "2.0.0", 51 | "async": "3.2.2", 52 | "backbone": "^1.3.3", 53 | "backbone.babysitter": "^0.1.12", 54 | "backbone.marionette": "~3.x.x", 55 | "backbone.radio": "^2.0.0", 56 | "backbone.wreqr": "^1.4.0", 57 | "bootstrap": "^3.4.1", 58 | "butter-provider": "0.11.0", 59 | "butter-sanitize": "^0.1.1", 60 | "butter-settings-popcorntime.app": "0.0.10", 61 | "chromecast-api": "^0.4", 62 | "dayjs": "^1.11", 63 | "dlnacasts2": "0.2.0", 64 | "edit-json-file": "^1.7.0", 65 | "flag-icons": "^6.7", 66 | "i18n": "0.x.x", 67 | "iconv-lite": "0.x.x", 68 | "jquery": "^3.7", 69 | "jschardet": "1.6.0", 70 | "json-rpc2": "^2.0", 71 | "lodash": "^4.17.19", 72 | "memoizee": "0.x.x", 73 | "mkdirp": "*", 74 | "mousetrap": "~1.6.5", 75 | "mv": "2.x.x", 76 | "mime": "^3.0.0", 77 | "nedb-promises": "^5.0.3", 78 | "node-tvdb": "^4.1.0", 79 | "opensubtitles-api": "^5.1.2", 80 | "patch-package": "^8.0", 81 | "postinstall-postinstall": "^2.1.0", 82 | "readdirp": "2.x.x", 83 | "request": "2.88.x", 84 | "rimraf": "^3.0.0", 85 | "sanitizer": "0.x.x", 86 | "semver": "^7.5", 87 | "send": "^0.19.0", 88 | "socks-proxy-agent": "^6.2.1", 89 | "srt-to-vtt": "^1.1", 90 | "torrentcollection6": "^1.0", 91 | "trakt.tv": "7.x.x", 92 | "trakt.tv-images": "5.x.x", 93 | "trakt.tv-matcher": "7.x.x", 94 | "trakt.tv-ondeck": "7.x.x", 95 | "underscore": "^1.13.6", 96 | "urijs": "^1.19.11", 97 | "video.js": "4.11.4", 98 | "videojs-youtube": "1.2.10", 99 | "webtorrent": "^1.9.7", 100 | "webtorrent-health": "1.x.x" 101 | }, 102 | "devDependencies": { 103 | "del": "^6", 104 | "git-describe": "^4.1.1", 105 | "gulp": "^4.0.2", 106 | "gulp-gzip": "^1.4.2", 107 | "gulp-jsbeautifier": "^3.0", 108 | "gulp-jshint": "^2.1.0", 109 | "gulp-load-plugins": "^2.0", 110 | "gulp-rename": "^2.0.0", 111 | "gulp-stylus": "^3", 112 | "gulp-tar": "^4.0", 113 | "gulp-zip": "^4.2.0", 114 | "guppy-pre-commit": "^0.4.0", 115 | "i18n-unused": "^0.16", 116 | "jshint": "^2.13.6", 117 | "jshint-stylish": "^2.2.1", 118 | "nib": "^1.2.0", 119 | "nw-builder": "^3.7.4", 120 | "stylus": "^0.62", 121 | "yargs": "^17" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /patches/upnp-mediarenderer-client+1.4.0.patch: -------------------------------------------------------------------------------- 1 | https://github.com/thibauts/node-upnp-mediarenderer-client/pull/36 2 | Fix Samsung DLNA 3 | --- a/node_modules/upnp-mediarenderer-client/index.js 4 | +++ b/node_modules/upnp-mediarenderer-client/index.js 5 | @@ -144,7 +144,7 @@ MediaRendererClient.prototype.load = function(url, options, callback) { 6 | 7 | this.callAction('ConnectionManager', 'PrepareForConnection', params, function(err, result) { 8 | if(err) { 9 | - if(err.code !== 'ENOACTION') { 10 | + if( ! ['ENOACTION', 'EUPNP'].includes(err.code)) { 11 | return callback(err); 12 | } 13 | // 14 | -------------------------------------------------------------------------------- /patches/webtorrent+1.9.7.patch: -------------------------------------------------------------------------------- 1 | --- a/node_modules/webtorrent/lib/torrent.js 2 | +++ b/node_modules/webtorrent/lib/torrent.js 3 | @@ -1318,11 +1318,11 @@ 4 | _updateWireWrapper (wire) { 5 | const self = this 6 | 7 | - if (typeof window !== 'undefined' && typeof window.requestIdleCallback === 'function') { 8 | - window.requestIdleCallback(() => { self._updateWire(wire) }, { timeout: 250 }) 9 | - } else { 10 | + //if (typeof window !== 'undefined' && typeof window.requestIdleCallback === 'function') { 11 | + // window.requestIdleCallback(() => { self._updateWire(wire) }, { timeout: 250 }) 12 | + //} else { 13 | self._updateWire(wire) 14 | - } 15 | + //} 16 | } 17 | 18 | /** 19 | -------------------------------------------------------------------------------- /src/app/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 11, 3 | 4 | // Environments 5 | "browser" : true, 6 | "node" : true, 7 | "jquery" : true, 8 | "devel" : true, 9 | 10 | // Options 11 | "curly" : true, 12 | "bitwise" : false, 13 | "immed" : true, 14 | "latedef" : true, 15 | "nonbsp" : true, 16 | "nonew" : true, 17 | "validthis": true, 18 | "debug" : true, 19 | "boss" : true, 20 | "eqeqeq" : true, 21 | "expr" : true, 22 | "eqnull" : true, 23 | "quotmark" : "single", 24 | "sub" : true, 25 | "trailing" : true, 26 | "undef" : true, 27 | "laxbreak" : true, 28 | "loopfunc" : true, 29 | "indent" : 4, 30 | "newcap" : false, 31 | 32 | // Globals 33 | "globals": { 34 | // App 35 | "nw": true, 36 | "App": true, 37 | "Settings": true, 38 | "AdvSettings": true, 39 | "ScreenResolution": true, 40 | "Database": true, 41 | "detectLanguage": true, 42 | "Common": true, 43 | "startupTime": true, 44 | "deleteFolder": true, 45 | "deleteCookies": true, 46 | "isVideo": true, 47 | "minimizeToTray": true, 48 | "promisifyDb": true, 49 | "db": true, 50 | "Promise": true, 51 | 52 | // Global variables 53 | "async": true, 54 | "inherits": true, 55 | "Q": true, 56 | "os": true, 57 | "dayjs": true, 58 | "crypt": true, 59 | "semver": true, 60 | "fs": true, 61 | "path": true, 62 | "mkdirp": true, 63 | "rimraf": true, 64 | "tar": true, 65 | "AdmZip": true, 66 | "zlib": true, 67 | "charsetDetect": true, 68 | "iconv": true, 69 | "gui": true, 70 | "win": true, 71 | "data_path": true, 72 | "i18n": true, 73 | "url": true, 74 | "tls": true, 75 | "http": true, 76 | "request": true, 77 | "querystring": true, 78 | "URI": true, 79 | "child": true, 80 | "WebTorrent": true, 81 | "torrentCollection": true, 82 | "trakt": true, 83 | 84 | // Third party 85 | "Backbone": true, 86 | "Mousetrap": true, 87 | "_": true, 88 | "videojs": true, 89 | "vjs": true 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/app/bootstrap.js: -------------------------------------------------------------------------------- 1 | (function(App) { 2 | 'use strict'; 3 | App.start(); 4 | 5 | /* load all the things ! */ 6 | var fs = require('fs'); 7 | 8 | function loadLocalProviders() { 9 | var providerPath = './src/app/lib/providers/'; 10 | 11 | var files = fs.readdirSync(providerPath); 12 | 13 | var head = document.getElementsByTagName('head')[0]; 14 | return files 15 | .map(function(file) { 16 | if (!file.match(/\.js$/) || file.match(/generic.js$/)) { 17 | return null; 18 | } 19 | 20 | return new Promise((resolve, reject) => { 21 | var script = document.createElement('script'); 22 | 23 | script.type = 'text/javascript'; 24 | script.src = 'lib/providers/' + file; 25 | 26 | script.onload = function() { 27 | script.onload = null; 28 | win.info('Loaded local provider:', file); 29 | resolve(file); 30 | }; 31 | 32 | head.appendChild(script); 33 | }); 34 | }) 35 | .filter(function(q) { 36 | return q; 37 | }); 38 | } 39 | 40 | function loadFromNPM(name, fn) { 41 | const P = require(name); 42 | return Promise.resolve(fn(P)); 43 | } 44 | 45 | function loadProvidersJSON(fn) { 46 | return pkJson.providers.map(function(providerPath) { 47 | win.info('Loaded provider:', providerPath); 48 | return loadFromNPM(`./${providerPath}`, fn); 49 | }); 50 | } 51 | 52 | function loadFromPackageJSON(regex, fn) { 53 | var packages = Object.keys(pkJson.dependencies).filter(function(p) { 54 | return p.match(regex); 55 | }); 56 | 57 | return packages.map(function(name) { 58 | win.info('Loaded npm', regex, name); 59 | return loadFromNPM(name, fn); 60 | }); 61 | } 62 | 63 | function loadNpmProviders() { 64 | return loadProvidersJSON(App.Providers.install); 65 | } 66 | 67 | function loadLegacyNpmProviders() { 68 | return loadFromPackageJSON(/butter-provider-/, App.Providers.install); 69 | } 70 | 71 | function loadNpmSettings() { 72 | return Promise.all( 73 | loadFromPackageJSON(/butter-settings-/, function(settings) { 74 | Settings = _.extend(Settings, settings); 75 | }) 76 | ); 77 | } 78 | 79 | function loadProviders() { 80 | return Promise.all( 81 | loadLocalProviders() 82 | ); 83 | } 84 | 85 | function loadProvidersDelayed() { 86 | return Promise.all( 87 | loadNpmProviders().concat(loadLegacyNpmProviders()) 88 | ); 89 | } 90 | 91 | App.bootstrapPromise = loadNpmSettings() 92 | .then(loadProviders) 93 | .then(loadProvidersDelayed) 94 | .then(function(values) { 95 | return _.filter( 96 | _.keys(Settings.providers).map(function(type) { 97 | return { 98 | provider: App.Config.getProviderForType(type), 99 | type: type 100 | }; 101 | }), 102 | function(p) { 103 | return p.provider; 104 | } 105 | ); 106 | }) 107 | .then(function(providers) { 108 | App.TabTypes = {}; 109 | 110 | _.each(providers, function(provider) { 111 | var p = Settings.providers[provider.type]; 112 | if (!p.name) { 113 | return; 114 | } 115 | 116 | App.TabTypes[provider.type] = p.name; 117 | }); 118 | 119 | return providers; 120 | }); 121 | })(window.App); 122 | -------------------------------------------------------------------------------- /src/app/butter-provider/anime.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const TVApi = require('./tv'); 4 | const sanitize = require('butter-sanitize'); 5 | const i18n = require('i18n'); 6 | 7 | class AnimeApi extends TVApi { 8 | 9 | fetch(filters) { 10 | const params = { 11 | sort: 'seeds', 12 | limit: '50', 13 | anime: 1 14 | }; 15 | 16 | params.locale = this.language; 17 | params.contentLocale = this.contentLanguage; 18 | if (!this.contentLangOnly) { 19 | params.showAll = 1; 20 | } 21 | 22 | if (filters.keywords) { 23 | params.keywords = filters.keywords.trim(); 24 | } 25 | if (filters.order) { 26 | params.order = filters.order; 27 | } 28 | if (filters.sorter && filters.sorter !== 'popularity') { 29 | params.sort = filters.sorter; 30 | } 31 | 32 | const uri = `shows/${filters.page}?` + new URLSearchParams(params); 33 | return this._get(0, uri).then(data => { 34 | data.forEach(entry => {entry.type = 'show'; entry.title = entry.slug.replace(/-/g, ' ').capitalizeEach();}); 35 | 36 | return { 37 | results: sanitize(data), 38 | hasMore: true 39 | }; 40 | }); 41 | } 42 | 43 | formatFiltersFromServer(sorters, data) 44 | { 45 | let filters = { 46 | genres: {}, 47 | sorters: {}, 48 | }; 49 | for (const genre of sorters) { 50 | filters.sorters[genre] = i18n.__(genre.capitalizeEach()); 51 | } 52 | 53 | filters.genres = { 54 | 'All': 'Anime', 55 | }; 56 | 57 | return filters; 58 | } 59 | 60 | filters() { 61 | const params = { 62 | contentLocale: this.contentLanguage, 63 | }; 64 | if (!this.contentLangOnly) { 65 | params.showAll = 1; 66 | } 67 | return this._get(0, 'shows/stat?' + new URLSearchParams(params)) 68 | .then((result) => this.formatFiltersFromServer( 69 | ['trending', 'popularity', 'updated', 'year', 'name', 'rating'], 70 | result 71 | )).catch(() => { 72 | const data = { 73 | sorters: ['trending', 'popularity', 'updated', 'year', 'name', 'rating'], 74 | }; 75 | let filters = { 76 | genres: {}, 77 | }; 78 | for (const sorter of data.sorters) { 79 | filters.sorters[sorter] = i18n.__(sorter.capitalizeEach()); 80 | } 81 | 82 | return Promise.resolve(filters); 83 | }); 84 | } 85 | } 86 | 87 | AnimeApi.prototype.config = { 88 | name: 'AnimeApi', 89 | uniqueId: 'tvdb_id', 90 | tabName: 'Anime', 91 | type: 'anime', 92 | metadata: 'trakttv:show-metadata' 93 | }; 94 | 95 | module.exports = AnimeApi; 96 | -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Bold.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/OpenSans-Bold.svg -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/OpenSans-Bold.woff -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Regular.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/OpenSans-Regular.svg -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/OpenSans-Regular.woff -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Semibold.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/OpenSans-Semibold.svg -------------------------------------------------------------------------------- /src/app/fonts/OpenSans-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/OpenSans-Semibold.woff -------------------------------------------------------------------------------- /src/app/fonts/vjs.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/vjs.eot -------------------------------------------------------------------------------- /src/app/fonts/vjs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/vjs.ttf -------------------------------------------------------------------------------- /src/app/fonts/vjs.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/fonts/vjs.woff -------------------------------------------------------------------------------- /src/app/global.js: -------------------------------------------------------------------------------- 1 | /** Global variables **/ 2 | var _ = require('underscore'), 3 | async = require('async'), 4 | inherits = require('util').inherits, 5 | // Machine readable 6 | os = require('os'), 7 | dayjs = require('dayjs'), 8 | crypt = require('crypto'), 9 | semver = require('semver'), 10 | // Files 11 | fs = require('fs'), 12 | path = require('path'), 13 | mkdirp = require('mkdirp'), 14 | rimraf = require('rimraf'), 15 | jsonFileEditor = require('edit-json-file'), 16 | // Compression 17 | AdmZip = require('adm-zip'), 18 | zlib = require('zlib'), 19 | // Encoding/Decoding 20 | charsetDetect = require('jschardet'), 21 | iconv = require('iconv-lite'), 22 | // GUI 23 | win = nw.Window.get(), 24 | data_path = nw.App.dataPath, 25 | i18n = require('i18n'), 26 | // Connectivity 27 | url = require('url'), 28 | tls = require('tls'), 29 | http = require('http'), 30 | request = require('request'), 31 | // Web 32 | URI = require('urijs'), 33 | Trakt = require('trakt.tv'), 34 | // Torrent engines 35 | WebTorrent = require('webtorrent'), 36 | torrentCollection = require('torrentcollection6'), 37 | // NodeJS 38 | child = require('child_process'), 39 | // package.json 40 | pkJson = nw.App.manifest, 41 | // supported external players list 42 | extPlayerlst = '', 43 | // setting default filters status 44 | curSetDefaultFilters = false; 45 | 46 | dayjs.extend(require('dayjs/plugin/relativeTime')); 47 | dayjs.extend(require('dayjs/plugin/localizedFormat')); 48 | -------------------------------------------------------------------------------- /src/app/images/bg-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/bg-header.jpg -------------------------------------------------------------------------------- /src/app/images/butter-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | BUTTER 4 | 5 | 6 | Project 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/images/butter.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/butter.icns -------------------------------------------------------------------------------- /src/app/images/butter.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/butter.ico -------------------------------------------------------------------------------- /src/app/images/butter_uninstall.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/butter_uninstall.ico -------------------------------------------------------------------------------- /src/app/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icon.png -------------------------------------------------------------------------------- /src/app/images/icons/Eztv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/Eztv.png -------------------------------------------------------------------------------- /src/app/images/icons/NnmClub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/NnmClub.png -------------------------------------------------------------------------------- /src/app/images/icons/Rutor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/Rutor.png -------------------------------------------------------------------------------- /src/app/images/icons/Rutracker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/Rutracker.png -------------------------------------------------------------------------------- /src/app/images/icons/T1337x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/T1337x.png -------------------------------------------------------------------------------- /src/app/images/icons/TorrentGalaxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/TorrentGalaxy.png -------------------------------------------------------------------------------- /src/app/images/icons/Yts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/Yts.png -------------------------------------------------------------------------------- /src/app/images/icons/airplay-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/airplay-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/airplay-xbmc-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/airplay-xbmc-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/chromecast-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/chromecast-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/dlna-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/dlna-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/events/aprilsfool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/events/aprilsfool.png -------------------------------------------------------------------------------- /src/app/images/icons/events/halloween.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/events/halloween.png -------------------------------------------------------------------------------- /src/app/images/icons/events/newyear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/events/newyear.png -------------------------------------------------------------------------------- /src/app/images/icons/events/pt_anniv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/events/pt_anniv.png -------------------------------------------------------------------------------- /src/app/images/icons/events/stpatrick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/events/stpatrick.png -------------------------------------------------------------------------------- /src/app/images/icons/events/stvalentine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/events/stvalentine.png -------------------------------------------------------------------------------- /src/app/images/icons/events/xmas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/events/xmas.png -------------------------------------------------------------------------------- /src/app/images/icons/external-bomi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-bomi-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-bsplayer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-bsplayer-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-fleex-player-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-fleex-player-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-iina-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-iina-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-mpc-be-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-mpc-be-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-mpc-hc-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-mpc-hc-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-mplayer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-mplayer-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-mpv-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-mpv-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-mpvnet-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-mpvnet-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-potplayer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-potplayer-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-smplayer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-smplayer-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/external-vlc-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/external-vlc-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/flag-none.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | -------------------------------------------------------------------------------- /src/app/images/icons/flag-question.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 29 | -------------------------------------------------------------------------------- /src/app/images/icons/icon-blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/icon-blog.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-ci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/icon-ci.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-discourse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/icon-discourse.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/icon-github.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-popcorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/icon-popcorn.png -------------------------------------------------------------------------------- /src/app/images/icons/icon-reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/icon-reddit.png -------------------------------------------------------------------------------- /src/app/images/icons/imdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/imdb.png -------------------------------------------------------------------------------- /src/app/images/icons/local-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/local-icon.png -------------------------------------------------------------------------------- /src/app/images/icons/nyaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/nyaa.png -------------------------------------------------------------------------------- /src/app/images/icons/player/Sound0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/player/Sound0.png -------------------------------------------------------------------------------- /src/app/images/icons/player/Sound1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/player/Sound1.png -------------------------------------------------------------------------------- /src/app/images/icons/player/Sound2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/player/Sound2.png -------------------------------------------------------------------------------- /src/app/images/icons/player/Sound3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/player/Sound3.png -------------------------------------------------------------------------------- /src/app/images/icons/player/Subtitles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/player/Subtitles.png -------------------------------------------------------------------------------- /src/app/images/icons/rarbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/rarbg.png -------------------------------------------------------------------------------- /src/app/images/icons/solidtorrents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/solidtorrents.png -------------------------------------------------------------------------------- /src/app/images/icons/topbar_sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/topbar_sprite.png -------------------------------------------------------------------------------- /src/app/images/icons/topbar_sprite_win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/topbar_sprite_win.png -------------------------------------------------------------------------------- /src/app/images/icons/tpb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/icons/tpb.png -------------------------------------------------------------------------------- /src/app/images/popcorntime.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/popcorntime.icns -------------------------------------------------------------------------------- /src/app/images/popcorntime.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/popcorntime.ico -------------------------------------------------------------------------------- /src/app/images/posterholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcorn-official/popcorn-desktop/61af271d70848542e656f0dc27b5bb251b899a06/src/app/images/posterholder.png -------------------------------------------------------------------------------- /src/app/language/.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | lang_map = af_ZA: af-ZA, am_ET: am-ET, ar_AE: ar-AE, ar_BH: ar-BH, ar_DZ: ar-DZ, ar_EG: ar-EG, ar_IQ: ar-IQ, ar_JO: ar-JO, ar_KW: ar-KW, ar_LB: ar-LB, ar_LY: ar-LY, ar_MA: ar-MA, ar_OM: ar-OM, ar_QA: ar-QA, ar_SA: ar-SA, ar_SY: ar-SY, ar_TN: ar-TN, ar_YE: ar-YE, arn_CL: arn-CL, as_IN: as-IN, az_AZ: az-AZ, ba_RU: ba-RU, be_BY: be-BY, bg_BG: bg-BG, bn_BD: bn-BD, bn_IN: bn-IN, bo_CN: bo-CN, br_FR: br-FR, bs_BA: bs-BA, ca_ES: ca-ES, co_FR: co-FR, cs_CZ: cs-CZ, cy_GB: cy-GB, da_DK: da-DK, de_AT: de-AT, de_CH: de-CH, de_DE: de-DE, de_LI: de-LI, de_LU: de-LU, dsb_DE: dsb-DE, dv_MV: dv-MV, el_GR: el-GR, en_AU: en-AU, en_BZ: en-BZ, en_CA: en-CA, en_GB: en-gb, en_IE: en-IE, en_IN: en-IN, en_JM: en-JM, en_MY: en-MY, en_NZ: en-NZ, en_PH: en-PH, en_SG: en-SG, en_TT: en-TT, en_US: en-US, en_ZA: en-ZA, en_ZW: en-ZW, es_AR: es-ar, es_BO: es-BO, es_CL: es-CL, es_CO: es-CO, es_CR: es-CR, es_DO: es-DO, es_EC: es-EC, es_ES: es-ES, es_GT: es-GT, es_HN: es-HN, es_MX: es-mx, es_NI: es-NI, es_PA: es-PA, es_PE: es-PE, es_PR: es-PR, es_PY: es-PY, es_SV: es-SV, es_US: es-US, es_UY: es-UY, es_VE: es-VE, et_EE: et-EE, eu_ES: eu-ES, fa_IR: fa-IR, fi_FI: fi-FI, fil_PH: fil-PH, fo_FO: fo-FO, fr_BE: fr-be, fr_CA: fr-CA, fr_CH: fr-CH, fr_FR: fr-FR, fr_LU: fr-LU, fr_MC: fr-MC, fy_NL: fy-NL, ga_IE: ga-IE, gd_GB: gd-GB, gl_ES: gl-ES, gsw_FR: gsw-FR, gu_IN: gu-IN, ha_NG: ha-NG, he_IL: he-IL, hi_IN: hi-IN, hr_BA: hr-BA, hr_HR: hr-HR, hsb_DE: hsb-DE, hu_HU: hu-HU, hy_AM: hy-AM, id_ID: id-ID, ig_NG: ig-NG, ii_CN: ii-CN, is_IS: is-IS, it_CH: it-CH, it_IT: it-IT, iu_CA: iu-CA, ja_JP: ja-JP, ka_GE: ka-GE, kk_KZ: kk-KZ, kl_GL: kl-GL, km_KH: km-KH, kn_IN: kn-IN, ko_KR: ko-KR, kok_IN: kok-IN, ku_IQ: ku-iq, ky_KG: ky-KG, lb_LU: lb-LU, lo_LA: lo-LA, lt_LT: lt-LT, lv_LV: lv-LV, mi_NZ: mi-NZ, mk_MK: mk-MK, ml_IN: ml-IN, mn_CN: mn-CN, mn_MN: mn-MN, moh_CA: moh-CA, mr_IN: mr-IN, ms_BN: ms-BN, ms_MY: ms-MY, mt_MT: mt-MT, nb_NO: nb-NO, ne_NP: ne-NP, nl_BE: nl-BE, nl_NL: nl-NL, nn_NO: nn-NO, nso_ZA: nso-ZA, oc_FR: oc-FR, or_IN: or-IN, pa_IN: pa-IN, pl_PL: pl-PL, prs_AF: prs-AF, ps_AF: ps-AF, pt_BR: pt-br, pt_PT: pt-PT, qut_GT: qut-GT, quz_BO: quz-BO, quz_EC: quz-EC, quz_PE: quz-PE, rm_CH: rm-CH, ro_RO: ro-RO, ru_RU: ru-RU, rw_RW: rw-RW, sa_IN: sa-IN, sah_RU: sah-RU, se_FI: se-FI, se_NO: se-NO, se_SE: se-SE, si_LK: si-LK, sk_SK: sk-SK, sl_SI: sl-SI, sma_NO: sma-NO, sma_SE: sma-SE, smj_NO: smj-NO, smj_SE: smj-SE, smn_FI: smn-FI, sms_FI: sms-FI, sq_AL: sq-AL, sr_BA: sr-BA, sr_CS: sr-CS, sr_ME: sr-ME, sr_RS: sr-RS, sv_FI: sv-FI, sv_SE: sv-SE, sw_KE: sw-KE, syr_SY: syr-SY, ta_IN: ta-IN, te_IN: te-IN, tg_TJ: tg-TJ, th_TH: th-TH, tk_TM: tk-TM, tn_ZA: tn-ZA, tr_TR: tr-TR, tt_RU: tt-RU, tzm_DZ: tzm-DZ, ug_CN: ug-CN, uk_UA: uk-UA, ur_PK: ur-PK, uz_UZ: uz-UZ, vi_VN: vi-VN, wo_SN: wo-SN, xh_ZA: xh-ZA, yo_NG: yo-NG, zh_CN: zh-cn, zh_HK: zh-HK, zh_MO: zh-MO, zh_SG: zh-SG, zh_TW: zh-tw, zu_ZA: zu-ZA 4 | 5 | [popcorn-time-app.desktop] 6 | file_filter = .json 7 | source_lang = en 8 | source_file = en.json 9 | type = KEYVALUEJSON 10 | minimum_perc = 80 11 | -------------------------------------------------------------------------------- /src/app/lib/device/airplay.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var airplayer = require('airplayer'), 5 | netw = require('network-address'), 6 | collection = App.Device.Collection; 7 | 8 | class Airplay extends App.Device.Loaders.Device { 9 | constructor(attrs) { 10 | super(Object.assign( { 11 | type: 'airplay', 12 | typeFamily: 'external' 13 | }, attrs)); 14 | } 15 | makeID(baseID) { 16 | return 'airplay-' + baseID.replace('.', ''); 17 | } 18 | initialize(attrs) { 19 | this.device = attrs.device; 20 | this.attributes.id = this.makeID(this.device.host); 21 | this.attributes.name = this.device.name || this.device.serverInfo.model; 22 | this.attributes.address = netw(); 23 | } 24 | play(streamModel) { 25 | var url = streamModel.attributes.src; 26 | this.device.play(url, function (err, res) { 27 | if (err) { 28 | throw err; 29 | } 30 | }); 31 | 32 | } 33 | stop() { 34 | this.device.destroy(); 35 | } 36 | pause() { 37 | this.device.pause(); 38 | } 39 | unpause() { 40 | this.device.resume(); 41 | } 42 | 43 | static scan() { 44 | airplayer().on('update', function (player) { 45 | win.info('Found A Device Device: %s at %s', player.name, player.host); 46 | collection.add(new Airplay({ 47 | device: player 48 | })); 49 | }); 50 | 51 | win.info('Scanning: Local Network for Airplay devices'); 52 | } 53 | } 54 | 55 | App.Device.Loaders.Airplay = Airplay; 56 | 57 | })(window.App); 58 | -------------------------------------------------------------------------------- /src/app/lib/device/xbmc.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var collection = App.Device.Collection; 5 | 6 | class AirplayXBMC extends App.Device.Loaders.Airplay { 7 | constructor(attrs) { 8 | super(Object.assign( { 9 | type: 'airplay-xbmc', 10 | typeFamily: 'external' 11 | }, attrs)); 12 | } 13 | static makeID(baseID) { 14 | return 'airplay-xbmc' + baseID.replace(':', ''); 15 | } 16 | initialize(attrs) { 17 | this.device = attrs.device; 18 | this.attributes.address = this.device.info[0]; 19 | } 20 | 21 | static scan() { 22 | var browser = require('airplay-xbmc').createBrowser(); 23 | browser.on('deviceOn', function (device) { 24 | collection.add(new AirplayXBMC({ 25 | device: device 26 | })); 27 | }); 28 | 29 | browser.on('deviceOff', function (device) { 30 | var model = collection.get({ 31 | id: self.makeID(device.id) 32 | }); 33 | if (model) { 34 | model.destroy(); 35 | } 36 | }); 37 | 38 | browser.start(); 39 | } 40 | } 41 | 42 | App.Device.Loaders.AirplayXBMC = AirplayXBMC; 43 | })(window.App); 44 | -------------------------------------------------------------------------------- /src/app/lib/models/anime_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var AnimeCollection = App.Model.Collection.extend({ 5 | model: App.Model.Movie, 6 | popid: 'mal_id', 7 | type: 'animes', 8 | getProviders: function () { 9 | return { 10 | torrents: App.Config.getProviderForType('anime') 11 | }; 12 | }, 13 | }); 14 | 15 | App.Model.AnimeCollection = AnimeCollection; 16 | })(window.App); 17 | -------------------------------------------------------------------------------- /src/app/lib/models/content_item.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var ContentItem = Backbone.Model.extend({ 5 | events: { 6 | 'change:torrents': 'updateHealth' 7 | }, 8 | 9 | idAttribute: 'imdb_id', 10 | 11 | initialize: function (attrs) { 12 | var providers = Object.assign(attrs.providers, 13 | this.getProviders()); 14 | this.set('providers', providers); 15 | 16 | providers.metadata && 17 | providers.metadata.getImages(attrs) 18 | .then(this.set.bind(this)) 19 | .catch(e => win.error('Error loading metadata', e)); 20 | 21 | this.updateHealth(); 22 | this.on('change:torrents', this.updateHealth.bind(this)); 23 | }, 24 | 25 | getProviders: function() { 26 | return {}; 27 | }, 28 | 29 | updateHealth: function () { 30 | var torrents = this.get('torrents'); 31 | 32 | if (!torrents) { 33 | return false; 34 | } 35 | 36 | _.each(torrents, function (torrent) { 37 | torrent.health = Common.healthMap[Common.calcHealth(torrent)]; 38 | }); 39 | 40 | this.set('torrents', torrents, { 41 | silent: true 42 | }); 43 | } 44 | }); 45 | 46 | App.Model.ContentItem = ContentItem; 47 | })(window.App); 48 | -------------------------------------------------------------------------------- /src/app/lib/models/favorite_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var FavoriteCollection = Backbone.Collection.extend({ 5 | model: App.Model.Movie, 6 | 7 | initialize: function (models, options) { 8 | this.providers = { 9 | torrent: App.Providers.get('Favorites') 10 | }; 11 | 12 | options = options || {}; 13 | options.filter = options.filter || new App.Model.Filter(); 14 | 15 | this.filter = _.defaults(_.clone(options.filter.attributes), { 16 | page: 1 17 | }); 18 | this.hasMore = true; 19 | 20 | Backbone.Collection.prototype.initialize.apply(this, arguments); 21 | }, 22 | 23 | fetch: function () { 24 | var self = this; 25 | 26 | if (this.state === 'loading' && !this.hasMore) { 27 | return; 28 | } 29 | 30 | this.state = 'loading'; 31 | self.trigger('loading', self); 32 | 33 | var torrent = this.providers.torrent; 34 | var torrentPromise = torrent.fetch(this.filter); 35 | 36 | return Promise.all([torrentPromise]) 37 | .then(function (movies) { 38 | movies = movies.flat(); 39 | 40 | // If a new request was started... 41 | _.each(movies, function (movie) { 42 | var id = movie.imdb_id; 43 | }); 44 | 45 | if (_.isEmpty(movies)) { 46 | self.hasMore = false; 47 | } 48 | 49 | self.add(movies); 50 | self.trigger('sync', self); 51 | self.state = 'loaded'; 52 | self.trigger('loaded', self, self.state); 53 | }) 54 | .catch(function (err) { 55 | self.state = 'error'; 56 | self.trigger('loaded', self, self.state); 57 | win.error('FavoriteCollection.fetch()', err); 58 | }); 59 | }, 60 | 61 | fetchMore: function () { 62 | this.filter.page += 1; 63 | this.fetch(); 64 | } 65 | 66 | }); 67 | 68 | App.Model.FavoriteCollection = FavoriteCollection; 69 | })(window.App); 70 | -------------------------------------------------------------------------------- /src/app/lib/models/filter.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Filter = Backbone.Model.extend({ 5 | 6 | initialize: function () { 7 | this.set('load', false); 8 | this.set('genres', []); 9 | this.set('sorters', []); 10 | this.set('kinds', []); 11 | this.set('types', []); 12 | this.set('ratings', []); 13 | this.init(); 14 | 15 | this.get('provider').filters().then((filters) => { 16 | this.set('genres', filters.genres || []); 17 | this.set('sorters', filters.sorters || []); 18 | this.set('kinds', filters.kinds || []); 19 | this.set('types', filters.types || []); 20 | this.set('ratings', filters.ratings || []); 21 | 22 | this.init(); 23 | this.set('load', true); 24 | App.vent.trigger('filter-bar:render'); 25 | }); 26 | }, 27 | 28 | init() { 29 | this.set('sorter', this.get('sorter') || Object.keys(this.get('sorters'))[0]); 30 | this.set('genre', this.get('genre') || Object.keys(this.get('genres'))[0]); 31 | this.set('kind', this.get('kind') || Object.keys(this.get('kinds'))[0]); 32 | this.set('type', this.get('type') || Object.keys(this.get('types'))[0]); 33 | this.set('order', this.get('order') || -1); 34 | this.set('rating', this.get('rating') || Object.keys(this.get('ratings'))[0]); 35 | } 36 | }); 37 | 38 | App.Model.Filter = Filter; 39 | })(window.App); 40 | -------------------------------------------------------------------------------- /src/app/lib/models/generic_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var getDataFromProvider = function (providers, collection) { 5 | var filters = Object.assign(collection.filter, {page: providers.torrent.page}); 6 | return providers.torrent.fetch(filters) 7 | .then(function (torrents) { 8 | // If a new request was started... 9 | _.each(torrents.results, function (movie) { 10 | var id = movie[collection.popid]; 11 | /* XXX(xaiki): check if we already have this 12 | * torrent if we do merge our torrents with the 13 | * ones we already have and update. 14 | */ 15 | var model = collection.get(id); 16 | if (model) { 17 | var ts = model.get('torrents'); 18 | _.extend(ts, movie.torrents); 19 | model.set('torrents', ts); 20 | 21 | return; 22 | } 23 | 24 | movie.providers = providers; 25 | }); 26 | 27 | return torrents; 28 | }) 29 | .catch(function (err) { 30 | collection.state = 'error'; 31 | collection.trigger('loaded', collection, collection.state); 32 | }); 33 | }; 34 | 35 | var PopCollection = Backbone.Collection.extend({ 36 | popid: 'imdb_id', 37 | initialize: function (models, options) { 38 | this.providers = this.getProviders(); 39 | 40 | //XXX(xaiki): this is a bit of hack 41 | this.providers.torrents.forEach(t => { 42 | t.hasMore = true; 43 | t.page = 1; 44 | }); 45 | 46 | options = options || {}; 47 | options.filter = options.filter || new App.Model.Filter(); 48 | 49 | this.filter = _.clone(options.filter.attributes); 50 | this.hasMore = true; 51 | 52 | Backbone.Collection.prototype.initialize.apply(this, arguments); 53 | }, 54 | 55 | fetch: function () { 56 | var self = this; 57 | 58 | if (this.state === 'loading' && !this.hasMore) { 59 | return; 60 | } 61 | 62 | this.state = 'loading'; 63 | self.trigger('loading', self); 64 | 65 | var metadata = this.providers.metadata; 66 | var torrents = this.providers.torrents; 67 | 68 | var torrentPromises = torrents.filter(torrentProvider => ( 69 | !torrentProvider.loading && torrentProvider.hasMore 70 | )).map((torrentProvider) => { 71 | var providers = { 72 | torrent: torrentProvider, 73 | metadata: metadata 74 | }; 75 | 76 | torrentProvider.loading = true; 77 | return getDataFromProvider(providers, self) 78 | .then(torrentProvider.loading = false) 79 | .then(function (torrents) { 80 | // set state, can't fail 81 | if (torrents.results.length !== 0) { 82 | torrentProvider.page++; 83 | } else { 84 | torrentProvider.hasMore = false; 85 | } 86 | 87 | self.add(torrents.results); 88 | 89 | // set state, can't fail 90 | self.trigger('sync', self); 91 | self.state = 'loaded'; 92 | self.trigger('loaded', self, self.state); 93 | }) 94 | .catch(function (err) { 95 | win.error('provider error err', err); 96 | }); 97 | }); 98 | }, 99 | 100 | fetchMore: function () { 101 | this.fetch(); 102 | } 103 | }); 104 | 105 | App.Model.Collection = PopCollection; 106 | })(window.App); 107 | -------------------------------------------------------------------------------- /src/app/lib/models/lang.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | App.Model.Lang = Backbone.Model.extend({ 5 | defaults: { 6 | hasNull: false, 7 | selected: undefined 8 | } 9 | }); 10 | })(window.App); 11 | -------------------------------------------------------------------------------- /src/app/lib/models/movie.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Movie = Backbone.Model.extend({ 5 | events: { 6 | 'change:torrents': 'updateHealth', 7 | }, 8 | 9 | idAttribute: 'imdb_id', 10 | 11 | initialize: function () { 12 | this.updateHealth(); 13 | }, 14 | 15 | updateHealth: function () { 16 | var torrents = this.get('torrents'); 17 | 18 | _.each(torrents, function (torrent) { 19 | torrent.health = Common.healthMap[Common.calcHealth(torrent)]; 20 | }); 21 | 22 | this.set('torrents', torrents, { 23 | silent: true 24 | }); 25 | } 26 | }); 27 | 28 | App.Model.Movie = Movie; 29 | })(window.App); 30 | -------------------------------------------------------------------------------- /src/app/lib/models/movie_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var MovieCollection = App.Model.Collection.extend({ 5 | model: App.Model.Movie, 6 | popid: 'imdb_id', 7 | type: 'movies', 8 | getProviders: function () { 9 | return { 10 | torrents: App.Config.getProviderForType('movie'), 11 | metadata: App.Config.getProviderForType('metadata') 12 | }; 13 | } 14 | }); 15 | 16 | App.Model.MovieCollection = MovieCollection; 17 | })(window.App); 18 | -------------------------------------------------------------------------------- /src/app/lib/models/notification.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Notification = Backbone.Model.extend({ 5 | defaults: { 6 | body: 'Notification Body', 7 | buttons: [], 8 | showClose: true, 9 | showRestart: false, 10 | title: 'Notification Title', 11 | type: 'info' 12 | }, 13 | 14 | // Added 'showRestart' option here since we'll use it more than once. 15 | initialize: function () { 16 | this.toggleShowRestart(); 17 | // If this property is changed after init, add/remove the button. 18 | this.on('change:showRestart', this.toggleShowRestart.bind(this)); 19 | }, 20 | 21 | addShowRestart: function () { 22 | this.set('buttons', this.get('buttons').concat([{ 23 | title: i18n.__('Restart'), 24 | action: this.restartButter 25 | }])); 26 | }, 27 | 28 | removeShowRestart: function () { 29 | this.set('buttons', this.get('buttons').filter(function (b) { 30 | return b.title !== i18n.__('Restart'); 31 | })); 32 | }, 33 | 34 | toggleShowRestart: function () { 35 | this[(this.get('showRestart') ? 'add' : 'remove') + 'ShowRestart'](); 36 | }, 37 | 38 | restartButter: function () { 39 | App.vent.trigger('restartButter'); 40 | } 41 | }); 42 | 43 | App.Model.Notification = Notification; 44 | })(window.App); 45 | -------------------------------------------------------------------------------- /src/app/lib/models/show.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Show = App.Model.Movie.extend({ 5 | idAttribute: 'tvdb_id', 6 | updateHealth: function () { 7 | var torrents = this.get('torrents'); 8 | 9 | _.each(torrents, function (torrent) { 10 | _.each(torrent, function (episode, key) { 11 | torrent[key].health = Common.healthMap[Common.calcHealth(episode)]; 12 | }); 13 | }); 14 | 15 | this.set('torrents', torrents, { 16 | silent: true 17 | }); 18 | } 19 | }); 20 | 21 | App.Model.Show = Show; 22 | })(window.App); 23 | -------------------------------------------------------------------------------- /src/app/lib/models/show_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var ShowCollection = App.Model.Collection.extend({ 5 | model: App.Model.Movie, 6 | popid: 'imdb_id', 7 | type: 'shows', 8 | getProviders: function () { 9 | return { 10 | torrents: App.Config.getProviderForType('tvshow') 11 | }; 12 | }, 13 | }); 14 | 15 | App.Model.ShowCollection = ShowCollection; 16 | })(window.App); 17 | -------------------------------------------------------------------------------- /src/app/lib/models/stream_info.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var StreamInfo = Backbone.Model.extend({ 5 | updateInfos: function () { 6 | var torrentModel = this.get('torrentModel'); 7 | 8 | this.set({ 9 | title: torrentModel.get('title'), 10 | filename: torrentModel.get('video_file').name, 11 | device: torrentModel.get('device'), 12 | quality: torrentModel.get('quality'), 13 | defaultSubtitle: torrentModel.get('defaultSubtitle'), 14 | subtitle: torrentModel.get('subtitle'), 15 | videoFile: torrentModel.get('video_file').path, 16 | size: torrentModel.get('video_file').size, 17 | poster: torrentModel.get('poster'), 18 | backdrop: torrentModel.get('backdrop'), 19 | tvdb_id: torrentModel.get('tvdb_id'), 20 | imdb_id: torrentModel.get('imdb_id'), 21 | episode_id: torrentModel.get('episode_id'), 22 | episode: torrentModel.get('episode'), 23 | season: torrentModel.get('season') 24 | }); 25 | }, 26 | updateStats: function () { 27 | var torrentModel = this.get('torrentModel'), 28 | torrent = torrentModel.get('torrent'); 29 | 30 | var converted_speed = 0; 31 | var converted_downloaded = 0; 32 | 33 | var upload_speed = torrent.uploadSpeed; // upload speed 34 | var final_upload_speed = Common.fileSize(0) + '/s'; 35 | if (!isNaN(upload_speed) && upload_speed !== 0) { 36 | final_upload_speed = Common.fileSize(upload_speed) + '/s'; 37 | } 38 | 39 | var download_speed = torrent.downloadSpeed; // download speed 40 | var final_download_speed = Common.fileSize(0) + '/s'; 41 | if (!isNaN(download_speed) && download_speed !== 0) { 42 | final_download_speed = Common.fileSize(download_speed) + '/s'; 43 | } 44 | 45 | var downloaded = torrent.files[torrentModel.get('video_file').index] ? torrent.files[torrentModel.get('video_file').index].downloaded || 0 : 0; // downloaded 46 | 47 | var final_downloaded = Common.fileSize(0); 48 | var final_downloaded_percent = 0; 49 | if (downloaded !== 0) { 50 | final_downloaded = Common.fileSize(downloaded); 51 | final_downloaded_percent = 100 / this.get('size') * downloaded; 52 | } 53 | 54 | if (final_downloaded_percent >= 100) { 55 | final_downloaded_percent = 100; 56 | } 57 | 58 | var downloadTimeLeft = Math.round((this.get('size') - downloaded) / torrent.downloadSpeed); // time to wait before download complete 59 | if (isNaN(downloadTimeLeft) || downloadTimeLeft < 0) { 60 | downloadTimeLeft = 0; 61 | } else if (!isFinite(downloadTimeLeft)) { // infinite 62 | downloadTimeLeft = undefined; 63 | } 64 | 65 | this.set({ 66 | pieces: 0, 67 | downloaded: downloaded, 68 | active_peers: torrent.numPeers, 69 | uploadSpeed: final_upload_speed, 70 | downloadSpeed: final_download_speed, 71 | downloadedFormatted: final_downloaded, 72 | downloadedPercent: (final_downloaded_percent || 0), 73 | time_left: downloadTimeLeft, 74 | }); 75 | } 76 | 77 | }); 78 | 79 | App.Model.StreamInfo = StreamInfo; 80 | })(window.App); 81 | -------------------------------------------------------------------------------- /src/app/lib/models/watchlist_collection.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var WatchlistCollection = App.Model.Collection.extend({ 5 | initialize: function (model, options) { 6 | this.hasMore = false; 7 | this.providers = { 8 | torrents: [App.Providers.get('Watchlist')] 9 | }; 10 | }, 11 | fetch: function () { 12 | return App.Providers.get('Watchlist').fetch().then((items) => { 13 | for (var i in items.results) { //hack FIXME - #557 14 | items.results[i].providers = { 15 | torrent: App.Providers.get('Watchlist') 16 | }; 17 | } 18 | this.add(items.results); 19 | this.state = 'loaded'; 20 | this.trigger('loaded', this, this.state); 21 | }).catch((error) => { 22 | this.state = 'error'; 23 | this.trigger('loaded', this, this.state); 24 | win.error('WatchlistCollection.fetch()', error); 25 | }); 26 | }, 27 | fetchMore: function () { 28 | return; 29 | } 30 | 31 | }); 32 | 33 | App.Model.WatchlistCollection = WatchlistCollection; 34 | })(window.App); 35 | -------------------------------------------------------------------------------- /src/app/lib/providers/generic.js: -------------------------------------------------------------------------------- 1 | (function(App) { 2 | 'use strict'; 3 | var cache = (App.Providers._cache = {}); 4 | var registry = (App.Providers._registry = {}); 5 | 6 | App.Providers.Generic = require('butter-provider'); 7 | 8 | function updateProviderConnection (moviesServer, seriesServer, animeServer, proxy) { 9 | if (moviesServer && moviesServer.includes('://yts')) { 10 | var MovieBrowser = App.View.PCTBrowser.extend({ 11 | collectionModel: App.Model.MovieCollection, 12 | provider: 'YTSApi', 13 | }); 14 | App.View.MovieBrowser = MovieBrowser; 15 | cache[Object.keys(App.Providers._cache)[0]] = App.Providers.get('YTSApi'); 16 | } 17 | moviesServer ? cache[Object.keys(App.Providers._cache)[0]].setApiUrls(moviesServer) : null; 18 | seriesServer ? cache[Object.keys(App.Providers._cache)[1]].setApiUrls(seriesServer) : null; 19 | animeServer ? cache[Object.keys(App.Providers._cache)[2]].setApiUrls(animeServer) : null; 20 | for (let provider in cache) { 21 | cache[provider].proxy = proxy; 22 | } 23 | } 24 | 25 | function updateProviderLanguage (language, contentLanguage, contentLangOnly = false) { 26 | for (let provider in cache) { 27 | if (cache[provider] && cache[provider].hasOwnProperty('language')) { 28 | cache[provider].language = language; 29 | } 30 | if (cache[provider] && cache[provider].hasOwnProperty('contentLanguage')) { 31 | cache[provider].contentLanguage = contentLanguage; 32 | } 33 | if (cache[provider] && cache[provider].hasOwnProperty('contentLangOnly')) { 34 | cache[provider].contentLangOnly = contentLangOnly; 35 | } 36 | } 37 | } 38 | 39 | function delProvider(name) { 40 | if (cache[name]) { 41 | win.info('Delete provider cache:', name); 42 | return delete cache[name]; 43 | } 44 | } 45 | 46 | function installProvider(PO) { 47 | var name = PO.prototype.config ? PO.prototype.config.name : null; 48 | 49 | if (!name) { 50 | return win.error(PO, PO.prototype.config, 'doesn\'t have a name'); 51 | } 52 | 53 | if (registry[name]) { 54 | return win.error( 55 | 'double definition of', 56 | name, 57 | PO, 58 | PO.prototype.config, 59 | 'is the same as', 60 | registry[name] 61 | ); 62 | } 63 | 64 | registry[name] = PO; 65 | 66 | return name; 67 | } 68 | 69 | function getProviderFromRegistry(name) { 70 | return registry[name]; 71 | } 72 | 73 | function getProvider(name) { 74 | if (!name) { 75 | /* XXX(xaiki): this is for debug purposes, will it bite us later ? */ 76 | /* XXX(vankasteelj): it did. */ 77 | win.error( 78 | 'asked for an empty provider, this should never happen, dumping provider cache and registry', 79 | cache, 80 | registry 81 | ); 82 | return cache; 83 | } 84 | 85 | var config = App.Providers.Generic.parseArgs(name); 86 | 87 | if (cache[name]) { 88 | return cache[name]; 89 | } 90 | 91 | var provider = getProviderFromRegistry(config.name); 92 | 93 | if (!provider) { 94 | if (installProvider(require('butter-provider-' + config.name))) { 95 | win.warn( 96 | 'I loaded', 97 | config.name, 98 | 'from npm but you didn\'t add it to your package.json' 99 | ); 100 | provider = getProviderFromRegistry(config.name); 101 | } else { 102 | win.error('couldn\'t find provider', config.name); 103 | return null; 104 | } 105 | } 106 | 107 | win.info('Spawning new provider:', name, config); 108 | var p = (cache[name] = new provider(config)); 109 | 110 | //HACK(xaiki): set the provider name in the returned object. 111 | p.name = name; 112 | return p; 113 | } 114 | 115 | App.Providers.get = getProvider; 116 | App.Providers.delete = delProvider; 117 | App.Providers.install = installProvider; 118 | App.Providers.updateConnection = updateProviderConnection; 119 | App.Providers.updateLanguage = updateProviderLanguage; 120 | 121 | App.Providers.getFromRegistry = getProviderFromRegistry; 122 | })(window.App); 123 | -------------------------------------------------------------------------------- /src/app/lib/providers/icons.js: -------------------------------------------------------------------------------- 1 | (function(App) { 2 | 'use strict'; 3 | 4 | class Icons { 5 | 6 | constructor() { 7 | const dir = data_path + '/icons'; 8 | if (!fs.existsSync(dir)) { 9 | fs.mkdirSync(dir); 10 | } 11 | this.files = fs.readdirSync(dir); 12 | } 13 | 14 | async getLink(provider, name) { 15 | const file = '/icons/' + name + '.png'; 16 | if (this.files.indexOf(name + '.png') === -1) { 17 | const res = await provider.getBin(0, 'icons/' + name + '.png'); 18 | const data = await res.arrayBuffer(); 19 | fs.writeFileSync(data_path + file, Buffer.from(data)); 20 | } 21 | return 'file://' + data_path + file; 22 | } 23 | } 24 | 25 | Icons.prototype.config = { 26 | name: 'Icons' 27 | }; 28 | 29 | App.Providers.Icons = new Icons(); 30 | App.Providers.install(Icons); 31 | 32 | })(window.App); 33 | -------------------------------------------------------------------------------- /src/app/lib/providers/opensubtitles.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var OS = require('opensubtitles-api'), 4 | openSRT; 5 | 6 | var OpenSubtitles = function () { 7 | 8 | }; 9 | 10 | OpenSubtitles.prototype.constructor = OpenSubtitles; 11 | OpenSubtitles.prototype.config = { 12 | name: 'OpenSubtitles' 13 | }; 14 | 15 | var normalizeLangCodes = function (data) { 16 | Object.keys(data).forEach(function(key,index) { 17 | if (key === 'pb' || key.indexOf('pb|') === 0) { 18 | data[key.replace('pb','pt-br')] = data[key]; 19 | delete data[key]; 20 | } 21 | }); 22 | return data; 23 | }; 24 | 25 | var formatForButter = function (data_obj) { 26 | var data = {}; 27 | var multi_id = 0; 28 | var multi_langcode = ''; 29 | var multi_urls = {}; 30 | 31 | // formating subtitle object data to pre-multiple subtitle format 32 | for (const[langcode,value] of Object.entries(data_obj)) { 33 | multi_id = 1; 34 | multi_urls = {}; 35 | value.forEach(function(subtitle) { 36 | // filtering out already existing urls 37 | if ( !(subtitle.url in multi_urls) ) { 38 | multi_langcode = langcode; 39 | if (multi_id > 1) { 40 | // first subtitle without multi-subtitle format because of defaultSubtitle from settings 41 | multi_langcode += '|' + multi_id.toString(); 42 | } 43 | data[multi_langcode] = subtitle; 44 | multi_urls[subtitle.url] = ''; 45 | multi_id++; 46 | } 47 | }); 48 | } 49 | 50 | data = normalizeLangCodes(data); 51 | for (var lang in data) { 52 | data[lang] = data[lang].url; 53 | } 54 | 55 | win.info(Object.keys(data).length + ' subtitles found'); 56 | 57 | return Common.sanitize(data); 58 | }; 59 | 60 | OpenSubtitles.prototype.fetch = function (queryParams) { 61 | openSRT = new OS({ 62 | useragent: 'Popcorn Time NodeJS', 63 | username: AdvSettings.get('opensubtitlesUsername'), 64 | password: AdvSettings.get('opensubtitlesPassword') 65 | }); 66 | queryParams.extensions = ['srt']; 67 | queryParams.limit = 'all'; 68 | return openSRT.search(queryParams) 69 | .then(formatForButter); 70 | }; 71 | 72 | OpenSubtitles.prototype.detail = function (id, attrs) { 73 | return this.fetch({ 74 | imdbid: id 75 | }).then(function (data) { 76 | App.vent.trigger('update:subtitles', data); 77 | return { 78 | subtitle: data 79 | }; 80 | }); 81 | }; 82 | 83 | OpenSubtitles.prototype.upload = function (queryParams) { 84 | openSRT = new OS({ 85 | useragent: 'Popcorn Time NodeJS', 86 | username: AdvSettings.get('opensubtitlesUsername'), 87 | password: AdvSettings.get('opensubtitlesPassword') 88 | }); 89 | return openSRT.upload(queryParams); 90 | }; 91 | 92 | App.Providers.install(OpenSubtitles); 93 | 94 | })(window.App); 95 | -------------------------------------------------------------------------------- /src/app/lib/subtitle/server.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | var server; 3 | var httpServer; 4 | var PORT = 9999; 5 | var subtitlePath = {}; 6 | var encoding = 'utf8'; 7 | var send = require('send'); 8 | 9 | server = http.createServer(function (req, res) { 10 | var uri = url.parse(req.url); 11 | var ext = path.extname(uri.pathname).substr(1); 12 | var sub_path = subtitlePath[ext]; 13 | var sub_dir = path.dirname(sub_path); 14 | var sub_uri = '/' + path.basename(sub_path); 15 | 16 | var headers = function (res, path, stat) { 17 | if (req.headers.origin) { 18 | res.setHeader('Access-Control-Allow-Origin', req.headers.origin); 19 | } 20 | res.setHeader('Content-Type', 'text/' + ext + ';charset=' + encoding); 21 | }; 22 | 23 | if (ext in subtitlePath) { 24 | send(req, sub_uri, { 25 | root: sub_dir 26 | }) 27 | .on('headers', headers) 28 | .pipe(res); 29 | } else { 30 | res.writeHead(404); 31 | res.end(); 32 | win.error('SubtitlesServer: No subtitle with format %s available.', ext); 33 | } 34 | }); 35 | 36 | function startListening(cb) { 37 | httpServer = server.listen(PORT); 38 | 39 | 40 | } 41 | 42 | function stopServer(cb) { 43 | httpServer.close(function () { 44 | httpServer = null; 45 | if (cb) { 46 | cb(); 47 | } 48 | }); 49 | } 50 | 51 | var SubtitlesServer = { 52 | start: function (data, cb) { 53 | 54 | encoding = data.encoding || 'utf8'; 55 | if (data.vtt) { 56 | fs.readFile(data.vtt, function (err, data) { 57 | if (err) { 58 | return; 59 | } 60 | }); 61 | subtitlePath['vtt'] = data.vtt; 62 | } 63 | 64 | if (data.srt) { 65 | fs.readFile(data.srt, function (err, data) { 66 | if (err) { 67 | return; 68 | } 69 | }); 70 | subtitlePath['srt'] = data.srt; 71 | } 72 | 73 | if (!httpServer) { 74 | startListening(cb); 75 | } 76 | }, 77 | 78 | stop: function () { 79 | if (httpServer) { 80 | stopServer(); 81 | } 82 | } 83 | }; 84 | App.SubtitlesServer = SubtitlesServer; 85 | })(window.App); 86 | -------------------------------------------------------------------------------- /src/app/lib/views/about.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var About = Marionette.View.extend({ 5 | template: '#about-tpl', 6 | className: 'about', 7 | 8 | ui: { 9 | success_alert: '.success_alert' 10 | }, 11 | 12 | events: { 13 | 'click .close-icon': 'closeAbout', 14 | 'mousedown #changelog': 'showChangelog', 15 | 'mousedown .update-app': 'updateApp', 16 | 'contextmenu .links': 'copytoclip' 17 | }, 18 | 19 | onAttach: function () { 20 | Mousetrap.bind(['esc', 'backspace'], function (e) { 21 | App.vent.trigger('about:close'); 22 | }); 23 | $('.links,#changelog').tooltip(); 24 | $('#movie-detail').hide(); 25 | }, 26 | 27 | onBeforeDestroy: function () { 28 | Mousetrap.unbind(['esc', 'backspace']); 29 | $('#movie-detail').show(); 30 | }, 31 | 32 | closeAbout: function () { 33 | if ($('.changelog-overlay').css('display') === 'block') { 34 | this.closeChangelog(); 35 | } else { 36 | App.vent.trigger('about:close'); 37 | } 38 | }, 39 | 40 | copytoclip: (e) => Common.openOrClipboardLink(e, $(e.target)[0].href, i18n.__('link'), true), 41 | 42 | updateApp: function(e) { 43 | if (e.button === 2) { 44 | Common.openOrClipboardLink(e, Settings.projectUrl, i18n.__('link'), true); 45 | } else { 46 | let updateMode = e === 'enable' ? e : (e ? 'about' : ''); 47 | App.Updater.onlyNotification(updateMode); 48 | } 49 | }, 50 | 51 | showChangelog: function (e) { 52 | if (e.button === 2) { 53 | Common.openOrClipboardLink(e, (App.git ? App.git.semver : App.settings.version), i18n.__('version number'), true); 54 | } else { 55 | fs.readFile('./CHANGELOG.md', 'utf-8', function (err, contents) { 56 | if (!err) { 57 | $('.changelog-text').html(contents.replace(/\n/g, '
')); 58 | $('.changelog-overlay').show(); 59 | } else { 60 | nw.Shell.openExternal(Settings.changelogUrl); 61 | } 62 | }); 63 | } 64 | }, 65 | 66 | closeChangelog: function () { 67 | $('.changelog-overlay').hide(); 68 | } 69 | 70 | }); 71 | 72 | App.View.About = About; 73 | })(window.App); 74 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/anime_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var AnimeBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.AnimeCollection, 6 | providerType: 'anime', 7 | }); 8 | 9 | App.View.AnimeBrowser = AnimeBrowser; 10 | })(window.App); 11 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/favorite_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var FavoriteBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.FavoriteCollection, 6 | provider: 'Favorites', 7 | }); 8 | 9 | App.View.FavoriteBrowser = FavoriteBrowser; 10 | })(window.App); 11 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/movie_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var MovieBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.MovieCollection, 6 | providerType: 'movie', 7 | }); 8 | 9 | App.View.MovieBrowser = MovieBrowser; 10 | })(window.App); 11 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/show_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var ShowBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.ShowCollection, 6 | providerType: 'tvshow', 7 | }); 8 | 9 | App.View.ShowBrowser = ShowBrowser; 10 | })(window.App); 11 | -------------------------------------------------------------------------------- /src/app/lib/views/browser/watchlist_browser.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var WatchlistBrowser = App.View.PCTBrowser.extend({ 5 | collectionModel: App.Model.WatchlistCollection, 6 | provider: 'Watchlist', 7 | }); 8 | 9 | App.View.WatchlistBrowser = WatchlistBrowser; 10 | })(window.App); 11 | -------------------------------------------------------------------------------- /src/app/lib/views/disclaimer.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var DisclaimerModal = Marionette.View.extend({ 5 | template: '#disclaimer-tpl', 6 | className: 'disclaimer', 7 | 8 | events: { 9 | 'click .btn-accept': 'acceptDisclaimer', 10 | 'click .btn-close': 'closeApp', 11 | }, 12 | 13 | initialize: function () { 14 | Mousetrap.pause(); 15 | win.warn('Show Disclaimer'); 16 | }, 17 | 18 | acceptDisclaimer: function (e) { 19 | e.preventDefault(); 20 | Mousetrap.unpause(); 21 | AdvSettings.set('dhtEnable', document.getElementById('dhtEnableFR').checked ? true : false); 22 | AdvSettings.set('updateNotification', document.getElementById('updateNotificationFR').checked ? true : false); 23 | AdvSettings.set('disclaimerAccepted', 1); 24 | if (document.getElementById('dhtEnableFR').checked) { 25 | App.Updater.updateDHT(); 26 | App.vent.trigger('notification:show', new App.Model.Notification({ 27 | title: i18n.__('Please wait') + '...', 28 | body: i18n.__('Updating the API Server URLs'), 29 | showClose: false, 30 | type: 'danger' 31 | })); 32 | } else { 33 | App.Updater.updateDHTOld(); 34 | } 35 | App.vent.trigger('disclaimer:close'); 36 | }, 37 | 38 | closeApp: function (e) { 39 | e.preventDefault(); 40 | nw.App.quit(); 41 | } 42 | 43 | }); 44 | 45 | App.View.DisclaimerModal = DisclaimerModal; 46 | })(window.App); 47 | -------------------------------------------------------------------------------- /src/app/lib/views/init_modal.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var fixer; 4 | var InitModal = Marionette.View.extend({ 5 | template: '#initializing-tpl', 6 | className: 'init-container', 7 | 8 | ui: { 9 | initstatus: '.init-status', 10 | initbar: '#initbar-contents', 11 | waitingblock: '#waiting-block' 12 | }, 13 | 14 | events: { 15 | 'click .fixApp': 'fixApp', 16 | }, 17 | 18 | initialize: function () { 19 | win.info('Loaded DB'); 20 | }, 21 | 22 | onAttach: function () { 23 | this.model.on('change', this.updateModal.bind(this)); 24 | this.updateModal(); 25 | }, 26 | 27 | updateModal: function () { 28 | var self = this; 29 | 30 | this.ui.initbar.animate({ 31 | width: this.model.get('done') * 100 + '%' 32 | }, 1000, 'swing'); 33 | this.ui.initstatus.text(i18n.__('Status: %s ...', this.model.get('status'))); 34 | 35 | if (fixer) { 36 | clearTimeout(fixer); 37 | } 38 | 39 | fixer = setTimeout(function () { 40 | self.ui.waitingblock.show(); 41 | }, 7000); 42 | }, 43 | 44 | onBeforeDestroy: function () { 45 | clearTimeout(fixer); 46 | }, 47 | 48 | fixApp: function (e) { 49 | 50 | e.preventDefault(); 51 | 52 | Database.deleteDatabases().then(function () { 53 | App.vent.trigger('restartButter'); 54 | }); 55 | }, 56 | 57 | }); 58 | 59 | App.View.InitModal = InitModal; 60 | })(window.App); 61 | -------------------------------------------------------------------------------- /src/app/lib/views/keyboard.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | var Keyboard = Marionette.View.extend({ 5 | template: '#keyboard-tpl', 6 | className: 'keyboard', 7 | 8 | events: { 9 | 'click .close-icon': 'closeKeyboard', 10 | }, 11 | 12 | 13 | onAttach: function () { 14 | $('.search input').blur(); 15 | Mousetrap.bind(['esc', 'backspace'], function (e) { 16 | App.vent.trigger('keyboard:close'); 17 | }); 18 | }, 19 | 20 | onBeforeDestroy: function () {}, 21 | 22 | closeKeyboard: function () { 23 | App.vent.trigger('keyboard:close'); 24 | }, 25 | 26 | }); 27 | 28 | App.View.Keyboard = Keyboard; 29 | })(window.App); 30 | -------------------------------------------------------------------------------- /src/app/lib/views/lang_dropdown.js: -------------------------------------------------------------------------------- 1 | (function (App){ 2 | 'use strict'; 3 | 4 | App.View.LangDropdown = Marionette.View.extend({ 5 | template: '#lang-dropdown-tpl', 6 | ui: { 7 | selected: '.selected-lang', 8 | }, 9 | events: { 10 | 'click .flag-icon': 'closeDropdown', 11 | }, 12 | 13 | initialize: function () { 14 | var self = this; 15 | 16 | this.type = this.model.get('type'); 17 | this.selected = this.model.get('selected'); 18 | this.values = this.model.get('values'); 19 | this.hasNull = this.model.get('hasNull'); 20 | 21 | if (this.hasNull) { 22 | this.values = Object.assign({}, {none: undefined}, this.values); 23 | this.model.set('values', this.values); 24 | } else if (!this.selected && this.values) { 25 | var values = Object.keys(this.values); 26 | if (values.length) { 27 | this.selected = values.pop(); 28 | } 29 | } 30 | }, 31 | 32 | onAttach: function () { 33 | if (this.selected && this.selected !== 'none') { 34 | this.setLang(this.selected); 35 | } 36 | }, 37 | 38 | updateLangs: function (newLangs) { 39 | if (this.hasNull) { 40 | newLangs = Object.assign({}, {none: undefined}, newLangs); 41 | } 42 | this.model.set('values', newLangs); 43 | this.values = newLangs; 44 | this.render(); 45 | 46 | if ((Settings.subtitle_language !== 'none') && (Settings.subtitle_language in newLangs)) { 47 | this.setLang(Settings.subtitle_language); 48 | } 49 | 50 | $('.tooltipped').tooltip({ 51 | delay: { 52 | 'show': 800, 53 | 'hide': 100 54 | } 55 | }); 56 | }, 57 | 58 | setLang: function (value) { 59 | this.model.set('selected', value); 60 | const langClass = value === 'none' ? value : value.substr(0,2); 61 | this.ui.selected.removeClass().addClass('flag toggle selected-lang').addClass(langClass); 62 | let title = App.Localization.nativeName(value); 63 | if (langClass !== 'none' && langClass !== 'en') { 64 | title += ' (' + App.Localization.name(value).replace(/\(|\)/g, '') + ')'; 65 | } 66 | this.ui.selected.attr('title', title) 67 | .tooltip({delay: {show: 800, hide: 100}, html: true}).tooltip('fixTitle'); 68 | App.vent.trigger(this.type + ':lang', value); 69 | }, 70 | 71 | closeDropdown: function (e) { 72 | var value = $(e.currentTarget).attr('data-lang'); 73 | 74 | if (value) { 75 | this.setLang(value); 76 | } 77 | }, 78 | }); 79 | })(window.App); 80 | -------------------------------------------------------------------------------- /src/app/lib/views/notification.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | App.View.Notification = Marionette.View.extend({ 5 | template: '#notification-tpl', 6 | className: 'notificationWrapper', 7 | 8 | ui: { 9 | notification: '.notification' 10 | }, 11 | 12 | events: { 13 | 'click .close': 'closeNotification' 14 | }, 15 | 16 | initialize: function () { 17 | if (!(this.model instanceof App.Model.Notification)) { 18 | throw new Error('When instantiating a Notification, you must pass an App.Model.Notification.'); 19 | } 20 | }, 21 | 22 | onAttach: function () { 23 | var _this = this; 24 | // Uniquely identify each button and wire up the callback. 25 | this.model.get('buttons').forEach(function (button, index) { 26 | _this.$('.action-button-' + index).on('click', button.action); 27 | }); 28 | 29 | this.autoClose(this.model.get('autoclose')); 30 | }, 31 | 32 | autoClose: function (timeout) { 33 | var _this = this; 34 | if (!timeout) { 35 | return; 36 | } 37 | 38 | timeout = timeout === true ? 6000 : timeout; 39 | 40 | setTimeout(function () { 41 | _this.closeNotification(); 42 | }, timeout); 43 | }, 44 | 45 | closeNotification: function () { 46 | App.vent.trigger('notification:close'); 47 | } 48 | 49 | }); 50 | })(window.App); 51 | -------------------------------------------------------------------------------- /src/app/lib/views/quality_selector.js: -------------------------------------------------------------------------------- 1 | (function (App){ 2 | 'use strict'; 3 | 4 | App.View.QualitySelector = Marionette.View.extend({ 5 | template: '#quality-selector-tpl', 6 | ui: { 7 | list: '.sdow-quality', 8 | }, 9 | events: { 10 | 'click .qselect': 'selectItem', 11 | }, 12 | initialize: function () { 13 | this.updateTorrents(this.model.get('torrents')); 14 | }, 15 | 16 | onAttach: function () { 17 | this.initQuality(); 18 | }, 19 | 20 | initQuality: function() { 21 | var selectedKey = null; 22 | for (let [key, torrent] of Object.entries(this.model.get('sortedTorrents'))) { 23 | if (!torrent) { 24 | continue; 25 | } 26 | if (!selectedKey || Common.qualityCollator.compare(key, Settings[this.model.get('defaultQualityKey')]) <= 0) { 27 | selectedKey = key; 28 | } 29 | } 30 | this.selectQuality(selectedKey); 31 | }, 32 | 33 | updateTorrents: function (torrents) { 34 | let keys = Object.keys(torrents).sort(Common.qualityCollator.compare); 35 | let sortedTorrents = {}; 36 | for (let key of this.model.get('required')) { 37 | sortedTorrents[key] = false; 38 | } 39 | for (let key of keys) { 40 | // TODO: it exist in episodes - need know why 41 | if (key === '0') { 42 | continue; 43 | } 44 | sortedTorrents[key] = torrents[key]; 45 | } 46 | 47 | this.model.set('sortedTorrents', sortedTorrents); 48 | this.render(); 49 | this.initQuality(); 50 | }, 51 | 52 | selectNext: function () { 53 | var lastKey = $(this.ui.list).find('div.active').text(); 54 | var nextKey = null; 55 | for (let [key, torrent] of Object.entries(this.model.get('sortedTorrents'))) { 56 | if (nextKey === true) { 57 | if (!torrent) { 58 | continue; 59 | } 60 | nextKey = key; 61 | break; 62 | } 63 | if (lastKey === key) { 64 | nextKey = true; 65 | } 66 | } 67 | if (nextKey === true) { 68 | nextKey = Object.keys(this.model.get('sortedTorrents'))[0]; 69 | } 70 | this.selectQuality(nextKey); 71 | AdvSettings.set(this.model.get('defaultQualityKey'), nextKey); 72 | }, 73 | 74 | selectItem: function (e) { 75 | var key = $(e.currentTarget).text(); 76 | this.selectQuality(key); 77 | AdvSettings.set(this.model.get('defaultQualityKey'), key); 78 | }, 79 | 80 | selectQuality: function (key) { 81 | $(this.ui.list).find('div').removeClass('active'); 82 | $(this.ui.list).find('div:contains("'+key+'")').addClass('active'); 83 | var torrents = this.model.get('sortedTorrents'); 84 | var callback = this.model.get('selectCallback'); 85 | callback(torrents[key], key); 86 | }, 87 | }); 88 | })(window.App); 89 | -------------------------------------------------------------------------------- /src/app/lib/views/title_bar.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | 4 | // use of darwin string instead of mac (mac os x returns darwin as platform) 5 | var ButtonOrder = { 6 | 'win32': ['min', 'max', 'close'], 7 | 'darwin': ['close', 'min', 'max'], 8 | 'linux': ['min', 'max', 'close'] 9 | }; 10 | 11 | var TitleBar = Marionette.View.extend({ 12 | template: '#header-tpl', 13 | 14 | events: { 15 | 'click .btn-os.os-max': 'maximize', 16 | 'click .btn-os.os-min': 'minimize', 17 | 'click .btn-os.os-close': 'closeWindow', 18 | 'click .btn-os.fullscreen': 'toggleFullscreen' 19 | }, 20 | 21 | initialize: function () { 22 | this.nativeWindow = win; 23 | }, 24 | 25 | templateContext: { 26 | getButtons: function () { 27 | return ButtonOrder[App.Config.platform]; 28 | }, 29 | 30 | fsTooltipPos: function () { 31 | return App.Config.platform === 'darwin' ? 'left' : 'right'; 32 | }, 33 | 34 | events: function () { 35 | var date = new Date(); 36 | var today = ('0' + (date.getMonth() +  1)).slice(-2) + ('0' + (date.getDate())).slice(-2); 37 | if (today === '1231' || today === '0101') { 38 | return 'newyear'; 39 | } else if (today >= '1218' || today <= '0103') { 40 | return 'xmas'; 41 | } else if (today >= '1027' && today <= '1103') { 42 | return 'halloween'; 43 | } else if (today === '0220') { 44 | return 'pt_anniv'; 45 | } else if (today === '0214') { 46 | return 'stvalentine'; 47 | } else if (today === '0317') { 48 | return 'stpatrick'; 49 | } else if (today === '0401') { 50 | return 'aprilsfool'; 51 | } 52 | } 53 | }, 54 | 55 | maximize: function () { 56 | if (this.nativeWindow.isFullscreen) { 57 | this.nativeWindow.toggleFullscreen(); 58 | } else { 59 | if (window.screen.availHeight <= this.nativeWindow.height) { 60 | this.nativeWindow.restore(); 61 | if (process.platform === 'win32') { 62 | $('.os-max').removeClass('os-is-max'); 63 | } 64 | } else { 65 | this.nativeWindow.maximize(); 66 | if (process.platform === 'win32') { 67 | $('.os-max').addClass('os-is-max'); 68 | } 69 | } 70 | } 71 | }, 72 | 73 | minimize: function () { 74 | var that = this.nativeWindow; 75 | if (AdvSettings.get('minimizeToTray')) { 76 | minimizeToTray(); 77 | } else { 78 | that.minimize(); 79 | } 80 | }, 81 | 82 | closeWindow: function () { 83 | this.nativeWindow.close(); 84 | }, 85 | 86 | toggleFullscreen: function () { 87 | win.toggleFullscreen(); 88 | this.$el.find('.btn-os.fullscreen').toggleClass('active'); 89 | }, 90 | 91 | onAttach: function () { 92 | $('.tooltipped').tooltip({ 93 | delay: { 94 | 'show': 800, 95 | 'hide': 100 96 | } 97 | }); 98 | } 99 | 100 | }); 101 | 102 | App.View.TitleBar = TitleBar; 103 | })(window.App); 104 | -------------------------------------------------------------------------------- /src/app/lib/views/windows_title_bar.js: -------------------------------------------------------------------------------- 1 | (function (App) { 2 | 'use strict'; 3 | var WindowsTitleBar = Marionette.View.extend({ 4 | // Dirty way of getting proper value before app database initializes 5 | template: pkJson.window.frame ? false : '#win-header-tpl', 6 | 7 | events: { 8 | 'click .window-minimize': 'minimize', 9 | 'click .window-maximize': 'maximize', 10 | 'click .window-close': 'closeWindow' 11 | }, 12 | 13 | initialize: function () { 14 | if (Settings.nativeWindowFrame) { 15 | return; 16 | } 17 | 18 | this.nativeWindow = win; 19 | 20 | this.nativeWindow.on('maximize', function () { 21 | $('.window-maximize').addClass('window-umaximize'); 22 | }); 23 | 24 | this.nativeWindow.on('restore', function () { 25 | $('.window-maximize').removeClass('window-umaximize'); 26 | }); 27 | }, 28 | 29 | templateContext: { 30 | events: function () { 31 | var date = new Date(); 32 | var today = ('0' + (date.getMonth() +  1)).slice(-2) + ('0' + (date.getDate())).slice(-2); 33 | if (today === '1231' || today === '0101') { 34 | return 'newyear'; 35 | } else if (today >= '1218' || today <= '0103') { 36 | return 'xmas'; 37 | } else if (today >= '1027' && today <= '1103') { 38 | return 'halloween'; 39 | } else if (today === '0220') { 40 | return 'pt_anniv'; 41 | } else if (today === '0214') { 42 | return 'stvalentine'; 43 | } else if (today === '0317') { 44 | return 'stpatrick'; 45 | } else if (today === '0401') { 46 | return 'aprilsfool'; 47 | } 48 | } 49 | }, 50 | 51 | maximize: function () { 52 | if (this.nativeWindow.isFullscreen) { 53 | this.nativeWindow.toggleFullscreen(); 54 | } else { 55 | if (window.screen.availHeight <= this.nativeWindow.height) { 56 | this.nativeWindow.restore(); 57 | if (process.platform === 'win32') { 58 | $('.window-maximize').removeClass('window-umaximize'); 59 | } 60 | } else { 61 | this.nativeWindow.maximize(); 62 | if (process.platform === 'win32') { 63 | $('.window-maximize').addClass('window-umaximize'); 64 | } 65 | } 66 | } 67 | }, 68 | 69 | minimize: function () { 70 | var that = this.nativeWindow; 71 | if (AdvSettings.get('minimizeToTray')) { 72 | minimizeToTray(); 73 | } else { 74 | that.minimize(); 75 | } 76 | }, 77 | 78 | closeWindow: function () { 79 | this.nativeWindow.close(); 80 | }, 81 | 82 | onAttach: function () { 83 | $('.tooltipped').tooltip({ 84 | delay: { 85 | 'show': 800, 86 | 'hide': 100 87 | } 88 | }); 89 | } 90 | }); 91 | 92 | App.View.WindowsTitleBar = WindowsTitleBar; 93 | })(window.App); 94 | -------------------------------------------------------------------------------- /src/app/media_name.js: -------------------------------------------------------------------------------- 1 | ((App) => { 2 | const mediaNameSymbol = Symbol('torrentMediaName'); 3 | const maxTimeInCacheMs = 60 * 1000; 4 | 5 | class MediaNamePlugin { 6 | constructor() { 7 | this._infoHashToMediaName = new Map(); 8 | this._infoHashToInsertTime = new Map(); 9 | this._cleanupTask = setInterval(() => this.cleanMediaNames(), maxTimeInCacheMs); 10 | } 11 | 12 | cleanMediaNames() { 13 | const now = Date.now(); 14 | for (const [infoHash, insertTime] of this._infoHashToInsertTime) { 15 | if (now - insertTime >= maxTimeInCacheMs) { 16 | this._infoHashToInsertTime.delete(infoHash); 17 | this._infoHashToMediaName.delete(infoHash); 18 | } 19 | } 20 | } 21 | 22 | setMediaName(infoHash, mediaName) { 23 | this._infoHashToMediaName.set(infoHash, mediaName); 24 | this._infoHashToInsertTime.set(infoHash, Date.now()); 25 | } 26 | 27 | getMediaName(torrent) { 28 | if (torrent.name) { 29 | return torrent.name; 30 | } 31 | if (torrent[mediaNameSymbol]) { 32 | return torrent[mediaNameSymbol]; 33 | } 34 | const mediaName = this._infoHashToMediaName.get(torrent.infoHash); 35 | this._infoHashToInsertTime.delete(torrent.infoHash); 36 | this._infoHashToMediaName.delete(torrent.infoHash); 37 | if (mediaName) { 38 | torrent[mediaNameSymbol] = mediaName; 39 | return mediaName; 40 | } 41 | return i18n.__('Unknown torrent'); 42 | } 43 | } 44 | App.plugins.mediaName = new MediaNamePlugin(); 45 | })(window.App); 46 | -------------------------------------------------------------------------------- /src/app/styl/Dutchys_-_Dark_Orange_theme.styl: -------------------------------------------------------------------------------- 1 | // THEME: DUTCHYS DARK ORANGE 2 | 3 | //Color variables: black & yellow 4 | Black = #070707 5 | YellowL = #e67300 6 | YellowM = #e67300 7 | YellowD = #e67300 8 | YellowD2 = #e67300 9 | 10 | //Fonts 11 | $Font = 'Open Sans' 12 | $MainFont = 'Open Sans Semibold' 13 | $MainFontBold = 'Open Sans Bold' 14 | $AlternateFont = sans-serif 15 | $ButtonFont = 'Open Sans Semibold' 16 | 17 | //Main app 18 | $BgColor1 = Black 19 | $BgColor2 = lighten(Black,5%) 20 | $Text1 = #fff 21 | $Text2 = #C9C9C9 22 | $Text3 = #939597 23 | $Text4 = #fff 24 | $TextError = #797979 25 | $CloseButton = #fff 26 | $CloseButtonHover = YellowL 27 | $SpinnerColor1 = YellowD2 28 | $SpinnerColor2 = YellowD 29 | $ScrollBarBg = #30333c 30 | $ScrollBarThumb = YellowD2 31 | $ScrollBarThumbHover = YellowD 32 | 33 | //Top Bar 34 | $TitleBarBg = Black 35 | $TitleBarText = #fff 36 | $TbarIconFilter = none 37 | $FullScreenButton = #999 38 | $FullScreenButtonHover = YellowL 39 | $FilterBarBg = Black 40 | $FilterBarText = #e9e9e9 41 | $FilterBarTextHover = #fff 42 | $FilterBarActive = YellowL 43 | $SearchBoxBg = #191818 44 | $DropDownBg = rgba(Black,0.9) 45 | $DropDownBgHover = rgba(YellowL,0.15) 46 | $DropDownTextHover = #fff 47 | $DropDownText = #d9d9d9 48 | $DropDownOpacity = 0.97 49 | $FilterBarIcon = $Text1 50 | $FilterBarIconHover = YellowD 51 | $FilterBarIcon-PosterWidth = #bababa 52 | $TopBarShadow = 0px 6px 8px -4px rgba(0, 0, 0, .9) 53 | 54 | //Buttons 55 | $ButtonBg = YellowL 56 | $ButtonBgHover = YellowM 57 | $ButtonBgActive = YellowD 58 | $ButtonBgDisabled = #737577 59 | $ButtonBgOk = #27AE60 60 | $ButtonBgWarning = #F15153 61 | $ButtonText = #000 62 | $ButtonTextHover = #000 63 | $ButtonImgInvert = 1 64 | $ButtonRadius = 3px 65 | 66 | //Checkbox 67 | $CheckboxBg = #232324 68 | $CheckboxCheck = YellowD 69 | 70 | //Posters 71 | $PosterRadius = 4px 72 | $PosterHoverBorder = $ButtonBgActive 73 | $PosterHoverOverlay = rgba(23, 24, 27, 0.6) 74 | $PosterHoverOverlayOpq = rgba(23, 24, 27, 1) 75 | $PosterShadow = 0px 0px 10px 76 | 77 | //TV Series 78 | $ShowBgColor1 = $BgColor1 79 | $ShowBgColor2 = $BgColor2 80 | $EpisodeDetailsBg = $BgColor1 81 | $ShowText1 = $Text1 82 | $ShowText2 = $Text2 83 | $ShowOddHighlight = lighten($ShowBgColor1,50%) 84 | $ShowOddHighlightOp = 0.05 85 | $SaisonListText = $Text1 86 | $EpisodeListText = $Text2 87 | $EpisodeSelectorBg = darken(YellowD2,2%) 88 | $EpisodeSelectorHover = #26272d 89 | $EpisodeSelectorHoverTran = lighten($EpisodeSelectorHover,43%) 90 | $EpisodeSelectorText = $ButtonText 91 | $ShowWatchedIcon_false = rgba(#fff,0.2) 92 | $ShowWatchedIcon_hover = rgba(#fff,0.4) 93 | $ShowWatchedIcon_true = #fff 94 | $QualitySelectorBg = $ButtonBg 95 | $QualitySelectorText = $ButtonText 96 | 97 | //File selector 98 | $FileSelectorText = #eee 99 | $FileSelectorBg = rgba(255, 255, 255, 0.05) 100 | $FileSelectorShadow = rgba(0, 0, 0, 0.67) 101 | 102 | //Settings 103 | $SettingsCategoriesText = YellowD 104 | $SettingsSeparator = #333 105 | $InputBoxBg = #232324 106 | $InputBoxText = $Text2 107 | $SettingsText1 = $Text2 108 | $SettingsText2 = $Text3 109 | $SettingsButtonText = $ButtonText 110 | $SettingsButtonTextHover = $ButtonText 111 | 112 | // Notifications 113 | $NotificationText = #fff 114 | $NotificationInfo = #3498DB 115 | $NotificationSuccess = #27AE60 116 | $NotificationWarning = #dbb756 117 | $NotificationOrange = #f07f53 118 | $NotificationError = #f15153 119 | $NotificationBorderColor = #000 120 | $NotificationBtn = #000 121 | $NotificationBtnText = #fff 122 | $NotifAlertBg = #000 123 | $NotifAlertText = #fff 124 | 125 | //Misc 126 | $FavoriteColor = #df2d37 127 | $WarningColor = #FFA500 128 | $PngLogo = brightness(1.0) 129 | $PngLogo_BgColo1 = brightness(1.0) 130 | 131 | //Player 132 | $PlayerColor = YellowL 133 | 134 | // THEME: DUTCHYS DARK ORANGE - END 135 | 136 | @import 'views/app' 137 | -------------------------------------------------------------------------------- /src/app/styl/Official_-_Black_&_Yellow_theme.styl: -------------------------------------------------------------------------------- 1 | // OFFICIAL POPCORN-THEME: BLACK AND YELLOW 2 | 3 | //Color variables: black & yellow 4 | Black = #070707 5 | YellowL = #f3cc16 6 | YellowM = #ffe035 7 | YellowD = #ffcc00 8 | YellowD2 = #e3bd0c 9 | 10 | //Fonts 11 | $Font = 'Open Sans' 12 | $MainFont = 'Open Sans Semibold' 13 | $MainFontBold = 'Open Sans Bold' 14 | $AlternateFont = sans-serif 15 | $ButtonFont = 'Open Sans Semibold' 16 | 17 | //Main app 18 | $BgColor1 = Black 19 | $BgColor2 = lighten(Black,5%) 20 | $Text1 = #fff 21 | $Text2 = #C9C9C9 22 | $Text3 = #939597 23 | $Text4 = #fff 24 | $TextError = #797979 25 | $CloseButton = #fff 26 | $CloseButtonHover = YellowL 27 | $SpinnerColor1 = YellowD2 28 | $SpinnerColor2 = YellowD 29 | $ScrollBarBg = #30333c 30 | $ScrollBarThumb = YellowD2 31 | $ScrollBarThumbHover = YellowD 32 | 33 | //Top Bar 34 | $TitleBarBg = Black 35 | $TitleBarText = #fff 36 | $TbarIconFilter = none 37 | $FullScreenButton = #999 38 | $FullScreenButtonHover = YellowL 39 | $FilterBarBg = Black 40 | $FilterBarText = #e9e9e9 41 | $FilterBarTextHover = #fff 42 | $FilterBarActive = YellowL 43 | $SearchBoxBg = #191818 44 | $DropDownBg = rgba(Black,0.9) 45 | $DropDownBgHover = rgba(YellowL,0.15) 46 | $DropDownTextHover = #fff 47 | $DropDownText = #d9d9d9 48 | $DropDownOpacity = 0.97 49 | $FilterBarIcon = $Text1 50 | $FilterBarIconHover = YellowD 51 | $FilterBarIcon-PosterWidth = #bababa 52 | $TopBarShadow = 0px 6px 8px -4px rgba(0, 0, 0, .9) 53 | 54 | //Buttons 55 | $ButtonBg = YellowL 56 | $ButtonBgHover = YellowM 57 | $ButtonBgActive = YellowD 58 | $ButtonBgDisabled = #737577 59 | $ButtonBgOk = #27AE60 60 | $ButtonBgWarning = #F15153 61 | $ButtonText = #000 62 | $ButtonTextHover = #000 63 | $ButtonImgInvert = 1 64 | $ButtonRadius = 3px 65 | 66 | //Checkbox 67 | $CheckboxBg = #232324 68 | $CheckboxCheck = YellowD 69 | 70 | //Posters 71 | $PosterRadius = 4px 72 | $PosterHoverBorder = $ButtonBgActive 73 | $PosterHoverOverlay = rgba(23, 24, 27, 0.6) 74 | $PosterHoverOverlayOpq = rgba(23, 24, 27, 1) 75 | $PosterShadow = 0px 0px 10px 76 | 77 | //TV Series 78 | $ShowBgColor1 = $BgColor1 79 | $ShowBgColor2 = $BgColor2 80 | $EpisodeDetailsBg = $BgColor1 81 | $ShowText1 = $Text1 82 | $ShowText2 = $Text2 83 | $ShowOddHighlight = lighten($ShowBgColor1,50%) 84 | $ShowOddHighlightOp = 0.05 85 | $SaisonListText = $Text1 86 | $EpisodeListText = $Text2 87 | $EpisodeSelectorBg = darken(YellowD2,2%) 88 | $EpisodeSelectorHover = #26272d 89 | $EpisodeSelectorHoverTran = lighten($EpisodeSelectorHover,43%) 90 | $EpisodeSelectorText = $ButtonText 91 | $ShowWatchedIcon_false = rgba(#fff,0.2) 92 | $ShowWatchedIcon_hover = rgba(#fff,0.4) 93 | $ShowWatchedIcon_true = #fff 94 | $QualitySelectorBg = $ButtonBg 95 | $QualitySelectorText = $ButtonText 96 | 97 | //File selector 98 | $FileSelectorText = #eee 99 | $FileSelectorBg = rgba(255, 255, 255, 0.05) 100 | $FileSelectorShadow = rgba(0, 0, 0, 0.67) 101 | 102 | //Settings 103 | $SettingsCategoriesText = YellowD 104 | $SettingsSeparator = #333 105 | $InputBoxBg = #232324 106 | $InputBoxText = $Text2 107 | $SettingsText1 = $Text2 108 | $SettingsText2 = $Text3 109 | $SettingsButtonText = $ButtonText 110 | $SettingsButtonTextHover = $ButtonText 111 | 112 | // Notifications 113 | $NotificationText = #fff 114 | $NotificationInfo = #3498DB 115 | $NotificationSuccess = #27AE60 116 | $NotificationWarning = #dbb756 117 | $NotificationOrange = #f07f53 118 | $NotificationError = #f15153 119 | $NotificationBorderColor = #000 120 | $NotificationBtn = #000 121 | $NotificationBtnText = #fff 122 | $NotifAlertBg = #000 123 | $NotifAlertText = #fff 124 | 125 | //Misc 126 | $FavoriteColor = #df2d37 127 | $WarningColor = #FFA500 128 | $PngLogo = brightness(1.0) 129 | $PngLogo_BgColo1 = brightness(1.0) 130 | 131 | //Player 132 | $PlayerColor = YellowL 133 | 134 | // OFFICIAL POPCORN-THEME: BLACK AND YELLOW - END 135 | 136 | @import 'views/app' 137 | -------------------------------------------------------------------------------- /src/app/styl/Official_-_Dark_theme.styl: -------------------------------------------------------------------------------- 1 | // OFFICIAL POPCORN-THEME: DARK (original) 2 | 3 | //Fonts 4 | $Font = 'Open Sans' 5 | $MainFont = 'Open Sans Semibold' 6 | $MainFontBold = 'Open Sans Bold' 7 | $AlternateFont = sans-serif 8 | $ButtonFont = 'Open Sans Semibold' 9 | 10 | //Main app 11 | $BgColor1 = #17181b 12 | $BgColor2 = #212225 13 | $Text1 = #fff 14 | $Text2 = #c3c5c7 15 | $Text3 = #a3a5a7 16 | $Text4 = #5b5b5b 17 | $TextError = #727272 18 | $CloseButton = #cccccc 19 | $CloseButtonHover = #fff 20 | $SpinnerColor1 = #2187e7 21 | $SpinnerColor2 = rgba(0,183,229,0.9) 22 | $ScrollBarBg = #30333c 23 | $ScrollBarThumb = #83888c 24 | $ScrollBarThumbHover = #93989c 25 | 26 | //Top Bar 27 | $TitleBarBg = #17181b 28 | $TitleBarText = #fff 29 | $TbarIconFilter = none 30 | $FullScreenButton = #999 31 | $FullScreenButtonHover = #b2b2b2 32 | $FilterBarBg = #17181b 33 | $FilterBarText = #c3c5c7 34 | $FilterBarTextHover = #fff 35 | $FilterBarActive = #2d72d9 36 | $SearchBoxBg = #0d0d0e 37 | $DropDownBg = #17181b 38 | $DropDownBgHover = #0b131f 39 | $DropDownTextHover = #fff 40 | $DropDownText = #c3c5c7 41 | $DropDownOpacity = 1.00 42 | $FilterBarIcon = #909498 43 | $FilterBarIconHover = #2468cc 44 | $FilterBarIcon-PosterWidth = #6a6e72 45 | $TopBarShadow = 0px 6px 8px -4px rgba(0, 0, 0, .9) 46 | 47 | //$ButtonBg 48 | $ButtonBg = #1f375f 49 | $ButtonBgHover = #225694 50 | $ButtonBgActive = #2468cc 51 | $ButtonBgDisabled = #393b3d 52 | $ButtonBgOk = #27AE60 53 | $ButtonBgWarning = #F15153 54 | $ButtonText = #fff 55 | $ButtonTextHover = #fff 56 | $ButtonImgInvert = 0 57 | $ButtonRadius = 3px 58 | 59 | //Checkbox 60 | $CheckboxBg = #545454 61 | $CheckboxCheck = #4bfff4 62 | 63 | //Posters 64 | $PosterRadius = 4px 65 | $PosterHoverBorder = #2d75df 66 | $PosterHoverOverlay = rgba(23, 24, 27, 0.6) 67 | $PosterHoverOverlayOpq = rgba(23, 24, 27, 1) 68 | $PosterShadow = 0px 0px 10px 69 | 70 | //TV Series 71 | $ShowBgColor1 = #17181b 72 | $ShowBgColor2 = #1f2025 73 | $EpisodeDetailsBg = #1f2025 74 | $ShowText1 = #fff 75 | $ShowText2 = #8d9296 76 | $ShowOddHighlight = lighten($ShowBgColor1,58%) 77 | $ShowOddHighlightOp = 0.05 78 | $SaisonListText = #fff 79 | $EpisodeListText = #8d9296 80 | $EpisodeSelectorBg = #1b2d4c 81 | $EpisodeSelectorHover = #26272d 82 | $EpisodeSelectorHoverTran = lighten($EpisodeSelectorHover,43%) 83 | $EpisodeSelectorText = #fff 84 | $ShowWatchedIcon_false = rgba(#fff,0.2) 85 | $ShowWatchedIcon_hover = rgba(#fff,0.4) 86 | $ShowWatchedIcon_true = #fff 87 | $QualitySelectorBg = rgba(43, 110, 210, 0.84) 88 | $QualitySelectorText = $ButtonText 89 | 90 | //File selector 91 | $FileSelectorText = #eee 92 | $FileSelectorBg = rgba(255, 255, 255, 0.05) 93 | $FileSelectorShadow = rgba(0, 0, 0, 0.67) 94 | 95 | //Settings 96 | $SettingsCategoriesText = #2d75df 97 | $SettingsSeparator = #3a3b3e 98 | $InputBoxBg = #393b3d 99 | $InputBoxText = #c3c5c7 100 | $SettingsText1 = #c3c5c7 101 | $SettingsText2 = #8a8b8e 102 | $SettingsButtonText = #e3e5e7 103 | $SettingsButtonTextHover = #e3e5e7 104 | 105 | // Notifications 106 | $NotificationText = #fff 107 | $NotificationInfo = #3498DB 108 | $NotificationSuccess = #27AE60 109 | $NotificationWarning = #dbb756 110 | $NotificationOrange = #f07f53 111 | $NotificationError = #f15153 112 | $NotificationBorderColor = #000 113 | $NotificationBtn = #000 114 | $NotificationBtnText = #fff 115 | $NotifAlertBg = #000 116 | $NotifAlertText = #fff 117 | 118 | //Misc 119 | $PngLogo = brightness(1.0) 120 | $PngLogo_BgColo1 = brightness(1.0) 121 | $WarningColor = #FFFF00 122 | $FavoriteColor = #df2d37 123 | 124 | //Player 125 | $PlayerColor = #2d75df 126 | 127 | // OFFICIAL POPCORN-THEME: DARK - END 128 | 129 | @import 'views/app' 130 | -------------------------------------------------------------------------------- /src/app/styl/Official_-_Flat_UI_theme.styl: -------------------------------------------------------------------------------- 1 | // OFFICIAL POPCORN-THEME: FLAT UI 2 | 3 | //Fonts 4 | $Font = 'Open Sans' 5 | $MainFont = 'Open Sans Semibold' 6 | $MainFontBold = 'Open Sans Bold' 7 | $AlternateFont = sans-serif 8 | $ButtonFont = 'Open Sans Semibold' 9 | 10 | //Main app 11 | $BgColor1 = #fff 12 | $BgColor2 = #eff0f2 13 | $Text1 = #34495e 14 | $Text2 = #d9d9d9 15 | $Text3 = rgba(#34495e,.8) 16 | $Text4 = rgba(#34495e,.8) 17 | $TextError = rgba(#2c3e50,.8) 18 | $CloseButton = #e74c3c 19 | $CloseButtonHover = #c0392b 20 | $SpinnerColor1 = rgba(#fff,.3) 21 | $SpinnerColor2 = #1ABC9C 22 | $ScrollBarBg = #ebedef 23 | $ScrollBarThumb = #1abc9c 24 | $ScrollBarThumbHover = #16a085 25 | 26 | //Top Bar 27 | $TitleBarBg = #34495e 28 | $TitleBarText = #fff 29 | $TbarIconFilter = none 30 | $FullScreenButton = #fff 31 | $FullScreenButtonHover = #1abc9c 32 | $FilterBarBg = #34495e 33 | $FilterBarText = #fff 34 | $FilterBarTextHover = #16a085 35 | $FilterBarActive = #16a085 36 | $SearchBoxBg = #495c6e 37 | $DropDownBg = #34495e 38 | $DropDownBgHover = #1ABC9C 39 | $DropDownTextHover = #fff 40 | $DropDownText = #e1e4e7 41 | $DropDownOpacity = 1.00 42 | $FilterBarIcon = #fff 43 | $FilterBarIconHover = #1ABC9C 44 | $FilterBarIcon-PosterWidth = #bababa 45 | $TopBarShadow = 0px 6px 8px -4px rgba(0, 0, 0, 0) 46 | 47 | //Buttons 48 | $ButtonBg = #1ABC9C 49 | $ButtonBgHover = #48c9b0 50 | $ButtonBgActive = #16a085 51 | $ButtonBgDisabled = #d1d5d8 52 | $ButtonBgOk = #27AE60 53 | $ButtonBgWarning = #F15153 54 | $ButtonText = #fff 55 | $ButtonTextHover = #fff 56 | $ButtonImgInvert = 0 57 | $ButtonRadius = 6px 58 | 59 | //Checkbox 60 | $CheckboxBg = #34495e 61 | $CheckboxCheck = #fff 62 | 63 | //Posters 64 | $PosterRadius = 0px 65 | $PosterHoverBorder = #1ABC9C 66 | $PosterHoverOverlay = rgba(#34495e, 0.5) 67 | $PosterHoverOverlayOpq = rgba(#34495e, 1) 68 | $PosterShadow = 0px 0px 1px 69 | 70 | //TV Series 71 | $ShowBgColor1 = #fff 72 | $ShowBgColor2 = #eff0f2 73 | $EpisodeDetailsBg = #fff 74 | $ShowText1 = #34495e 75 | $ShowText2 = #7f8c8d 76 | $ShowOddHighlight = lighten($ShowBgColor1,50%) 77 | $ShowOddHighlightOp = 0.1 78 | $SaisonListText = #34495e 79 | $EpisodeListText = #34495e 80 | $EpisodeSelectorBg = rgba(#003366,0.6) 81 | $EpisodeSelectorHover = darken($ShowBgColor2,10%) 82 | $EpisodeSelectorHoverTran = darken($EpisodeSelectorHover,43%) 83 | $EpisodeSelectorText = #fff 84 | $ShowWatchedIcon_false = rgba(#34495e,0.3) 85 | $ShowWatchedIcon_hover = rgba(#34495e,0.6) 86 | $ShowWatchedIcon_true = #34495e 87 | $QualitySelectorBg = $ButtonBg 88 | $QualitySelectorText = $ButtonText 89 | 90 | //File selector 91 | $FileSelectorText = #34495e 92 | $FileSelectorBg = rgba(0,0,0, 0.07) 93 | $FileSelectorShadow = rgba(0, 0, 0, 0) 94 | 95 | //Settings 96 | $SettingsCategoriesText = #16a085 97 | $SettingsSeparator = #2c3e50 98 | $InputBoxBg = #34495e 99 | $InputBoxText = #e1e4e7 100 | $SettingsText1 = #34495e 101 | $SettingsText2 = #999 102 | $SettingsButtonText = #fff 103 | $SettingsButtonTextHover = #fff 104 | 105 | // Notifications 106 | $NotificationText = #fff 107 | $NotificationInfo = #3498DB 108 | $NotificationSuccess = #27AE60 109 | $NotificationWarning = #dbb756 110 | $NotificationOrange = #f07f53 111 | $NotificationError = #f15153 112 | $NotificationBorderColor = #000 113 | $NotificationBtn = #000 114 | $NotificationBtnText = #fff 115 | $NotifAlertBg = #000 116 | $NotifAlertText = #fff 117 | 118 | //Misc 119 | $FavoriteColor = #e74c3c 120 | $WarningColor = #000 121 | $PngLogo = brightness(1.0) 122 | $PngLogo_BgColo1 = brightness(0.4) 123 | 124 | //Player 125 | $PlayerColor = #16a085 126 | 127 | // OFFICIAL POPCORN-THEME: FLAT - END 128 | 129 | @import 'views/app' 130 | -------------------------------------------------------------------------------- /src/app/styl/Official_-_Light_theme.styl: -------------------------------------------------------------------------------- 1 | // OFFICIAL POPCORN-THEME: LIGHT 2 | 3 | //Color variables: light colors 4 | White = #f8f8f8 5 | Grey = #f0f0f0 6 | GreenL = #00a300 7 | GreenD = #008f00 8 | Black = #1d1d1d 9 | 10 | //Fonts 11 | $Font = 'Open Sans' 12 | $MainFont = 'Open Sans Semibold' 13 | $MainFontBold = 'Open Sans Bold' 14 | $AlternateFont = sans-serif 15 | $ButtonFont = 'Open Sans Semibold' 16 | 17 | //Main app 18 | $BgColor1 = White 19 | $BgColor2 = Grey 20 | $Text1 = Black 21 | $Text2 = #373737 22 | $Text3 = #515151 23 | $Text4 = #6b6b6b 24 | $TextError = #3d3d3d 25 | $CloseButton = #b8b8b8 26 | $CloseButtonHover = #008f00 27 | $SpinnerColor1 = #00a300 28 | $SpinnerColor2 = rgba(70,186,78,0.9) 29 | $ScrollBarBg = #d1d1d1 30 | $ScrollBarThumb = GreenD 31 | $ScrollBarThumbHover = GreenL 32 | 33 | //Top Bar 34 | $TitleBarBg = #fafafa 35 | $TitleBarText = #000 36 | $TbarIconFilter = invert(100%) 37 | $FullScreenButton = #3d3d3d 38 | $FullScreenButtonHover = GreenD 39 | $FilterBarBg = #fafafa 40 | $FilterBarText = #414141 41 | $FilterBarTextHover = Black 42 | $FilterBarActive = GreenD 43 | $SearchBoxBg = Grey 44 | $DropDownBg = White 45 | $DropDownBgHover = Grey 46 | $DropDownTextHover = Black 47 | $DropDownText = #373737 48 | $DropDownOpacity = 0.97 49 | $FilterBarIcon = $Text2 50 | $FilterBarIconHover = GreenD 51 | $FilterBarIcon-PosterWidth = $Text4 52 | $TopBarShadow = 0px 6px 8px -4px rgba(0, 0, 0, .9) 53 | 54 | //Buttons 55 | $ButtonBg = #00a300 56 | $ButtonBgHover = #00b700 57 | $ButtonBgActive = #008f00 58 | $ButtonBgDisabled = #d8d8d8 59 | $ButtonBgOk = #27AE60 60 | $ButtonBgWarning = #F15153 61 | $ButtonText = #fff 62 | $ButtonTextHover = #fff 63 | $ButtonImgInvert = 0 64 | $ButtonRadius = 3px 65 | 66 | //Checkbox 67 | $CheckboxBg = #d8d8d8 68 | $CheckboxCheck = GreenD 69 | 70 | //Posters 71 | $PosterRadius = 4px 72 | $PosterHoverBorder = GreenL 73 | $PosterHoverOverlay = rgba(23, 24, 27, 0.6) 74 | $PosterHoverOverlayOpq = rgba(23, 24, 27, 1) 75 | $PosterShadow = 0px 0px 10px 76 | 77 | //TV Series 78 | $ShowBgColor1 = $BgColor1 79 | $ShowBgColor2 = $BgColor2 80 | $EpisodeDetailsBg = $BgColor1 81 | $ShowText1 = $Text1 82 | $ShowText2 = $Text2 83 | $ShowOddHighlight = lighten($ShowBgColor1,50%) 84 | $ShowOddHighlightOp = 0.1 85 | $SaisonListText = Black 86 | $EpisodeListText = $Text2 87 | $EpisodeSelectorBg = GreenD 88 | $EpisodeSelectorHover = darken($ShowBgColor2,10%) 89 | $EpisodeSelectorHoverTran = darken($EpisodeSelectorHover,43%) 90 | $EpisodeSelectorText = $ButtonText 91 | $ShowWatchedIcon_false = rgba(#1d1d1d,0.3) 92 | $ShowWatchedIcon_hover = rgba(#1d1d1d,0.7) 93 | $ShowWatchedIcon_true = rgba(#1d1d1d,0.95) 94 | 95 | //File selector 96 | $FileSelectorText = #1d1d1d 97 | $FileSelectorBg = rgba(0,0,0, 0.07) 98 | $FileSelectorShadow = rgba(0, 0, 0, 0.67) 99 | 100 | //Settings 101 | $SettingsCategoriesText = GreenD 102 | $SettingsSeparator = #aaa 103 | $InputBoxBg = #d8d8d8 104 | $InputBoxText = $Text1 105 | $SettingsText1 = $Text1 106 | $SettingsText2 = $Text4 107 | $SettingsButtonText = $ButtonText 108 | $SettingsButtonTextHover = $ButtonText 109 | $QualitySelectorBg = $ButtonBg 110 | $QualitySelectorText = $ButtonText 111 | 112 | // Notifications 113 | $NotificationText = #fff 114 | $NotificationInfo = #3498DB 115 | $NotificationSuccess = #27AE60 116 | $NotificationWarning = #dbb756 117 | $NotificationOrange = #f07f53 118 | $NotificationError = #f15153 119 | $NotificationBorderColor = #000 120 | $NotificationBtn = #000 121 | $NotificationBtnText = #fff 122 | $NotifAlertBg = #000 123 | $NotifAlertText = #fff 124 | 125 | //Misc 126 | $FavoriteColor = #ee1111 127 | $WarningColor = #A52A2A 128 | $PngLogo = brightness(0.3) 129 | $PngLogo_BgColo1 = brightness(0.3) 130 | 131 | //Player 132 | $PlayerColor = GreenL 133 | 134 | // OFFICIAL POPCORN-THEME: LIGHT - END 135 | 136 | @import 'views/app' 137 | -------------------------------------------------------------------------------- /src/app/styl/Sebastiaans_-_Black_&_Red_theme.styl: -------------------------------------------------------------------------------- 1 | // THEME: BLACK & RED 2 | 3 | //Color variables: black & red 4 | Black = #070707 5 | RedL = #F31616 6 | RedM = #E32020 7 | RedD = #D40000 8 | 9 | //Fonts 10 | $Font = 'Open Sans' 11 | $MainFont = 'Open Sans Semibold' 12 | $MainFontBold = 'Open Sans Bold' 13 | $AlternateFont = sans-serif 14 | $ButtonFont = 'Open Sans Semibold' 15 | 16 | //Main app 17 | $BgColor1 = Black 18 | $BgColor2 = lighten(Black,5%) 19 | $Text1 = #fff 20 | $Text2 = #C9C9C9 21 | $Text3 = #939597 22 | $Text4 = #fff 23 | $TextError = #797979 24 | $CloseButton = #fff 25 | $CloseButtonHover = RedL 26 | $SpinnerColor1 = RedM 27 | $SpinnerColor2 = RedD 28 | $ScrollBarBg = #30333c 29 | $ScrollBarThumb = RedM 30 | $ScrollBarThumbHover = RedD 31 | 32 | //Top Bar 33 | $TitleBarBg = Black 34 | $TitleBarText = #fff 35 | $TbarIconFilter = none 36 | $FullScreenButton = #999 37 | $FullScreenButtonHover = RedL 38 | $FilterBarBg = Black 39 | $FilterBarText = #e9e9e9 40 | $FilterBarTextHover = #fff 41 | $FilterBarActive = RedL 42 | $SearchBoxBg = #191818 43 | $DropDownBg = rgba(Black,0.9) 44 | $DropDownBgHover = rgba(RedL,0.15) 45 | $DropDownTextHover = #fff 46 | $DropDownText = #d9d9d9 47 | $DropDownOpacity = 0.97 48 | $FilterBarIcon = $Text1 49 | $FilterBarIconHover = RedD 50 | $FilterBarIcon-PosterWidth = #bababa 51 | $TopBarShadow = 0px 6px 8px -4px rgba(0, 0, 0, .9) 52 | 53 | //Buttons 54 | $ButtonBg = RedL 55 | $ButtonBgHover = RedD 56 | $ButtonBgActive = RedD 57 | $ButtonBgDisabled = #737577 58 | $ButtonBgOk = #27AE60 59 | $ButtonBgWarning = #F15153 60 | $ButtonText = #fff 61 | $ButtonTextHover = #fff 62 | $ButtonImgInvert = 0 63 | $ButtonRadius = 3px 64 | 65 | //Checkbox 66 | $CheckboxBg = #545454 67 | $CheckboxCheck = RedD 68 | 69 | //Posters 70 | $PosterRadius = 4px 71 | $PosterHoverBorder = $ButtonBgActive 72 | $PosterHoverOverlay = rgba(23, 24, 27, 0.6) 73 | $PosterHoverOverlayOpq = rgba(23, 24, 27, 1) 74 | $PosterShadow = 0px 0px 10px 75 | 76 | //TV Series 77 | $ShowBgColor1 = $BgColor1 78 | $ShowBgColor2 = $BgColor2 79 | $EpisodeDetailsBg = $BgColor1 80 | $ShowText1 = $Text1 81 | $ShowText2 = $Text2 82 | $ShowOddHighlight = lighten($ShowBgColor1,50%) 83 | $ShowOddHighlightOp = 0.05 84 | $SaisonListText = $Text1 85 | $EpisodeListText = $Text2 86 | $EpisodeSelectorBg = darken(RedD,2%) 87 | $EpisodeSelectorHover = #26272d 88 | $EpisodeSelectorHoverTran = lighten($EpisodeSelectorHover,43%) 89 | $EpisodeSelectorText = $ButtonText 90 | $ShowWatchedIcon_false = rgba(#fff,0.2) 91 | $ShowWatchedIcon_hover = rgba(#fff,0.4) 92 | $ShowWatchedIcon_true = #fff 93 | $QualitySelectorBg = $ButtonBg 94 | $QualitySelectorText = $ButtonText 95 | 96 | //File selector 97 | $FileSelectorText = #eee 98 | $FileSelectorBg = rgba(255, 255, 255, 0.05) 99 | $FileSelectorShadow = rgba(0, 0, 0, 0.67) 100 | 101 | //Settings 102 | $SettingsCategoriesText = RedD 103 | $SettingsSeparator = #333 104 | $InputBoxBg = #232324 105 | $InputBoxText = $Text2 106 | $SettingsText1 = $Text2 107 | $SettingsText2 = $Text3 108 | $SettingsButtonText = $ButtonText 109 | $SettingsButtonTextHover = $ButtonText 110 | 111 | // Notifications 112 | $NotificationText = #fff 113 | $NotificationInfo = #3498DB 114 | $NotificationSuccess = #27AE60 115 | $NotificationWarning = #dbb756 116 | $NotificationOrange = #f07f53 117 | $NotificationError = #f15153 118 | $NotificationBorderColor = #000 119 | $NotificationBtn = #000 120 | $NotificationBtnText = #fff 121 | $NotifAlertBg = #000 122 | $NotifAlertText = #fff 123 | 124 | //Misc 125 | $PngLogo = brightness(1.0) 126 | $FavoriteColor = #df2d37 127 | $PosterFavHover = RedD 128 | $PosterFavSelected = RedD 129 | $PosterWatchedHover = RedD 130 | $FavoriteColor = #df2d37 131 | 132 | //Player 133 | $PlayerColor = RedL 134 | 135 | // THEME: BLACK & RED - END 136 | 137 | @import 'views/app' 138 | -------------------------------------------------------------------------------- /src/app/styl/views/app.styl: -------------------------------------------------------------------------------- 1 | support-for-ie = false 2 | vendor-prefixes = webkit official 3 | 4 | @import 'nib' 5 | 6 | global-reset() 7 | 8 | $TitleHeight = 32px 9 | $FilterBarHeight = 35px 10 | $HeaderHeight = 65px 11 | $MovieListPadding = calc(100% - 67px) 12 | 13 | @import 'fonts' 14 | @import 'misc' 15 | @import 'bootstrap_theme' 16 | @import 'button' 17 | @import 'player' 18 | @import 'videojs' 19 | @import 'dropdowns' 20 | @import 'main_window' 21 | @import 'initializing' 22 | @import 'title_bar' 23 | @import 'windows_titlebar' 24 | @import 'filter_bar' 25 | @import 'list' 26 | @import 'item' 27 | @import 'movie_detail' 28 | @import 'movie_error' 29 | @import 'show_detail' 30 | @import 'torrent_list' 31 | @import 'about' 32 | @import 'keyboard' 33 | @import 'file_selector' 34 | @import 'torrent_collection' 35 | @import 'settings_container' 36 | @import 'loading' 37 | @import 'notification' 38 | @import 'seedbox' 39 | -------------------------------------------------------------------------------- /src/app/styl/views/bootstrap_theme.styl: -------------------------------------------------------------------------------- 1 | .nav 2 | font-size 0.8em 3 | 4 | .header_icon 5 | font-size 20px 6 | color $Text3 7 | 8 | ul.nav-hor > li 9 | float left 10 | 11 | .dropdown-menu 12 | background-color $DropDownBg 13 | opacity $DropDownOpacity 14 | 15 | & > li > a 16 | transition all 0.2s 17 | color $DropDownText 18 | opacity 1 19 | padding 4px 20px 20 | 21 | &.active 22 | color $DropDownTextHover 23 | background-color $DropDownBgHover 24 | background-image none 25 | border-left 4px solid $FilterBarText 26 | 27 | &:focus, 28 | &:hover 29 | color $DropDownTextHover 30 | background-color $DropDownBgHover 31 | background-image none 32 | border-left 4px solid $FilterBarActive 33 | 34 | .playerchoice 35 | padding-left 5px 36 | margin-left 8px 37 | margin-right -5px 38 | border-left 1px solid rgba(255,255,255,.2) 39 | 40 | img 41 | width 20px 42 | height 20px 43 | margin 7.5px 2px 44 | float left 45 | filter invert($ButtonImgInvert) 46 | 47 | &:after 48 | content '' 49 | width 30px 50 | height 100% 51 | top 0 52 | right 0 53 | position absolute 54 | 55 | .playerchoicemenu 56 | font-size 1em 57 | text-align left 58 | left auto 59 | right -2px 60 | 61 | & > li > a 62 | padding-left 10px 63 | padding-right 25px 64 | text-overflow ellipsis 65 | overflow hidden 66 | max-width 200px 67 | 68 | img 69 | height 20px 70 | width 20px 71 | position absolute 72 | right 3px 73 | -webkit-filter $PngLogo 74 | 75 | .playerchoicetoolbar 76 | background $DropDownBg 77 | position absolute 78 | width 50px 79 | height 22px 80 | top -26px 81 | right 0px 82 | border-radius 30px 83 | 84 | .playerchoicerefresh 85 | top 3px 86 | left 6px 87 | padding 2px 88 | position absolute 89 | color $DropDownText 90 | opacity 0.3 91 | transition color 0.4s ease, opacity 0.4s ease 92 | 93 | &:hover 94 | color $DropDownTextHover 95 | opacity 0.97 96 | 97 | &.spin 98 | color $DropDownTextHover 99 | opacity 0.97 100 | 101 | .playerchoicehelp 102 | top 3px 103 | right 6px 104 | padding 2px 105 | position absolute 106 | color $DropDownText 107 | opacity 0.3 108 | transition color 0.4s ease, opacity 0.4s ease 109 | 110 | &:hover 111 | color $DropDownTextHover 112 | opacity 0.97 113 | 114 | .splayerlist 115 | display inline-block 116 | width 420px 117 | line-height 20px 118 | padding 4px 0px 14px 119 | -------------------------------------------------------------------------------- /src/app/styl/views/button.styl: -------------------------------------------------------------------------------- 1 | .button 2 | cursor pointer 3 | height 35px 4 | margin-right 15px 5 | border-radius $ButtonRadius 6 | background-clip padding-box 7 | background-color $ButtonBg 8 | padding 0 15px 9 | transition background-color 0.5s 10 | color $ButtonText 11 | font-family $MainFont 12 | font-smoothing antialiased 13 | text-align center 14 | font-size 12px 15 | line-height 34px 16 | 17 | &:hover 18 | background $ButtonBgHover 19 | text-decoration none 20 | 21 | &:active 22 | box-shadow inset 0 1px 4px rgba(0,0,0,0.6) 23 | background $ButtonBgActive 24 | 25 | &.disabled 26 | cursor not-allowed 27 | opacity 0.2 28 | filter grayscale(100%) 29 | -------------------------------------------------------------------------------- /src/app/styl/views/fonts.styl: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family 'Open Sans' 3 | src url(../fonts/OpenSans-Regular.woff) 4 | font-weight normal 5 | font-style normal 6 | 7 | @font-face 8 | font-family 'Open Sans Bold' 9 | src url(../fonts/OpenSans-Bold.woff) 10 | font-weight normal 11 | font-style normal 12 | 13 | @font-face 14 | font-family 'Open Sans Semibold' 15 | src url(../fonts/OpenSans-Semibold.woff) 16 | font-weight normal 17 | font-style normal 18 | 19 | @font-face 20 | font-family 'VideoJS' 21 | src url('../fonts/vjs.eot') 22 | src url('../fonts/vjs.eot?#iefix') format('embedded-opentype'), url('../fonts/vjs.woff') format('woff'), url('../fonts/vjs.ttf') format('truetype') 23 | font-weight normal 24 | font-style normal 25 | -------------------------------------------------------------------------------- /src/app/styl/views/list.styl: -------------------------------------------------------------------------------- 1 | .list 2 | height 100% 3 | width calc(100% - 5px) 4 | fixed top left 5 | text-align center 6 | scrollable() 7 | 8 | &::-webkit-scrollbar-track 9 | margin-top $HeaderHeight 10 | margin-bottom 5px 11 | 12 | .items_show 13 | padding-top $HeaderHeight 14 | -webkit-user-select none 15 | text-align left 16 | display flex 17 | flex-direction row 18 | flex-wrap wrap 19 | justify-content space-between 20 | align-content flex-start 21 | align-items flex-start 22 | width calc(50% - 5px) 23 | height 100% 24 | float left 25 | background rgba(138, 138, 138, 0.14) 26 | scrollable() 27 | 28 | &::-webkit-scrollbar-track 29 | margin-top $HeaderHeight 30 | 31 | .title_history 32 | color $Text1 33 | padding 25px 10px 10px 34 | font-size 1.2em 35 | text-align left 36 | 37 | .items 38 | padding-top 0 39 | 40 | .items_movie 41 | padding-top $HeaderHeight 42 | -webkit-user-select none 43 | text-align left 44 | display flex 45 | flex-direction row 46 | flex-wrap wrap 47 | justify-content space-between 48 | align-content flex-start 49 | align-items flex-start 50 | width calc(50% - 5px) 51 | height 100% 52 | float left 53 | scrollable() 54 | 55 | &::-webkit-scrollbar-track 56 | margin-top $HeaderHeight 57 | 58 | .title_history 59 | color $Text1 60 | padding 25px 10px 10px 61 | font-size 1.2em 62 | text-align left 63 | 64 | .items 65 | padding-top 0 66 | .items 67 | padding-top $HeaderHeight 68 | -webkit-user-select none 69 | text-align left 70 | display flex 71 | flex-direction row 72 | flex-wrap wrap 73 | justify-content space-between 74 | align-content flex-start 75 | align-items flex-start 76 | width calc(100% - 5px) 77 | will-change transform, scroll-position 78 | 79 | .error 80 | font-size 1.5em 81 | text-align center 82 | margin-top 180px 83 | width 80% 84 | margin-left 10% 85 | position absolute 86 | color $TextError 87 | font-weight bold 88 | 89 | > .spinner 90 | position fixed 91 | width 100% 92 | height 100% 93 | background rgba(0, 0, 0, 0.5) center center no-repeat 94 | pointer-events all 95 | z-index 1 96 | 97 | .loading-container 98 | margin 50vh auto 0px 99 | opacity .8 100 | .default-frame 101 | .list 102 | &::-webkit-scrollbar-track 103 | margin-top $FilterBarHeight 104 | .items_show 105 | &::-webkit-scrollbar-track 106 | margin-top $FilterBarHeight 107 | .items_movie 108 | &::-webkit-scrollbar-track 109 | margin-top $FilterBarHeight 110 | -------------------------------------------------------------------------------- /src/app/styl/views/main_window.styl: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | .main-window-region 4 | background-color $BgColor1 5 | height 100% 6 | -moz-user-select none 7 | -khtml-user-select none 8 | -webkit-user-select none 9 | -o-user-select none 10 | 11 | .trailer_mouse_catch 12 | display none 13 | width 100% 14 | height calc(100% - 115px) 15 | top 75px 16 | z-index 12 17 | position absolute 18 | 19 | #main-window 20 | width 100% 21 | height 100% 22 | background-color $BgColor1 23 | box-shadow 0 0 40px rgba(0, 0, 0, .75) 24 | margin-left auto 25 | margin-right auto 26 | 27 | font-family $MainFont, $Font, $AlternateFont 28 | -webkit-font-smoothing antialiased 29 | 30 | cursor default 31 | 32 | button 33 | background-color transparent 34 | border none 35 | cursor pointer 36 | outline none 37 | 38 | #content 39 | position absolute 40 | width 100% 41 | height 100% 42 | overflow hidden 43 | 44 | .drop-indicator 45 | position fixed 46 | background-color $ButtonBgActive 47 | z-index 1000 48 | display none 49 | 50 | &.right 51 | top 4px 52 | right 0px 53 | width 4px 54 | height 100% 55 | &.left 56 | bottom 4px 57 | left 0px 58 | width 4px 59 | height 100% 60 | &.bottom 61 | bottom 0px 62 | right 4px 63 | width 100% 64 | height 4px 65 | &.top 66 | top 0px 67 | left 4px 68 | width 100% 69 | height 4px 70 | 71 | #drop-mask 72 | position absolute 73 | width 100% 74 | height 100% 75 | z-index 999 76 | display none 77 | -webkit-filter blur(5px) 78 | filter blur(5px) 79 | 80 | .close-icon 81 | top 45px 82 | right 20px 83 | position fixed 84 | color $CloseButton 85 | font-size 25px 86 | line-height 25px 87 | cursor pointer 88 | z-index 100 89 | transition all .5s 90 | font-smoothing antialiased 91 | 92 | &:hover 93 | color $CloseButtonHover 94 | transition all .5s 95 | 96 | .movie-detail, .shows-container-contain 97 | .close-icon 98 | top 75px 99 | 100 | .knm 101 | transition all 3000ms ease-in-out 102 | transform rotate(180deg) 103 | 104 | .dragzone 105 | width 86% 106 | height 32px 107 | z-index 11 108 | 109 | .notification_alert 110 | position fixed 111 | pointer-events none 112 | background-color rgba($NotifAlertBg,.8) 113 | border-radius 4px 114 | padding 10px 115 | top 78px 116 | right 20px 117 | color $NotifAlertText 118 | font-family $MainFont, $AlternateFont 119 | font-size 13px 120 | z-index 999 121 | display none 122 | 123 | #main-window.default-frame 124 | .movie-detail 125 | padding-top 35px 126 | .filter-bar 127 | top 0 128 | .list .items 129 | padding-top 30px 130 | -------------------------------------------------------------------------------- /src/app/styl/views/misc.styl: -------------------------------------------------------------------------------- 1 | fadeicon(image, posx, posx2, size) 2 | width size 3 | height size 4 | position relative 5 | &:before 6 | content "" 7 | position absolute 8 | top 0 9 | left 0 10 | bottom 0 11 | right 0 12 | width size 13 | height size 14 | background-image url(../images/icons/image.png) 15 | background-position posx 0px 16 | background-repeat no-repeat 17 | opacity 1 18 | -webkit-transition opacity 0.5s 19 | &:after 20 | content "" 21 | position absolute 22 | top 0 23 | left 0 24 | bottom 0 25 | right 0 26 | width size 27 | height size 28 | background-image url(../images/icons/image.png) 29 | background-position posx2 0px 30 | background-repeat no-repeat 31 | opacity 0 32 | -webkit-transition opacity 0.5s 33 | &:hover:before 34 | opacity 0 35 | &:hover:after 36 | opacity 1 37 | 38 | .left 39 | float left 40 | 41 | .right 42 | float right 43 | 44 | scrollable() 45 | overflow-x hidden 46 | overflow-y overlay 47 | -webkit-overflow-scrolling touch 48 | 49 | &::-webkit-scrollbar 50 | width 5px 51 | 52 | &::-webkit-scrollbar-track 53 | background-color $ScrollBarBg 54 | border-radius 2px 55 | 56 | &::-webkit-scrollbar-thumb 57 | background-color $ScrollBarThumb 58 | border-radius 2px 59 | 60 | &:hover 61 | background-color $ScrollBarThumbHover 62 | 63 | &::-webkit-resizer, 64 | &::-webkit-scrollbar-corner, 65 | &::-webkit-scrollbar-button, 66 | &::-webkit-scrollbar-track-piece 67 | display none 68 | 69 | .tooltip-arrow 70 | position absolute 71 | width 0 72 | height 0 73 | border-color transparent 74 | border-style solid 75 | 76 | .tooltip 77 | &.top 78 | .tooltip-arrow 79 | bottom 0 80 | left 50% 81 | margin-left -5px 82 | border-width 5px 5px 0 83 | border-top-color rgba(9, 13, 18, 0.75) 84 | transition all 0.5s 85 | &.top-left 86 | .tooltip-arrow 87 | bottom 0 88 | left 5px 89 | border-width 5px 5px 0 90 | border-top-color rgba(9, 13, 18, 0.75) 91 | transition all 0.5s 92 | &.top-right 93 | .tooltip-arrow 94 | right 5px 95 | bottom 0 96 | border-width 5px 5px 0 97 | border-top-color rgba(9, 13, 18, 0.75) 98 | transition all 0.5s 99 | &.right 100 | .tooltip-arrow 101 | top 50% 102 | left 0 103 | margin-top -5px 104 | border-width 5px 5px 5px 0 105 | border-right-color rgba(9, 13, 18, 0.75) 106 | transition all 0.5s 107 | &.left 108 | .tooltip-arrow 109 | top 50% 110 | right 0 111 | margin-top -5px 112 | border-width 5px 0 5px 5px 113 | border-left-color rgba(9, 13, 18, 0.75) 114 | transition all 0.5s 115 | &.bottom 116 | .tooltip-arrow 117 | top 0 118 | left 50% 119 | margin-left -5px 120 | border-width 0 5px 5px 121 | border-bottom-color rgba(9, 13, 18, 0.75) 122 | transition all 0.5s 123 | &.bottom-left 124 | .tooltip-arrow 125 | top 0 126 | left 5px 127 | border-width 0 5px 5px 128 | border-bottom-color rgba(9, 13, 18, 0.75) 129 | transition all 0.5s 130 | &.bottom-right 131 | .tooltip-arrow 132 | top 0 133 | right 5px 134 | border-width 0 5px 5px 135 | border-bottom-color rgba(9, 13, 18, 0.75) 136 | transition all 0.5s 137 | 138 | .tooltip-inner 139 | max-width 280px 140 | padding 4px 8px 141 | color #fff 142 | text-align center 143 | text-decoration none 144 | background-color rgba(9, 13, 18, 0.75) 145 | border-radius 4px 146 | font-family $MainFont !important 147 | font-size 12px !important 148 | line-height 18px !important 149 | 150 | .toolcimg 151 | max-width 102px 152 | border 1px solid black 153 | border-radius 5% 154 | box-shadow 0 4px 8px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.3) 155 | -------------------------------------------------------------------------------- /src/app/styl/views/movie_error.styl: -------------------------------------------------------------------------------- 1 | #movie-error 2 | .button 3 | position absolute 4 | cursor pointer 5 | height 35px 6 | width 250px 7 | top 0 8 | margin-top 275px 9 | border-radius $ButtonRadius 10 | background-clip padding-box 11 | background-color $ButtonBg 12 | padding 0 15px 13 | transition background-color 0.5s 14 | color $ButtonText 15 | font-family $MainFont 16 | font-smoothing antialiased 17 | text-align center 18 | font-size 12px 19 | line-height 34px 20 | 21 | &:hover 22 | background $ButtonBgHover 23 | text-decoration none 24 | 25 | &:active 26 | box-shadow inset 0 1px 4px rgba(0,0,0,0.6) 27 | background $ButtonBgActive 28 | 29 | .button-text 30 | margin-left 12px 31 | margin-right 12px 32 | 33 | &:after 34 | content '' 35 | width 70% 36 | height 100% 37 | top 0 38 | left 0 39 | position absolute 40 | 41 | i 42 | font-size 14px 43 | 44 | .retry-button 45 | visibility hidden 46 | position relative 47 | display inline-block 48 | 49 | .change-api, 50 | .online-search 51 | visibility hidden 52 | width auto 53 | min-width 250px 54 | max-width 300px 55 | text-overflow ellipsis 56 | white-space nowrap 57 | position relative 58 | display inline-block 59 | -------------------------------------------------------------------------------- /src/app/styl/views/notification.styl: -------------------------------------------------------------------------------- 1 | #notification .notification 2 | display block 3 | position absolute 4 | top 70px 5 | border-radius 3px 6 | height auto 7 | min-width 300px 8 | -webkit-user-select none 9 | opacity 0.9 10 | right 60px 11 | z-index 5000 12 | font-size 13px 13 | font-weight bold 14 | text-align center 15 | line-height 27px 16 | padding 11px 15px 17 | color $NotificationText 18 | border 1px solid rgba($NotificationBorderColor,.7) 19 | transition all .5s 20 | 21 | &:empty 22 | display none 23 | 24 | &:hover 25 | opacity 1 26 | 27 | &.info 28 | background $NotificationInfo 29 | 30 | &.success 31 | background $NotificationSuccess 32 | 33 | &.warning 34 | background $NotificationWarning 35 | 36 | &.danger 37 | background $NotificationOrange 38 | 39 | &.error 40 | background $NotificationError 41 | 42 | .close 43 | position absolute 44 | top 6px 45 | right 8px 46 | opacity 0.7 47 | color white 48 | &:hover 49 | opacity 1 50 | 51 | .btn-grp 52 | position relative 53 | top 6px 54 | padding-bottom 6px 55 | margin-right auto 56 | margin-left auto 57 | 58 | .btn 59 | background $NotificationBtn 60 | opacity .4 61 | color $NotificationBtnText 62 | transition all .5s 63 | 64 | &:hover 65 | background $NotificationBtn 66 | opacity .6 67 | 68 | &:active 69 | box-shadow inset 0 1px 4px rgba(0,0,0,0.6) 70 | box-shadow inset 0 1px 4px rgba(0,0,0,0.6) 71 | background $NotificationBtn 72 | opacity .7 73 | 74 | label 75 | cursor pointer 76 | 77 | .btn.chnglog 78 | margin-right 5px 79 | 80 | h1 81 | font-size 20px 82 | 83 | p 84 | font-weight 100 85 | line-height 20px 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/app/styl/views/torrent_list.styl: -------------------------------------------------------------------------------- 1 | #torrent-list, #torrent-show-list 2 | overflow auto 3 | color $Text1 4 | font-size 13px 5 | overflow-x hidden 6 | overflow-y overlay 7 | -webkit-overflow-scrolling touch 8 | 9 | &::-webkit-scrollbar 10 | width 5px 11 | 12 | &::-webkit-scrollbar-track 13 | background-color $ScrollBarBg 14 | border-radius 2px 15 | margin-top 10px 16 | margin-bottom 10px 17 | 18 | &::-webkit-scrollbar-thumb 19 | background-color $ScrollBarThumb 20 | border-radius 2px 21 | 22 | &:hover 23 | background-color $ScrollBarThumbHover 24 | 25 | &::-webkit-resizer, 26 | &::-webkit-scrollbar-corner, 27 | &::-webkit-scrollbar-button, 28 | &::-webkit-scrollbar-track-piece 29 | display none 30 | 31 | table 32 | width 100% 33 | table-layout auto 34 | tr 35 | cursor pointer 36 | 37 | &:nth-of-type(odd) 38 | td 39 | background rgba(0,0,0,0) 40 | 41 | &:hover > 42 | td 43 | background rgba($EpisodeSelectorHoverTran,20%) 44 | 45 | td.transpOff 46 | background $EpisodeSelectorHover 47 | 48 | &.disabled 49 | cursor not-allowed 50 | opacity 0.7 51 | filter grayscale(30%) 52 | 53 | td 54 | padding 3px 55 | color $EpisodeListText 56 | 57 | &.ellipsis 58 | position relative 59 | &.ellipsis:before 60 | content ' ' 61 | visibility hidden 62 | &.ellipsis span 63 | position absolute 64 | left 0 65 | right 0 66 | white-space nowrap 67 | overflow hidden 68 | text-overflow ellipsis 69 | max-width fit-content 70 | margin-top -9px 71 | padding 9px 3px 9px 0 72 | 73 | &.provider 74 | width 35px 75 | line-height 30px 76 | padding-left 8px 77 | img 78 | cursor pointer 79 | height 16px 80 | vertical-align top 81 | margin-top 7px 82 | 83 | &.info 84 | width 0.1% 85 | white-space nowrap 86 | text-align right 87 | padding 3px 10px 88 | &.action 89 | width 40px 90 | cursor pointer 91 | padding-left 8px 92 | 93 | &:hover 94 | color $ShowText1 95 | transition color .5s 96 | 97 | #torrent-show-list 98 | font-size 15px 99 | margin 6px 1px 19px 0px 100 | max-height 281px 101 | 102 | table 103 | td 104 | &.info, &.action 105 | color $ShowWatchedIcon_false 106 | 107 | tr:nth-of-type(odd) 108 | td 109 | background rgba($ShowOddHighlight, $ShowOddHighlightOp) 110 | &.transpOff 111 | background $ShowBgColor2 112 | 113 | &:hover > 114 | td 115 | background rgba($EpisodeSelectorHoverTran,20%) 116 | 117 | td.transpOff 118 | background $EpisodeSelectorHover 119 | -------------------------------------------------------------------------------- /src/app/styl/views/windows_titlebar.styl: -------------------------------------------------------------------------------- 1 | .windows-titlebar 2 | position absolute 3 | top 0 4 | left 0 5 | width 100% 6 | height 24px 7 | display flex 8 | align-items center 9 | background-color $TitleBarBg 10 | border-top 1px solid #000 11 | font-size 12px 12 | color $TitleBarText 13 | font-family $MainFont 14 | 15 | .windows-titlebar .events 16 | top 0 17 | left 50% 18 | margin-left -78px 19 | 20 | .icon 21 | height 20px 22 | width 20px 23 | margin-left auto 24 | margin-top 5px 25 | 26 | .windows-titlebar-title 27 | top initial !important 28 | padding-left 12px !important 29 | padding-top 5px 30 | font-size 14px !important 31 | color inherit !important 32 | text-shadow black 0 0 0 33 | width auto 34 | margin-right -64px 35 | opacity 0.97 36 | 37 | .window-controls 38 | margin-left auto 39 | display flex 40 | align-items center 41 | -webkit-app-region no-drag 42 | 43 | .window-control 44 | padding 0 45 | height 24px 46 | width 30px 47 | cursor initial !important 48 | opacity 0.97 49 | 50 | .window-close:hover 51 | opacity 1 52 | background-color rgba(232, 17, 35, .9) !important 53 | 54 | .window-minimize, 55 | .window-maximize 56 | opacity 0.87 57 | 58 | .window-minimize:hover, 59 | .window-maximize:hover 60 | opacity 1 61 | background-color #3a3939 !important 62 | 63 | .control-icon 64 | width 100% 65 | height 100% 66 | 67 | .window-close-icon 68 | background url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 11 11' fill='none'%3E%3Cpath d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z' fill='%23fff'/%3E%3C/svg%3E") no-repeat 50% 50% 69 | background-size 10px 70 | filter $TbarIconFilter 71 | 72 | .window-close:hover .window-close-icon 73 | background url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 11 11' fill='none'%3E%3Cpath d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z' fill='%23fff'/%3E%3C/svg%3E") no-repeat 50% 50% 74 | background-size 10px 75 | filter none 76 | 77 | .window-maximize-icon 78 | background url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 11 11' fill='none'%3E%3Cpath d='M11 0v11H0V0h11zM9.899 1.101H1.1V9.9h8.8V1.1z' fill='%23fff'/%3E%3C/svg%3E") no-repeat 50% 50% 79 | background-size 10px 80 | filter $TbarIconFilter 81 | 82 | .window-maximize:hover .window-maximize-icon 83 | background url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 11 11' fill='none'%3E%3Cpath d='M11 0v11H0V0h11zM9.899 1.101H1.1V9.9h8.8V1.1z' fill='%23fff'/%3E%3C/svg%3E") no-repeat 50% 50% 84 | background-size 10px 85 | filter none 86 | 87 | .window-umaximize .window-maximize-icon 88 | background url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 11 11' fill='none'%3E%3Cpath d='M11 8.798H8.798V11H0V2.202h2.202V0H11v8.798zm-3.298-5.5h-6.6v6.6h6.6v-6.6zM9.9 1.1H3.298v1.101h5.5v5.5h1.1v-6.6z' fill='%23fff'/%3E%3C/svg%3E") no-repeat 50% 50% 89 | background-size 10px 90 | filter $TbarIconFilter 91 | 92 | .window-umaximize:hover .window-maximize-icon 93 | background url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 11 11' fill='none'%3E%3Cpath d='M11 8.798H8.798V11H0V2.202h2.202V0H11v8.798zm-3.298-5.5h-6.6v6.6h6.6v-6.6zM9.9 1.1H3.298v1.101h5.5v5.5h1.1v-6.6z' fill='%23fff'/%3E%3C/svg%3E") no-repeat 50% 50% 94 | background-size 10px 95 | filter none 96 | 97 | .window-minimize-icon 98 | background url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 11 11' fill='none'%3E%3Cpath d='M11 4.399V5.5H0V4.399h11z' fill='%23fff'/%3E%3C/svg%3E") no-repeat 50% 50% 99 | background-size 10px 100 | filter $TbarIconFilter 101 | 102 | .window-minimize:hover .window-minimize-icon 103 | background url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 11 11' fill='none'%3E%3Cpath d='M11 4.399V5.5H0V4.399h11z' fill='%23fff'/%3E%3C/svg%3E") no-repeat 50% 50% 104 | background-size 10px 105 | filter none 106 | -------------------------------------------------------------------------------- /src/app/templates/about.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 | 8 | 16 | 17 |
18 |
19 | <%= i18n.__("%s is the result of many developers and designers putting a bunch of APIs together to make the experience of watching torrent movies as simple as possible.", Settings.projectName) %>
20 | <%= i18n.__("We are an open source project. We are from all over the world. We love our movies. And boy, do we love popcorn.") %> 21 |
22 |
23 | 24 |
25 | <% if (Settings.projectUrl) { %><% } %> 26 | <% if (Settings.sourceUrl) { %><% } %> 27 | <% if (Settings.projectForum) { %><% } %> 28 | <% if (Settings.projectBlog) { %><% } %> 29 | <% if (Settings.projectBlog) { %><% } %> 30 |
31 | 32 |
33 | <%= i18n.__("Made with") %> <%= i18n.__("by a bunch of geeks from All Around The World") %> 34 |
35 | 36 |
37 |
38 |
<%=i18n.__("Changelog")%>
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /src/app/templates/browser.tpl: -------------------------------------------------------------------------------- 1 |
2 |
-------------------------------------------------------------------------------- /src/app/templates/changelog.tpl: -------------------------------------------------------------------------------- 1 |
2 |

<%= title %>

3 |

<%= description %>

4 |
    5 | <% for(var i = 0; i < changeLog.length; i++) { %> 6 |
  • <%= changeLog[i] %>
  • 7 | <% } %> 8 |
-------------------------------------------------------------------------------- /src/app/templates/disclaimer.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |

<%= i18n.__("Terms of Service") %>

7 |
8 |

Your Acceptance

9 | 10 |

By using the '<%= Settings.projectName %>' app you signify your agreement to (1) these terms and conditions (the 'Terms of Service').

11 | 12 |

Privacy Policy.

13 | 14 |

You understand that by using '<%= Settings.projectName %>' you may encounter material that you may deem to be offensive, indecent, or objectionable, and that such content may or may not be identified as having explicit material. '<%= Settings.projectName %>' will have no liability to you for such material – you agree that your use of '<%= Settings.projectName %>' is at your sole risk.

15 | 16 |

DISCLAIMERS

17 | 18 |

YOU EXPRESSLY AGREE THAT YOUR USE OF '<%= Settings.projectName %>' IS AT YOUR SOLE RISK. '<%= Settings.projectName %>' AND ALL PRODUCTS ARE PROVIDED TO YOU “AS IS” WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. '<%= Settings.projectName %>' MAKES ABSOLUTELY NO WARRANTIES WHATSOEVER, EXPRESS OR IMPLIED. TO THE FULLEST EXTENT POSSIBLE UNDER APPLICABLE LAWS, YIFY DISCLAIMS ALL WARRANTIES, EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR OTHER VIOLATIONS OF RIGHTS.

19 | 20 |

LIMITATION OF LIABILITY

21 | 22 |

'<%= Settings.projectName %>' IS NOT RESPONSIBLE FOR ANY PROBLEMS OR TECHNICAL MALFUNCTION OF ANY WEBSITE, NETWORK, COMPUTER SYSTEMS, SERVERS, PROVIDERS, COMPUTER EQUIPMENT, OR SOFTWARE, OR FOR ANY FAILURE DUE TO TECHNICAL PROBLEMS OR TRAFFIC CONGESTION ON THE INTERNET OR '<%= Settings.projectName %>' OR COMBINATION THEREOF, INCLUDING ANY INJURY OR DAMAGE TO USERS OR TO ANY COMPUTER OR OTHER DEVICE ON OR THROUGH WHICH '<%= Settings.projectName %>' IS PROVIDED. UNDER NO CIRCUMSTANCES WILL '<%= Settings.projectName %>' BE LIABLE FOR ANY LOSS OR DAMAGE, INCLUDING PERSONAL INJURY OR DEATH, RESULTING FROM YOUR USE OF '<%= Settings.projectName %>'.

23 | 24 |

SOURCE MATERIAL

25 | 26 |

ALL MOVIES ARE NOT HOSTED ON ANY SERVER AND ARE STREAMED USING THE P2P BIT TORRENT PROTOCOL. ALL MOVIES ARE PULLED IN FROM OPEN PUBLIC APIS. BY WATCHING A MOVIE WITH THIS APPLICATION YOU MIGHT BE COMMITTING COPYRIGHT VIOLATIONS DEPENDING ON YOUR COUNTRY´S LAWS. WE DO NOT TAKE ANY RESPONSIBILITIES.

27 | 28 |

Ability to Accept Terms of Service

29 | 30 |

By using '<%= Settings.projectName %>' or accessing this site you affirm that you are either more than 18 years of age, or an emancipated minor, or possess legal parental or guardian consent, and are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations, and warranties set forth in these Terms of Service, and to abide by and comply with these Terms of Service. In any case, you affirm that you are over the age of 13, as the Service is not intended for children under 13. If you are under 13 years of age, then please do not use the Service. There are lots of other great web sites for you. Talk to your parents about what sites are appropriate for you.

31 |
32 | 33 | > 34 | 35 | 36 | 37 | > 38 | 39 |
40 | <%= i18n.__("I Accept") %> <%= i18n.__("Leave") %> 41 |
42 |
43 | -------------------------------------------------------------------------------- /src/app/templates/file-selector.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 |
style="opacity:1"<%; break; case 'vlow': %> style="opacity:0.9"<%; break; case 'low': %> style="opacity:0.8"<%; break; case 'high': %> style="opacity:0.6"<%; break; case 'vhigh': %> style="opacity:0.5"<%; break; default: %><%}} if (localFile) { %> style="opacity:1 !important"<%}%>>
4 |
5 | 6 |
<%=i18n.__('Please select a file to play')%>
7 |
8 |
    9 | <% _.each(files, function(file, index) { 10 | if (file.display !== false) { %> 11 |
  • 12 | <% if (Settings.activateSeedbox && !localFile) { %> 13 | 14 | <% } else { %><% } %><%=Common.fileSize(file.length) %> 15 | <%=file.name %> 16 |
  • 17 | <% }}); %> 18 |
19 |
20 | 21 |
22 | 23 | <% if (Settings.activateTorrentCollection) { %> 24 |
25 | <% } %> 26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /src/app/templates/header.tpl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 |

11 | <%= Settings.projectName %> 12 |
13 |

14 | -------------------------------------------------------------------------------- /src/app/templates/initializing.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | <%= i18n.__("Made with") %> <%= i18n.__("by a bunch of geeks from All Around The World") %> 5 |
6 |
7 |
<%= i18n.__("Initializing %s. Please Wait...", Settings.projectName) %>
8 |
9 |
10 |
11 |
12 | 13 |

14 | <%= i18n.__("Cancel") %> 15 |

16 | 17 |
18 | 19 |

20 | <%= i18n.__("Loading stuck ? Click here !") %> 21 |

22 | -------------------------------------------------------------------------------- /src/app/templates/item.tpl: -------------------------------------------------------------------------------- 1 | <% 2 | if (typeof rating === 'object') { var rating = rating.percentage /10; } 3 | %> 4 |
5 |
6 |
7 | 8 | 9 | 10 | <% if(typeof rating !== 'undefined'){ 11 | var p_rating = Math.round(rating) / 2; %> 12 |
style="display: block;"<% } %> > 13 |
14 | <% for (var i = 1; i <= Math.floor(p_rating); i++) { %> 15 | 16 | <% }; %> 17 | <% if (p_rating % 1 > 0) { %> 18 | 19 | 20 | 21 | 22 | <% }; %> 23 | <% for (var i = Math.ceil(p_rating); i < 5; i++) { %> 24 | 25 | <% }; %> 26 |
27 |
<%= parseFloat(rating).toFixed(1) %>/10
28 |
29 | <%} %> 30 |
31 |
32 | 33 |

<%= title1 %>

34 | <% if (typeof title2 !== 'undefined' && title2 !== '') { %> 35 |

20){ %> title="<%= title2 %>" data-toggle="tooltip" data-placement="auto bottom" <% } %> ><%= title2 %>

36 | <%} %> 37 |

38 | <% if (typeof year !== 'undefined') { %> 39 | <%= year %> 40 | <% } %> 41 |

42 | 43 | <% if (typeof item_data !== 'undefined') { %> 44 |

45 | <%= i18n.__(item_data) %> 46 |

47 | <% } else if (typeof num_seasons !== 'undefined') { %> 48 |

49 | <%= num_seasons %> <%= num_seasons == 1 ? i18n.__("Season") : i18n.__("Seasons") %> 50 |

51 | <% } else if (typeof qualityList !== 'undefined') { %> 52 |

53 | <%= qualityList %> 54 |

55 | <% } %> 56 | -------------------------------------------------------------------------------- /src/app/templates/lang-dropdown.tpl: -------------------------------------------------------------------------------- 1 |
2 | 7 | 23 |
24 | -------------------------------------------------------------------------------- /src/app/templates/list.tpl: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
-------------------------------------------------------------------------------- /src/app/templates/main-window.tpl: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/app/templates/movie-detail.tpl: -------------------------------------------------------------------------------- 1 | <% 2 | if(typeof health === "undefined"){ health = false; }; 3 | if(typeof synopsis === "undefined"){ synopsis = "Synopsis not available."; }; 4 | if(typeof runtime === "undefined"){ runtime = "N/A"; }; 5 | if (genre) { 6 | for(var i = 0; i < genre.length; i++) { 7 | genre[i] = i18n.__(genre[i].capitalizeEach()).toLowerCase(); 8 | } 9 | } else { 10 | var genre = [undefined]; 11 | }; 12 | %> 13 |
14 |
style="opacity:<%=Settings.moviesUITransparency%>"<%}%>>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
<%= displayTitle %>
28 |
29 |
<%= year %>
30 |
<%= runtime %> min
31 |
<%= genre.join(" / ") %>
32 | <% if((typeof(certification) !== 'undefined') && (certification !== null) && (certification !== '') && (certification !== 'NR')) { %> 33 |
<%= certification %>
34 | <% } %> 35 |
36 |
37 |
38 |
39 | <% var p_rating = Math.round(rating) / 2; %> 40 | <% for (var i = 1; i <= Math.floor(p_rating); i++) { %> 41 | 42 | <% }; %> 43 | <% if (p_rating % 1 > 0) { %> 44 | 45 | 46 | 47 | 48 | <% }; %> 49 | <% for (var i = Math.ceil(p_rating); i < 5; i++) { %> 50 | 51 | <% }; %> 52 |
53 | 54 |
55 | 56 |
57 |
58 | 59 | 60 |
61 |
62 |
<%= displaySynopsis %>
63 |
64 |
65 |
66 |
67 | -------------------------------------------------------------------------------- /src/app/templates/movie-error.tpl: -------------------------------------------------------------------------------- 1 |
2 |

<%= error %>

3 |
4 |
  <%= i18n.__("Retry") %>
5 |
6 |
7 |
  <%= i18n.__("Change API Server") %>
8 |
9 | 12 |
13 | -------------------------------------------------------------------------------- /src/app/templates/notification.tpl: -------------------------------------------------------------------------------- 1 |