├── .gitignore ├── public ├── js │ ├── following.js │ ├── script.js │ └── first.js └── css │ └── style.css ├── views ├── first.ejs ├── following.ejs └── layout.ejs ├── README.md ├── package.json └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /public/js/following.js: -------------------------------------------------------------------------------- 1 | const getDataFromAPI = (name) => 2 | (window.location.href = `https://www.twitchdatabase.com/following/${name}`); 3 | -------------------------------------------------------------------------------- /views/first.ejs: -------------------------------------------------------------------------------- 1 |

Enter a name to see their first followers...

2 | 3 |
-------------------------------------------------------------------------------- /views/following.ejs: -------------------------------------------------------------------------------- 1 |

Enter a name to see who they're following...

2 | 3 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twitch Following 2 | Simple web app that shows you who Twitch users are following. 3 | 4 | # Running 5 | - Run `npm i` to install dependencies. 6 | - Run `npm start` to start the web server. 7 | - Open https://localhost:8081 to visit the web app. 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitch-following", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node ." 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "ejs": "^3.1.5", 14 | "express": "^4.17.1", 15 | "express-ejs-layouts": "^2.5.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/js/script.js: -------------------------------------------------------------------------------- 1 | const input = document.querySelector('input'); 2 | const resultDiv = document.querySelector('.result-div'); 3 | 4 | const clientID = 'cclk5hafv1i7lksfauerry4w7ythu2'; 5 | 6 | let currentlySearching = false; 7 | 8 | let starterName = location.search.substring(1); 9 | 10 | if (starterName) { 11 | input.value = starterName.toLowerCase(); 12 | history.pushState(null, null, `?${input.value.toLowerCase()}`); 13 | getDataFromAPI(starterName); 14 | } 15 | 16 | input.onkeydown = event => { 17 | if (event.which === 13) { 18 | input.value = input.value.toLowerCase(); 19 | history.pushState(null, null, `?${input.value.toLowerCase()}`); 20 | getDataFromAPI(input.value); 21 | } 22 | } -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | min-height: 100%; 3 | } 4 | 5 | body { 6 | position: absolute; 7 | width: 100%; 8 | padding-bottom: 286px; 9 | } 10 | 11 | body, input { 12 | font-family: 'Open Sans', sans-serif !important; 13 | } 14 | 15 | a { 16 | text-decoration: none !important; 17 | } 18 | 19 | .bg-black { 20 | background-color: #000; 21 | } 22 | 23 | .bg-purple { 24 | background-color: #9146FF; 25 | } 26 | 27 | .bg-grey { 28 | background-color: #0E0E10; 29 | } 30 | 31 | .text-purple, .text-purple:hover { 32 | color: #9146FF; 33 | } 34 | 35 | .ad { 36 | position: absolute; 37 | bottom: 50px; 38 | width: 100%; 39 | height: 286px; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | height: 50px; 47 | line-height: 50px; 48 | } 49 | 50 | .following-card { 51 | transition: opacity 1s, transform 0.2s; 52 | } 53 | 54 | .following-card:hover { 55 | transform: scale(1.05); 56 | } 57 | 58 | .first-card p { 59 | margin: 0; 60 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const expressLayouts = require('express-ejs-layouts'); 3 | 4 | const app = express(); 5 | 6 | app.set('view engine', 'ejs'); 7 | 8 | app.use(expressLayouts); 9 | 10 | app.use(express.static('public')); 11 | 12 | const featuredChannels = ['streamcanvas']; 13 | 14 | app.use((req, res, next) => { 15 | res.locals.showFirstFollowersButton = new Date() < new Date('02/20/2022'); 16 | next(); 17 | }); 18 | 19 | app.get('/', (req, res) => { 20 | res.render('following', { 21 | tool: 'following', 22 | featuredChannel: 23 | featuredChannels[Math.floor(Math.random() * featuredChannels.length)], 24 | }); 25 | }); 26 | 27 | app.use((req, res, next) => { 28 | if (new Date() > new Date('02/20/2022')) { 29 | res.redirect('https://www.twitchdatabase.com'); 30 | } else { 31 | next(); 32 | } 33 | }); 34 | 35 | app.get('/first', (req, res) => { 36 | res.render('first', { 37 | tool: 'first', 38 | featuredChannel: 39 | featuredChannels[Math.floor(Math.random() * featuredChannels.length)], 40 | }); 41 | }); 42 | 43 | app.listen(8083); 44 | -------------------------------------------------------------------------------- /public/js/first.js: -------------------------------------------------------------------------------- 1 | const getDataFromAPI = name => { 2 | resultDiv.innerHTML = `

Looking for ${name}'s Twitch ID...

`; 3 | fetch(`https://api.twitch.tv/kraken/users?login=${name}`, { 4 | headers: { 5 | 'Accept': 'application/vnd.twitchtv.v5+json', 6 | 'Client-ID': clientID 7 | } 8 | }) 9 | .then(res => res.json()) 10 | .then(usersData => { 11 | if (usersData && usersData.users && usersData.users[0]) { 12 | const user = usersData.users[0]; 13 | resultDiv.innerHTML = `

Looking for ${user.display_name}'s first followers...`; 14 | fetch(`https://api.twitch.tv/kraken/channels/${user._id}/follows?limit=100&direction=asc`, { 15 | headers: { 16 | 'Accept': 'application/vnd.twitchtv.v5+json', 17 | 'Client-ID': clientID 18 | } 19 | }) 20 | .then(res => res.json()) 21 | .then(firstFollowers => { 22 | resultDiv.innerHTML = `

${user.display_name}'s first ${firstFollowers.follows.length} followers:

`; 23 | resultDiv.insertAdjacentHTML('beforeend', ` 24 |
25 |
26 |
27 |
28 |
29 |

#

30 |
31 |
32 |

Name

33 |
34 |
35 |

Date

36 |
37 |
38 |
39 |
40 |
`); 41 | firstFollowers.follows.forEach((follower, index) => { 42 | resultDiv.insertAdjacentHTML('beforeend', ` 43 |
44 |
45 |
46 |
47 |
48 |

${index + 1}

49 |
50 | 53 |
54 |

${follower.created_at.replace('T', ' ').replace('Z', '')}

55 |
56 |
57 |
58 |
59 |
`); 60 | }); 61 | }); 62 | } else { 63 | resultDiv.innerHTML = `

Couldn't find that user. Did you type it right?

`; 64 | } 65 | }); 66 | }; -------------------------------------------------------------------------------- /views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Twitch Following 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 49 |
50 | <%- body %> 51 | 52 |
53 |
54 | 55 |
56 | 59 | 60 | --------------------------------------------------------------------------------