19 | ```
20 | After this, all scraped data will be written into .csv files.
21 |
--------------------------------------------------------------------------------
/frontend/src/components/TableComp.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Table } from "semantic-ui-react";
3 |
4 | class TableComp extends Component {
5 | renderRows() {
6 | return Object.keys(this.props.column_1).map((game, id) => {
7 | return (
8 |
9 |
10 | {this.props.column_1[id][this.props.column_1_key]}
11 |
12 |
13 | {this.props.column_2[id][this.props.column_2_key]}
14 |
15 |
16 | );
17 | });
18 | }
19 | render() {
20 | return (
21 |
22 |
23 |
24 | {this.props.headers[0]}
25 | {this.props.headers[1]}
26 |
27 |
28 | {this.renderRows()}
29 |
30 | );
31 | }
32 | }
33 |
34 | export default TableComp;
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Katarina Supe
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 |
--------------------------------------------------------------------------------
/backend/import-data/teams.csv:
--------------------------------------------------------------------------------
1 | user_id,team_name
2 | 569342750,dreamsmp
3 | 124425501,riotgames
4 | 88946548,ader
5 | 88946548,nrg
6 | 156037856,esostreamteam
7 | 146549780,sktt1
8 | 77415295,gemagema
9 | 107305687,rustnchill
10 | 30104304,unitedtalentagency
11 | 38594688,luminosity
12 | 106013742,theteam
13 | 61433001,onmyway
14 | 38746172,otk
15 | 38746172,clique
16 | 54739364,esa
17 | 32053915,lestream
18 | 22253819,unitedtalentagency
19 | 29578325,bts
20 | 29578325,btsstats
21 | 37522866,friendzone
22 | 29722828,wve
23 | 32402794,csgopros
24 | 188890121,monke
25 | 188890121,freak
26 | 188890121,streamersalliance
27 | 68240113,esa
28 | 68240113,germench
29 | 152596920,geng
30 | 86131599,geng
31 | 181077473,tribogaules
32 | 45302947,chrchads
33 | 116885541,gfuel
34 | 46431370,faze
35 | 410965603,ggarmy
36 | 14408894,plantbasedgang
37 | 11897156,friendzone
38 | 11897156,callousrow
39 | 11897156,zoo
40 | 11897156,competitiveoverwatch
41 | 11897156,enemy
42 | 57025612,g2esports
43 | 38779717,ths
44 | 87791915,unique_streamers
45 | 135052907,plus1
46 | 44574567,theclique
47 | 21588571,gfuel
48 | 59635827,nrg
49 | 59635827,sanfranciscoshock
50 | 54121470,appearingoffline
51 | 54121470,tatmanarmy
52 |
--------------------------------------------------------------------------------
/frontend/src/components/RadioPick.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Form, Radio } from "semantic-ui-react";
3 |
4 | export default class RadioPick extends Component {
5 | state = {
6 | value: this.props.defaultValue,
7 | };
8 |
9 | twoCalls = (e, { value }) => {
10 | this.handleChange(e, { value });
11 | this.changeStateParent(e, { value });
12 | };
13 |
14 | handleChange = (e, { value }) => this.setState({ value });
15 | changeStateParent = (e, { value }) => {
16 | this.props.updateStateParent({ value });
17 | };
18 |
19 | render() {
20 | return (
21 |
23 |
30 |
31 |
32 |
39 |
40 |
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/src/components/DropdownStreamers.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Dropdown } from "semantic-ui-react";
3 |
4 | class DropdownStreamers extends Component {
5 | state = {
6 | value: "Followers",
7 | };
8 |
9 | twoCalls = (e, { value }) => {
10 | this.handleChange(e, { value });
11 | this.changeStateParent(e, { value });
12 | };
13 |
14 | changeStateParent = (e, { value }) => {
15 | this.props.updateStateParent({ value });
16 | };
17 |
18 | handleChange = (e, { value }) => {
19 | this.setState({ value });
20 | };
21 |
22 | render() {
23 | const { value } = this.state;
24 | return (
25 |
33 |
34 |
40 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default DropdownStreamers;
53 |
--------------------------------------------------------------------------------
/backend/models.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 | from app import memgraph
3 | from gqlalchemy import Node, Field, Relationship
4 |
5 |
6 | class User(Node):
7 | name: str = Field(index=True, exists=True, unique=True, db=memgraph)
8 |
9 |
10 | class Stream(User):
11 | name: Optional[str] = Field(
12 | index=True, exists=True, unique=True, db=memgraph, label="User"
13 | )
14 | id: str = Field(index=True, exists=True, unique=True, db=memgraph)
15 | url: Optional[str] = Field()
16 | followers: Optional[int] = Field()
17 | createdAt: Optional[str] = Field()
18 | totalViewCount: Optional[int] = Field()
19 | description: Optional[str] = Field()
20 |
21 |
22 | class Language(Node):
23 | name: str = Field(unique=True, db=memgraph)
24 |
25 |
26 | class Game(Node):
27 | name: str = Field(unique=True, db=memgraph)
28 |
29 |
30 | class Team(Node):
31 | name: str = Field(unique=True, db=memgraph)
32 |
33 |
34 | class Speaks(Relationship, type="SPEAKS"):
35 | pass
36 |
37 |
38 | class Plays(Relationship, type="PLAYS"):
39 | pass
40 |
41 |
42 | class IsPartOf(Relationship, type="IS_PART_OF"):
43 | pass
44 |
45 |
46 | class Vip(Relationship, type="VIP"):
47 | pass
48 |
49 |
50 | class Moderator(Relationship, type="MODERATOR"):
51 | pass
52 |
53 |
54 | class Chatter(Relationship, type="CHATTER"):
55 | pass
56 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@babel/core": "^7.15.0",
7 | "@babel/preset-env": "^7.15.0",
8 | "@testing-library/jest-dom": "^5.11.4",
9 | "@testing-library/react": "^11.1.0",
10 | "@testing-library/user-event": "^12.1.10",
11 | "d3": "^7.0.1",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "react-scripts": "4.0.3",
15 | "react-scroll-to-component": "^1.0.2",
16 | "semantic-ui-css": "^2.4.1",
17 | "semantic-ui-react": "^2.0.3",
18 | "web-vitals": "^1.0.1"
19 | },
20 | "scripts": {
21 | "start": "craco start",
22 | "build": "craco build",
23 | "test": "craco test",
24 | "eject": "craco eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | },
44 | "proxy": "http://twitch-app:5000",
45 | "devDependencies": {
46 | "@craco/craco": "^6.2.0",
47 | "@semantic-ui-react/craco-less": "^1.2.3",
48 | "semantic-ui-less": "^2.4.1"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Twitch Analytics Demo
3 |
4 |
5 | ## :open_file_folder: Dataset
6 | The data was collected using [Twitch API](https://dev.twitch.tv/docs/api/). The files which we'll use are located in `/backend/import-data/` folder and are called: `streamers.csv`, `teams.csv`, `vips.csv`, `moderators.csv` and `chatters.csv`.
7 |
8 | ## :arrow_forward: Starting the app
9 |
10 | You can simply start the app by running:
11 |
12 | ```
13 | docker-compose build
14 | docker-compose up core
15 | docker-compose up twitch-app
16 | ```
17 |
18 | If you get the error `mgclient.OperationalError: couldn't connect to host: Connection refused` please try running `docker-compose up twitch-app` again.
19 | When data loading is done, run:
20 |
21 | ```
22 | docker-compose up react-app
23 | ```
24 |
25 | Check out the app at `localhost:3000`.
26 |
27 | To start streaming the rest of the data run:
28 |
29 | ```
30 | docker-compose up twitch-stream
31 | ```
32 |
33 | Notice how the number of nodes and edges are changing on the navigation bar. Also if you refresh the results of the PageRank below, you'll see the rank difference.
34 |
35 | ## :bar_chart: General statistics
36 |
37 | Choose from top games, teams, vips, moderators or streamers.
38 |
39 | 
40 |
41 | ## :eyes: Graph visualization
42 |
43 | Find your favorite streamer or the streamers who stream your favorite game in your language.
44 |
45 | 
46 |
47 | Check out the **PageRank** and **Betweenness Centrality** [MAGE](https://memgraph.com/docs/mage) algorithms.
48 |
49 | 
50 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
17 |
18 |
22 |
23 |
32 | Twitch Demo
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/frontend/src/components/Counter.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { Button } from "semantic-ui-react";
3 |
4 | class Counter extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | isLoaded: false,
9 | error: false,
10 | counter: "0",
11 | };
12 | }
13 |
14 | fetch() {
15 | fetch("/" + this.props.count)
16 | .then((res) => res.json())
17 | .then(
18 | (result) => {
19 | this.setState({
20 | isLoaded: true,
21 | counter: result[this.props.count],
22 | });
23 | },
24 | (error) => {
25 | this.setState({
26 | isLoaded: true,
27 | error,
28 | });
29 | }
30 | );
31 | }
32 |
33 | componentDidMount() {
34 | this.interval = setInterval(() => this.fetch(), 3000);
35 | }
36 | componentWillUnmount() {
37 | clearInterval(this.interval);
38 | }
39 |
40 | render() {
41 | const { error, isLoaded, counter } = this.state;
42 | if (error) {
43 | return Error: {error.message}
;
44 | } else if (!isLoaded) {
45 | return (
46 |
49 | );
50 | } else {
51 | if (this.props.count === "nodes") {
52 | return (
53 |
57 | );
58 | } else {
59 | return (
60 |
64 | );
65 | }
66 | }
67 | }
68 | }
69 |
70 | export default Counter;
71 |
--------------------------------------------------------------------------------
/twitch-stream/setup.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from gqlalchemy import Memgraph, MemgraphKafkaStream
3 | from kafka.admin import KafkaAdminClient, NewTopic
4 | from kafka.errors import TopicAlreadyExistsError, NoBrokersAvailable
5 | from time import sleep
6 |
7 |
8 | log = logging.getLogger(__name__)
9 |
10 |
11 | def connect_to_memgraph(memgraph_ip, memgraph_port):
12 | memgraph = Memgraph(host=memgraph_ip, port=int(memgraph_port))
13 | while True:
14 | try:
15 | if memgraph._get_cached_connection().is_active():
16 | return memgraph
17 | except:
18 | log.info("Memgraph probably isn't running.")
19 | sleep(1)
20 |
21 |
22 | def get_admin_client(kafka_ip, kafka_port):
23 | retries = 30
24 | while True:
25 | try:
26 | admin_client = KafkaAdminClient(
27 | bootstrap_servers=kafka_ip + ":" + kafka_port, client_id="twitch-stream"
28 | )
29 | return admin_client
30 | except NoBrokersAvailable:
31 | retries -= 1
32 | if not retries:
33 | raise
34 | log.info("Failed to connect to Kafka")
35 | sleep(1)
36 |
37 |
38 | def run(memgraph, kafka_ip, kafka_port):
39 | admin_client = get_admin_client(kafka_ip, kafka_port)
40 | log.info("Connected to Kafka")
41 |
42 | topic_list = [
43 | NewTopic(name="chatters", num_partitions=1, replication_factor=1),
44 | ]
45 |
46 | try:
47 | admin_client.create_topics(new_topics=topic_list, validate_only=False)
48 | except TopicAlreadyExistsError:
49 | pass
50 | log.info("Created topics")
51 |
52 | log.info("Creating stream connections on Memgraph")
53 | stream = MemgraphKafkaStream(
54 | name="chatter_stream",
55 | topics=["chatters"],
56 | transform="twitch.chatters",
57 | bootstrap_servers="'kafka:9092'",
58 | )
59 | memgraph.create_stream(stream)
60 | memgraph.start_stream(stream)
61 |
--------------------------------------------------------------------------------
/twitch-stream/dummy.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | from argparse import ArgumentParser
4 | from kafka import KafkaProducer
5 | from kafka.errors import NoBrokersAvailable
6 | from time import sleep
7 | import setup
8 |
9 |
10 | KAFKA_IP = os.getenv('KAFKA_IP', 'kafka')
11 | KAFKA_PORT = os.getenv('KAFKA_PORT', '9092')
12 | MEMGRAPH_IP = os.getenv('MEMGRAPH_IP', 'memgraph-mage')
13 | MEMGRAPH_PORT = os.getenv('MEMGRAPH_PORT', '7687')
14 |
15 |
16 | def parse_args():
17 | """
18 | Parse input command line arguments.
19 | """
20 | parser = ArgumentParser(
21 | description="A Twitch stream machine powered by Memgraph.")
22 | parser.add_argument("--file", help="File with chatter data.")
23 | parser.add_argument(
24 | "--interval",
25 | type=int,
26 | help="Interval for sending data in seconds.")
27 | return parser.parse_args()
28 |
29 |
30 | def create_kafka_producer():
31 | retries = 30
32 | while True:
33 | try:
34 | producer = KafkaProducer(
35 | bootstrap_servers=KAFKA_IP + ':' + KAFKA_PORT)
36 | return producer
37 | except NoBrokersAvailable:
38 | retries -= 1
39 | if not retries:
40 | raise
41 | print("Failed to connect to Kafka")
42 | sleep(1)
43 |
44 |
45 | def main():
46 | args = parse_args()
47 |
48 | memgraph = setup.connect_to_memgraph(MEMGRAPH_IP, MEMGRAPH_PORT)
49 | setup.run(memgraph, KAFKA_IP, KAFKA_PORT)
50 |
51 | producer = create_kafka_producer()
52 | with open(args.file) as f:
53 | for line in f.readlines():
54 | line_list = line.strip().split(",")
55 | line_json = {
56 | 'user_id': line_list[0],
57 | 'chatter_login': line_list[1]
58 | }
59 |
60 | print(f'Sending data to chatters')
61 | producer.send("chatters", json.dumps(line_json).encode('utf8'))
62 | sleep(args.interval)
63 |
64 |
65 | if __name__ == "__main__":
66 | main()
67 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | networks:
3 | app-tier:
4 | driver: bridge
5 | services:
6 | zookeeper:
7 | image: "bitnami/zookeeper:3.7"
8 | ports:
9 | - "2181:2181"
10 | environment:
11 | - ALLOW_ANONYMOUS_LOGIN=yes
12 | networks:
13 | - app-tier
14 | logging:
15 | driver: none
16 | kafka:
17 | image: "bitnami/kafka:2"
18 | logging:
19 | driver: none
20 | ports:
21 | - "9093:9093"
22 | environment:
23 | - KAFKA_BROKER_ID=1
24 | - ALLOW_PLAINTEXT_LISTENER=yes
25 | - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
26 | - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CLIENT:PLAINTEXT,EXTERNAL:PLAINTEXT
27 | - KAFKA_CFG_LISTENERS=CLIENT://:9092,EXTERNAL://:9093
28 | - KAFKA_CFG_ADVERTISED_LISTENERS=CLIENT://kafka:9092,EXTERNAL://localhost:9093
29 | - KAFKA_INTER_BROKER_LISTENER_NAME=CLIENT
30 | depends_on:
31 | - zookeeper
32 | networks:
33 | - app-tier
34 | memgraph-mage:
35 | build: ./memgraph
36 | entrypoint:
37 | [
38 | "/usr/lib/memgraph/memgraph",
39 | ]
40 | ports:
41 | - "7687:7687"
42 | networks:
43 | - app-tier
44 | core:
45 | image: tianon/true
46 | restart: "no"
47 | depends_on:
48 | - kafka
49 | - memgraph-mage
50 | twitch-app:
51 | build: ./backend
52 | volumes:
53 | - ./backend:/app
54 | ports:
55 | - "5000:5000"
56 | environment:
57 | MG_HOST: memgraph-mage
58 | MG_PORT: 7687
59 | depends_on:
60 | - memgraph-mage
61 | networks:
62 | - app-tier
63 | react-app:
64 | build: ./frontend
65 | volumes:
66 | - ./frontend:/app
67 | - /app/node_modules
68 | ports:
69 | - "3000:3000"
70 | depends_on:
71 | - twitch-app
72 | networks:
73 | - app-tier
74 | twitch-stream:
75 | build: ./twitch-stream
76 | volumes:
77 | - ./twitch-stream:/app
78 | entrypoint:
79 | [
80 | "python3",
81 | "dummy.py",
82 | "--file=chatters.csv",
83 | "--interval=1"
84 | ]
85 | environment:
86 | KAFKA_IP: kafka
87 | KAFKA_PORT: "9092"
88 | MEMGRAPH_IP: memgraph-mage
89 | MEMGRAPH_PORT: "7687"
90 | depends_on:
91 | - core
92 | networks:
93 | - app-tier
94 |
--------------------------------------------------------------------------------
/frontend/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/semantic-ui/theme.config:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | ████████╗██╗ ██╗███████╗███╗ ███╗███████╗███████╗
4 | ╚══██╔══╝██║ ██║██╔════╝████╗ ████║██╔════╝██╔════╝
5 | ██║ ███████║█████╗ ██╔████╔██║█████╗ ███████╗
6 | ██║ ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══╝ ╚════██║
7 | ██║ ██║ ██║███████╗██║ ╚═╝ ██║███████╗███████║
8 | ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝
9 |
10 | */
11 |
12 | /*******************************
13 | Theme Selection
14 | *******************************/
15 |
16 | /* To override a theme for an individual element
17 | specify theme name below
18 | */
19 |
20 | /* Global */
21 | @site : 'default';
22 | @reset : 'default';
23 |
24 | /* Elements */
25 | @button : 'default';
26 | @container : 'default';
27 | @divider : 'default';
28 | @flag : 'default';
29 | @header : 'default';
30 | @icon : 'default';
31 | @image : 'default';
32 | @input : 'default';
33 | @label : 'default';
34 | @list : 'default';
35 | @loader : 'default';
36 | @placeholder : 'default';
37 | @rail : 'default';
38 | @reveal : 'default';
39 | @segment : 'default';
40 | @step : 'default';
41 |
42 | /* Collections */
43 | @breadcrumb : 'default';
44 | @form : 'default';
45 | @grid : 'default';
46 | @menu : 'default';
47 | @message : 'default';
48 | @table : 'default';
49 |
50 | /* Modules */
51 | @accordion : 'default';
52 | @checkbox : 'default';
53 | @dimmer : 'default';
54 | @dropdown : 'default';
55 | @embed : 'default';
56 | @modal : 'default';
57 | @nag : 'default';
58 | @popup : 'default';
59 | @progress : 'default';
60 | @rating : 'default';
61 | @search : 'default';
62 | @shape : 'default';
63 | @sidebar : 'default';
64 | @sticky : 'default';
65 | @tab : 'default';
66 | @transition : 'default';
67 |
68 | /* Views */
69 | @ad : 'default';
70 | @card : 'default';
71 | @comment : 'default';
72 | @feed : 'default';
73 | @item : 'default';
74 | @statistic : 'default';
75 |
76 | /*******************************
77 | Folders
78 | *******************************/
79 |
80 | /* Path to theme packages */
81 | @themesFolder : 'themes';
82 |
83 | /* Path to site override folder */
84 | @siteFolder : '../../src/semantic-ui/site';
85 |
86 |
87 | /*******************************
88 | Import Theme
89 | *******************************/
90 |
91 | @import (multiple) "~semantic-ui-less/theme.less";
92 | @fontPath : '../../../themes/@{theme}/assets/fonts';
93 |
94 | /* End Config */
95 |
--------------------------------------------------------------------------------
/frontend/src/components/Vips.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | Grid,
4 | Segment,
5 | Header,
6 | Dimmer,
7 | Loader,
8 | } from "semantic-ui-react";
9 | import TableComp from "./TableComp";
10 |
11 | class Vips extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | error: null,
16 | isLoaded: false,
17 | vips: [],
18 | streamers: [],
19 | numOfVips: "10",
20 | header: "Top 10 vips",
21 | };
22 | }
23 |
24 | fetchData(number) {
25 | fetch("/top-vips/" + number)
26 | .then((res) => res.json())
27 | .then(
28 | (result) => {
29 | this.setState({
30 | isLoaded: true,
31 | vips: result.vips,
32 | streamers: result.streamers,
33 | });
34 | },
35 | (error) => {
36 | this.setState({
37 | isLoaded: true,
38 | error,
39 | });
40 | }
41 | );
42 | this.setState({
43 | numOfVips: number,
44 | header: "Top " + number + " vips",
45 | });
46 | }
47 | componentDidMount() {
48 | this.fetchData(this.state.numOfVips);
49 | }
50 |
51 | render() {
52 | const { error, isLoaded, header } = this.state;
53 | const paragraph =
54 | "Find out which user has the most vip badges.";
55 | const headers = ["Vip", "Number of badges"];
56 | if (error) {
57 | return Error: {error.message}
;
58 | } else if (!isLoaded) {
59 | return (
60 |
61 |
62 |
63 | Getting top vips
64 |
65 |
66 |
67 | );
68 | } else {
69 | return (
70 |
71 |
72 |
73 |
74 |
77 | {paragraph}
78 |
79 |
80 |
87 |
88 |
89 |
90 |
91 | );
92 | }
93 | }
94 | }
95 |
96 | export default Vips;
97 |
--------------------------------------------------------------------------------
/frontend/src/components/Teams.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | Grid,
4 | Segment,
5 | Header,
6 | Dimmer,
7 | Loader,
8 | } from "semantic-ui-react";
9 | import TableComp from "./TableComp";
10 |
11 | class Teams extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | error: null,
16 | isLoaded: false,
17 | teams: [],
18 | members: [],
19 | numOfTeams: "10",
20 | header: "Top 10 teams",
21 | };
22 | }
23 |
24 | fetchData(number) {
25 | fetch("/top-teams/" + number)
26 | .then((res) => res.json())
27 | .then(
28 | (result) => {
29 | this.setState({
30 | isLoaded: true,
31 | teams: result.teams,
32 | members: result.members,
33 | });
34 | },
35 | (error) => {
36 | this.setState({
37 | isLoaded: true,
38 | error,
39 | });
40 | }
41 | );
42 | this.setState({
43 | numOfTeams: number,
44 | header: "Top " + number + " teams",
45 | });
46 | }
47 |
48 | componentDidMount() {
49 | this.fetchData(this.state.numOfTeams);
50 | }
51 |
52 | render() {
53 | const { error, isLoaded, header } = this.state;
54 | const paragraph =
55 | "Find out which teams are the most popular.";
56 | const headers = ["Team", "Number of members"];
57 | if (error) {
58 | return Error: {error.message}
;
59 | } else if (!isLoaded) {
60 | return (
61 |
62 |
63 |
64 | Getting top teams
65 |
66 |
67 |
68 | );
69 | } else {
70 | return (
71 |
72 |
73 |
74 |
75 |
78 | {paragraph}
79 |
80 |
81 |
88 |
89 |
90 |
91 |
92 | );
93 | }
94 | }
95 | }
96 |
97 | export default Teams;
98 |
--------------------------------------------------------------------------------
/frontend/src/components/Moderators.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | Grid,
4 | Segment,
5 | Header,
6 | Dimmer,
7 | Loader,
8 | } from "semantic-ui-react";
9 | import TableComp from "./TableComp";
10 |
11 | class Moderators extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | error: null,
16 | isLoaded: false,
17 | moderators: [],
18 | streamers: [],
19 | numOfMods: "10",
20 | header: "Top 10 moderators",
21 | };
22 | }
23 |
24 | fetchData(number) {
25 | fetch("/top-moderators/" + number)
26 | .then((res) => res.json())
27 | .then(
28 | (result) => {
29 | this.setState({
30 | isLoaded: true,
31 | moderators: result.moderators,
32 | streamers: result.streamers,
33 | });
34 | },
35 | (error) => {
36 | this.setState({
37 | isLoaded: true,
38 | error,
39 | });
40 | }
41 | );
42 | this.setState({
43 | numOfMods: number,
44 | header: "Top " + number + " moderators",
45 | });
46 | }
47 |
48 | componentDidMount() {
49 | this.fetchData(this.state.numOfMods);
50 | }
51 |
52 | render() {
53 | const { error, isLoaded, header } = this.state;
54 | const headers = ["Moderator", "Number of channels"];
55 | const paragraph =
56 | "Find out which user is the most popular channel moderator.";
57 | if (error) {
58 | return Error: {error.message}
;
59 | } else if (!isLoaded) {
60 | return (
61 |
62 |
63 |
64 | Getting top moderators
65 |
66 |
67 |
68 | );
69 | } else {
70 | return (
71 |
72 |
73 |
74 |
75 |
78 | {paragraph}
79 |
80 |
81 |
88 |
89 |
90 |
91 |
92 | );
93 | }
94 | }
95 | }
96 |
97 | export default Moderators;
98 |
--------------------------------------------------------------------------------
/frontend/src/components/FindStreamer.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { Grid, Segment, Header, Dimmer, Loader } from "semantic-ui-react";
3 | import AutoSearch from "./AutoSearch";
4 | import Graph from "./Graph";
5 |
6 | class FindStreamer extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | error: null,
11 | isLoaded: false,
12 | nodes: [],
13 | links: [],
14 | streamerName: "Fextralife",
15 | };
16 | }
17 |
18 | fetchData(name) {
19 | fetch("/streamer/" + name)
20 | .then((res) => res.json())
21 | .then(
22 | (result) => {
23 | this.setState({
24 | isLoaded: true,
25 | nodes: result.nodes,
26 | links: result.links,
27 | });
28 | },
29 | (error) => {
30 | this.setState({
31 | isLoaded: true,
32 | error,
33 | });
34 | }
35 | );
36 | this.setState({
37 | streamerName: name,
38 | });
39 | }
40 |
41 | componentDidMount() {
42 | this.fetchData(this.state.streamerName);
43 | }
44 |
45 | updateStreamerName = (strName) => {
46 | this.fetchData(strName);
47 | };
48 |
49 | render() {
50 | const { error, isLoaded } = this.state;
51 | if (error) {
52 | return Error: {error.message}
;
53 | } else if (!isLoaded) {
54 | return (
55 |
56 |
57 |
58 | Finding your streamer
59 |
60 |
61 |
62 | );
63 | } else {
64 | const header = "Get to know your favourite streamer";
65 | const paragraph =
66 | "Games, teams and languages - all you need to know about your favourite streamer.";
67 | const square = { width: 250, height: 250 };
68 | return (
69 |
70 |
71 |
72 |
73 |
76 | {paragraph}
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | );
89 | }
90 | }
91 | }
92 |
93 | export default FindStreamer;
94 |
--------------------------------------------------------------------------------
/frontend/src/components/Games.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | Grid,
4 | Segment,
5 | Header,
6 | Dimmer,
7 | Loader,
8 | } from "semantic-ui-react";
9 | import TableComp from "./TableComp";
10 |
11 | class Games extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | error: null,
16 | isLoaded: false,
17 | games: [],
18 | players: [],
19 | numOfGames: "10",
20 | header: "Top 10 games",
21 | };
22 | }
23 |
24 | fetchData(number) {
25 | fetch("/top-games/" + number)
26 | .then((res) => res.json())
27 | .then(
28 | (result) => {
29 | this.setState({
30 | isLoaded: true,
31 | games: result.games,
32 | players: result.players,
33 | });
34 | },
35 | (error) => {
36 | this.setState({
37 | isLoaded: true,
38 | error,
39 | });
40 | }
41 | );
42 | this.setState({
43 | numOfGames: number,
44 | header: "Top " + number + " games",
45 | });
46 | }
47 |
48 | componentDidMount() {
49 | this.fetchData(this.state.numOfGames);
50 | }
51 |
52 |
53 | render() {
54 |
55 | const { error, isLoaded, header } = this.state;
56 | const paragraph =
57 | "Find out which games are played by the largest number of streamers.";
58 | const headers = ["Game", "Number of players"];
59 | if (error) {
60 | return Error: {error.message}
;
61 | } else if (!isLoaded) {
62 | return (
63 |
64 |
65 |
66 | Getting top games
67 |
68 |
69 |
70 | );
71 | } else {
72 | return (
73 |
74 |
75 |
76 |
77 |
80 | {paragraph}
81 |
82 |
83 |
90 |
91 |
92 |
93 |
94 | );
95 | }
96 | }
97 | }
98 |
99 | export default Games;
100 |
--------------------------------------------------------------------------------
/frontend/src/components/FindStreamer2.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { Grid, Segment, Header, Dimmer, Loader } from "semantic-ui-react";
3 | import Graph from "./Graph";
4 | import AutoCompleteGame from "./AutoCompleteGame";
5 |
6 | class FindStreamer2 extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | error: null,
11 | isLoaded: false,
12 | nodes: [],
13 | links: [],
14 | streamerName: "Fextralife",
15 | game: "League of Legends",
16 | language: "en",
17 | };
18 | }
19 |
20 | fetchData(gameName, lang) {
21 | fetch("/streamers/" + lang + "/" + gameName)
22 | .then((res) => res.json())
23 | .then(
24 | (result) => {
25 | this.setState({
26 | isLoaded: true,
27 | nodes: result.nodes,
28 | links: result.links,
29 | });
30 | },
31 | (error) => {
32 | this.setState({
33 | isLoaded: true,
34 | error,
35 | });
36 | }
37 | );
38 |
39 | this.setState({
40 | game: gameName,
41 | language: lang,
42 | });
43 | }
44 |
45 | componentDidMount() {
46 | this.fetchData(this.state.game, this.state.language);
47 | }
48 |
49 | updateGameLang = (gameName, lang) => {
50 | this.fetchData(gameName, lang);
51 | };
52 |
53 | render() {
54 | const { error, isLoaded } = this.state;
55 | if (error) {
56 | return Error: {error.message}
;
57 | } else if (!isLoaded) {
58 | return (
59 |
60 |
61 |
62 | Finding all streamers
63 |
64 |
65 |
66 | );
67 | } else {
68 | const header = "Find streamers by game and language";
69 | const paragraph =
70 | "Find all streamers who stream your favourite game in your language.";
71 | const square = { width: 250, height: 250 };
72 | return (
73 |
74 |
75 |
76 |
77 |
80 | {paragraph}
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | );
93 | }
94 | }
95 | }
96 |
97 | export default FindStreamer2;
98 |
--------------------------------------------------------------------------------
/frontend/src/components/GraphPR.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useD3 } from "../hooks/useD3";
3 | import * as d3 from "d3";
4 |
5 | function GraphPR(props) {
6 | const ref = useD3(
7 | (svg) => {
8 | svg.selectAll("*").remove();
9 | const height = 500;
10 | const width = 750;
11 | const simulation = d3
12 | .forceSimulation(props.nodes)
13 | .force(
14 | "x",
15 | d3.forceX().x((d) => d.x)
16 | )
17 | .force(
18 | "y",
19 | d3.forceY().y((d) => d.y)
20 | )
21 | .force("charge", d3.forceManyBody())
22 | .force("center", d3.forceCenter(width / 2, height / 2))
23 | .force(
24 | "collide",
25 | d3.forceCollide().radius((d) => d.rank * 3000)
26 | );
27 |
28 | const node = svg
29 | .append("g")
30 | .attr("stroke", "#fff")
31 | .attr("stroke-width", 1.5)
32 | .selectAll("circle")
33 | .data(props.nodes)
34 | .join("circle")
35 | .attr("r", function (d) {
36 | return d.rank * 1000;
37 | })
38 | .attr("class", "node")
39 | .attr("fill", "#ff7701")
40 | .call(drag(simulation));
41 |
42 | var label = svg
43 | .selectAll(null)
44 | .data(props.nodes)
45 | .enter()
46 | .append("text")
47 | .text(function (d) {
48 | return d.name;
49 | })
50 | .style("text-anchor", "middle")
51 | .style("fill", "#1b1c1d")
52 | .style("font-family", "Arial")
53 | .attr("font-weight", "700")
54 | .style("font-size", "12px");
55 |
56 | simulation.on("tick", () => {
57 | node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
58 | label
59 | .attr("x", function (d) {
60 | return d.x;
61 | })
62 | .attr("y", function (d) {
63 | return d.y - d.rank * 1000 - 5;
64 | });
65 | });
66 | },
67 | [props.nodes.length]
68 | );
69 |
70 | const drag = (simulation) => {
71 | function dragstarted(event) {
72 | if (!event.active) simulation.alphaTarget(0.3).restart();
73 | event.subject.fx = event.subject.x;
74 | event.subject.fy = event.subject.y;
75 | }
76 |
77 | function dragged(event) {
78 | event.subject.fx = event.x;
79 | event.subject.fy = event.y;
80 | }
81 |
82 | function dragended(event) {
83 | if (!event.active) simulation.alphaTarget(0);
84 | event.subject.fx = null;
85 | event.subject.fy = null;
86 | }
87 |
88 | return d3
89 | .drag()
90 | .on("start", dragstarted)
91 | .on("drag", dragged)
92 | .on("end", dragended);
93 | };
94 | return (
95 |
104 | );
105 | }
106 |
107 | export default GraphPR;
108 |
--------------------------------------------------------------------------------
/frontend/src/components/HeaderComp.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | Button,
4 | Container,
5 | Menu,
6 | Segment,
7 | Visibility,
8 | Header,
9 | MenuItem,
10 | } from "semantic-ui-react";
11 | import Counter from "./Counter";
12 |
13 | function TwitchHeading(props) {
14 | return (
15 |
16 |
27 |
37 |
38 | );
39 | }
40 |
41 | class HeaderComp extends Component {
42 | state = {};
43 | hideFixedMenu = () => this.setState({ fixed: false });
44 | showFixedMenu = () => this.setState({ fixed: true });
45 | handleClick = (e) => this.props.scrollTarget(e.target.id);
46 | handleClickFromHeader = (id) => this.props.scrollTarget(id);
47 | render() {
48 | const { fixed } = this.state;
49 |
50 | return (
51 |
56 |
62 |
99 |
100 |
101 |
102 | );
103 | }
104 | }
105 |
106 | export default HeaderComp;
107 |
--------------------------------------------------------------------------------
/frontend/src/components/GraphBC.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useD3 } from "../hooks/useD3";
3 | import * as d3 from "d3";
4 |
5 | function GraphPR(props) {
6 | const ref = useD3(
7 | (svg) => {
8 | svg.selectAll("*").remove();
9 | const height = 500;
10 | const width = 750;
11 |
12 | const simulation = d3
13 | .forceSimulation(props.nodes)
14 | .force(
15 | "x",
16 | d3.forceX().x((d) => d.x)
17 | )
18 | .force(
19 | "y",
20 | d3.forceY().y((d) => d.y)
21 | )
22 | .force("charge", d3.forceManyBody())
23 | .force("center", d3.forceCenter(width / 2, height / 2))
24 | .force(
25 | "collide",
26 | d3.forceCollide().radius((d) => d.betweenness_centrality * 7000000)
27 | );
28 |
29 | const node = svg
30 | .append("g")
31 | .attr("stroke", "#fff")
32 | .attr("stroke-width", 1.5)
33 | .selectAll("circle")
34 | .data(props.nodes)
35 | .join("circle")
36 | .attr("r", function (d) {
37 | return d.betweenness_centrality * 3000000;
38 | })
39 | .attr("class", "node")
40 | .attr("fill", "#ff7701")
41 | .call(drag(simulation));
42 |
43 | var label = svg
44 | .selectAll(null)
45 | .data(props.nodes)
46 | .enter()
47 | .append("text")
48 | .text(function (d) {
49 | return d.name;
50 | })
51 | .style("text-anchor", "middle")
52 | .style("fill", "#1b1c1d")
53 | .style("font-family", "Arial")
54 | .attr("font-weight", "700")
55 | .style("font-size", "12px");
56 |
57 | simulation.on("tick", () => {
58 | node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
59 | label
60 | .attr("x", function (d) {
61 | return d.x;
62 | })
63 | .attr("y", function (d) {
64 | return d.y - d.betweenness_centrality * 3000000 - 5;
65 | });
66 | });
67 | },
68 | [props.nodes.length]
69 | );
70 |
71 | const drag = (simulation) => {
72 | function dragstarted(event) {
73 | if (!event.active) simulation.alphaTarget(0.5).restart();
74 | event.subject.fx = event.subject.x;
75 | event.subject.fy = event.subject.y;
76 | }
77 |
78 | function dragged(event) {
79 | event.subject.fx = event.x;
80 | event.subject.fy = event.y;
81 | }
82 |
83 | function dragended(event) {
84 | if (!event.active) simulation.alphaTarget(0);
85 | event.subject.fx = null;
86 | event.subject.fy = null;
87 | }
88 |
89 | return d3
90 | .drag()
91 | .on("start", dragstarted)
92 | .on("drag", dragged)
93 | .on("end", dragended);
94 | };
95 | return (
96 |
105 | );
106 | }
107 |
108 | export default GraphPR;
109 |
--------------------------------------------------------------------------------
/frontend/src/components/BC.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { Grid, Segment, Header, Dimmer, Loader } from "semantic-ui-react";
3 | import GraphBC from "./GraphBC";
4 | import TableComp from "./TableComp";
5 |
6 | class BC extends Component {
7 | _isMounted = false;
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | error: null,
12 | isLoaded: false,
13 | nodes: [],
14 | };
15 | }
16 |
17 | componentDidMount() {
18 | this._isMounted = true;
19 | fetch("/betweenness-centrality")
20 | .then((res) => res.json())
21 | .then(
22 | (result) => {
23 | this.setState({
24 | isLoaded: true,
25 | nodes: result.bc,
26 | });
27 | },
28 | (error) => {
29 | this.setState({
30 | isLoaded: true,
31 | error,
32 | });
33 | }
34 | );
35 | }
36 |
37 | componentWillUnmount() {
38 | this._isMounted = false;
39 | }
40 | render() {
41 | const { error, isLoaded } = this.state;
42 | if (error) {
43 | return Error: {error.message}
;
44 | } else if (!isLoaded) {
45 | return (
46 |
47 |
48 |
49 | Calculating Betweenness centrality
50 |
51 |
52 |
53 | );
54 | } else {
55 | const { nodes } = this.state;
56 | const header = "Which Twitch user has the most influence?";
57 | const paragraph =
58 | "Find out which user is most influential using Betweenness centrality algorithm.";
59 | const headers = ["Streamer", "BC"];
60 | let top10nodes = nodes.slice(0, 10);
61 | return (
62 |
63 |
64 |
65 |
68 |
69 | {paragraph}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
85 |
92 |
93 |
94 |
95 | );
96 | }
97 | }
98 | }
99 |
100 | export default BC;
101 |
--------------------------------------------------------------------------------
/frontend/src/components/AutoSearch.js:
--------------------------------------------------------------------------------
1 | import _ from "lodash";
2 | import React, { useState } from "react";
3 | import { Search, Grid, Label, Button } from "semantic-ui-react";
4 |
5 | const initialState = {
6 | loading: false,
7 | results: [],
8 | value: "",
9 | };
10 |
11 | function exampleReducer(state, action) {
12 | switch (action.type) {
13 | case "CLEAN_QUERY":
14 | return initialState;
15 | case "START_SEARCH":
16 | return { ...state, loading: true, value: action.query };
17 | case "FINISH_SEARCH":
18 | return { ...state, loading: false, results: action.results };
19 | case "UPDATE_SELECTION":
20 | return { ...state, value: action.selection };
21 |
22 | default:
23 | throw new Error();
24 | }
25 | }
26 | const resultRenderer = ({ title }) => ;
27 |
28 | function AutoSearch(props) {
29 | const [state, dispatch] = React.useReducer(exampleReducer, initialState);
30 | const { loading, results, value } = state;
31 | const [hasError, setErrors] = useState(false);
32 | const [streamers, setStreamers] = useState({});
33 |
34 | const handleClick = () => {
35 | if (value !== "") props.updateStateParent(value);
36 | };
37 |
38 | async function fetchNames() {
39 | const res = await fetch("/streamers");
40 | res
41 | .json()
42 | .then((res) => setStreamers(res.streamers))
43 | .catch((err) => setErrors(err));
44 | console.log("Fetched names");
45 | }
46 |
47 | React.useEffect(() => {
48 | fetchNames();
49 | console.log("Fetched names again");
50 | }, []);
51 |
52 | const timeoutRef = React.useRef();
53 | const handleSearchChange = React.useCallback(
54 | (e, data) => {
55 | clearTimeout(timeoutRef.current);
56 | dispatch({ type: "START_SEARCH", query: data.value });
57 |
58 | timeoutRef.current = setTimeout(() => {
59 | if (data.value.length === 0) {
60 | dispatch({ type: "CLEAN_QUERY" });
61 | return;
62 | }
63 |
64 | const re = new RegExp(_.escapeRegExp(data.value), "i");
65 | console.log(re);
66 | const isMatch = (result) => re.test(result.title);
67 |
68 | dispatch({
69 | type: "FINISH_SEARCH",
70 | results: _.filter(streamers, isMatch),
71 | });
72 | }, 300);
73 | },
74 | [streamers]
75 | );
76 | React.useEffect(() => {
77 | return () => {
78 | clearTimeout(timeoutRef.current);
79 | };
80 | }, []);
81 |
82 | if (hasError) {
83 | return Error: {hasError.message}
;
84 | } else {
85 | return (
86 |
87 |
88 |
91 | dispatch({
92 | type: "UPDATE_SELECTION",
93 | selection: data.result.title,
94 | })
95 | }
96 | onSearchChange={handleSearchChange}
97 | resultRenderer={resultRenderer}
98 | results={results}
99 | value={value}
100 | />
101 |
102 |
103 |
104 |
105 |
106 | );
107 | }
108 | }
109 |
110 | export default AutoSearch;
111 |
--------------------------------------------------------------------------------
/frontend/src/components/PageRank.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { Grid, Segment, Header, Dimmer, Loader, Button, Icon } from "semantic-ui-react";
3 | import GraphPR from "./GraphPR";
4 | import TableComp from "./TableComp";
5 |
6 | class PageRank extends Component {
7 | _isMounted = false;
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | error: null,
12 | isLoaded: false,
13 | nodes: [],
14 | };
15 | this.handleRefresh = this.handleRefresh.bind(this);
16 | }
17 |
18 |
19 | fetchData(){
20 | fetch("/page-rank")
21 | .then((res) => res.json())
22 | .then(
23 | (result) => {
24 | this.setState({
25 | isLoaded: true,
26 | nodes: result.page_rank,
27 | });
28 | },
29 | (error) => {
30 | this.setState({
31 | isLoaded: true,
32 | error,
33 | });
34 | }
35 | );
36 | }
37 |
38 | handleRefresh = () => {
39 | this.fetchData();
40 | };
41 |
42 | componentDidMount() {
43 | this._isMounted = true;
44 | this.fetchData();
45 | }
46 |
47 | componentWillUnmount() {
48 | this._isMounted = false;
49 | }
50 | render() {
51 | const { error, isLoaded } = this.state;
52 | if (error) {
53 | return Error: {error.message}
;
54 | } else if (!isLoaded) {
55 | return (
56 |
57 |
58 |
59 | Calculating PageRank
60 |
61 |
62 |
63 | );
64 | } else {
65 | const { nodes } = this.state;
66 | const header = "Who is the most popular Twitch user?";
67 | const paragraph =
68 | "Find out which user is most popular using PageRank algorithm.";
69 | const headers = ["Streamer", "Rank"];
70 | let top10nodes = nodes.slice(0, 10);
71 | return (
72 |
73 |
74 |
75 |
78 |
79 | {paragraph}
80 |
81 |
82 |
92 |
93 |
94 |
95 |
96 |
97 |
102 |
103 |
104 |
105 |
106 |
113 |
114 |
115 |
116 | );
117 | }
118 | }
119 | }
120 |
121 | export default PageRank;
122 |
--------------------------------------------------------------------------------
/backend/twitch_data.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from csv import reader
3 | import models
4 | from app import memgraph
5 | import traceback
6 |
7 |
8 | def load_streams(path):
9 | with open(path) as read_obj:
10 | csv_reader = reader(read_obj)
11 | header = next(csv_reader)
12 | if header != None:
13 | for row in csv_reader:
14 | stream = models.Stream(
15 | id=row[1],
16 | name=row[3],
17 | url=row[6],
18 | followers=row[7],
19 | createdAt=row[10],
20 | totalViewCount=row[9],
21 | description=row[8],
22 | ).save(memgraph)
23 |
24 | language = models.Language(name=row[5]).save(memgraph)
25 | game = models.Game(name=row[4]).save(memgraph)
26 |
27 | speaks_rel = models.Speaks(
28 | _start_node_id=stream._id, _end_node_id=language._id
29 | ).save(memgraph)
30 |
31 | plays_rel = models.Plays(
32 | _start_node_id=stream._id, _end_node_id=game._id
33 | ).save(memgraph)
34 |
35 |
36 | def load_teams(path):
37 | try:
38 | with open(path) as read_obj:
39 | csv_reader = reader(read_obj)
40 | header = next(csv_reader)
41 | if header != None:
42 | for row in csv_reader:
43 | stream = models.Stream(id=row[0]).load(db=memgraph)
44 | team = models.Team(name=row[1]).save(memgraph)
45 | is_part_of_rel = models.IsPartOf(
46 | _start_node_id=stream._id, _end_node_id=team._id
47 | ).save(memgraph)
48 | except Exception as e:
49 | traceback.print_exc()
50 |
51 |
52 | def load_vips(path):
53 | with open(path) as read_obj:
54 | csv_reader = reader(read_obj)
55 | header = next(csv_reader)
56 | if header != None:
57 | for row in csv_reader:
58 | stream = models.Stream(id=row[0]).load(db=memgraph)
59 | vip = models.User(name=row[1]).save(memgraph)
60 | vip_rel = models.Vip(
61 | _start_node_id=vip._id, _end_node_id=stream._id
62 | ).save(memgraph)
63 |
64 |
65 | def load_moderators(path):
66 | with open(path) as read_obj:
67 | csv_reader = reader(read_obj)
68 | header = next(csv_reader)
69 | if header != None:
70 | for row in csv_reader:
71 | stream = models.Stream(id=row[0]).load(db=memgraph)
72 | moderator = models.User(name=row[1]).save(memgraph)
73 | moderator_rel = models.Moderator(
74 | _start_node_id=moderator._id, _end_node_id=stream._id
75 | ).save(memgraph)
76 |
77 |
78 | def load_chatters(path):
79 | with open(path) as read_obj:
80 | csv_reader = reader(read_obj)
81 | header = next(csv_reader)
82 | if header != None:
83 | for row in csv_reader:
84 | stream = models.Stream(id=row[0]).load(db=memgraph)
85 | chatter = models.User(name=row[1]).save(memgraph)
86 | chatter_rel = models.Chatter(
87 | _start_node_id=chatter._id, _end_node_id=stream._id
88 | ).save(memgraph)
89 |
90 |
91 | def load():
92 | path_streams = Path("import-data/streamers.csv")
93 | path_teams = Path("import-data/teams.csv")
94 | path_vips = Path("import-data/vips.csv")
95 | path_moderators = Path("import-data/moderators.csv")
96 | path_chatters = Path("import-data/chatters.csv")
97 |
98 | load_streams(path_streams)
99 | load_teams(path_teams)
100 | load_vips(path_vips)
101 | load_moderators(path_moderators)
102 | load_chatters(path_chatters)
103 |
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import "semantic-ui-css/semantic.min.css";
3 | import "semantic-ui-less/semantic.less";
4 | import React, { Component } from "react";
5 | import HeaderComp from "./components/HeaderComp";
6 | import Games from "./components/Games";
7 | import Teams from "./components/Teams";
8 | import Vips from "./components/Vips";
9 | import Moderators from "./components/Moderators";
10 | import Streamers from "./components/Streamers";
11 | import GraphHeader from "./components/GraphHeader";
12 | import FindStreamer from "./components/FindStreamer";
13 | import FindStreamer2 from "./components/FindStreamer2";
14 | import PageRank from "./components/PageRank";
15 | import BC from "./components/BC";
16 | import scrollToComponent from "react-scroll-to-component";
17 | import { Tab } from "semantic-ui-react";
18 |
19 | class App extends Component {
20 | constructor(props) {
21 | super(props);
22 | this.headerComp = React.createRef();
23 | this.graphHeader = React.createRef();
24 | this.generalStats = React.createRef();
25 | this.scrollToAppComponent = this.scrollToAppComponent.bind(this);
26 | this.state = {
27 | color: "orange",
28 | };
29 | }
30 |
31 | handleColorChange = (e) => this.setState({ color: e.target.value });
32 | scrollToAppComponent = (compId) => {
33 | switch (compId) {
34 | case "GeneralStats":
35 | scrollToComponent(this.generalStats.current, {
36 | offset: 0,
37 | align: "top",
38 | duration: 500,
39 | ease: "inCirc",
40 | });
41 | break;
42 | case "GraphHeader":
43 | scrollToComponent(this.graphHeader.current, {
44 | offset: 0,
45 | align: "top",
46 | duration: 500,
47 | ease: "inCirc",
48 | });
49 | break;
50 | case "HeaderComp":
51 | console.log("header");
52 | scrollToComponent(this.headerComp.current, {
53 | offset: 0,
54 | align: "top",
55 | duration: 500,
56 | ease: "inCirc",
57 | });
58 | break;
59 | default:
60 | // do nothing
61 | }
62 | };
63 |
64 | handleClick = (e) => {
65 | this.scrollToAppComponent(e.target.id);
66 | };
67 | render() {
68 | const { color } = this.state;
69 | const panesStats = [
70 | {
71 | menuItem: { key: "games", icon: "game", content: "Games" },
72 | render: () => (
73 |
74 |
75 |
76 | ),
77 | },
78 | {
79 | menuItem: { key: "teams", icon: "users", content: "Teams" },
80 | render: () => (
81 |
82 |
83 |
84 | ),
85 | },
86 | {
87 | menuItem: { key: "vips", icon: "star", content: "Vips" },
88 | render: () => (
89 |
90 |
91 |
92 | ),
93 | },
94 | {
95 | menuItem: { key: "moderators", icon: "trophy", content: "Moderators" },
96 | render: () => (
97 |
98 |
99 |
100 | ),
101 | },
102 | {
103 | menuItem: {
104 | key: "streamers",
105 | icon: "user",
106 | content: "Streamers",
107 | },
108 | render: () => (
109 |
110 |
111 |
112 | ),
113 | },
114 | ];
115 |
116 | return (
117 |
118 |
122 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | );
134 | }
135 | }
136 |
137 | export default App;
138 |
--------------------------------------------------------------------------------
/frontend/src/components/Graph.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useD3 } from "../hooks/useD3";
3 | import * as d3 from "d3";
4 |
5 | function Graph(props) {
6 | const ref = useD3(
7 | (svg) => {
8 | svg.selectAll("*").remove();
9 | const height = 300;
10 | const width = 300;
11 | const simulation = d3
12 | .forceSimulation(props.nodes)
13 | .force(
14 | "link",
15 | d3
16 | .forceLink(props.links)
17 | .distance(100)
18 | .id((d) => d.id)
19 | )
20 | .force("charge", d3.forceManyBody())
21 | .force("center", d3.forceCenter(width / 2, height / 2));
22 | const link = svg
23 | .append("g")
24 | .attr("stroke", "#999")
25 | .attr("stroke-opacity", 0.6)
26 | .selectAll("line")
27 | .data(props.links)
28 | .join("line")
29 | .attr("stroke-width", 1);
30 |
31 | const node = svg
32 | .append("g")
33 | .attr("stroke", "#fff")
34 | .attr("stroke-width", 1.5)
35 | .selectAll("circle")
36 | .data(props.nodes)
37 | .join("circle")
38 | .attr("r", 10)
39 | .attr("class", "node")
40 | .attr("fill", function (d) {
41 | if (d.label === "Team") return "red";
42 | else if (d.label === "Stream") return "orange";
43 | else if (d.label === "Game") return "blue";
44 | else if (d.label === "Language") return "purple";
45 | })
46 | .call(drag(simulation));
47 |
48 | var label = svg
49 | .selectAll(null)
50 | .data(props.nodes)
51 | .enter()
52 | .append("text")
53 | .text(function (d) {
54 | return d.name; //return d.label for label
55 | })
56 | .style("text-anchor", "middle")
57 | .style("fill", "white")
58 | .style("font-family", "Arial")
59 | .style("font-size", "12px");
60 |
61 | simulation.on("tick", () => {
62 | link
63 | .attr("x1", (d) => d.source.x)
64 | .attr("y1", (d) => d.source.y)
65 | .attr("x2", (d) => d.target.x)
66 | .attr("y2", (d) => d.target.y);
67 |
68 | node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
69 | label
70 | .attr("x", function (d) {
71 | return d.x;
72 | })
73 | .attr("y", function (d) {
74 | return d.y - 15;
75 | });
76 | });
77 | },
78 | [props.nodes.length]
79 | );
80 |
81 | const refEmpty = useD3((svg) => {
82 | svg.selectAll("*").remove();
83 | const height = 300;
84 | const width = 300;
85 | var g = svg.append("g").attr("transform", function (d, i) {
86 | return "translate(0,0)";
87 | });
88 | g.append("text")
89 | .attr("x", width / 2)
90 | .attr("y", height / 2)
91 | .attr("fill", "#ff7701")
92 | .attr("font-weight", "50")
93 | .text("No data available")
94 | .style("text-anchor", "middle")
95 | .style("font-family", "Arial")
96 | .style("font-size", "20px");
97 | });
98 |
99 | const drag = (simulation) => {
100 | function dragstarted(event) {
101 | if (!event.active) simulation.alphaTarget(0.3).restart();
102 | event.subject.fx = event.subject.x;
103 | event.subject.fy = event.subject.y;
104 | }
105 |
106 | function dragged(event) {
107 | event.subject.fx = event.x;
108 | event.subject.fy = event.y;
109 | }
110 |
111 | function dragended(event) {
112 | if (!event.active) simulation.alphaTarget(0);
113 | event.subject.fx = null;
114 | event.subject.fy = null;
115 | }
116 |
117 | return d3
118 | .drag()
119 | .on("start", dragstarted)
120 | .on("drag", dragged)
121 | .on("end", dragended);
122 | };
123 | if (props.nodes.length !== 0) {
124 | return (
125 |
134 | );
135 | } else {
136 | return (
137 |
146 | );
147 | }
148 | }
149 |
150 | export default Graph;
151 |
--------------------------------------------------------------------------------
/frontend/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
68 |
--------------------------------------------------------------------------------
/frontend/src/components/AutoCompleteGame.js:
--------------------------------------------------------------------------------
1 | import _ from "lodash";
2 | import React, { useState } from "react";
3 | import { Search, Grid, Label, Button } from "semantic-ui-react";
4 |
5 | const initialState = {
6 | loading: false,
7 | results: [],
8 | value: "",
9 | };
10 |
11 | const initialStateLanguage = {
12 | loadingLang: false,
13 | resultsLang: [],
14 | valueLang: "",
15 | };
16 |
17 | function exampleReducer(state, action) {
18 | switch (action.type) {
19 | case "CLEAN_QUERY":
20 | return initialState;
21 | case "START_SEARCH":
22 | return { ...state, loading: true, value: action.query };
23 | case "FINISH_SEARCH":
24 | return { ...state, loading: false, results: action.results };
25 | case "UPDATE_SELECTION":
26 | return { ...state, value: action.selection };
27 |
28 | default:
29 | throw new Error();
30 | }
31 | }
32 |
33 | function exampleReducerL(state, action) {
34 | switch (action.type) {
35 | case "CLEAN_QUERY":
36 | return initialStateLanguage;
37 | case "START_SEARCH":
38 | return { ...state, loadingLang: true, valueLang: action.query };
39 | case "FINISH_SEARCH":
40 | return { ...state, loadingLang: false, resultsLang: action.results };
41 | case "UPDATE_SELECTION":
42 | return { ...state, valueLang: action.selection };
43 |
44 | default:
45 | throw new Error();
46 | }
47 | }
48 | const resultRenderer = ({ title }) => ;
49 | const resultRendererL = ({ title }) => ;
50 |
51 | function AutoCompleteGame(props) {
52 | const [state, dispatch] = React.useReducer(exampleReducer, initialState);
53 | const [stateLang, dispatchLanguage] = React.useReducer(
54 | exampleReducerL,
55 | initialStateLanguage
56 | );
57 | const { loadingLang, resultsLang, valueLang } = stateLang;
58 | const { loading, results, value } = state;
59 | const [hasError, setErrors] = useState(false);
60 | const [games, setGames] = useState({});
61 | const [languages, setLanguages] = useState({});
62 |
63 | const handleClick = () => {
64 | if (value !== "" && valueLang !== "")
65 | props.updateStateParent(value, valueLang);
66 | };
67 |
68 | async function fetchLanguages() {
69 | const res = await fetch("/languages");
70 | res
71 | .json()
72 | .then((res) => setLanguages(res.languages))
73 | .catch((err) => setErrors(err));
74 | }
75 |
76 | async function fetchGames() {
77 | const res = await fetch("/games");
78 | res
79 | .json()
80 | .then((res) => setGames(res.games))
81 | .catch((err) => setErrors(err));
82 | }
83 |
84 | React.useEffect(() => {
85 | fetchGames();
86 | fetchLanguages();
87 | }, []);
88 |
89 | const timeoutRef = React.useRef();
90 | const handleSearchChange = React.useCallback(
91 | (e, data) => {
92 | clearTimeout(timeoutRef.current);
93 | dispatch({ type: "START_SEARCH", query: data.value });
94 |
95 | timeoutRef.current = setTimeout(() => {
96 | if (data.value.length === 0) {
97 | dispatch({ type: "CLEAN_QUERY" });
98 | return;
99 | }
100 |
101 | const re = new RegExp(_.escapeRegExp(data.value), "i");
102 | console.log(re);
103 | const isMatch = (result) => re.test(result.title);
104 |
105 | dispatch({
106 | type: "FINISH_SEARCH",
107 | results: _.filter(games, isMatch),
108 | });
109 | }, 300);
110 | },
111 | [games]
112 | );
113 |
114 | const handleSearchChangeL = React.useCallback(
115 | (e, data) => {
116 | clearTimeout(timeoutRef.current);
117 | dispatchLanguage({ type: "START_SEARCH", query: data.value });
118 |
119 | timeoutRef.current = setTimeout(() => {
120 | if (data.value.length === 0) {
121 | dispatchLanguage({ type: "CLEAN_QUERY" });
122 | return;
123 | }
124 |
125 | const re = new RegExp(_.escapeRegExp(data.value), "i");
126 | console.log(re);
127 | const isMatch = (result) => re.test(result.title);
128 |
129 | dispatchLanguage({
130 | type: "FINISH_SEARCH",
131 | results: _.filter(languages, isMatch),
132 | });
133 | }, 300);
134 | },
135 | [languages]
136 | );
137 |
138 | React.useEffect(() => {
139 | return () => {
140 | clearTimeout(timeoutRef.current);
141 | };
142 | }, []);
143 |
144 | if (hasError) {
145 | return Error: {hasError.message}
;
146 | } else {
147 | return (
148 |
149 |
150 |
151 |
154 | dispatch({
155 | type: "UPDATE_SELECTION",
156 | selection: data.result.title,
157 | })
158 | }
159 | onSearchChange={handleSearchChange}
160 | resultRenderer={resultRenderer}
161 | results={results}
162 | value={value}
163 | />
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
174 | dispatchLanguage({
175 | type: "UPDATE_SELECTION",
176 | selection: data.result.title,
177 | })
178 | }
179 | onSearchChange={handleSearchChangeL}
180 | resultRenderer={resultRendererL}
181 | results={resultsLang}
182 | value={valueLang}
183 | />
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | );
196 | }
197 | }
198 |
199 | export default AutoCompleteGame;
200 |
--------------------------------------------------------------------------------
/frontend/src/components/Streamers.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | Grid,
4 | Segment,
5 | Header,
6 | Dimmer,
7 | Loader,
8 | } from "semantic-ui-react";
9 | import DropdownStreamers from "./DropdownStreamers";
10 | import TableComp from "./TableComp";
11 |
12 | class Streamers extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | error: null,
17 | isViewsLoaded: false,
18 | isFollowersLoaded: false,
19 | streamersViews: [],
20 | streamersFollowers: [],
21 | views: [],
22 | followers: [],
23 | numOfStrViews: "10",
24 | numOfStrFollowers: "10",
25 | headerViews: "Top 10 streamers by views",
26 | headerFollowers: "Top 10 streamers by followers",
27 | byViews: true,
28 | };
29 | }
30 |
31 | fetchDataViews(number) {
32 | fetch("/top-streamers-by-views/" + number)
33 | .then((res) => res.json())
34 | .then(
35 | (result) => {
36 | this.setState({
37 | isViewsLoaded: true,
38 | streamersViews: result.streamers,
39 | views: result.views,
40 | });
41 | },
42 | (error) => {
43 | this.setState({
44 | isViewsLoaded: true,
45 | error,
46 | });
47 | }
48 | );
49 | this.setState({
50 | numOfStrFollowers: number,
51 | headerViews: "Top " + number + " streamers by views",
52 | byViews: true,
53 | });
54 | }
55 |
56 | fetchDataFollowers(number) {
57 | fetch("/top-streamers-by-followers/" + number)
58 | .then((res) => res.json())
59 | .then(
60 | (result) => {
61 | this.setState({
62 | isFollowersLoaded: true,
63 | streamersFollowers: result.streamers,
64 | followers: result.followers,
65 | });
66 | },
67 | (error) => {
68 | this.setState({
69 | isFollowersLoaded: true,
70 | error,
71 | });
72 | }
73 | );
74 | this.setState({
75 | numOfStrViews: number,
76 | headerFollowers: "Top " + number + " streamers by followers",
77 | byViews: false,
78 | });
79 | }
80 |
81 | componentDidMount() {
82 | this.fetchDataViews(this.state.numOfStrViews);
83 | this.fetchDataFollowers(this.state.numOfStrFollowers);
84 | }
85 |
86 |
87 | changeFetch = (pick) => {
88 | if (pick.value === "Views") {
89 | this.setState({ byViews: true });
90 | this.fetchDataViews("10");
91 | } else {
92 | this.setState({ byViews: false });
93 | this.fetchDataFollowers("10");
94 | }
95 | };
96 |
97 | render() {
98 | const {
99 | error,
100 | isViewsLoaded,
101 | isFollowersLoaded,
102 | headerViews,
103 | headerFollowers,
104 | byViews,
105 | } = this.state;
106 | const paragraphViews =
107 | "Check out most popular streamers by view count. Change the option below if you want to get the most popular streamers by their number of followers.";
108 | const headersViews = ["Streamer", "Number of views"];
109 | const paragraphFollowers =
110 | "Find out which streamers have the largest fan base. Change the option below if you want to get the most popular streamers by the number of views.";
111 | const headersFollowers = ["Streamer", "Number of followers"];
112 | if (error) {
113 | return Error: {error.message}
;
114 | } else if (!isViewsLoaded || !isFollowersLoaded) {
115 | return (
116 |
117 |
118 |
119 | Getting top streamers
120 |
121 |
122 |
123 | );
124 | } else if (byViews) {
125 | return (
126 |
127 |
128 |
129 |
130 |
131 | {headerViews}
132 |
133 | {paragraphViews}
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
149 |
150 |
151 |
152 |
153 | );
154 | } else {
155 | console.log(byViews);
156 | return (
157 |
158 |
159 |
160 |
161 |
162 | {headerFollowers}
163 |
164 | {paragraphFollowers}
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
180 |
181 |
182 |
183 |
184 | );
185 | }
186 | }
187 | }
188 |
189 | export default Streamers;
190 |
--------------------------------------------------------------------------------
/backend/import-data/vips.csv:
--------------------------------------------------------------------------------
1 | user_id,vip_login
2 | 569342750,captainpuffy
3 | 569342750,punz
4 | 72550899,amandaxvida
5 | 72550899,bigjon
6 | 72550899,blairmc44
7 | 72550899,crickeymikey
8 | 72550899,evikka
9 | 72550899,exilon___
10 | 72550899,hihicaptain
11 | 72550899,itzyaboiburi
12 | 72550899,jhnnycrwsh
13 | 72550899,littlenicky1
14 | 72550899,mepparn
15 | 72550899,my_trolling_account
16 | 72550899,patrok
17 | 72550899,sam_v89
18 | 72550899,sswphoenix
19 | 72550899,sweetpea1217
20 | 72550899,twitchmusicid
21 | 88946548,lyric
22 | 88946548,pokelawls
23 | 88946548,tennp0
24 | 254489093,gabrielucas20
25 | 254489093,guiilenda
26 | 254489093,nahzinhaa
27 | 254489093,stealthz_k1ller_vapo
28 | 254489093,warz1n
29 | 119506529,fillesces
30 | 119506529,varvalian
31 | 77415295,crcornn
32 | 77415295,dmf_kyochan
33 | 77415295,iam_xq
34 | 77415295,sasatikk
35 | 77415295,vuelokun2
36 | 107305687,fuslie
37 | 107305687,saiiren
38 | 485855940,daikai4104
39 | 485855940,eggroland
40 | 485855940,kaiwing1027
41 | 485855940,kongyue0720
42 | 485855940,liang_0714
43 | 485855940,mapletom
44 | 485855940,riven696
45 | 485855940,unified168108
46 | 485855940,yuwanxd
47 | 485855940,zod0416
48 | 38594688,heresyaboyjason
49 | 106013742,adeptabyss
50 | 106013742,arcey_bot
51 | 106013742,arctura_alpha
52 | 106013742,artesianbuilds
53 | 106013742,bond_83
54 | 106013742,deaf_kage
55 | 106013742,decaf_teabagger
56 | 106013742,fknheadeyes
57 | 106013742,gabrez_
58 | 106013742,haylow1221
59 | 106013742,jonathanocon
60 | 106013742,kiki
61 | 106013742,laughing_boi
62 | 106013742,onitork
63 | 106013742,phlux_za
64 | 106013742,realamanda
65 | 106013742,ryanwalkinshaw
66 | 106013742,techinica
67 | 106013742,themrdart
68 | 106013742,tobitalks_official
69 | 61433001,8packbysummer
70 | 61433001,benneths
71 | 61433001,camilo
72 | 61433001,duhjaip
73 | 61433001,ffrizy
74 | 61433001,gregthejet
75 | 61433001,hawkamania3
76 | 61433001,masanz
77 | 61433001,metwbs
78 | 61433001,nathantheheatfan
79 | 61433001,nolanroseborough_
80 | 61433001,ovophantuums
81 | 61433001,splashedition
82 | 61433001,tacticz__
83 | 61433001,turkdeer_
84 | 38746172,apollorook
85 | 38746172,bratom
86 | 38746172,btwieatass
87 | 38746172,dantheman0700
88 | 38746172,dentonio
89 | 38746172,dohton
90 | 38746172,gachiguru
91 | 38746172,grannynonny
92 | 38746172,jayhilaneh
93 | 38746172,kaiserbowl
94 | 38746172,musty_meats
95 | 38746172,taykum64
96 | 38746172,tirrinwow
97 | 38746172,xshadowturkeyx
98 | 38746172,yeah_crusade
99 | 431882702,ceddygotbandz
100 | 431882702,drizzy
101 | 431882702,grizzleyworldrpyt
102 | 73779954,blinkvii
103 | 54739364,argick
104 | 54739364,hekigan
105 | 54739364,joshimuz
106 | 54739364,maxiethehatter
107 | 54739364,naro
108 | 54739364,ouranea
109 | 54739364,rasmus_1990
110 | 54739364,riekelt
111 | 54739364,robo_sparkle
112 | 189290002,danoce
113 | 189290002,hannahhroseee
114 | 189290002,heyyitzace
115 | 189290002,josephswag
116 | 189290002,methloh
117 | 189290002,muselk
118 | 189290002,sozo_labs
119 | 189290002,xolex20k
120 | 32053915,fcgeopolitics
121 | 32053915,poneeeyclub
122 | 32053915,saeya_s
123 | 26469355,cinnamontoastken
124 | 26469355,fortyone
125 | 26469355,guapohtx
126 | 26469355,kristoferyee
127 | 26469355,miggitymaan
128 | 26469355,slasher2099
129 | 26469355,wolfd20
130 | 26469355,xee889
131 | 29578325,areshd
132 | 29578325,johnxfire
133 | 37522866,carter
134 | 37522866,chuckmainstreet
135 | 37522866,cragsand
136 | 37522866,moonjuice
137 | 37522866,pluto
138 | 37522866,ruthless_ruby
139 | 37522866,socallmeastro
140 | 268314886,byeolyeon
141 | 268314886,hongda206
142 | 268314886,kwwaa211
143 | 29722828,113peanut
144 | 29722828,butterflyouo
145 | 29722828,shizukuouo
146 | 29722828,wolfnight0126
147 | 32402794,kemony
148 | 32402794,nikdo__
149 | 32402794,ss_dhillon
150 | 32402794,sylar_ev1l
151 | 188890121,4lehikc
152 | 188890121,astapok
153 | 188890121,b1ack_5wan
154 | 188890121,baga335
155 | 188890121,d_ermakov
156 | 188890121,dima_maysky192
157 | 188890121,harlans_
158 | 188890121,kamikpro
159 | 188890121,mrnykers
160 | 188890121,patalahinart
161 | 188890121,sazonoov
162 | 188890121,skk1per197
163 | 188890121,vas9h
164 | 68240113,mibbio
165 | 68240113,nargacuga1
166 | 68240113,scadedy
167 | 68240113,traumfalke
168 | 7010591,nnslul
169 | 152596920,sneper418
170 | 86131599,dzukill
171 | 86131599,twiddli
172 | 181077473,diego_x2000
173 | 181077473,dizzin_
174 | 181077473,esp1_
175 | 181077473,feipakarai
176 | 181077473,ggbrun0
177 | 181077473,ianvmonstertg
178 | 181077473,kalil420
179 | 181077473,leandroforgotten
180 | 181077473,sallizz
181 | 181077473,togurotv
182 | 45302947,baboabe
183 | 45302947,boxbox
184 | 45302947,brodieraps
185 | 45302947,jackieepooh
186 | 45302947,kristoferyee
187 | 45302947,mohr
188 | 45302947,xclutchz
189 | 57317154,beejsterguy
190 | 57317154,faderon
191 | 57317154,ibadda
192 | 57317154,imthirty
193 | 57317154,sonof7less
194 | 57317154,stark4machines
195 | 72480716,donkritical
196 | 72480716,jackarter
197 | 72480716,katangou
198 | 72480716,malcomtwitch63
199 | 72480716,nyuw
200 | 72480716,raistlien
201 | 72480716,zllak
202 | 116885541,saiiren
203 | 29795919,ensthor
204 | 29795919,heirentou
205 | 29795919,lolbbq
206 | 29795919,looperkele
207 | 29795919,skyballss
208 | 29795919,wildberryjam
209 | 46431370,backwoodgg
210 | 410965603,abdoking999
211 | 410965603,aefder
212 | 410965603,andeyyy01
213 | 410965603,bce_npocmo
214 | 410965603,billybeezmo
215 | 410965603,blue20_
216 | 410965603,halfullcup
217 | 410965603,jhinfex
218 | 410965603,lionheart
219 | 410965603,oldmonk_97
220 | 410965603,renimious
221 | 410965603,saiakuzob
222 | 410965603,scabu0
223 | 410965603,sufianzzz
224 | 410965603,tightxeyes
225 | 410965603,xycrial
226 | 14408894,bloodwarrios
227 | 14408894,drunkev
228 | 14408894,esrk
229 | 14408894,missdoitbig
230 | 14408894,requiemslaps
231 | 11897156,aughtyvon
232 | 11897156,bbarto22
233 | 11897156,beckjae
234 | 11897156,bigoletigbiddies
235 | 11897156,dfggilfslayer
236 | 11897156,diablopinata
237 | 11897156,fimbii
238 | 11897156,geargg
239 | 11897156,inyouendo
240 | 11897156,jayrizzl
241 | 11897156,kikxd
242 | 11897156,larrulobster
243 | 11897156,lunarflash
244 | 11897156,onesaltyvd
245 | 11897156,sarcasticviper
246 | 11897156,sarmien_winn
247 | 11897156,seraph2015
248 | 11897156,spacerecycler
249 | 11897156,springwhisper
250 | 11897156,tenchikenshi3
251 | 11897156,trute
252 | 11897156,wizecamel
253 | 57025612,bis_dk
254 | 57025612,frozzbert
255 | 57025612,giac_t
256 | 57025612,giftfrom
257 | 57025612,pillowez
258 | 57025612,rockyhardball
259 | 57025612,rsarabroza
260 | 57025612,sinsemilla83
261 | 57025612,spinelsc
262 | 57025612,teddym4n
263 | 57025612,twitchtjafs
264 | 57025612,wildmonkeylover
265 | 57025612,zheiju
266 | 38779717,ahrel
267 | 38779717,broxh_
268 | 38779717,buudgie
269 | 38779717,cursedhuman
270 | 38779717,liquidgold121
271 | 38779717,mawlez
272 | 38779717,micnl7
273 | 38779717,oceanicgamer
274 | 87791915,1buhorpaduha1
275 | 87791915,6ahah41k
276 | 87791915,absolutely_1
277 | 87791915,bloodraiser
278 | 87791915,chikobano
279 | 87791915,dunkard0
280 | 87791915,evillyru
281 | 87791915,golooboygoose
282 | 87791915,landebrauf
283 | 87791915,starsky
284 | 135052907,carter
285 | 135052907,kevindurantow
286 | 135052907,kristoferyee
287 | 135052907,weest
288 | 44574567,bon5a1
289 | 44574567,dhnx
290 | 44574567,dinokinsgg
291 | 44574567,iggydio
292 | 44574567,saligia73
293 | 44574567,sl0wlux
294 | 44574567,thegoodjuicer
295 | 44574567,timmy2
296 | 21588571,cak3tin
297 | 21588571,cartoonz
298 | 21588571,ekahs_hs
299 | 21588571,justd5
300 | 21588571,kikibabe_707
301 | 21588571,kim_reiko
302 | 21588571,mephiticshepard
303 | 21588571,mooseknucle
304 | 59635827,lexible
305 | 59635827,prowvz
306 | 59635827,vadikus007
307 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
352 | # dependencies
353 | /node_modules
354 | /.pnp
355 | .pnp.js
356 |
357 | # testing
358 | /coverage
359 |
360 | # production
361 | /build
362 |
363 | # misc
364 | .DS_Store
365 | .env.local
366 | .env.development.local
367 | .env.test.local
368 | .env.production.local
369 |
370 | npm-debug.log*
371 | yarn-debug.log*
372 | yarn-error.log*
373 |
374 | # Memgraph directories mg_lib and mg_log
375 | mg_lib/
376 | mg_log/
--------------------------------------------------------------------------------
/scripts/scraper.py:
--------------------------------------------------------------------------------
1 | from twitch.helix.api import TwitchHelix
2 | import csv
3 | import pandas as pd
4 | import shlex
5 | import subprocess
6 | import json
7 | import multiprocessing
8 | import time
9 | import collections
10 | import itertools
11 | import math
12 | import sys
13 |
14 |
15 | def get_streams(client):
16 | streams = client.get_streams()
17 | streams_file = open('streams.csv', 'w', encoding="utf-8", newline='')
18 | csv_writer = csv.writer(streams_file)
19 | count = 0
20 |
21 | def dict_filter(x, y): return dict([(i, x[i]) for i in x if i in set(y)])
22 | new_dict_keys = ("id", "user_id", "user_login", "user_name",
23 | "game_name", "language", "thumbnail_url")
24 |
25 | for stream in streams:
26 | small_stream = dict_filter(stream, new_dict_keys)
27 | if count == 0:
28 | # Writing headers of CSV file
29 | header = small_stream.keys()
30 | csv_writer.writerow(header)
31 | count += 1
32 |
33 | # Writing data of CSV file
34 | csv_writer.writerow(small_stream.values())
35 | print("writing new stream")
36 | streams_file.close()
37 |
38 |
39 | def get_users(client):
40 | col_list = ["user_id"]
41 | df = pd.read_csv("streams.csv", usecols=col_list)
42 | user_ids = df.values.tolist()
43 | print(user_ids)
44 |
45 | def dict_filter(x, y): return dict([(i, x[i]) for i in x if i in set(y)])
46 | new_dict_keys = ("id", "description", "view_count", "created_at")
47 | users = client.get_users(ids=user_ids)
48 |
49 | users_file = open('users.csv', 'w', encoding="utf-8", newline='')
50 | csv_writer = csv.writer(users_file)
51 | count = 0
52 | for user in users:
53 | small_user = dict_filter(user, new_dict_keys)
54 | if count == 0:
55 | # Writing headers of CSV file
56 | header = small_user.keys()
57 | csv_writer.writerow(header)
58 | count += 1
59 |
60 | # Writing data of CSV file
61 | csv_writer.writerow(small_user.values())
62 | print("writing new stream")
63 |
64 | users_file.close()
65 |
66 |
67 | def get_teams(my_client_id, my_oauth_token):
68 | col_list = ["user_id"]
69 | df = pd.read_csv("streams.csv", usecols=col_list)
70 | user_ids = df.values.tolist()
71 | all_teams = {}
72 |
73 | for user_id in user_ids:
74 | user_teams = []
75 | http_addr = "https://api.twitch.tv/helix/teams/channel?broadcaster_id=" + \
76 | str(user_id[0])
77 | print(http_addr)
78 | curl_command_line = '''curl -X GET ''' + http_addr + \
79 | ''' -H "Authorization: Bearer ''' + my_oauth_token + '''"''' + \
80 | ''' -H "Client-Id: ''' + my_client_id + '''"'''
81 | args = shlex.split(curl_command_line)
82 | subproc = subprocess.run(
83 | args, capture_output=True, text=True, encoding="utf-8").stdout
84 | if subproc is not None:
85 | data = json.loads(subproc)
86 | if data is not None:
87 | teams = data['data']
88 | if teams is not None:
89 | for team in teams:
90 | user_teams.append(team['team_name'])
91 |
92 | if not user_teams:
93 | all_teams[user_id[0]] = ""
94 | else:
95 | all_teams[user_id[0]] = user_teams
96 |
97 | # we have dictionary with user_id's as keys and list of the teams they belong to as values
98 | count = 0
99 | with open('teams.csv', 'w', encoding="utf-8", newline='') as f:
100 | for user_id in user_ids:
101 | if count == 0:
102 | f.write("user_id,team_name\n")
103 | count += 1
104 | for team in all_teams[user_id[0]]:
105 | f.write("%s,%s\n" % (user_id[0], team))
106 |
107 |
108 | def get_followers(my_client_id, my_oauth_token):
109 | col_list = ["user_id"]
110 | df = pd.read_csv("streams.csv", usecols=col_list)
111 | user_ids = df.values.tolist()
112 | followers = dict()
113 | for user_id in user_ids:
114 | http_addr = "https://api.twitch.tv/helix/users/follows?to_id=" + \
115 | str(user_id[0])
116 | print(http_addr)
117 | curl_command_line = '''curl -X GET ''' + http_addr + \
118 | ''' -H "Authorization: Bearer ''' + my_oauth_token + '''"''' + \
119 | ''' -H "Client-Id: ''' + my_client_id + '''"'''
120 | args = shlex.split(curl_command_line)
121 | subproc = subprocess.run(
122 | args, capture_output=True, text=True, encoding="utf-8").stdout
123 | if subproc is not None:
124 | data = json.loads(subproc)
125 | if data['total'] is not None:
126 | num_of_follows = data['total']
127 | followers[str(user_id[0])] = num_of_follows
128 |
129 | count = 0
130 | with open('followers.csv', 'w', encoding="utf-8", newline='') as f:
131 | for key in followers.keys():
132 | if count == 0:
133 | f.write("user_id,followers\n")
134 | count += 1
135 | f.write("%s,%s\n" % (key, followers[key]))
136 |
137 |
138 | def get_chatters(my_client_id, my_oauth_token):
139 | col_list = ["user_id", "user_login"]
140 | df = pd.read_csv("streams.csv", usecols=col_list)
141 | user_logins = df.values.tolist()
142 | with open('moderators.csv', 'w', encoding="utf-8", newline='') as m:
143 | with open('vips.csv', 'w', encoding="utf-8", newline='') as v:
144 | with open('full_chatters.csv', 'w', encoding="utf-8", newline='') as c:
145 | m.write("user_id,moderator_login\n")
146 | v.write("user_id,vip_login\n")
147 | c.write("user_id,chatter_login\n")
148 | for user_login in user_logins:
149 | http_addr = "http://tmi.twitch.tv/group/user/" + \
150 | user_login[1] + \
151 | "/chatters" # user_login[1] is user_login
152 | print(http_addr)
153 | curl_command_line = '''curl -X GET ''' + http_addr + \
154 | ''' -H "Authorization: Bearer ''' + my_oauth_token + '''"''' + \
155 | ''' -H "Client-Id: ''' + my_client_id + '''"'''
156 | args = shlex.split(curl_command_line)
157 | data = json.loads(subprocess.run(
158 | args, capture_output=True, text=True).stdout)
159 | # for every user fill three csvs - vips, moderators, viewers
160 | moderators = data['chatters']['moderators']
161 | vips = data['chatters']['vips']
162 | chatters = data['chatters']['viewers']
163 | for moderator in moderators:
164 | # user_login[0] is user_id
165 | m.write("%s,%s\n" % (user_login[0], moderator))
166 | for vip in vips:
167 | v.write("%s,%s\n" % (user_login[0], vip))
168 | for chatter in chatters:
169 | c.write("%s,%s\n" % (user_login[0], chatter))
170 |
171 |
172 | def make_streamers_csv():
173 | csv_input_1 = pd.read_csv('streams.csv')
174 | csv_input_2 = pd.read_csv('followers.csv')
175 | csv_input_3 = pd.read_csv('users.csv')
176 | csv_input_1['followers'] = csv_input_2['followers']
177 | csv_input_1['description'] = csv_input_3['description']
178 | csv_input_1['view_count'] = csv_input_3['view_count']
179 | csv_input_1['created_at'] = csv_input_3['created_at']
180 | csv_input_1.to_csv('streamers.csv', index=False)
181 |
182 |
183 | def count_by_id():
184 | with open("full_chatters.csv", encoding="utf-8", newline='') as csvfile:
185 | counter = collections.Counter(row["user_id"]
186 | for row in csv.DictReader(csvfile))
187 |
188 | with open("num_of_chatters.csv", "w", encoding="utf-8", newline='') as outfile:
189 | writer = csv.writer(outfile)
190 | writer.writerow(("user_id", "num_of_chatters"))
191 | for id, count in counter.items():
192 | writer.writerow((id, count))
193 |
194 |
195 | def copy_rows():
196 | file_in = 'full_chatters.csv'
197 | file_out = 'chatters.csv'
198 |
199 | col_list = ["num_of_chatters"]
200 | df = pd.read_csv("num_of_chatters.csv", usecols=col_list)
201 | num_of_chatters = df.values.tolist()
202 | with open(file_in, "r", encoding="utf-8", newline='') as f_input, \
203 | open(file_out, "w", encoding="utf-8", newline='') as f_output:
204 | # write header
205 | line = f_input.readline()
206 | f_output.write(line)
207 | for count in num_of_chatters:
208 | start = 1
209 | ten_percent = math.floor(count[0] / 10)
210 | line = f_input.readline()
211 | # append ten percent of chatters for that streamer
212 | while start != ten_percent:
213 | f_output.write(line)
214 | print("writing line")
215 | start += 1
216 | line = f_input.readline()
217 | # read chatters for that streamer
218 | while start != count[0]:
219 | print("reading line")
220 | line = f_input.readline()
221 | start += 1
222 |
223 |
224 | def main(my_client_id, my_oauth_token):
225 | client = TwitchHelix(client_id=my_client_id,
226 | oauth_token=my_oauth_token)
227 | p = multiprocessing.Process(
228 | target=get_streams, name="Get_streams", args=(client,))
229 | p.start()
230 | time.sleep(2) # number of streams must be max 100 for other calls to work
231 | if p.is_alive():
232 | p.terminate()
233 | p.join()
234 | get_users(client)
235 | get_teams(my_client_id, my_oauth_token)
236 | get_followers(my_client_id, my_oauth_token)
237 | get_chatters(my_client_id, my_oauth_token)
238 | make_streamers_csv()
239 | count_by_id()
240 | copy_rows()
241 |
242 |
243 | # run script with: python scraper.py
244 | if __name__ == "__main__":
245 | my_client_id = str(sys.argv[1])
246 | my_oauth_token = str(sys.argv[2])
247 | main(my_client_id, my_oauth_token)
248 |
--------------------------------------------------------------------------------
/backend/import-data/moderators.csv:
--------------------------------------------------------------------------------
1 | user_id,moderator_login
2 | 190835892,moobot
3 | 190835892,ssakdook
4 | 569342750,applllejuicee
5 | 569342750,fossabot
6 | 569342750,middaze
7 | 569342750,nvmreality
8 | 569342750,tanglerine
9 | 124425501,lounell_
10 | 124425501,moobot
11 | 124425501,papasmithy
12 | 124425501,ravager01
13 | 124425501,ssakdook
14 | 124425501,thatatlusguy
15 | 124425501,wolf_schroeder
16 | 72550899,i_love_gin
17 | 72550899,montanablack88
18 | 72550899,mrsatchels
19 | 72550899,nh6plusplus
20 | 72550899,petricov86
21 | 72550899,sadgegandalf
22 | 72550899,snoopy_vienna
23 | 72550899,streamelements
24 | 72550899,thealphabrian
25 | 88946548,0rionbby
26 | 88946548,akameuwu_
27 | 88946548,amazefulbot
28 | 88946548,bardokitty
29 | 88946548,bennt_
30 | 88946548,blackxnova
31 | 88946548,msferal
32 | 88946548,muffinwithnobrim
33 | 88946548,pizzadawg
34 | 88946548,sentzue
35 | 254489093,bolivia_
36 | 254489093,gbeltraonfl
37 | 254489093,pigzadaxd
38 | 254489093,streamelements
39 | 156037856,autumnyxis
40 | 156037856,streamlabs
41 | 146549780,951soso
42 | 146549780,cctr45
43 | 146549780,ssakdook
44 | 119506529,bullchan
45 | 119506529,nightbot
46 | 119506529,oiyo014
47 | 119506529,powder8man
48 | 77415295,henbamochi
49 | 77415295,nightbot
50 | 77415295,protect_jas
51 | 77415295,shi_lv42
52 | 107305687,aplod_
53 | 107305687,dimonffs
54 | 107305687,munchkinnn
55 | 107305687,nightbot
56 | 107305687,stopthot
57 | 107305687,streamelements
58 | 107305687,tinakitten
59 | 485855940,cliozz
60 | 485855940,kristinehuang
61 | 485855940,moobot
62 | 485855940,po11po11
63 | 30104304,alzero83
64 | 30104304,belutima
65 | 30104304,cwiet
66 | 30104304,dashxero
67 | 30104304,evilmajikman
68 | 30104304,freedomarx7
69 | 30104304,goldenwolf98
70 | 30104304,gravemind_z
71 | 30104304,guitar3544
72 | 30104304,kasumimoon69
73 | 30104304,megaquint
74 | 30104304,nicob7700
75 | 30104304,nightbot
76 | 30104304,reginald_butterbean
77 | 30104304,rezac
78 | 30104304,samurairocker
79 | 30104304,sol_install
80 | 30104304,streamlabs
81 | 30104304,thebacon_nation
82 | 30104304,thirstytripod
83 | 30104304,tm1nus
84 | 30104304,universal_bojack
85 | 30104304,unkassassin
86 | 30104304,varryochoco
87 | 30104304,viewtifulotaku7
88 | 30104304,xyberkn1ght
89 | 30104304,zero_x_automata
90 | 38594688,crayator
91 | 38594688,lunarylaura
92 | 38594688,nightbot
93 | 38594688,omelettedufromage
94 | 38594688,streamelements
95 | 38594688,weeeitssabby
96 | 106013742,azrelix
97 | 106013742,bullisticg
98 | 106013742,coldblood06
99 | 106013742,colombo94
100 | 106013742,doodiez
101 | 106013742,gamecubegll
102 | 106013742,kikkalee
103 | 106013742,kingboomber
104 | 106013742,owwak
105 | 106013742,streamelements
106 | 106013742,welyn
107 | 61433001,bee_ryan6
108 | 61433001,caris_levert22
109 | 61433001,cassiusclayx3
110 | 61433001,casualmysterio
111 | 61433001,chrome_skillz
112 | 61433001,dj_jollyrancherz
113 | 61433001,itschaddy_
114 | 61433001,killerdragonxl
115 | 61433001,kobe0802
116 | 61433001,kobe4life
117 | 61433001,lemonwinner
118 | 61433001,markynextdoor
119 | 61433001,persianess
120 | 61433001,streamelements
121 | 61433001,yamszn
122 | 61433001,zmelo_
123 | 490592527,achilios
124 | 490592527,andueh1
125 | 490592527,martin527
126 | 490592527,moobot
127 | 490592527,teischente
128 | 490592527,teusvult
129 | 490592527,toc_riot
130 | 490592527,valchatad
131 | 38746172,asmongold
132 | 38746172,campingtroll
133 | 38746172,combatlogs
134 | 38746172,djpenguin3
135 | 38746172,fandy
136 | 38746172,fred
137 | 38746172,rikh
138 | 38746172,separatedice
139 | 38746172,sodapoppin
140 | 38746172,sonicsama
141 | 38746172,soycrates
142 | 38746172,streamelements
143 | 38746172,supibot
144 | 38746172,syn4ack
145 | 38746172,thepositivebot
146 | 38746172,wojitoo
147 | 38746172,zalgradis
148 | 431882702,badxhabitx
149 | 431882702,djrezava
150 | 431882702,droy
151 | 431882702,glizzy
152 | 431882702,jay23xxh
153 | 431882702,jayy3three
154 | 431882702,kayn
155 | 431882702,lulubird97
156 | 431882702,nightbot
157 | 431882702,streamelements
158 | 431882702,wekool
159 | 73779954,23456789dat
160 | 73779954,bennt_
161 | 73779954,fallengrimm
162 | 73779954,filipwon
163 | 73779954,jordxnfair
164 | 73779954,nightbot
165 | 73779954,streamelements
166 | 73779954,zackbrogers
167 | 54739364,bonzaibier
168 | 54739364,fossabot
169 | 54739364,frici
170 | 54739364,jackjofa
171 | 54739364,junkoenoshimapuhuhu
172 | 54739364,lokisowilo
173 | 54739364,louis6321
174 | 54739364,maccoogan
175 | 54739364,martin527
176 | 54739364,mayaya
177 | 54739364,moobot
178 | 54739364,nordic_soy
179 | 54739364,ontwoplanks
180 | 54739364,phoenpc
181 | 54739364,princess_pwny
182 | 54739364,schwedy
183 | 54739364,vulpeshd
184 | 54739364,yorkiedesu
185 | 134586700,bihyeonglang
186 | 134586700,dasam22
187 | 134586700,hvsw1340
188 | 134586700,nightbot
189 | 134586700,parkjand
190 | 134586700,ssakdook
191 | 134586700,woori737
192 | 134586700,yuukid__
193 | 189290002,bulldogpaw
194 | 189290002,glitchthyme
195 | 189290002,jack_eeee
196 | 189290002,kyraaxo
197 | 189290002,mitchyprice
198 | 189290002,philnoo
199 | 189290002,rewardtts
200 | 189290002,squarkbot
201 | 189290002,squarkp
202 | 189290002,streamelements
203 | 32053915,atao_h4g
204 | 32053915,hecatte25
205 | 32053915,justegwenn
206 | 32053915,maarg0tte
207 | 32053915,moobot
208 | 32053915,siinero
209 | 26469355,apakura
210 | 26469355,arachneax
211 | 26469355,freddreaka
212 | 26469355,freshkidjayy
213 | 26469355,glenn
214 | 26469355,kritacul
215 | 26469355,peteryacono
216 | 26469355,shiftyd84
217 | 26469355,streamelements
218 | 26469355,trubbel
219 | 22253819,ariasaki
220 | 22253819,bastii_
221 | 22253819,jummychu
222 | 22253819,nightbot
223 | 22253819,pbjayz
224 | 22253819,peterparktv
225 | 22253819,polydere
226 | 22253819,prezmango
227 | 22253819,renniesaurus
228 | 22253819,shiphtur
229 | 29578325,martin527
230 | 29578325,moobot
231 | 29578325,mrdougbot
232 | 29578325,neenawman
233 | 29578325,nertaku
234 | 29578325,sseebbee
235 | 29578325,teischente
236 | 29578325,zexerous
237 | 37522866,andymilonakis
238 | 37522866,basedbidoof
239 | 37522866,beptobot
240 | 37522866,bimmmike
241 | 37522866,burristorn
242 | 37522866,camronglen
243 | 37522866,ghassan___
244 | 37522866,meghandeth_
245 | 37522866,moobot
246 | 37522866,mrwwonka
247 | 37522866,parblaze
248 | 37522866,sodapoppin
249 | 37522866,streamelements
250 | 268314886,bking0921
251 | 268314886,fjtldk33
252 | 268314886,ssakdook
253 | 38881685,autumnsnow
254 | 38881685,ayukura
255 | 38881685,bensn
256 | 38881685,boxboxbot
257 | 38881685,daseott
258 | 38881685,disguisedtoast
259 | 38881685,eggsareok
260 | 38881685,eiinna
261 | 38881685,enluna
262 | 38881685,epiknub
263 | 38881685,gangie
264 | 38881685,johnxim
265 | 38881685,kenneth_yeung
266 | 38881685,kristoferyee
267 | 38881685,lilypichu
268 | 38881685,matarra_
269 | 38881685,moobot
270 | 38881685,nightbot
271 | 38881685,streamelements
272 | 38881685,sword543
273 | 29722828,chuchu0706
274 | 29722828,guanweiboy
275 | 29722828,moobot
276 | 29722828,nightbot
277 | 29722828,norton0316
278 | 32402794,akquitted
279 | 32402794,aus24
280 | 32402794,decimated_exo
281 | 32402794,frankyfella
282 | 32402794,lozza_95
283 | 32402794,nightbot
284 | 32402794,psammy95
285 | 32402794,streamelements
286 | 32402794,timeoutwithbits
287 | 188890121,fossabot
288 | 188890121,igalreal7
289 | 188890121,maxidear
290 | 188890121,moobot
291 | 188890121,naivebtw
292 | 188890121,nightbot
293 | 188890121,peepochat_bot
294 | 188890121,rikkidi
295 | 188890121,streamelements
296 | 68240113,captainexo
297 | 68240113,germenchservice
298 | 68240113,maggotwarrior
299 | 68240113,missfoxytoxy
300 | 68240113,moobot
301 | 68240113,nexgam_civilization
302 | 68240113,nightbot
303 | 68240113,streamelements
304 | 68240113,venividirici
305 | 7010591,friedmombo
306 | 7010591,jtl012
307 | 7010591,kingpresiden
308 | 7010591,lmrainy
309 | 7010591,nightbot
310 | 7010591,selfdisgust
311 | 7010591,twilightpink
312 | 7010591,xcurryricex
313 | 152596920,ehduqdl0
314 | 152596920,nightbot
315 | 152596920,ssakdook
316 | 86131599,alt_next
317 | 86131599,envyzzz
318 | 86131599,naugghty
319 | 86131599,rocsie
320 | 181077473,gamersclubcs
321 | 181077473,streamelements
322 | 181077473,xdougrasx
323 | 45302947,akhil0607
324 | 45302947,explosivez10
325 | 45302947,kyams18
326 | 45302947,mubblin
327 | 45302947,offambrosia
328 | 45302947,quinnyvarn
329 | 45302947,streamelements
330 | 45302947,yanyanlve
331 | 57317154,eddalius
332 | 57317154,gamten
333 | 57317154,nightbot
334 | 57317154,srchow
335 | 57317154,streamelements
336 | 72480716,klayvan
337 | 72480716,poonylechat
338 | 72480716,streamelements
339 | 116885541,baby_bee
340 | 116885541,fornata
341 | 116885541,fossabot
342 | 116885541,sohdium
343 | 116885541,streamelements
344 | 116885541,viperdxb73
345 | 29795919,fishbowl16
346 | 29795919,mattsdfa
347 | 29795919,mellen
348 | 29795919,nonamenamer
349 | 29795919,paarah
350 | 29795919,streamlabs
351 | 29795919,underflowr
352 | 29795919,wrongcoder
353 | 46431370,lwgibson
354 | 46431370,marco1520
355 | 46431370,notspkdom
356 | 46431370,trplaysgames
357 | 46431370,yiorgos
358 | 46431370,zhamzees
359 | 410965603,adomatoo
360 | 410965603,echojam69
361 | 410965603,jelledot
362 | 410965603,korrysssss
363 | 410965603,mnim73
364 | 410965603,streamelements
365 | 410965603,triedge95
366 | 410965603,vero
367 | 410965603,verrachi
368 | 410965603,xlice
369 | 410965603,yanderrey
370 | 14408894,curtisbeef
371 | 14408894,jstaggah
372 | 14408894,m4ntis87
373 | 14408894,neostormx
374 | 14408894,noisedified
375 | 14408894,nz_trip_
376 | 14408894,papabear
377 | 14408894,rippen_tv
378 | 14408894,streamelements
379 | 14408894,thewizardhoyd
380 | 14408894,viewage_bot
381 | 11897156,aikasanvr
382 | 11897156,cragsand
383 | 11897156,crazymango_vr
384 | 11897156,criken
385 | 11897156,elisera
386 | 11897156,flynnok
387 | 11897156,jellypeanut
388 | 11897156,ketsumi
389 | 11897156,magiikmushroom1
390 | 11897156,murdercrumpet
391 | 11897156,notoriousnorman
392 | 11897156,oathmeal
393 | 11897156,pluslez
394 | 11897156,saiiren
395 | 11897156,sn0wbreeze
396 | 11897156,sodapoppin
397 | 11897156,sorry
398 | 11897156,their0njew
399 | 11897156,theyishai
400 | 11897156,wifipunk
401 | 11897156,zanventv
402 | 57025612,g2esports
403 | 57025612,moobot
404 | 57025612,siimiizz
405 | 57025612,streamlabs
406 | 57025612,thijsmod
407 | 57025612,weirdgus
408 | 38779717,doritoschip
409 | 38779717,hammerrazor
410 | 38779717,hooblie
411 | 38779717,lemothegnome
412 | 38779717,moobot
413 | 38779717,squishmittenns
414 | 38779717,streamelements
415 | 38779717,thecheekyguy
416 | 38779717,towelliee
417 | 87791915,bl00ddiamond
418 | 87791915,botvertolet
419 | 87791915,bud_light_na_bot
420 | 87791915,frontbosspro
421 | 87791915,geekyd_boyyy
422 | 87791915,misterkotyara
423 | 87791915,mr_fabler
424 | 87791915,streamelements
425 | 135052907,chachi_3
426 | 135052907,fossabot
427 | 135052907,streamelements
428 | 135052907,ughgloss
429 | 44574567,alykitty_
430 | 44574567,antiq24
431 | 44574567,clipsbott
432 | 44574567,coatsiie
433 | 44574567,fossabot
434 | 44574567,jshreaper
435 | 44574567,poeticw
436 | 44574567,silvdi
437 | 44574567,streamelements
438 | 44574567,tiffanyco520
439 | 44574567,townieouthere
440 | 44574567,xnox_
441 | 21588571,dizzykitten
442 | 21588571,eddric
443 | 21588571,fairlight_excalibur
444 | 21588571,logiceftbot
445 | 21588571,mikle255
446 | 21588571,omegatooyew
447 | 59635827,goronado
448 | 59635827,nevixow
449 | 59635827,slmn
450 | 54121470,caralyn
451 | 54121470,cent
452 | 54121470,fieldagentreaper
453 | 54121470,fossabot
454 | 54121470,jarkner
455 | 54121470,namesjd
456 | 54121470,october
457 | 54121470,rafig1717
458 | 54121470,zakmaniscool
459 |
--------------------------------------------------------------------------------
/backend/import-data/streamers.csv:
--------------------------------------------------------------------------------
1 | id,user_id,user_login,user_name,game_name,language,thumbnail_url,followers,description,view_count,created_at
2 | 43056089885,190835892,lck_korea,LCK_Korea,League of Legends,ko,https://static-cdn.jtvnw.net/previews-ttv/live_user_lck_korea-{width}x{height}.jpg,972515,LCK 트위치 공식 중계 채널입니다. 더 많은 정보는 LCK 소셜 미디어에서 확인하세요. ,299890046,2018-01-15 01:40:09.577844
3 | 43055791485,569342750,badboyhalo,BadBoyHalo,Gartic Phone,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_badboyhalo-{width}x{height}.jpg,2135028,Thank you for the BadBoyHalo username twitch :D,7900833,2020-08-19 04:08:41.492237
4 | 43056090413,124425501,lck,LCK,League of Legends,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_lck-{width}x{height}.jpg,1207546,"For more information, schedules and stats head to the LCK's social media.",121821783,2016-05-16 18:35:22.083040
5 | 42787446556,72550899,roshtein,ROSHTEIN,Slots,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_roshtein-{width}x{height}.jpg,643440,"""A Casino-philosopher with an incredible technique"" is what describes Roshtein. - Welcome to a journey where you will witness extraordinary combats, great choreography & brutal laughs. Roshtein & the TROOPS have shared this journey since 2016, when he first went live. So, sit back, relax & enjoy.",64123726,2014-10-06 10:15:37.179623
6 | 43054519053,88946548,aceu,aceu,TrackMania,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_aceu-{width}x{height}.jpg,1751342,bowl,48111844,2015-04-19 04:47:51.776353
7 | 43054202413,254489093,casimito,casimito,Sports,pt,https://static-cdn.jtvnw.net/previews-ttv/live_user_casimito-{width}x{height}.jpg,543253,"Apresentador da TNT Sports Brasil e do SBT! O maior NERDOLA do país e que ama Ararinhas Azuis. Espero que tenha um bom divertimento por aqui, na live que mais premia no BRASIL!",12824331,2018-09-03 03:12:15.562987
8 | 39735900699,156037856,fextralife,Fextralife,New World,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_fextralife-{width}x{height}.jpg,625986,"Fextralife is a gaming community & game news source fueled by a team of dedicated gamers! We play Action, Adventure and RPGs, some Survival & FPS too!. Elden Ring, Baldur's Gate 3 hype! Check out our 200+ Wikis, reviews on Youtube & more! This week STREAMS: New World, Genshin 2.0, The Ascent, PoE",1568845490,2017-05-07 00:21:11.451083
9 | 43056099373,146549780,lol_woolf,울프,League of Legends,ko,https://static-cdn.jtvnw.net/previews-ttv/live_user_lol_woolf-{width}x{height}.jpg,352620,T1의 인플루언서 이자 방송인으로서 활동하고있는 울프 이재완입니다.방송시간 : LCK 시즌 - 1라운드 수목금토일 4시 30분 / 2라운드 목금토일 4시 30분 비즈니스 및 문의메일 : Wolf@t1.gg,20658447,2017-01-31 14:21:14.313034
10 | 43055662573,119506529,raderaderader,らっだぁ,Minecraft,ja,https://static-cdn.jtvnw.net/previews-ttv/live_user_raderaderader-{width}x{height}.jpg,216276,マイクラとか色々やります。配信画面のハートを押すとフォローになります。通知をオンにすると配信開始がすぐにわかります。アーカイブのサブスク限定は動画にするやつです。,5306332,2016-03-23 06:03:38.592791
11 | 43054550877,77415295,jasper7se,Jasper7se,Escape from Tarkov,ja,https://static-cdn.jtvnw.net/previews-ttv/live_user_jasper7se-{width}x{height}.jpg,143804,じゃすぱー 21歳 音楽 対戦ゲーム大好き,14476016,2014-12-17 23:20:19.824704
12 | 43050434429,107305687,ray__c,Ray__C,Grand Theft Auto V,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_ray__c-{width}x{height}.jpg,359966,"Hey, I'm Ray C. Good vibes and competitive gaming is what I do!",11957223,2015-11-16 22:54:58.802130
13 | 39735806139,485855940,lolpacifictw,lolpacifictw,League of Legends,zh,https://static-cdn.jtvnw.net/previews-ttv/live_user_lolpacifictw-{width}x{height}.jpg,131122,,22688599,2020-01-15 18:59:19.627668
14 | 43053681293,30104304,maximilian_dood,Maximilian_DOOD,Final Fantasy VII Remake,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_maximilian_dood-{width}x{height}.jpg,959659,"Maximilian's Livestream Of Hype! Modern fighting games, classic fighting games, playthrus and YoVideogames on the weekend. MY DOODS!",75679591,2012-04-28 03:03:56.967712
15 | 43056209309,38594688,fresh,Fresh,Fortnite,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_fresh-{width}x{height}.jpg,4423916,Yes | Content Creator for Luminosity,59577225,2012-12-19 05:42:05.829844
16 | 43028172461,106013742,pestily,Pestily,Just Chatting,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_pestily-{width}x{height}.jpg,972826,"3x EFT World Champion 2020, Not afraid to jump head first down a stair case, Done some cool stuff for kids",55075001,2015-11-03 08:34:47.674315
17 | 43053854605,61433001,lospollostv,LosPollosTV,Just Chatting,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_lospollostv-{width}x{height}.jpg,600653,NBA 2K15 KOK Invitational National Champion,19810457,2014-04-22 21:59:19.974780
18 | 43055309501,490592527,valorant,VALORANT,VALORANT,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_valorant-{width}x{height}.jpg,1211839,VALORANT is a 5v5 character based tactical shooter video game from Riot Games. ,46603368,2020-02-06 00:22:59.266894
19 | 43051591021,38746172,esfandtv,EsfandTV,Final Fantasy XIV Online,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_esfandtv-{width}x{height}.jpg,877267,Amouranth mod and OTK Cofounder,63214735,2012-12-25 06:50:47.432492
20 | 43054851725,431882702,teegrizzley,TeeGrizzley,Grand Theft Auto V,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_teegrizzley-{width}x{height}.jpg,677654,I TRY TO GO LIVE EVERY DAY FROM 7-12PM PST.....WITH THE WORLD BEING BK OPEN AND ME DOING SHOWS MY TIMES ARE RANDOM SO CATCH ME IF YOU CAN AND MUCH LOVE.........follow our YOUTUBE AT GRIZZLEY WORLD RP SUPPORTERS WIILL BE REWARDED. ,4082203,2019-04-23 15:25:10.067818
21 | 39734930011,73779954,diegosaurs,Diegosaurs,Apex Legends,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_diegosaurs-{width}x{height}.jpg,596960,"Hello, names Diego and I’m 24 :) ❤️",20100975,2014-10-26 04:36:26.305394
22 | 42794284060,54739364,esamarathon,ESAMarathon,Metal Gear Solid,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_esamarathon-{width}x{height}.jpg,258956,"Home of ESA, a biannual speedrunning charity marathon in Sweden, and Break the Record: LIVE, the best competitive speedrunning competition",96452919,2014-01-10 17:44:50.027732
23 | 43055345149,134586700,pjs9073,쫀득이_,Just Chatting,ko,https://static-cdn.jtvnw.net/previews-ttv/live_user_pjs9073-{width}x{height}.jpg,272126,,21501790,2016-09-13 03:16:34.159214
24 | 39735956923,189290002,x2twins,x2Twins,Fortnite,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_x2twins-{width}x{height}.jpg,2751500,Professional Fortnite Players,42818369,2018-01-06 23:21:29.054727
25 | 43055895901,32053915,rivenzi,Rivenzi,Sports,fr,https://static-cdn.jtvnw.net/previews-ttv/live_user_rivenzi-{width}x{height}.jpg,140140,"Je suis Rivenzi, Le père Castor breton qui vous permettra: aussi bien de vous délecter de sujets d'Histoire, que de vous dégoûter par son level sur une multitude de jeux, une RÉGALADE ! Rejoignez alors l’épopée de la communauté des cerveaux !!!",7538295,2012-07-11 19:37:59.360919
26 | 43050157549,26469355,koil,koil,Grand Theft Auto V,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_koil-{width}x{height}.jpg,420075,"Comment, rate and subscribe.",26699371,2011-11-30 01:44:29.044500
27 | 43051710493,22253819,scarra,Scarra,Final Fantasy XIV Online,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_scarra-{width}x{height}.jpg,1479919,I'm an ex league of legends pro mid laner turned streamer. Also fps prodigy.,107322670,2011-05-07 09:53:48.159182
28 | 43041026509,29578325,beyondthesummit,BeyondTheSummit,Dota 2,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_beyondthesummit-{width}x{height}.jpg,1019306,"Beyond The Summit is a broadcasting studio and tournament organizer for a variety of esports titles (including Dota 2, SSBM, CS:GO, & DBFZ). Following and subscribing enables us to continue to create unique content and host amazing events like The Summit. Thank you for your support!",480537396,2012-04-07 04:16:39.984897
29 | 43050341981,37522866,cyr,cyr,Just Chatting,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_cyr-{width}x{height}.jpg,380566,Hello please check out my professional gaming channel here on Twitch.tv!,14818278,2012-11-08 05:33:17.657480
30 | 43056270381,268314886,jiudau,지우다우,LOST ARK,ko,https://static-cdn.jtvnw.net/previews-ttv/live_user_jiudau-{width}x{height}.jpg,13666,실력방송이 아닌 광대방송입니다. 과몰입자제요망 XD,1785040,2018-10-20 16:54:25.290649
31 | 43051951565,38881685,boxbox,boxbox,VALORANT,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_boxbox-{width}x{height}.jpg,1895061,I LIKE TO STREAM LEAGUE OF LEGENDS AND SOMETIMES OTHER GAMES,115193942,2012-12-30 06:58:18.599144
32 | 39735193931,29722828,never_loses,NeVeR_LosEs,League of Legends,zh,https://static-cdn.jtvnw.net/previews-ttv/live_user_never_loses-{width}x{height}.jpg,232007,"Taiwan Server ID : FW NL / NeVeR_LosEs S3.4.5 Challenger , main AD 有問題會盡量回答 喜歡的話請按追隨唷!",45597076,2012-04-12 06:14:31.029178
33 | 43056177069,712534655,korafal3ardafs55,korafal3ardafs55,,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_korafal3ardafs55-{width}x{height}.jpg,37,,57772,2021-07-30 07:46:00.483678
34 | 43054654253,32402794,silent,Silent,Grand Theft Auto V,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_silent-{width}x{height}.jpg,142297,Hi. I play games. ,5918201,2012-07-24 23:54:04.170614
35 | 42794458812,188890121,dmitry_lixxx,Dmitry_Lixxx,Just Chatting,ru,https://static-cdn.jtvnw.net/previews-ttv/live_user_dmitry_lixxx-{width}x{height}.jpg,595675,,27996398,2018-01-04 20:31:54.286850
36 | 43053679357,68240113,germench,Germench,Metal Gear Solid,de,https://static-cdn.jtvnw.net/previews-ttv/live_user_germench-{width}x{height}.jpg,32775,"Nachdem der deutschsprachige Restream von AGDQ 2014 noch auf Akaikees und Exes Kanälen gehostet wurde, haben wir für 2015 diesen neuen Kanal aufgemacht. Lasst den Rummel rollen!",44310683,2014-08-05 17:01:55.972822
37 | 39735145947,7010591,uberhaxornova,UberHaxorNova,Grand Theft Auto V,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_uberhaxornova-{width}x{height}.jpg,489039,I like to eat nerds rope.,25024643,2009-06-28 03:45:34.509044
38 | 43055058589,152596920,lol_ambition,앰비션_,League of Legends,ko,https://static-cdn.jtvnw.net/previews-ttv/live_user_lol_ambition-{width}x{height}.jpg,433156,"방송보러 와주시는 모든분들께 감사드립니다 :)매일 오후 2시, 대체로 특별한사유가 없는 경우 방송 진행합니다!방송일정은 트게더를 확인해주세요!",49742005,2017-04-07 04:07:21.364531
39 | 43055908029,86131599,lol_nemesis,lol_nemesis,League of Legends,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_lol_nemesis-{width}x{height}.jpg,437427,"Nemesis, Professional League of Legends player and more",17807482,2015-03-24 16:42:36.639379
40 | 43044311949,181077473,gaules,Gaules,Cuphead,pt,https://static-cdn.jtvnw.net/previews-ttv/live_user_gaules-{width}x{height}.jpg,2858752,"Mais um guerreiro da Maior Tribo do Mundo! Atuei como jogador profissional de CS por quase uma década, fui o primeiro treinador a ser campeão do mundo em 2007 com o MIBR. Acertei um pouco, errei muito, ganhei bastante coisa e tbm perdi demais! Atualmente faço live todos os dias aqui na Twitch! ",315070879,2017-11-09 14:10:52.832570
41 | 43054492445,45302947,iitztimmy,iiTzTimmy,VALORANT,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_iitztimmy-{width}x{height}.jpg,817991,Where dreams are made,17160122,2013-06-28 04:15:32.224934
42 | 39736025403,57317154,steelmage,Steelmage,Path of Exile,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_steelmage-{width}x{height}.jpg,99464,I play too much PoE,15987924,2014-02-21 02:00:07.933075
43 | 43054961629,72480716,tonton,Tonton,The Ascent,fr,https://static-cdn.jtvnw.net/previews-ttv/live_user_tonton-{width}x{height}.jpg,272803,Je crois qu'il est préférable de ne pas connaître les raisons qui font que je suis complètement siphonné du bulbe...,25473998,2014-10-05 07:34:02.455349
44 | 43055567405,116885541,adeptthebest,adeptthebest,Grand Theft Auto V,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_adeptthebest-{width}x{height}.jpg,449084,The Best At Everything.,11685751,2016-02-24 21:54:16.125146
45 | 42788436412,29795919,nl_kripp,nl_Kripp,Path of Exile,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_nl_kripp-{width}x{height}.jpg,1392098,Gamer YouTuber Streamer Joker // Business: om.kripp@gmail.com,257686748,2012-04-15 02:25:31.150280
46 | 39735484011,46431370,imnio,ImNio,Call of Duty: Warzone,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_imnio-{width}x{height}.jpg,265166,,2924568,2013-07-21 21:44:53.717459
47 | 42794168268,410965603,okcode,OkCode,Genshin Impact,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_okcode-{width}x{height}.jpg,133965,"well, hi...",4681187,2019-01-20 16:33:09.348270
48 | 42793617996,14408894,cdnthe3rd,CDNThe3rd,Just Chatting,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_cdnthe3rd-{width}x{height}.jpg,2008827,Home of The Onward and Upward Vibrations and Multi-Zhon-Don(Multi-Genre) Gaming Experiences | [+],65320902,2010-08-07 00:42:01.432281
49 | 43053763805,11897156,roflgator,roflgator,Gartic Phone,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_roflgator-{width}x{height}.jpg,225202,content machine,10347344,2010-04-15 17:47:43.417235
50 | 43056251149,57025612,thijs,Thijs,Hearthstone,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_thijs-{width}x{height}.jpg,770678,"I'm Thijs, 26 years old and playing Hearthstone since 2013. After my 2 times HS European Championship I started full-time streaming. Email: thijsnlbusiness@gmail.com",105089649,2014-02-17 20:50:47.402453
51 | 43055008621,38779717,pandatv,PandaTV,Final Fantasy XIV Online,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_pandatv-{width}x{height}.jpg,170894,"in short I guess the best bio for me is that I'm just a pretty chill dude and always keen for a chat, I try and read the chat as much as I can and always try reply to everyone. Thanks for reading guys. ^_^",26892798,2012-12-26 20:36:59.550108
52 | 42786499836,87791915,ilame,iLame,Dota 2,ru,https://static-cdn.jtvnw.net/previews-ttv/live_user_ilame-{width}x{height}.jpg,290927,"Half Bruneian, Half Filipino and usually roleplaying BENJI aka Mr. B on NoPixel. Business: iamlysium@gmail.com",7166265,2013-06-10 12:02:12.888433
53 | 43054350317,135052907,sleepy,sleepy,Overwatch,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_sleepy-{width}x{height}.jpg,539147,"Snoop Dogg official page for chillin, smoking while gaming GGL",2090333,2015-09-02 06:34:08.689151
54 | 39735946539,44574567,lysium,Lysium,Grand Theft Auto V,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_lysium-{width}x{height}.jpg,204153,Business inquiries email to AnthonyKongphan@gmail.com • SPONSORED BY GFUEL: Use discount code AK - https://gfuel.com/ • SPONSORED BY ARTESIAN: Get up to $100 off by visiting https://artesianbuilds.com/gaming/?aff=Anthonykongphan & entering discount code ANTHONY at checkout! ,34718372,2011-04-08 03:58:07.249666
55 | 43056395261,101240144,doggydogg20,Doggydogg20,Twitch,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_doggydogg20-{width}x{height}.jpg,471560,https://vk.com/ilame https://www.youtube.com/ilame,39716240,2015-04-07 07:08:04.976001
56 | 43053716557,21588571,anthony_kongphan,Anthony_Kongphan,New World,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_anthony_kongphan-{width}x{height}.jpg,505477,im tired,9174479,2016-09-20 03:54:19.849312
57 | 39734994331,59635827,supertf,supertf,DOOM,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_supertf-{width}x{height}.jpg,355179,Professional Overwatch and video game player :),11340710,2014-03-25 20:08:27.250375
58 | 43054797149,54121470,bronx,Bronx,Dead by Daylight,en,https://static-cdn.jtvnw.net/previews-ttv/live_user_bronx-{width}x{height}.jpg,73380,"I stream DBD, edit YouTube videos, and mod TimTheTatman’s chat ",1507022,2013-12-30 16:48:25.343452
59 |
--------------------------------------------------------------------------------
/twitch-stream/chatters.csv:
--------------------------------------------------------------------------------
1 | 569342750,animefan_cat16
2 | 569342750,animefreaknerdz
3 | 569342750,animegolden
4 | 569342750,animekanin
5 | 569342750,animezene
6 | 569342750,animiceatspig
7 | 569342750,animu2
8 | 569342750,aniqeee
9 | 569342750,anisaswag
10 | 569342750,anisoptera77
11 | 569342750,aniszurf
12 | 569342750,anitrain303
13 | 569342750,anixiae
14 | 569342750,anjalay
15 | 569342750,anjashalo
16 | 569342750,anjaymabar123412
17 | 569342750,anjela02
18 | 569342750,anka_skakankaka
19 | 569342750,ankster_
20 | 569342750,anlhq
21 | 569342750,anlixastra
22 | 569342750,anmayl
23 | 569342750,ann4roqu3
24 | 569342750,ann_aperitif
25 | 569342750,anna007sh
26 | 569342750,anna4223
27 | 569342750,anna80x5
28 | 569342750,anna__2882
29 | 569342750,anna_at
30 | 569342750,anna_banana_28
31 | 569342750,anna_cecilia_
32 | 569342750,anna_contre_
33 | 569342750,anna_jane_
34 | 569342750,anna_l4ura
35 | 569342750,anna_lynn1420
36 | 569342750,anna_mckxnna
37 | 569342750,anna_raa
38 | 569342750,anna_t24
39 | 569342750,anna_v4mp
40 | 569342750,annaaa55
41 | 569342750,annaask_04
42 | 569342750,annabananaaxx
43 | 569342750,annabananafana
44 | 569342750,annabean853
45 | 569342750,annabeann
46 | 569342750,annabel0273
47 | 569342750,annabie_
48 | 569342750,annacoolkid
49 | 569342750,annadaniella
50 | 569342750,annadeitrick27
51 | 569342750,annagrohp1
52 | 569342750,annagryga_
53 | 569342750,annahigarcia
54 | 569342750,annaisdehyrdated
55 | 569342750,annaisntlive
56 | 569342750,annaispogchampp
57 | 569342750,annakatea
58 | 569342750,annakn0wles
59 | 569342750,annalieser
60 | 569342750,annalololll
61 | 569342750,annalouise__
62 | 569342750,annalyssac_07
63 | 569342750,annamaula
64 | 569342750,annananat
65 | 569342750,annanevertalks
66 | 569342750,annanna__
67 | 569342750,annathatsm
68 | 569342750,annathealienn
69 | 569342750,annawasfound13
70 | 569342750,annawasntfound
71 | 569342750,annaxokate
72 | 569342750,anne_0404
73 | 569342750,anne_e22
74 | 569342750,anne_rain
75 | 569342750,annearlert
76 | 569342750,annefinityuwu
77 | 569342750,anneke____
78 | 569342750,annemarix
79 | 569342750,annepic
80 | 569342750,annetteofc
81 | 569342750,anneviiii
82 | 569342750,annfeigned
83 | 569342750,anni_1407
84 | 569342750,anni_drew
85 | 569342750,annicachoi
86 | 569342750,annie_m_
87 | 569342750,anniebunn
88 | 569342750,anniee7
89 | 569342750,annieeok
90 | 569342750,annieexv
91 | 569342750,anniemybeloved
92 | 569342750,anniepor
93 | 569342750,annijaaa14
94 | 569342750,annika_seavey
95 | 569342750,annikawasfound_
96 | 569342750,annilabs
97 | 569342750,annilitealt
98 | 569342750,annisa1337
99 | 569342750,annlino
100 | 569342750,annnnggiiee
101 | 569342750,annouko_hehe
102 | 569342750,annoying_sunni
103 | 569342750,annoyingiirl
104 | 569342750,annthea_
105 | 569342750,annundso
106 | 569342750,annx13_
107 | 569342750,annxqy
108 | 569342750,anoellie
109 | 569342750,anokhiroy
110 | 569342750,anomaly_em
111 | 569342750,anon_bomb
112 | 569342750,anon_insomnia
113 | 569342750,anon_lol_
114 | 569342750,anongoingcry
115 | 569342750,anonimusema
116 | 569342750,anony9903
117 | 569342750,anonymous404_
118 | 569342750,anonymous_804
119 | 569342750,anonymousasteroid
120 | 569342750,anonymousflowers
121 | 569342750,anooence
122 | 569342750,anoposssum
123 | 569342750,anotherbraincell
124 | 569342750,anotherskyee
125 | 569342750,anoukblrd
126 | 569342750,anoukiey
127 | 569342750,anoverratedartist
128 | 569342750,anovi97
129 | 569342750,anp_22
130 | 569342750,anqe1ina
131 | 569342750,anriaza
132 | 569342750,ansatzun
133 | 569342750,anskkii
134 | 569342750,ansl3y
135 | 569342750,ant1socialzay
136 | 569342750,ant246135
137 | 569342750,ant_tickler
138 | 569342750,antare_pink
139 | 569342750,antaria
140 | 569342750,antaries07
141 | 569342750,antariies
142 | 569342750,anteyata
143 | 569342750,antfrost
144 | 569342750,antheris_
145 | 569342750,anticinnamon
146 | 569342750,anticlover_
147 | 569342750,antilions
148 | 569342750,antiphilex
149 | 569342750,antique_banana_fan
150 | 569342750,antiquriian_
151 | 569342750,antisocialgamerlive
152 | 569342750,antithorn
153 | 569342750,antixsocial_133
154 | 569342750,antmaster_3321
155 | 569342750,antmettle
156 | 569342750,antoniaelg
157 | 569342750,antoniahdz_
158 | 569342750,antoniaokay
159 | 569342750,antonias1402
160 | 569342750,antoo_75
161 | 569342750,antsiiss
162 | 569342750,antsinjelly
163 | 569342750,anttea1
164 | 569342750,anuhx
165 | 569342750,anuluna11
166 | 569342750,anxas_prvt
167 | 569342750,anxgirl03
168 | 569342750,anxhyyyy
169 | 569342750,anxieturt
170 | 569342750,anxietyboba
171 | 569342750,anxietyexpress_
172 | 569342750,anxious__coconut
173 | 569342750,anxious_lemon_
174 | 569342750,anxious_racc00n
175 | 569342750,anxiousrealism
176 | 569342750,anxiousval
177 | 569342750,anxisrc_chara
178 | 569342750,anyahojsak
179 | 569342750,anyaiguess
180 | 569342750,anyawongs
181 | 569342750,anycarers
182 | 569342750,anythingmay
183 | 569342750,anywayswhat
184 | 569342750,ao_skyzi
185 | 569342750,aoithebunny
186 | 569342750,aonixty
187 | 569342750,aoora
188 | 569342750,aorcute
189 | 569342750,apersonnamedjd
190 | 569342750,apexgirl_94
191 | 569342750,aph_eats_bugs
192 | 569342750,aphaiaa
193 | 569342750,aphelium__
194 | 569342750,aphhrodiite
195 | 569342750,aphroditemb
196 | 569342750,aphvlio
197 | 569342750,apinkbee
198 | 569342750,apiwtd
199 | 569342750,apk06
200 | 569342750,aplam127
201 | 569342750,apmhirrhox
202 | 569342750,apollo_xoxo
203 | 569342750,apolloislester
204 | 569342750,apollomoonlive
205 | 569342750,apple2168
206 | 569342750,apple__s
207 | 569342750,apple_pi0513
208 | 569342750,applecone_
209 | 569342750,applejeecee
210 | 569342750,applejuicee3
211 | 569342750,applemallow
212 | 569342750,applepi15_
213 | 569342750,applepie505
214 | 569342750,applepie721
215 | 569342750,applepieee05
216 | 569342750,applepiemuffins
217 | 569342750,applepigeye
218 | 569342750,applepinekoma
219 | 569342750,applesareants2
220 | 569342750,applesmichel
221 | 569342750,applesyoranges
222 | 569342750,applpi_vina
223 | 569342750,applrrrr
224 | 569342750,appopai
225 | 569342750,appricott4
226 | 569342750,apricotdraws
227 | 569342750,april728
228 | 569342750,aprildgaf
229 | 569342750,aprililly
230 | 569342750,aprilwatchesstuff
231 | 569342750,aqeeladania09
232 | 569342750,aqira_qira
233 | 569342750,aqology
234 | 569342750,aqqlepiee
235 | 569342750,aquabib_
236 | 569342750,aquabiez
237 | 569342750,aquamxxrine
238 | 569342750,aquarius_1309
239 | 569342750,aquaticd0g
240 | 569342750,aquaticmoonn
241 | 569342750,aquifleur
242 | 569342750,aqvaomxrine
243 | 569342750,aqvliah
244 | 569342750,ar1ana06
245 | 569342750,ara_baby
246 | 569342750,arabellacrofts
247 | 569342750,aracelie389
248 | 569342750,aracelymcpanda
249 | 569342750,arachnace
250 | 569342750,aracule_
251 | 569342750,aral140
252 | 569342750,aramathine
253 | 569342750,aranxa__
254 | 569342750,arapaima_blue
255 | 569342750,araven4
256 | 569342750,arbusby
257 | 569342750,arcadiaave
258 | 569342750,arcadiella
259 | 569342750,arcaneepicyon
260 | 569342750,arcanefighter123
261 | 569342750,arch_adlight27
262 | 569342750,archerie_
263 | 569342750,archius1
264 | 569342750,arclyii_
265 | 569342750,arco_12
266 | 569342750,arcticcarno
267 | 569342750,arcticpoison
268 | 569342750,arcticxwolves
269 | 569342750,arcyanee
270 | 569342750,ardenthemuse
271 | 569342750,ardneleh
272 | 569342750,are_u_reddie
273 | 569342750,areamimadi
274 | 569342750,areem_
275 | 569342750,areevw
276 | 569342750,areinlle
277 | 569342750,arelent
278 | 569342750,aresea_
279 | 569342750,areyouserious34
280 | 569342750,arghdoescrime
281 | 569342750,ari1chu
282 | 569342750,ari5x
283 | 569342750,ari_512
284 | 569342750,ari_files
285 | 569342750,ari_h2323
286 | 569342750,ari_hi3130
287 | 569342750,ari_lia
288 | 569342750,ari_love87877
289 | 569342750,ari_reject
290 | 569342750,aria0550
291 | 569342750,aria141312
292 | 569342750,aria__23
293 | 569342750,aria_sta
294 | 569342750,ariaannna
295 | 569342750,ariaano
296 | 569342750,ariadafox
297 | 569342750,arialev
298 | 569342750,ariana23yee
299 | 569342750,ariana_mitchell
300 | 569342750,arianaa_tso
301 | 569342750,arianaajones
302 | 569342750,arianagrande027
303 | 569342750,arianamontenegro1
304 | 569342750,arianasboo
305 | 569342750,arianna_ahhhh
306 | 569342750,ariannaa28
307 | 569342750,ariannacastellon
308 | 569342750,ariannagraz
309 | 569342750,ariannarodri
310 | 569342750,ariaseen
311 | 569342750,ariaxxne
312 | 569342750,aribeker
313 | 569342750,arie_hallow
314 | 569342750,ariel_mybeauty
315 | 569342750,arielistotallynotmyname
316 | 569342750,arielizel
317 | 569342750,arielleeeeeeeeee
318 | 569342750,arielneedsalife
319 | 569342750,arielsw_
320 | 569342750,arielthemermaid13433
321 | 569342750,arielvictoriaa
322 | 569342750,aries110
323 | 569342750,aries_bb
324 | 569342750,ariescraft08
325 | 569342750,ariethepleb
326 | 569342750,ariii_6
327 | 569342750,ariisb0red
328 | 569342750,arimorenos
329 | 569342750,arinasabina
330 | 569342750,arinnotfound_
331 | 569342750,ariopointment
332 | 569342750,arisalt_
333 | 569342750,arisbelreyes__
334 | 569342750,arisbethh
335 | 569342750,arisoccer12
336 | 569342750,aristona_
337 | 569342750,ariust
338 | 569342750,arivou
339 | 569342750,arixeru
340 | 569342750,ariyesnoi
341 | 569342750,arkhan_grimly
342 | 569342750,arkin505
343 | 569342750,arkiww
344 | 569342750,arlee7322
345 | 569342750,arleia101
346 | 569342750,arlene_neoma
347 | 569342750,arlene_slytherpuff
348 | 569342750,arlenecarpina
349 | 569342750,arlinix
350 | 569342750,armareyaw
351 | 569342750,armincult
352 | 569342750,armlesslizard
353 | 569342750,armonika9
354 | 569342750,army3774
355 | 569342750,arnasio
356 | 569342750,aroa_speaks
357 | 569342750,aroberts_242
358 | 569342750,arogue_sith14
359 | 569342750,aromantic_pickle
360 | 569342750,aromanticbee
361 | 569342750,aroushanif
362 | 569342750,arqxa
363 | 569342750,arrigoniamelia
364 | 569342750,arrowntic
365 | 569342750,ars0n404
366 | 569342750,arshansaidur
367 | 569342750,arson_3828
368 | 569342750,arson_ala
369 | 569342750,arson_ghost_live
370 | 569342750,arson_husband
371 | 569342750,arson_o_clock
372 | 569342750,arson_tbh
373 | 569342750,arsonattheorphanage
374 | 569342750,arsonay
375 | 569342750,arsonbeez
376 | 569342750,arsonistrat
377 | 569342750,arsonmarss
378 | 569342750,art_angels2
379 | 569342750,art_anime_hayleexox
380 | 569342750,art_orion
381 | 569342750,artama_karta
382 | 569342750,artbunny13
383 | 569342750,artcmiis_
384 | 569342750,artdeiona
385 | 569342750,artdejamie
386 | 569342750,artemiria
387 | 569342750,artemis_hehe
388 | 569342750,artemisneomenia
389 | 569342750,artic_plum
390 | 569342750,artisticchild02
391 | 569342750,artistyray
392 | 569342750,artnidniddin
393 | 569342750,artomisslive
394 | 569342750,artrixave
395 | 569342750,arts_anime
396 | 569342750,artschoolwannabe_
397 | 569342750,artsybanana6
398 | 569342750,arttoanimation
399 | 569342750,artyraven
400 | 569342750,artystarlight
401 | 569342750,artytunes
402 | 569342750,aruel_cb
403 | 569342750,aruxchu
404 | 569342750,ary1901
405 | 569342750,arya_karpe
406 | 569342750,aryaa_2063
407 | 569342750,aryannarose
408 | 569342750,arycrybaby
409 | 569342750,arynala
410 | 569342750,arytosy
411 | 569342750,aryzeen
412 | 569342750,as5o0o5sa
413 | 569342750,asahichu_
414 | 569342750,asaltbh
415 | 569342750,asaltyrose
416 | 569342750,asapbrihaha
417 | 569342750,ase_trash00
418 | 569342750,aseapollo
419 | 569342750,asexualbean
420 | 569342750,asfshania
421 | 569342750,ash17_o4
422 | 569342750,ash6652
423 | 569342750,ash_0714_
424 | 569342750,ash__boo
425 | 569342750,ash_cervidae
426 | 569342750,ash_leaf17
427 | 569342750,ash_lovestea12
428 | 569342750,ash_s3925
429 | 569342750,ash_t00
430 | 569342750,ash_tea_18
431 | 569342750,asha1812
432 | 569342750,ashabutler
433 | 569342750,ashakeyy
434 | 569342750,asharm_
435 | 569342750,ashbutterr
436 | 569342750,ashecat_232
437 | 569342750,asheflower
438 | 569342750,asheiii_
439 | 569342750,asheliegh
440 | 569342750,ashely420
441 | 569342750,ashelyisdumb
442 | 569342750,asheo0_
443 | 569342750,asher_0166
444 | 569342750,ashercol3
445 | 569342750,ashey_kat
446 | 569342750,ashgodahhhh
447 | 569342750,ashhaly
448 | 569342750,ashiesberry
449 | 569342750,ashii143
450 | 569342750,ashimoon
451 | 569342750,ashio44
452 | 569342750,ashish0rny
453 | 569342750,ashisokay_
454 | 569342750,ashkatye
455 | 569342750,ashkiki
456 | 569342750,ashkpink
457 | 569342750,ashl189
458 | 569342750,ashl33wastaken_
459 | 569342750,ashleighevans4
460 | 569342750,ashleighmcnamara
461 | 569342750,ashleistp
462 | 569342750,ashlesfromfire
463 | 569342750,ashley37_
464 | 569342750,ashley_17u
465 | 569342750,ashley_aris
466 | 569342750,ashley_ish
467 | 569342750,ashley_jt
468 | 569342750,ashley_kim
469 | 569342750,ashley_linguine
470 | 569342750,ashley_pineda
471 | 569342750,ashley_yumz
472 | 569342750,ashleyarat
473 | 569342750,ashleybbys
474 | 569342750,ashleybejar
475 | 569342750,ashleyexists
476 | 569342750,ashleyinntit
477 | 569342750,ashleylu0306
478 | 569342750,ashleymei546
479 | 569342750,ashleymybelovedlol
480 | 569342750,ashleypissed
481 | 569342750,ashleysugden
482 | 569342750,ashleyswagggg
483 | 569342750,ashleywashaunted
484 | 569342750,ashleyymoree
485 | 569342750,ashleyyrg
486 | 569342750,ashlita_ama
487 | 569342750,ashlmaoo_xh
488 | 569342750,ashmmoney
489 | 569342750,ashmz
490 | 569342750,ashnighttime
491 | 569342750,asho000
492 | 569342750,ashotofgin
493 | 569342750,asht0n_arts
494 | 569342750,asht0n_m1dn1ght
495 | 569342750,ashteria_
496 | 569342750,ashtonaholic
497 | 569342750,ashtqray
498 | 569342750,ashtralive
499 | 569342750,ashtrinx
500 | 569342750,ashtyn_sab
501 | 569342750,ashuallyn
502 | 569342750,ashval19
503 | 569342750,ashweexd
504 | 569342750,asianesesoupe
505 | 569342750,asiduoss
506 | 569342750,asimpforu
507 | 569342750,askanforever
508 | 569342750,asleepingphantom
509 | 569342750,asmita_bhusal
510 | 569342750,asmolyeti
511 | 569342750,asnaluth
512 | 569342750,asoerialive
513 | 569342750,aspen044
514 | 569342750,aspen258
515 | 569342750,aspen_08_
516 | 569342750,aspenmo0n
517 | 569342750,aspiringhotmilf
518 | 569342750,asrt1id
519 | 569342750,asstrolive
520 | 569342750,assyla_alyssaaaa
521 | 569342750,astaramo
522 | 569342750,aster_louisse
523 | 569342750,asteriismo
524 | 569342750,asterislite
525 | 569342750,asterlie_
526 | 569342750,asterlyx
527 | 569342750,asters_away
528 | 569342750,astixe
529 | 569342750,astnogar
530 | 569342750,astrafox32
531 | 569342750,astrangeghostkid
532 | 569342750,astrayys
533 | 569342750,astreiavy
534 | 569342750,astrelluh
535 | 569342750,astrid_clausen
536 | 569342750,astridbj
537 | 569342750,astridhedemann
538 | 569342750,astrifex
539 | 569342750,astriiu
540 | 569342750,astrix_gaming_
541 | 569342750,astro_nomi
542 | 569342750,astrobunyy
543 | 569342750,astrocist
544 | 569342750,astrohoeworld
545 | 569342750,astrohwii
546 | 569342750,astrolinee
547 | 569342750,astrolynx_
548 | 569342750,astronatt_
549 | 569342750,astronokia
550 | 569342750,astrorosiie
551 | 569342750,astrozoe5
552 | 569342750,asuikz
553 | 569342750,asullock
554 | 569342750,asupersadghost
555 | 569342750,asurion_xx
556 | 569342750,asusraider
557 | 569342750,aswid
558 | 569342750,asyr4f17
559 | 569342750,asythe_
560 | 569342750,at_nat
561 | 569342750,ata1ius
562 | 569342750,atarixia
563 | 569342750,ataterpotato
564 | 569342750,atdeathfrog
565 | 569342750,aten
566 | 569342750,atfantazy
567 | 569342750,athaliah_
568 | 569342750,atheistchildlol
569 | 569342750,athelium1
570 | 569342750,athena1312
571 | 569342750,athena_16_
572 | 569342750,athena_thebadassgoddess
573 | 569342750,athenaashdown
574 | 569342750,athenahulley
575 | 569342750,athenaluvr
576 | 569342750,athenasas
577 | 569342750,athendeer
578 | 569342750,athens_69
579 | 569342750,atheyuh
580 | 569342750,athrhea
581 | 569342750,atiyyyy_
582 | 569342750,atlanticazure
583 | 569342750,atlaskaaa
584 | 569342750,atlasthegh0st
585 | 569342750,atley_d7
586 | 569342750,atlopus123
587 | 569342750,atnguyen161120
588 | 569342750,atolglassofwater
589 | 569342750,atom1c_bomb
590 | 569342750,atomic_no_83
591 | 569342750,atomicwizz
592 | 569342750,atomlitu
593 | 569342750,atris555555
594 | 569342750,atticus47
595 | 569342750,atticus_ly
596 | 569342750,atuasbathwater
597 | 569342750,atuesday3
598 | 569342750,atwentysomethingloser
599 | 569342750,atypicalaly
600 | 569342750,atypicalneo
601 | 569342750,aubreelmao
602 | 569342750,aubreexe
603 | 569342750,aubrey00
604 | 569342750,aubrey_hope
605 | 569342750,aubreyane
606 | 569342750,aubreybrooke
607 | 569342750,aubreylmaooo
608 | 569342750,aubreynoelle
609 | 569342750,aubreysim77
610 | 569342750,aubs_t
611 | 569342750,aubz32
612 | 569342750,audime_games
613 | 569342750,audraplants
614 | 569342750,audrasaurus
615 | 569342750,audree56
616 | 569342750,audregoe
617 | 569342750,audrey_krick
618 | 569342750,audreybenedict
619 | 569342750,audreylysann
620 | 569342750,audreym123
621 | 569342750,audreyoffline
622 | 569342750,audreypoglol
623 | 569342750,audreyvbr
624 | 569342750,audreyy728
625 | 569342750,audreyyeet
626 | 569342750,audrithine_
627 | 569342750,audshawott
628 | 569342750,audy43
629 | 569342750,aufowa
630 | 569342750,auftline
631 | 569342750,augbust
632 | 569342750,auguslatorre
633 | 569342750,auhleeshaa
634 | 569342750,auhthentic
635 | 569342750,aukkuwu
636 | 569342750,aundru
637 | 569342750,aunfriendlypotato
638 | 569342750,auo_camille
639 | 569342750,aur0ra_o7
640 | 569342750,aura_arson
641 | 569342750,aura_jpeg
642 | 569342750,aurademon1127
643 | 569342750,auraxxlol
644 | 569342750,aure123j
645 | 569342750,aureliamarc
646 | 569342750,aurelxaaa
647 | 569342750,auro00x
648 | 569342750,aurocent
649 | 569342750,aurora_s_n
650 | 569342750,aurorabees
651 | 569342750,aurorasha
652 | 569342750,aurorastfu
653 | 569342750,auskra510
654 | 569342750,aussie_roadkill
655 | 569342750,aussieashlee
656 | 569342750,austeja012
657 | 569342750,austenislost
658 | 569342750,austin_dior
659 | 569342750,austinmooon
660 | 569342750,aut0fr0g
661 | 569342750,auto_3clipse
662 | 569342750,autopilot_117
663 | 569342750,autrowcollector
664 | 569342750,autumn_owo
665 | 569342750,autumn_xdagua123x
666 | 569342750,autumnglass_
667 | 569342750,autumnheartt
668 | 569342750,autumnk28
669 | 569342750,autumnpumpkinpie
670 | 569342750,autumnscoot
671 | 569342750,autumnwall
672 | 569342750,auxolotl
673 | 569342750,auxsyc
674 | 569342750,auxtunmnn
675 | 569342750,auzzyurdxddy
676 | 569342750,ava2509
677 | 569342750,ava_824
678 | 569342750,ava__07
679 | 569342750,ava_chai
680 | 569342750,ava_ellen
681 | 569342750,ava_hhhh
682 | 569342750,ava_poels
683 | 569342750,ava_shammas
684 | 569342750,ava_yikes
685 | 569342750,avaaf__
686 | 569342750,avacadooowo
687 | 569342750,avacerber1
688 | 569342750,avadeel21
689 | 569342750,avaiscool503
690 | 569342750,avajacobsss
691 | 569342750,avalovesnature1
692 | 569342750,avamae1610
693 | 569342750,avani_ks
694 | 569342750,avanibean
695 | 569342750,avanichagorn
696 | 569342750,avarateunits
697 | 569342750,avari_i
698 | 569342750,avarosemusic
699 | 569342750,avatar_paul
700 | 569342750,avatxt
701 | 569342750,avazzx
702 | 569342750,avdav102
703 | 569342750,avdreiiq
704 | 569342750,avdreys
705 | 569342750,avee07
706 | 569342750,avellain
707 | 569342750,avelvetaa
708 | 569342750,aven_the_worm
709 | 569342750,averageclam
710 | 569342750,averi__m
711 | 569342750,avery10250
712 | 569342750,avery__a
713 | 569342750,avery_marie7
714 | 569342750,avery_rose1242
715 | 569342750,averyaries
716 | 569342750,averyarson
717 | 569342750,averyee17
718 | 569342750,averyjf
719 | 569342750,averymac13
720 | 569342750,averymcfeaters
721 | 569342750,averynotfound13
722 | 569342750,averyw29
723 | 569342750,averyxxphrog
724 | 569342750,avi_clouds
725 | 569342750,aviankid
726 | 569342750,aviavo
727 | 569342750,avidnovatic
728 | 569342750,avie_77
729 | 569342750,aviethesimp
730 | 569342750,aviizolmike
731 | 569342750,aviizu
732 | 569342750,aviorvague
733 | 569342750,aviqvs
734 | 569342750,avisisutopia
735 | 569342750,avo_cadoes
736 | 569342750,avocadogerms
737 | 569342750,avocadokikker
738 | 569342750,avocadomia
739 | 569342750,avocadorable_notfound
740 | 569342750,avocaron
741 | 569342750,avontaar
742 | 569342750,avranity
743 | 569342750,avryisme
744 | 569342750,avvmy
745 | 569342750,avylei
746 | 569342750,avysia_
747 | 569342750,awabzuhair16
748 | 569342750,aweitsang
749 | 569342750,awesomdodger1
750 | 569342750,awesomegirl24_7
751 | 569342750,awesomeisme12
752 | 569342750,awfulfrog
753 | 569342750,awfullyallie
754 | 569342750,awhhxlissaa
755 | 569342750,awhlimbo
756 | 569342750,awhtily
757 | 569342750,awilson_35
758 | 569342750,awimal
759 | 569342750,awizoyt
760 | 569342750,awktwo
761 | 569342750,awkward_cocoa
762 | 569342750,awkward_one_out
763 | 569342750,awkwardcivet
764 | 569342750,awkwardly1nhaze
765 | 569342750,awmiwastaken
766 | 569342750,awood000
767 | 569342750,awoofjk
768 | 569342750,awoogapi
769 | 569342750,awsomepuglife4dayz
770 | 569342750,awwleks
771 | 569342750,awxbear
772 | 569342750,axar7084
773 | 569342750,axdreymolina
774 | 569342750,axdreytheweirdo
775 | 569342750,axe_pal
776 | 569342750,axekai
777 | 569342750,axelafv
778 | 569342750,axofrostxaxolotl2021
779 | 569342750,axolotl_kisses
780 | 569342750,axolotlbread
781 | 569342750,ay__tkanice
782 | 569342750,ay_yolayla
783 | 569342750,aya_kuzuma
784 | 569342750,ayagil
785 | 569342750,ayahwastaken
786 | 569342750,ayaidkk
787 | 569342750,ayam_ee
788 | 569342750,ayaooisayshi
789 | 569342750,ayawaddles
790 | 569342750,ayawaren
791 | 569342750,ayben02
792 | 569342750,aydindril788
793 | 569342750,aye_ember
794 | 569342750,aye_itsash
795 | 569342750,ayebro_hey
796 | 569342750,ayeeenika
797 | 569342750,ayeitzlindsey
798 | 569342750,ayesabea
799 | 569342750,ayesha_yana
800 | 569342750,ayeshaaq_
801 | 569342750,ayetypical
802 | 569342750,ayeupitsrosie
803 | 569342750,ayl1nnn
804 | 569342750,aylahuffman_
805 | 569342750,aylenfalls
806 | 569342750,aylin_gm01
807 | 569342750,aylin_mk
808 | 569342750,aylinee21
809 | 569342750,aylssya
810 | 569342750,aylsyy7
811 | 569342750,aymey__
812 | 569342750,aynahush
813 | 569342750,ayo__alyssaa
814 | 569342750,ayo_amelieee
815 | 569342750,ayoitsalexis
816 | 569342750,ayoitsjeli
817 | 569342750,ayoitsmebella
818 | 569342750,ayojaxs
819 | 569342750,ayooomaya
820 | 569342750,ayosylvia
821 | 569342750,ayramichell
822 | 569342750,ayrixalt
823 | 569342750,ayscl
824 | 569342750,ayu129
825 | 569342750,ayumicourse
826 | 569342750,ayumitoshiyuki
827 | 569342750,ayup_12346
828 | 569342750,ayupkris
829 | 569342750,ayushi8090
830 | 569342750,ayushimo
831 | 569342750,ayvahgonzalez
832 | 569342750,ayxumi
833 | 569342750,az_360
834 | 569342750,azalea_murillo
835 | 569342750,azallium
836 | 569342750,azellev
837 | 569342750,azkareta
838 | 569342750,azslya
839 | 569342750,azteczay
840 | 569342750,aztilbee
841 | 569342750,aztrin
842 | 569342750,azu0426
843 | 569342750,azueli
844 | 569342750,azufrisky
845 | 569342750,azukaaaaaaaaaa
846 | 569342750,azul_bored
847 | 569342750,azura_is_a_muggle
848 | 569342750,azureangel64
849 | 569342750,azurediamond51
850 | 569342750,azuure_c
851 | 569342750,azxulg
852 | 569342750,azzyhhhh
853 | 569342750,b0dilx
854 | 569342750,b0nnief00t
855 | 569342750,b1ue_ghost
856 | 569342750,b1ynkie
857 | 569342750,b33s_kn33s
858 | 569342750,b34n5_
859 | 569342750,b3rrybliss
860 | 569342750,b3shersxx
861 | 569342750,b3zbarwna
862 | 569342750,b4art1st
863 | 569342750,b4i1ey_
864 | 569342750,b4ksu_
865 | 569342750,b4mbuz
866 | 569342750,b4rkbark
867 | 569342750,b9byfang
868 | 569342750,b_ec015
869 | 569342750,b_ellaa_
870 | 569342750,b_rose_2006
871 | 569342750,baabiiiiiii
872 | 569342750,baarinku
873 | 569342750,babashab
874 | 569342750,babasillyy
875 | 569342750,babayqaukes09
876 | 569342750,babbyariana
877 | 569342750,babenotfound12
878 |
--------------------------------------------------------------------------------