├── .node-version ├── static ├── mpv.png ├── tv.png ├── edge.png ├── opera.png ├── IOS_logo.png ├── android.png ├── androidtv.png ├── new_notice.gif ├── AMO-button_1.png ├── ajay_profile.jpg ├── sqrtFunction.png ├── LogoSponsorBlock256px.png ├── emails │ ├── InsightVenue1.png │ ├── InsightVenue2.png │ ├── InsightVenue3.png │ ├── InsightVenue4.png │ └── datos_acquiring_presentation.pdf ├── LogoSponsorBlockSimple256px.png ├── ChromeWebStore_BadgeWBorder_v2_206x58.png ├── dearrow.svg ├── iOS.svg ├── chromecast.svg ├── matrix.svg ├── kodi.svg └── safari.svg ├── .prettierignore ├── .prettierrc ├── Dockerfile ├── .editorconfig ├── .github └── workflows │ ├── sb-site.yml │ └── docker-build.yml ├── src ├── pages │ ├── 404.js │ ├── donate.js │ ├── emails.js │ ├── about.js │ ├── index.js │ └── stats.js └── components │ ├── donate.scss │ ├── seo.js │ ├── donate.js │ ├── layout.js │ └── layout.scss ├── nginx ├── nginx.conf └── default.conf ├── README.md ├── gatsby-config.js ├── LICENSE ├── package.json └── .gitignore /.node-version: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /static/mpv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/mpv.png -------------------------------------------------------------------------------- /static/tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/tv.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | package.json 3 | package-lock.json 4 | yarn.lock 5 | public 6 | -------------------------------------------------------------------------------- /static/edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/edge.png -------------------------------------------------------------------------------- /static/opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/opera.png -------------------------------------------------------------------------------- /static/IOS_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/IOS_logo.png -------------------------------------------------------------------------------- /static/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/android.png -------------------------------------------------------------------------------- /static/androidtv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/androidtv.png -------------------------------------------------------------------------------- /static/new_notice.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/new_notice.gif -------------------------------------------------------------------------------- /static/AMO-button_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/AMO-button_1.png -------------------------------------------------------------------------------- /static/ajay_profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/ajay_profile.jpg -------------------------------------------------------------------------------- /static/sqrtFunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/sqrtFunction.png -------------------------------------------------------------------------------- /static/LogoSponsorBlock256px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/LogoSponsorBlock256px.png -------------------------------------------------------------------------------- /static/emails/InsightVenue1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/emails/InsightVenue1.png -------------------------------------------------------------------------------- /static/emails/InsightVenue2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/emails/InsightVenue2.png -------------------------------------------------------------------------------- /static/emails/InsightVenue3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/emails/InsightVenue3.png -------------------------------------------------------------------------------- /static/emails/InsightVenue4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/emails/InsightVenue4.png -------------------------------------------------------------------------------- /static/LogoSponsorBlockSimple256px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/LogoSponsorBlockSimple256px.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": true, 4 | "singleQuote": false, 5 | "tabWidth": 4, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /static/emails/datos_acquiring_presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/emails/datos_acquiring_presentation.pdf -------------------------------------------------------------------------------- /static/ChromeWebStore_BadgeWBorder_v2_206x58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajayyy/SponsorBlockSite/HEAD/static/ChromeWebStore_BadgeWBorder_v2_206x58.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx as app 2 | EXPOSE 80 3 | COPY public/ /usr/share/nginx/html 4 | COPY nginx/nginx.conf /etc/nginx/nginx.conf 5 | COPY nginx/default.conf /etc/nginx/conf.d/default.conf -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | 10 | [src/**.{js,scss}] 11 | charset = utf-8 12 | indent_style = space 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.github/workflows/sb-site.yml: -------------------------------------------------------------------------------- 1 | name: sb-site 2 | on: 3 | push: 4 | branches: 5 | - master 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | uses: ./.github/workflows/docker-build.yml 11 | with: 12 | name: "sb-site" 13 | username: "ajayyy" 14 | secrets: 15 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Layout from "../components/layout"; 4 | import Seo from "../components/seo"; 5 | 6 | const NotFoundPage = () => ( 7 | 8 | 9 |
10 |

Not Found

11 |
12 |
13 | ); 14 | 15 | export default NotFoundPage; 16 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /var/log/nginx/error.log notice; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | 13 | http { 14 | include /etc/nginx/mime.types; 15 | default_type application/octet-stream; 16 | 17 | # log_format main '[$time_local] "$request" ' 18 | # '$status $body_bytes_sent "$http_referer" ' 19 | # '"$http_user_agent" "$http_x_forwarded_for"'; 20 | 21 | # access_log /var/log/nginx/access.log main; 22 | access_log off; 23 | #error_log /etc/nginx/logs/error.log warn; 24 | error_log /dev/null crit; 25 | 26 | sendfile on; 27 | #tcp_nopush on; 28 | 29 | keepalive_timeout 65; 30 | 31 | gzip on; 32 | 33 | include /etc/nginx/conf.d/*.conf; 34 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SponsorBlock Landing 2 | 3 | This uses the `gatsby` static site generator. 4 | 5 | Install `npm` to start. 6 | 7 | Then `cd SponsorBlockSite`. 8 | 9 | ### Install dependencies 10 | 11 | ```shell 12 | npm install 13 | ``` 14 | 15 | ### Developing 16 | 17 | ```shell 18 | npm run develop 19 | ``` 20 | 21 | This will start a development server on http://localhost:8000. 22 | 23 | Before making a pull request, run `npm run format` to format the code. 24 | 25 | ### Building 26 | 27 | ```shell 28 | npm run build 29 | ``` 30 | 31 | This will create a folder called `public`, which will contain a statically generated version of the site that can be easily served with any website or deployment service. 32 | 33 | You can then use `npm run serve` to preview the website before deployment. 34 | 35 | --- 36 | 37 | Thanks for the rewrite [@jplsek](https://github.com/jplsek)! 38 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: "SponsorBlock", 4 | description: 5 | "SponsorBlock is a crowdsourced browser extension to skip sponsor segments in YouTube videos.", 6 | author: "Ajay Ramachandran", 7 | icon: "https://sponsor.ajay.app/LogoSponsorBlockSimple256px.png", 8 | }, 9 | plugins: [ 10 | "gatsby-plugin-react-helmet", 11 | { 12 | resolve: "gatsby-plugin-manifest", 13 | options: { 14 | name: "SponsorBlock", 15 | short_name: "SponsorBlock", 16 | start_url: "/", 17 | background_color: "#fff", 18 | theme_color: "#fff", 19 | icon: "static/LogoSponsorBlockSimple256px.png", 20 | }, 21 | }, 22 | "gatsby-plugin-sass", 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/donate.scss: -------------------------------------------------------------------------------- 1 | .donate-modal { 2 | background-color: rgb(26, 26, 26, 0.95); 3 | border-radius: 15px; 4 | 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | flex-wrap: wrap; 9 | } 10 | 11 | .donate-modal-container { 12 | position: fixed; 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | top: 0; 17 | left: 0; 18 | width: 100%; 19 | height: 100%; 20 | } 21 | 22 | .donate-modal > div { 23 | width: 100%; 24 | 25 | display: flex; 26 | justify-content: center; 27 | padding: 10px; 28 | } 29 | 30 | .donate-modal a { 31 | text-decoration: none; 32 | color: #eee; 33 | border-radius: 15px; 34 | background-color: rgb(58, 58, 58, 0.9); 35 | padding: 15px; 36 | 37 | margin-right: 20px; 38 | transition: background-color 0.3s ease; 39 | } 40 | 41 | .donate-modal a:hover { 42 | background-color: rgba(70, 70, 70, 0.9); 43 | } 44 | 45 | .donate-modal a.active { 46 | background-color: rgba(139, 0, 0, 0.9); 47 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ajay Ramachandran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sponsorblock", 3 | "private": true, 4 | "description": "SponsorBlock is a crowdsourced browser extension to block sponsor segments of YouTube videos.", 5 | "version": "1.0.0", 6 | "author": "Ajay Ramachandran", 7 | "dependencies": { 8 | "@fortawesome/fontawesome-free": "^6.7.2", 9 | "classnames": "^2.5.1", 10 | "gatsby": "^5.13.1", 11 | "gatsby-plugin-manifest": "^5.14.0", 12 | "gatsby-plugin-react-helmet": "^6.14.0", 13 | "gatsby-plugin-sass": "^6.14.0", 14 | "lmdb": "^3.4.0", 15 | "msgpackr": "^1.11.4", 16 | "normalize.css": "^8.0.1", 17 | "prop-types": "^15.8.1", 18 | "react": "^18.2.0", 19 | "react-dom": "^18.2.0", 20 | "react-helmet": "^6.1.0", 21 | "sass": "^1.89.2" 22 | }, 23 | "devDependencies": { 24 | "prettier": "^3.2.4" 25 | }, 26 | "license": "MIT", 27 | "engines": { 28 | "node": ">=18" 29 | }, 30 | "scripts": { 31 | "build": "gatsby build", 32 | "develop": "gatsby develop", 33 | "format": "prettier --write \"**/*.{js,jsx,json,md,scss}\"", 34 | "start": "npm run develop", 35 | "serve": "gatsby serve", 36 | "clean": "gatsby clean" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/ajayyy/SponsorBlock" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variable files 55 | .env* 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # IDEs 72 | .vscode/ 73 | .idea/ 74 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name localhost; 5 | 6 | #access_log /var/log/nginx/host.access.log main; 7 | 8 | location / { 9 | root /usr/share/nginx/html; 10 | index index.html index.htm; 11 | } 12 | 13 | location /news { 14 | return 301 https://blog.ajay.app/sponsorblock; 15 | } 16 | 17 | location /viewer { 18 | return 301 https://sb.ltn.fi; 19 | } 20 | 21 | error_page 404 /404.html; 22 | 23 | # redirect server error pages to the static page /50x.html 24 | # 25 | error_page 500 502 503 504 /50x.html; 26 | location = /50x.html { 27 | root /usr/share/nginx/html; 28 | } 29 | 30 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 31 | # 32 | #location ~ \.php$ { 33 | # proxy_pass http://127.0.0.1; 34 | #} 35 | 36 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 37 | # 38 | #location ~ \.php$ { 39 | # root html; 40 | # fastcgi_pass 127.0.0.1:9000; 41 | # fastcgi_index index.php; 42 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 43 | # include fastcgi_params; 44 | #} 45 | 46 | # deny access to .htaccess files, if Apache's document root 47 | # concurs with nginx's one 48 | # 49 | #location ~ /\.ht { 50 | # deny all; 51 | #} 52 | } -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/ajayyy/sb-mirror/blob/main/.github/workflows/docker-build.yml 2 | name: multi-build-docker 3 | on: 4 | workflow_call: 5 | inputs: 6 | name: 7 | required: true 8 | type: string 9 | username: 10 | required: true 11 | type: string 12 | secrets: 13 | GH_TOKEN: 14 | required: true 15 | 16 | jobs: 17 | build_container: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: '18' 25 | - name: Install build dependencies 26 | run: sudo apt-get install -y build-essential python3 && npm ci 27 | - name: Build site 28 | run: npm run build 29 | - name: Docker meta 30 | id: meta 31 | uses: docker/metadata-action@v3 32 | with: 33 | images: | 34 | ghcr.io/${{ inputs.username }}/${{ inputs.name }} 35 | tags: | 36 | type-raw,value=alpine 37 | flavor: | 38 | latest=true 39 | - name: Login to GHCR 40 | uses: docker/login-action@v1 41 | with: 42 | registry: ghcr.io 43 | username: ${{ github.repository_owner }} 44 | password: ${{ secrets.GH_TOKEN }} 45 | - name: Set up QEMU 46 | uses: docker/setup-qemu-action@v1 47 | with: 48 | platforms: arm,arm64 49 | - name: Set up buildx 50 | uses: docker/setup-buildx-action@v1 51 | - name: push 52 | uses: docker/build-push-action@v2 53 | with: 54 | context: . 55 | platforms: linux/amd64,linux/arm64 56 | push: true 57 | tags: ${{ steps.meta.outputs.tags }} -------------------------------------------------------------------------------- /src/pages/donate.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Layout from "../components/layout"; 4 | import Seo from "../components/seo"; 5 | import { DonateComponent } from "../components/donate"; 6 | 7 | const IndexPage = () => ( 8 | 9 | 10 | 11 |
12 |

Donate

13 | 14 |

15 | Hi, I'm Ajay. 16 | For the past 5 years, I've been working open-source tools like SponsorBlock and DeArrow. 17 | Thanks to your support, I am now able to work on these open-source projects full time. 18 |

19 | 20 |

21 | If you'd like to help make that possible, consider supporting using an option below. 22 |

23 | 24 | 25 | 26 |
27 | 28 |

Creators

29 | 30 |

31 | Consider helping out the creators of videos you watch by 32 | donating to their crowdfunding campaigns, or subscribing to 33 | services such as YouTube Premium. 34 |

35 | 36 |

Contributors

37 | 38 |

39 | Also check out all the other contributors who have helped out 40 | with this project;{" "} 41 | 42 | SponsorBlock contributors 43 | 44 | ,{" "} 45 | 46 | SponsorBlockServer contributors 47 | {" "} 48 | and{" "} 49 | 50 | SponsorBlockSite contributors 51 | {" "} 52 | such as NDev,{" "} 53 | Joe Dowd,{" "} 54 | Michael Chang and more. 55 |

56 | 57 |

58 | Also, all of the segment submitters. 59 |

60 |
61 |
62 | ); 63 | 64 | export default IndexPage; 65 | -------------------------------------------------------------------------------- /static/dearrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 55 | -------------------------------------------------------------------------------- /static/iOS.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 28 | 48 | 52 | 53 | -------------------------------------------------------------------------------- /static/chromecast.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 46 | 49 | 52 | 56 | 57 | 58 | 60 | 61 | 63 | 64 | 66 | 67 | 69 | 70 | 72 | 73 | 75 | 76 | 78 | 79 | 81 | 82 | 84 | 85 | 87 | 88 | 90 | 91 | 93 | 94 | 96 | 97 | 99 | 100 | 102 | 103 | -------------------------------------------------------------------------------- /src/components/seo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that populates the for each page. 3 | */ 4 | 5 | import React from "react"; 6 | import PropTypes from "prop-types"; 7 | import Helmet from "react-helmet"; 8 | import { useStaticQuery, graphql } from "gatsby"; 9 | 10 | function Seo({ description, lang, meta, title, overwriteTitle }) { 11 | const { site } = useStaticQuery( 12 | graphql` 13 | query { 14 | site { 15 | siteMetadata { 16 | title 17 | description 18 | author 19 | icon 20 | } 21 | } 22 | } 23 | ` 24 | ); 25 | 26 | const metaDescription = description || site.siteMetadata.description; 27 | 28 | return ( 29 | 78 | ); 79 | } 80 | 81 | Seo.defaultProps = { 82 | lang: "en", 83 | meta: [], 84 | description: "", 85 | }; 86 | 87 | Seo.propTypes = { 88 | description: PropTypes.string, 89 | lang: PropTypes.string, 90 | meta: PropTypes.arrayOf(PropTypes.object), 91 | title: PropTypes.string.isRequired, 92 | overwriteTitle: PropTypes.string, 93 | }; 94 | 95 | export default Seo; 96 | -------------------------------------------------------------------------------- /static/matrix.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 37 | Matrix (protocol) logo 39 | 44 | 48 | 52 | 56 | 57 | 59 | 60 | 62 | Matrix (protocol) logo 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/components/donate.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./donate.scss"; 4 | 5 | export function DonateComponent() { 6 | const [activeButton, setActiveButton] = React.useState(null); 7 | const [showOtherDonationMethods, setShowOtherDonationMethods] = React.useState(false); 8 | 9 | return ( 10 | <> 11 |
12 |
13 | setActiveButton(1)}> 14 | $1 15 | 16 | 17 | setActiveButton(5)}> 18 | $5 19 | 20 | 21 | setActiveButton(15)}> 22 | $15 23 | 24 | 25 | setActiveButton("other")}> 26 | Custom 27 | 28 |
29 | 30 |
31 | 32 | One-time 33 | 34 | 35 | 36 | Monthly 37 | 38 | 39 | setShowOtherDonationMethods(!showOtherDonationMethods)}> 41 | Other ways to donate 42 | 43 |
44 |
45 | 46 | 47 |

48 | Recurring:{" "} 49 | 50 | GitHub (lowest fees) 51 | 52 | ,{" "} 53 | Patreon 54 | ,{" "} 55 | Liberapay 56 |

57 | 58 |

59 | One-time donation:{" "} 60 | 61 | GitHub (lowest fees) 62 | 63 | , Paypal,{" "} 64 | { 65 | alert("dev@ajay.app"); 66 | }}>Interac e-Transfer,{" "} 67 | { 68 | alert("BIC: TRWIBEB1XXX\nIBAN: BE74 9678 0459 0007"); 69 | }}>SEPA,{" "} 70 | 71 | BTC 72 | ,{" "} 73 | 74 | XMR 75 | 76 |

77 |
78 | 79 | ) 80 | } -------------------------------------------------------------------------------- /src/pages/emails.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Layout from "../components/layout"; 4 | import Seo from "../components/seo"; 5 | 6 | const IndexPage = () => ( 7 | 8 | 9 | 10 |
11 |

12 | List of emails I have received about inserting malware into my 13 | extension 14 |

15 |

16 | I've been seeing a lot of stories about extensions getting taken 17 | over by unknown developers and becoming malware. Sadly, with how 18 | many permissions many extensions have, it can do a lot of 19 | damage. I decided to compile a list of these sketchy emails I 20 | have received, to show the kinds of offers that exist. 21 |

22 |

23 | To be clear I will never do anything of this 24 | sort, but make sure any extensions you install are from trusted 25 | developers and have as few permissions as possible. Most of 26 | these scams wouldn't even work with SponsorBlock due to lack 27 | of permissions (it only has access to youtube.com), but they 28 | spam email all developers anyway. 29 |

30 |

From partners@infatica.io (A proxy service (botnet?)):

31 | 32 | Their explanation page 33 | {" "} 34 | (Archive) 35 |

36 | Here's the code for 37 | their sdk. In the email they told me it was "open", so that 38 | should mean it is meant to be public. 39 |

40 | scam 44 | scam 48 | scam 52 | scam 56 |

From extensionmetric.com

57 |

58 | The site disapeared exactly one month after their email, almost 59 | like they weren't planning on paying anything after all... 60 | Here's the code for 61 | their sdk. This one is FAR worse than infatica. There are some 62 | calls to getAllResponseHeaders, which could mean stealing logged 63 | in accounts. 64 |

65 | scam 69 | scam 73 |

From datos.live:

74 |

75 | Presentation pdf 76 |

77 | scam 81 | scam 85 |

From InsightVenue:

86 | scam 90 | scam 94 | scam 98 | scam 102 |

From admedia.com:

103 | scam 107 |

From invokevision.com/invoke.vision:

108 | scam 112 |

From admitad.com:

113 | scam 117 |

From monetisationsolutions@gmail.com:

118 | scam 122 |

From sponsoredsearchsolutions@gmail.com:

123 | scam 127 |
128 |
129 | ); 130 | 131 | export default IndexPage; 132 | -------------------------------------------------------------------------------- /src/components/layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Layout of the main website. 3 | */ 4 | 5 | import React from "react"; 6 | import PropTypes from "prop-types"; 7 | import { Link } from "gatsby"; 8 | 9 | import "normalize.css/normalize.css"; 10 | import "./layout.scss"; 11 | import "@fortawesome/fontawesome-free/css/all.min.css"; 12 | 13 | const Layout = ({ children }) => { 14 | return ( 15 | <> 16 |
24 | 93 | 94 | 166 |
167 |
{children}
168 | 169 | ); 170 | }; 171 | 172 | Layout.propTypes = { 173 | children: PropTypes.node.isRequired, 174 | }; 175 | 176 | export default Layout; 177 | -------------------------------------------------------------------------------- /static/kodi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 19 | 22 | 37 | 48 | 52 | 56 | 61 | 66 | 67 | -------------------------------------------------------------------------------- /src/components/layout.scss: -------------------------------------------------------------------------------- 1 | /* Theme */ 2 | $font-color: #c4c4c4; 3 | $side-padding: 15px; 4 | $page-background-color: #333333; 5 | 6 | :root { 7 | --categorystats-piechart-size: 200px; 8 | color-scheme: dark; 9 | } 10 | 11 | body { 12 | background-color: $page-background-color; 13 | color: $font-color; 14 | font-family: sans-serif; 15 | line-height: 1.5; 16 | 17 | font-size: 1.2rem; 18 | } 19 | 20 | a { 21 | transition: color 0.1s linear; 22 | 23 | color: inherit; 24 | cursor: pointer; 25 | } 26 | 27 | a:hover { 28 | color: white; 29 | } 30 | 31 | .fab { 32 | font-size: 1.3rem; 33 | vertical-align: middle; 34 | } 35 | 36 | img { 37 | height: auto; 38 | max-width: 100%; 39 | } 40 | 41 | /* Title on home page */ 42 | 43 | .title { 44 | background-color: rgb(38, 38, 38); 45 | padding: 1rem 1rem; 46 | text-align: center; 47 | font-size: 4rem; 48 | } 49 | 50 | .title img { 51 | margin-right: 3rem; 52 | vertical-align: middle; 53 | max-height: 115px; 54 | } 55 | 56 | /* Components */ 57 | .nav { 58 | list-style-type: none; 59 | margin: 0; 60 | padding: 0; 61 | display: flex; 62 | flex-wrap: wrap; 63 | } 64 | 65 | .nav > * { 66 | display: inline-block; 67 | } 68 | 69 | .nav-link { 70 | display: block; 71 | text-align: center; 72 | text-decoration: none; 73 | color: #eee; 74 | padding: 1rem 1rem; 75 | } 76 | 77 | .nav-link:hover, 78 | .nav-link:focus { 79 | background-color: #262626; 80 | color: red; 81 | } 82 | 83 | .nav-link.profile img { 84 | height: 1.8rem; 85 | 86 | margin-left: 0.6rem; 87 | 88 | vertical-align: middle; 89 | 90 | border-radius: 50%; 91 | } 92 | 93 | .fa { 94 | font-size: 1.4rem !important; 95 | } 96 | 97 | .container { 98 | max-width: 768px; 99 | margin: auto; 100 | padding-right: $side-padding; 101 | padding-left: $side-padding; 102 | } 103 | 104 | .container-fluid { 105 | margin: auto; 106 | padding-right: $side-padding; 107 | padding-left: $side-padding; 108 | } 109 | 110 | .text-center { 111 | text-align: center; 112 | } 113 | 114 | .text-large { 115 | font-size: 1.3rem; 116 | } 117 | 118 | .text-small { 119 | font-size: 0.8rem; 120 | } 121 | 122 | .text-normal { 123 | font-size: 1em; 124 | } 125 | 126 | .no-bottom-margin { 127 | margin-bottom: 0; 128 | } 129 | 130 | .pull-right { 131 | float: right; 132 | } 133 | 134 | .quote { 135 | margin: 2rem 4rem; 136 | border-left: 2px solid $font-color; 137 | padding-left: 1rem; 138 | font-style: italic; 139 | } 140 | 141 | table { 142 | margin: 0 auto 2rem; 143 | border-collapse: collapse; 144 | } 145 | 146 | table thead { 147 | white-space: nowrap; 148 | text-align: left; 149 | } 150 | 151 | table td, 152 | table th { 153 | padding: 5px; 154 | } 155 | 156 | .row--even td { 157 | background-color: lighten($page-background-color, 2%); 158 | } 159 | 160 | .pointer { 161 | cursor: pointer; 162 | } 163 | 164 | .celltype-number { 165 | text-align: right; 166 | font-variant-numeric: tabular-nums; 167 | } 168 | 169 | table.highlight-row-on-hover tr:hover td { 170 | background-color: darken($page-background-color, 8%); 171 | } 172 | 173 | .categorystats { 174 | display: flex; 175 | flex-direction: row; 176 | align-items: center; 177 | position: fixed; 178 | bottom: 16px; 179 | left: 50%; 180 | transform: translateX(-50%); 181 | background: #000; 182 | box-shadow: 4px 4px 10px #222, 2px 2px 16px #222; 183 | border-radius: 2px; 184 | padding: 8px; 185 | pointer-events: none; 186 | opacity: 0.9; 187 | } 188 | .categorystats--hidden { 189 | display: none; 190 | } 191 | 192 | .categorystats table { 193 | margin: 0; 194 | } 195 | 196 | .categorystats .dim { 197 | opacity: 0.3; 198 | filter: grayscale(0.5); 199 | } 200 | 201 | .categorystats table td, 202 | .categorystats table th { 203 | padding: 2px 5px; 204 | } 205 | 206 | @supports (background: conic-gradient(red, green, blue)) { 207 | .categorystats-piechart { 208 | display: block; 209 | margin: 16px; 210 | width: var(--categorystats-piechart-size); 211 | border-radius: 50%; 212 | background: conic-gradient(lime 40%, yellow 0 70%, red 0); 213 | } 214 | .categorystats-piechart:after { 215 | content: ""; 216 | display: block; 217 | padding-bottom: 100%; 218 | } 219 | } 220 | 221 | .sorted::after { 222 | content: "◢"; 223 | display: inline-block; 224 | vertical-align: top; 225 | margin: 0.2em 0 0 0.2em; 226 | font-size: 0.75em; 227 | transform: rotate(45deg); 228 | } 229 | 230 | .loading::before, 231 | .sort-loading::after { 232 | content: " "; 233 | display: inline-block; 234 | width: 0.5em; 235 | height: 0.5em; 236 | border-radius: 50%; 237 | border: 0.15em solid; 238 | border-color: #cccc #cccc #cccc transparent; 239 | animation: loader 1.2s linear infinite; 240 | } 241 | 242 | .loading::before { 243 | margin-right: 0.5em; 244 | } 245 | 246 | .sort-loading::after { 247 | margin-left: 0.1em; 248 | vertical-align: baseline; 249 | } 250 | 251 | .footer-links > a { 252 | display: block; 253 | } 254 | 255 | @keyframes loader { 256 | 0% { 257 | transform: rotate(0deg); 258 | } 259 | 100% { 260 | transform: rotate(360deg); 261 | } 262 | } 263 | 264 | .donate-ask { 265 | background-color: rgb(26, 26, 26, 0.95); 266 | border-radius: 15px; 267 | 268 | text-align: center; 269 | padding: 10px; 270 | 271 | margin-top: 1em; 272 | margin-bottom: 1em; 273 | } 274 | 275 | .donate-ask .donate-text { 276 | margin-top: 10px; 277 | margin-bottom: 10px; 278 | 279 | display: flex; 280 | align-items: center; 281 | justify-content: center; 282 | } 283 | 284 | .donate-ask .donate-text img { 285 | height: 2rem; 286 | border-radius: 100%; 287 | 288 | margin-right: 15px; 289 | } 290 | 291 | .donate-ask a { 292 | text-decoration: none; 293 | color: #eee; 294 | border-radius: 15px; 295 | background-color: rgb(58, 58, 58, 0.9); 296 | padding: 10px; 297 | 298 | transition: background-color 0.3s ease; 299 | 300 | display: block; 301 | width: fit-content; 302 | margin: auto; 303 | margin-top: 10px; 304 | margin-bottom: 10px; 305 | } 306 | 307 | .donate-ask a:hover { 308 | background-color: rgba(70, 70, 70, 0.9); 309 | } 310 | 311 | .topUsersLoading { 312 | text-align: center; 313 | font-size: 1.5em; 314 | font-weight: bold; 315 | width: 100%; 316 | } 317 | 318 | @media only screen and (max-width: 1000px) { 319 | :root { 320 | --categorystats-piechart-size: 150px; 321 | } 322 | } 323 | 324 | @media only screen and (max-width: 740px) { 325 | :root { 326 | --categorystats-piechart-size: 100px; 327 | } 328 | } 329 | 330 | @media only screen and (max-width: 600px) { 331 | :root { 332 | --categorystats-piechart-size: 0; 333 | } 334 | } 335 | 336 | /* Mobile */ 337 | 338 | @media only screen and (max-width: 600px) { 339 | .title { 340 | padding: 1rem 1rem; 341 | text-align: center; 342 | font-size: 2rem; 343 | } 344 | 345 | .title img { 346 | margin-right: 15px; 347 | max-height: 40px; 348 | } 349 | 350 | /* Nav Bar */ 351 | 352 | .nav-link { 353 | padding: 0.5rem 0.5rem; 354 | } 355 | 356 | .nav-link:hover, 357 | .nav-link:focus { 358 | background-color: #262626; 359 | color: red; 360 | } 361 | 362 | .nav .author { 363 | display: none; 364 | } 365 | 366 | .stats-table table { 367 | table-layout: fixed; 368 | 369 | width: 100%; 370 | 371 | border-collapse: collapse; 372 | } 373 | 374 | .stats-table .rank { 375 | width: 1.2rem; 376 | } 377 | 378 | .stats-table th, 379 | .stats-table td { 380 | overflow: hidden; 381 | 382 | font-size: 9px; 383 | 384 | text-align: center; 385 | } 386 | 387 | .container-fluid.stats-table { 388 | padding: 0; 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /src/pages/about.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Layout from "../components/layout"; 4 | import Seo from "../components/seo"; 5 | 6 | const IndexPage = () => ( 7 | 8 | 9 | 10 |
11 |

How it works

12 | 13 |

14 | When you visit a YouTube video, it will check the database to 15 | see if anyone has made any submissions for the video. If so, the 16 | segment will automatically get skipped when you reach it. 17 |

18 | 19 |
20 | The notice notification 25 |
26 | 27 |

28 | Once the sponsorship is skipped, you can upvote or downvote this 29 | segment and it will be recorded in the database. 30 |

31 | 32 |

33 | Instead of just sending the top reported segment, it finds all 34 | of the overlapping segments. Only one segment from each group of 35 | overlapping segments will be sent to the user. 36 |

37 | 38 |

Pseudo-random distribution

39 | 40 |

41 | To prevent one submission with a lot of votes never being able 42 | to be replaced, I decided to use a weighted random distribution 43 | based on the equation on the right. 44 |
45 | Square root function 50 | This formula makes the first few votes matter a lot more than 51 | votes on a submission that already has a lot of votes. This 52 | gives newly submitted segments a better chance of being sent out 53 | to users to get votes. So, most users will get the best 54 | submission, but some users will get lesser votes submissions so 55 | that they can either be upvoted or downvoted. Submissions with 56 | less than -1 votes are ignored entirely. 57 |

58 | 59 |

60 | You can read more about my algorithm{" "} 61 | 62 | here 63 | 64 | . 65 |

66 | 67 |

Submissions

68 | 69 |

70 | Anyone can submit segments, either by clicking on the button 71 | that is added to the YouTube player or by opening the extensions 72 | popup. The button in the YouTube player can be hidden. You click 73 | once to indicate the start of a segment, then click again to 74 | indicate the end. You can submit as many segments as there are 75 | in the video. Make sure to choose the correct category for each 76 | segment. 77 |

78 | 79 |

What data is stored?

80 | 81 |

82 | The bare minimum. Check{" "} 83 | 84 | this list 85 | {" "} 86 | for more information 87 |

88 | 89 |

Previous projects like this

90 | 91 |

92 | In January 2019, a group of people tried to do a similar thing, 93 | but instead of using other people's submissions to skip sponsor 94 | segments for everyone, they{" "} 95 | 96 | ran the data through a neural network 97 | 98 | . Sadly, this project was abandoned. 99 |

100 | 101 |

102 | I don't want something similar to happen to this project, that's 103 | why all this code is open-sourced and, most importantly, the 104 | database can be downloaded by anyone. The database may even be 105 | automatically backed up by archive.org! The database will always 106 | be available{" "} 107 | here. It is 108 | a csv and can be opened in any csv or text reading program. 109 | Certain sensitive info is not in this database and is not public 110 | such as individual votes (not vote counts) and hashed IP 111 | addresses. That information isn't needed by anyone else, only 112 | the server. 113 |

114 | 115 |

When was this started

116 | 117 |

118 | The project was started July 5th 2019 and was first released to 119 | the public July 26th 2019. 120 |

121 | 122 |

Contact Info

123 | 124 |

If you have any suggestions, feel free to tell me.

125 | 126 |

127 | You can find all my progress updates here:{" "} 128 | 129 | https://sponsor.ajay.app/news 130 | 131 |

132 | 133 |

134 | You can contact me by email at dev @ ajay.app if you have any 135 | questions. 136 |

137 | 138 |

139 | Feel free to join this Discord:{" "} 140 | 141 | https://discord.gg/SponsorBlock 142 | 143 |

144 | 145 |

146 | Or follow on Twitter:{" "} 147 | 148 | https://twitter.com/SponsorBlock 149 | 150 |

151 | 152 |

Credit

153 | 154 |

155 | Built and maintained by{" "} 156 | Ajay Ramachandran 157 |

158 | 159 |

160 | Thanks to all{" "} 161 | 162 | SponsorBlock contributors 163 | {" "} 164 | and{" "} 165 | 166 | SponsorBlockServer contributors 167 | {" "} 168 | such as NDev,{" "} 169 | Joe Dowd,{" "} 170 | Michael Chang and more. 171 |

172 | 173 |

174 | Logo by @munadikieh 175 |

176 | 177 |

178 | Some icons made by{" "} 179 | 183 | Gregor Cresnar 184 | {" "} 185 | from{" "} 186 | 187 | www.flaticon.com 188 | {" "} 189 | and are licensed by{" "} 190 | 194 | CC 3.0 BY 195 | 196 |

197 | 198 |

199 | Some icons made by{" "} 200 | 204 | Freepik 205 | {" "} 206 | from{" "} 207 | 208 | www.flaticon.com 209 | {" "} 210 | and are licensed by{" "} 211 | 215 | CC 3.0 BY 216 | 217 |

218 | 219 |

220 | The awesome{" "} 221 | Invidious API was used 222 | previously. 223 |

224 |
225 |
226 | ); 227 | 228 | export default IndexPage; 229 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | 3 | import { Link } from "gatsby"; 4 | 5 | import Layout from "../components/layout"; 6 | import Seo from "../components/seo"; 7 | 8 | const IndexPage = () => { 9 | const [totalStats, setTotalStats] = useState({ 10 | apiUsers: "Loading", 11 | totalSubmissions: "Loading", 12 | minutesSaved: "Loading", 13 | }); 14 | 15 | const chromeLink = "https://chrome.google.com/webstore/detail/mnjggcdmjocbbbhaepdhchncahnbgone"; 16 | const firefoxLink = "https://addons.mozilla.org/addon/sponsorblock"; 17 | 18 | useEffect(() => { 19 | fetch("https://sponsor.ajay.app/api/getTotalStats") 20 | .then((response) => response.json()) 21 | .then((resultData) => resultData.apiUsers !== undefined ? setTotalStats(resultData) : null); 22 | }, []); 23 | 24 | return ( 25 | 26 | 30 | 31 |
32 | Logo 33 | 34 | SponsorBlock 35 |
36 | 37 |
38 |
39 |

40 | SponsorBlock is an open-source crowdsourced browser 41 | extension and open API for skipping sponsor segments in 42 | YouTube videos. Users submit when a sponsor happens from 43 | the extension, and the extension automatically skips 44 | sponsors it knows about using a{" "} 45 | 46 | privacy preserving query system 47 | 48 | . It also supports skipping other categories, such as 49 | intros, outros and reminders to subscribe, and skipping 50 | to the point with highlight. 51 |

52 | 53 |

54 | Check{" "} 55 | 56 | status.sponsor.ajay.app 57 | {" "} 58 | for server status. 59 |

60 | 61 |

62 | There are currently{" "} 63 | {totalStats.apiUsers.toLocaleString()}{" "} 64 | users who have submitted{" "} 65 | 66 | {totalStats.totalSubmissions.toLocaleString()} 67 | {" "} 68 | skip segments, which have saved a total of{" "} 69 | 70 | {isNaN(totalStats.minutesSaved) 71 | ? totalStats.minutesSaved 72 | : Math.floor( 73 | totalStats.minutesSaved / 60 / 24 / 365 74 | )} 75 | {" "} 76 | years and{" "} 77 | 78 | {isNaN(totalStats.minutesSaved) 79 | ? totalStats.minutesSaved 80 | : ( 81 | (totalStats.minutesSaved / 60 / 24) % 82 | 365 83 | ).toFixed(2)} 84 | {" "} 85 | days of people's lives. Check out{" "} 86 | the leaderboard. 87 |

88 | 89 | 97 | 99 | 100 | 101 | Tired of clickbait? Also check out DeArrow 102 | 103 | 104 | 105 |
106 |

Download

107 | 108 | 110 | Download for Chrome 114 | 115 | 116 | 119 | Download for Firefox 123 | 124 | 125 | 129 | Download for Edge 134 | 135 | 136 | 140 | Download for Safari 145 | 146 | 147 | 151 | Download for Opera 152 | 153 | 154 | 158 | Download for Android 163 | 164 | 165 | 169 | Download for iOS 174 | 175 | 176 |

3rd Party Ports

177 | 178 | 182 | Download for MPV 183 | 184 | 185 | 189 | Download for Kodi 194 | 195 | 196 | 200 | Download for TVs 205 | 206 | 207 | 211 | Download for Android TV 216 | 217 | 218 | 222 | Download for Chromecast 227 | 228 |
229 | 230 |
231 |
232 | Ajay's avatar 236 | Support my full-time work on SponsorBlock 237 |
238 | 239 | 240 | Donate 241 | 242 |
243 | 244 |

245 | The{" "} 246 | 247 | source code 248 | {" "} 249 | is fully open and the{" "} 250 | database{" "} 251 | can be downloaded by anyone. I want to keep this as open 252 | as possible! You can view the docs for the{" "} 253 | 254 | public API 255 | {" "} 256 | or{" "} 257 | 258 | host a mirror 259 | 260 | . 261 |

262 | 263 |

264 | Check out how it works, and all the community tools. 265 |

266 | 267 |

268 | Come chat with us on{" "} 269 | Discord or{" "} 270 | 271 | Matrix 272 | 273 | . 274 |

275 | 276 |

277 | SponsorBlock works best alongside YouTube Premium and 278 | uBlock Origin. 279 |

280 | 281 |

Credit

282 | 283 |

284 | Built and maintained by{" "} 285 | Ajay Ramachandran 286 |

287 | 288 |

289 | Website rewritten by{" "} 290 | Jeremy Plsek 291 |

292 | 293 |

294 | Thanks to all{" "} 295 | 296 | SponsorBlock contributors 297 | 298 | ,{" "} 299 | 300 | SponsorBlockServer contributors 301 | {" "} 302 | and{" "} 303 | 304 | SponsorBlockSite contributors 305 | {" "} 306 | such as NDev,{" "} 307 | Joe Dowd,{" "} 308 | Michael Chang and 309 | more. 310 |

311 | 312 |

313 | Logo by{" "} 314 | @munadikieh 315 |

316 | 317 |

318 | 319 | Privacy Policy (Human Readable) 320 | 321 | {", "} 322 | 323 | Terms of Use 324 | 325 |

326 | 327 |

Links to other cool things

328 | 329 |

330 | 331 | yt-neuter 332 | 333 | 334 | yt uscripts 335 | 336 | 337 | uBlock Origin 338 | 339 | 340 | BlockTube 341 | 342 | 343 | Dark Reader 344 | 345 | 346 | Filmot 347 | 348 | 349 | NekoCap 350 | 351 | 352 | yt-dlp 353 | 354 | 355 | Restic 356 | 357 | 358 | Flashpoint 359 | 360 | 361 | Archive Team 362 | 363 | 364 | Data Horde 365 | 366 | 367 | Better Transit Ottawa 368 | 369 |

370 |
371 |
372 |
373 | ); 374 | }; 375 | 376 | export default IndexPage; 377 | -------------------------------------------------------------------------------- /src/pages/stats.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import classNames from "classnames"; 3 | 4 | import Layout from "../components/layout"; 5 | import Seo from "../components/seo"; 6 | 7 | const API_BASE = "https://sponsor.ajay.app"; 8 | let endpoint = "/api/getTopUsers?categoryStats=true"; 9 | let sortType = 0; 10 | let category = "all"; 11 | let checkboxShowStats = false; 12 | 13 | const IndexPage = () => { 14 | const [totalStats, setTotalStats] = useState({ 15 | apiUsers: 0, 16 | activeUsers: 0, 17 | userCount: 0, 18 | totalSubmissions: 0, 19 | minutesSaved: 0, 20 | viewCount: 0, 21 | }); 22 | const [categoryStats, setCategoryStats] = useState({ 23 | visible: false, 24 | data: [], 25 | }); 26 | 27 | const categoryStatsTitles = [ 28 | "Sponsor", 29 | "Intro", 30 | "Outro", 31 | "Interaction", 32 | "Self Promotion", 33 | "Non-Music Section", 34 | "Preview", 35 | "Highlight", 36 | "Filler", 37 | "Exclusive Access", 38 | "Chapter" 39 | ]; 40 | const categoryStatsColors = [ 41 | "#00d400", 42 | "#00ffff", 43 | "#0202ed", 44 | "#cc00ff", 45 | "#ffff00", 46 | "#ff9900", 47 | "#008fd6", 48 | "#ff1684", 49 | "#6600ff", 50 | "#008a5c", 51 | "#ffd679" 52 | ]; 53 | 54 | function generateCssConicGradientFromCategoryStats(data) { 55 | let lastPercentage = 0; 56 | const piechartCode = data.map((d, index) => { 57 | const percent = parseFloat(d[1]); 58 | const str = `${categoryStatsColors[index]} 0 ${ 59 | lastPercentage + percent 60 | }%`; 61 | lastPercentage += percent; 62 | return str; 63 | }); 64 | return `conic-gradient(${piechartCode.join(",")})`; 65 | } 66 | 67 | const [topUsers, setTopUsers] = useState([]); 68 | 69 | const [isTotalStatsLoading, setIsTotalStatsLoading] = useState(true); 70 | 71 | function setTopUserData(clickedElement) { 72 | const url = new URL(`${API_BASE}${endpoint}`); 73 | url.searchParams.append("sortType", sortType); 74 | url.searchParams.append("category", category); 75 | return fetch(url) 76 | .then((response) => { 77 | if (response.ok) return response.json() 78 | else return Promise.reject(response) 79 | }) 80 | .then((resultData) => { 81 | let size = resultData.userNames.length; 82 | 83 | if (clickedElement) { 84 | [...document.getElementsByClassName("sorted")].forEach( 85 | (el) => el.classList.remove("sorted") 86 | ); 87 | clickedElement.classList.add("sorted"); 88 | clickedElement.classList.remove("sort-loading"); 89 | } 90 | 91 | const transformedData = []; 92 | for (let i = 0; i < size; i++) { 93 | const days = Math.floor( 94 | resultData.minutesSaved[i] / 60 / 24 95 | ); 96 | const hours = ( 97 | (resultData.minutesSaved[i] / 60) % 98 | 24 99 | ).toFixed(1); 100 | 101 | let categoryStats = false; 102 | 103 | if ("categoryStats" in resultData) { 104 | const total = resultData.categoryStats[i].reduce( 105 | (accumulator, currentValue) => 106 | accumulator + currentValue, 107 | 0 108 | ); 109 | categoryStats = resultData.categoryStats[i].map( 110 | (value) => [ 111 | value, 112 | ((value / total) * 100).toFixed(2), 113 | ] 114 | ); 115 | } 116 | 117 | transformedData.push({ 118 | userName: resultData.userNames[i], 119 | viewCount: resultData.viewCounts[i], 120 | totalSubmissions: resultData.totalSubmissions[i], 121 | minutesSaved: 122 | (days > 0 ? days + "d " : "") + 123 | (hours > 0 ? hours + "h " : ""), 124 | categoryStats: categoryStats, 125 | }); 126 | } 127 | 128 | setTopUsers(transformedData); 129 | }) 130 | .catch((r) => console.error(r)); 131 | } 132 | 133 | useEffect(() => { 134 | fetch(API_BASE + "/api/getTotalStats?countContributingUsers=true") 135 | .then((response) => { 136 | if (response.ok) return response.json() 137 | else return Promise.reject(response) 138 | }) 139 | .then((resultData) => { 140 | setIsTotalStatsLoading(false); 141 | setTotalStats(resultData); 142 | }) 143 | .catch((r) => console.error(r)); 144 | setTopUserData(); 145 | }, []); 146 | 147 | const displayCategoryStats = (stats) => { 148 | if (stats === false) return; 149 | if (!checkboxShowStats) return; 150 | setCategoryStats({ visible: true, data: stats }); 151 | }; 152 | 153 | const hideCategoryStats = () => { 154 | setCategoryStats({ visible: false, data: [] }); 155 | }; 156 | 157 | return ( 158 | 159 | 160 | 161 |
162 |

Overall Stats

163 | 164 | 165 | 166 | 167 | 168 | 175 | 176 | 177 | 178 | 185 | 186 | 187 | 188 | 196 | 197 | 198 | 199 | 216 | 217 | 218 | 219 | 226 | 227 | 228 |
Active Users: 173 | {totalStats.apiUsers.toLocaleString()} users 174 |
Contributing Users: 183 | {totalStats.userCount.toLocaleString()} users 184 |
Submissions: 193 | {totalStats.totalSubmissions.toLocaleString()}{" "} 194 | segments 195 |
Time Saved: 204 | {Math.floor( 205 | totalStats.minutesSaved / 60 / 24 / 365 206 | )}{" "} 207 | years{" "} 208 | {Math.floor(totalStats.minutesSaved / 60 / 24) % 209 | 365}{" "} 210 | days{" "} 211 | {((totalStats.minutesSaved / 60) % 24).toFixed( 212 | 1 213 | )}{" "} 214 | hours 215 |
Skip Count: 224 | {totalStats.viewCount.toLocaleString()} skips 225 |
229 | 230 |
231 |

232 | Top Contributors 233 |

234 | 235 |
236 | Click a column title to change the sort 237 |
238 |
239 | 249 |
250 |
251 | 292 |
293 |
294 |
295 | 296 |
297 | 298 | 299 | 300 | 301 | 302 | 314 | 326 | 338 | 339 | 340 | 341 | 342 | {topUsers.length === 0 ? ( 343 | 344 | 352 | 353 | ) : ( 354 | topUsers.map((value, index) => ( 355 | { 361 | displayCategoryStats( 362 | value.categoryStats 363 | ); 364 | }} 365 | onMouseLeave={(_) => { 366 | hideCategoryStats(); 367 | }} 368 | > 369 | 372 | 373 | 376 | 379 | 382 | 383 | )) 384 | )} 385 | 386 |
RankUser Name { 305 | if (e.target.classList.contains("sorted")) 306 | return; 307 | e.target.classList.add("sort-loading"); 308 | sortType = 2; 309 | setTopUserData(e.target); 310 | }} 311 | > 312 | Submissions 313 | { 317 | if (e.target.classList.contains("sorted")) 318 | return; 319 | e.target.classList.add("sort-loading"); 320 | sortType = 0; 321 | setTopUserData(e.target); 322 | }} 323 | > 324 | Time Saved 325 | { 329 | if (e.target.classList.contains("sorted")) 330 | return; 331 | e.target.classList.add("sort-loading"); 332 | sortType = 1; 333 | setTopUserData(e.target); 334 | }} 335 | > 336 | Total Skips 337 |
345 |
Loading...
346 |
349 | Failing to load? Also try leaderboard.sbstats.uk 350 |
351 |
370 | {index + 1}. 371 | {value.userName} 374 | {value.totalSubmissions.toLocaleString()} 375 | 377 | {value.minutesSaved} 378 | 380 | {value.viewCount.toLocaleString()} 381 |
387 | 388 |
393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | {categoryStats.data.map((data, index) => ( 402 | 411 | 412 | 415 | 418 | 419 | ))} 420 | 421 |
CategorySubmissions
{categoryStatsTitles[index]} 413 | {data[0]} 414 | 416 | {data[1]}% 417 |
422 |
431 |
432 |
433 |
434 | ); 435 | }; 436 | 437 | export default IndexPage; 438 | -------------------------------------------------------------------------------- /static/safari.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | Safari browser logo 22 | 24 | 27 | 31 | 35 | 39 | 43 | 47 | 48 | 51 | 55 | 59 | 60 | 70 | 81 | 89 | 93 | 94 | 102 | 106 | 107 | 108 | 137 | 139 | 140 | 142 | image/svg+xml 143 | 145 | Safari browser logo 146 | 03/04/2018 147 | 148 | 149 | Apple, Inc. 150 | 151 | 152 | 153 | 154 | CMetalCore 155 | 156 | 157 | https://fthmb.tqn.com/gnIDz9kkyZVRsWbJJOlHIYf_42g=/768x0/filters:no_upscale()/Safari-Yosemite-56a01c725f9b58eba4af0427.jpg 158 | 159 | 160 | 161 | 166 | 168 | 174 | 179 | 184 | 189 | 196 | 198 | 215 | 232 | 237 | 238 | 239 | 240 | 241 | --------------------------------------------------------------------------------