├── .prettierrc
├── .gitignore
├── src
├── index.js
├── main.css
└── Main.elm
├── .dockerignore
├── backend
├── config.js
├── db.js
├── bot.js
└── index.js
├── fly.toml
├── public
└── index.html
├── elm.json
├── LICENSE
├── package.json
├── README.md
└── dump
└── results_ken_wheeler.json
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "arrowParens": "avoid"
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | build/
5 | npm-debug.log*
6 | .env*
7 | *.swp
8 | .vscode/
9 | site/
10 |
11 | package-lock\.json
12 |
13 | # elm-package generated files
14 | elm-stuff
15 |
16 | # elm-repl generated files
17 | repl-temp-*
18 |
19 | dump\.rdb
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './main.css';
2 | import { Elm } from './Main.elm';
3 |
4 | Elm.Main.init({
5 | node: document.getElementById('root'),
6 | flags: {
7 | apiBaseUrl:
8 | process.env.ELM_APP_API_URL ||
9 | 'https://ken-wheeler-aka.herokuapp.com/api/aka/ken_wheeler'
10 | }
11 | });
12 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # flyctl launch added from .gitignore
2 | **/.DS_Store
3 | **/node_modules
4 | **/dist
5 | **/build
6 | **/npm-debug.log*
7 | **/.env*
8 | **/*.swp
9 | **/.vscode
10 | **/site
11 |
12 | **/package-lock\.json
13 |
14 | # elm-package generated files
15 | **/elm-stuff
16 |
17 | # elm-repl generated files
18 | **/repl-temp-*
19 |
20 | **/dump\.rdb
21 | fly.toml
22 |
--------------------------------------------------------------------------------
/backend/config.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 |
3 | module.exports = {
4 | twitterKeys: {
5 | consumer_key: process.env.TWITTER_CONSUMER_KEY,
6 | consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
7 | access_token: process.env.TWITTER_ACCESS_TOKEN,
8 | access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET
9 | },
10 | twitterConfig: {
11 | check: process.env.TWITTER_CHECK_RATE * 1000 * 60,
12 | enabled: process.env.TWEETING_ENABLED === 'true' || false
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/fly.toml:
--------------------------------------------------------------------------------
1 | # fly.toml app configuration file generated for ken-wheeler-aka on 2023-06-20T01:08:58+03:00
2 | #
3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4 | #
5 |
6 | app = "ken-wheeler-aka"
7 | primary_region = "arn"
8 |
9 | [build]
10 | builder = "heroku/buildpacks:20"
11 |
12 |
13 | [http_service]
14 | internal_port = 8080
15 | force_https = true
16 | auto_stop_machines = true
17 | auto_start_machines = true
18 | min_machines_running = 1
19 |
20 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 | Ken Wheeler's display names
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "src"
5 | ],
6 | "elm-version": "0.19.1",
7 | "dependencies": {
8 | "direct": {
9 | "elm/browser": "1.0.1",
10 | "elm/core": "1.0.0",
11 | "elm/html": "1.0.0",
12 | "elm/http": "1.0.0",
13 | "elm/json": "1.0.0",
14 | "elm-explorations/markdown": "1.0.0"
15 | },
16 | "indirect": {
17 | "elm/time": "1.0.0",
18 | "elm/url": "1.0.0",
19 | "elm/virtual-dom": "1.0.2"
20 | }
21 | },
22 | "test-dependencies": {
23 | "direct": {},
24 | "indirect": {}
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Olavi Haapala
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ken-wheeler-aka",
3 | "version": "1.0.0",
4 | "description": "What is Ken Wheeler's display name on Twitter?",
5 | "main": "backend/index.js",
6 | "scripts": {
7 | "start": "NODE_OPTIONS='--openssl-legacy-provider' node ./backend/index.js",
8 | "dev": "NODE_OPTIONS='--openssl-legacy-provider' elm-app start",
9 | "build": "echo no-op",
10 | "build:frontend": "NODE_OPTIONS='--openssl-legacy-provider' NODE_ENV=production elm-app build",
11 | "test": "test"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/olpeh/ken-wheeler-aka.git"
16 | },
17 | "author": "Olavi Haapala",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/olpeh/ken-wheeler-aka/issues"
21 | },
22 | "homepage": "https://github.com/olpeh/ken-wheeler-aka#readme",
23 | "dependencies": {
24 | "@koa/cors": "^2.2.1",
25 | "dotenv": "^6.0.0",
26 | "koa": "^2.5.1",
27 | "koa-bodyparser": "^4.2.1",
28 | "koa-cache-control": "^2.0.0",
29 | "koa-request": "^1.0.0",
30 | "koa-route": "^3.2.0",
31 | "mongodb": "5.6.0",
32 | "redis": "^2.8.0",
33 | "request": "^2.87.0",
34 | "twit": "^2.2.10"
35 | },
36 | "devDependencies": {
37 | "create-elm-app": "4.0.0",
38 | "elm": "0.19.1",
39 | "elm-format": "0.8.2",
40 | "prettier": "1.18.2"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ken-wheeler-aka
2 |
3 | What are Ken Wheeler's display names on Twitter?
4 |
5 | ## Excuse me, what?
6 |
7 | Just a silly side project for collecting [Ken Wheelers](https://twitter.com/ken_wheeler/) display names on Twitter and displaying them on a simple web site.
8 |
9 | ## But why?
10 |
11 | Just for fun and for learning:
12 |
13 | - First time using the Twitter API for reading information
14 | - My first ever "real" application written in [Elm](http://elm-lang.org/)
15 | - Testing how deploying small prototypes using [Beaker Browser](https://beakerbrowser.com/) works
16 |
17 | ## Check it out!
18 |
19 | https://ken-wheeler-aka.vercel.app/
20 |
21 | ## How does it work?
22 |
23 | There is a small backend application running in Heroku, which polls the Twitter API for ken_wheelers name.
24 | The names are then stored into a database and the api provides them to the UI.
25 | The UI is built using Elm, and it is based on [create-elm-app](https://github.com/halfzebra/create-elm-app)
26 |
27 | ## Development
28 |
29 | Install dependencies: `yarn`
30 |
31 | Start the backend: `npm start`
32 |
33 | Deploy the backend: `fly deploy` (Fly.io)
34 |
35 | Start the frontend: `npm run dev`
36 |
37 | Build the frontend: `npm run build`
38 |
39 | Deploy the frontend: `git push` (Vercel)
40 |
41 | ## Acknowledgements
42 |
43 | - The elm app is heavily inspired by [elm-quicks](https://github.com/ohanhi/elm-quicks/) by [ohanhi](https://github.com/ohanhi/)
44 | - This project is sponsored by [Futurice's](https://futurice.com/) [Open Source Sponsorship program](http://spiceprogram.org/oss-sponsorship)
45 |
--------------------------------------------------------------------------------
/backend/db.js:
--------------------------------------------------------------------------------
1 | const { MongoClient } = require('mongodb');
2 | const mongoUrl = process.env.MONGODB_URI;
3 |
4 | const bot = require('./bot');
5 |
6 | let db;
7 |
8 | async function setup() {
9 | const client = new MongoClient(mongoUrl);
10 | const dbName = 'ken-wheeler-aka';
11 | await client.connect();
12 | db = client.db(dbName);
13 | console.log('Initializing db...');
14 | }
15 |
16 | async function getResultsForScreenName(screenName) {
17 | const collection = db.collection(`results_${screenName}`);
18 | return collection.find({}, { name: 1, _id: 0 }).toArray();
19 | }
20 |
21 | async function getPreviousResultForScreenName(collection) {
22 | return collection.findOne({}, { name: 1, _id: 0, sort: { _id: -1 } });
23 | }
24 |
25 | async function insertAndTweetIfChanged(screenName, result, tweetNowFn) {
26 | const collection = db.collection(`results_${screenName}`);
27 | await getPreviousResultForScreenName(collection).then(previousResult => {
28 | console.log('Found from db:', { previousResult, result });
29 |
30 | if (!previousResult || (previousResult && previousResult.name !== result)) {
31 | // Always cast to string in order to allow "undefined" as a string etc.
32 | const data = { name: `${result}` };
33 | collection.insertOne(data);
34 |
35 | if (screenName === 'ken_wheeler') {
36 | const tweetText = `.@ken_wheeler is now known as "${result}". See https://ken-wheeler-aka.vercel.app/`;
37 | tweetNowFn(tweetText);
38 | }
39 | }
40 | });
41 | }
42 |
43 | module.exports = {
44 | setup,
45 | getResultsForScreenName,
46 | insertAndTweetIfChanged
47 | };
48 |
--------------------------------------------------------------------------------
/src/main.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | width: 100%;
4 | height: 98%;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | body {
10 | font-family: 'Source Sans Pro', 'Trebuchet MS', 'Lucida Grande',
11 | 'Bitstream Vera Sans', 'Helvetica Neue', sans-serif;
12 | margin: 0;
13 | text-align: center;
14 | color: #293c4b;
15 | }
16 |
17 | .main {
18 | padding: 20px;
19 | padding-bottom: 50px;
20 | display: flex;
21 | flex-direction: column;
22 | justify-content: space-around;
23 | margin-bottom: 50px;
24 | }
25 |
26 | .loading {
27 | margin-top: 40px;
28 | }
29 |
30 | h1 {
31 | font-size: 30px;
32 | color: #428bc1;
33 | }
34 |
35 | .twitter-link {
36 | color: #428bc1;
37 | }
38 |
39 | .since {
40 | font-style: italic;
41 | color: #8a8a8a;
42 | font-size: 16px;
43 | }
44 |
45 | .display-names {
46 | text-align: left;
47 | margin: 0 auto;
48 | margin-top: 10px;
49 | }
50 |
51 | li {
52 | margin-top: 10px;
53 | }
54 |
55 | li.error {
56 | color: #cf0000;
57 | }
58 |
59 | .display-names li:first-of-type::after {
60 | content: ' (current)';
61 | font-weight: bold;
62 | }
63 |
64 | footer {
65 | font-size: 16px;
66 | position: fixed;
67 | bottom: 0;
68 | padding: 6px;
69 | padding-top: 0;
70 | text-align: center;
71 | width: 100%;
72 | background: #428bc1;
73 | color: #ffffff;
74 | }
75 |
76 | footer a {
77 | color: #ffffff !important;
78 | }
79 |
80 | footer ul {
81 | list-style: none;
82 | padding: 0;
83 | margin: 0 auto;
84 | text-align: center;
85 | }
86 |
87 | footer ul li {
88 | display: inline-block;
89 | }
90 |
91 | footer li:not(:last-child)::after {
92 | content: ' | ';
93 | }
94 |
--------------------------------------------------------------------------------
/backend/bot.js:
--------------------------------------------------------------------------------
1 | const Twit = require('twit');
2 | const config = require('./config');
3 | const db = require('./db');
4 |
5 | const { SCREEN_NAMES } = process.env;
6 |
7 | let bot;
8 |
9 | function setup() {
10 | bot = new Twit(config.twitterKeys);
11 | console.log('Bot starting...');
12 | runCheck();
13 | setInterval(runCheck, config.twitterConfig.check);
14 | }
15 |
16 | const runCheck = async () => {
17 | console.log('Checking for names via the Twitter API', new Date());
18 | const screenNames = SCREEN_NAMES.split(' ');
19 |
20 | for (const screenName of screenNames) {
21 | console.log(screenName);
22 |
23 | bot.get(
24 | 'users/show',
25 | { screen_name: screenName, include_entities: false },
26 | async (err, data, response) => {
27 | if (err) {
28 | console.error('ERROR,', err);
29 | } else {
30 | console.log('SUCCESS: received: ', data.name);
31 | const currentName = data.name;
32 | await db.insertAndTweetIfChanged(screenName, currentName, tweetNow);
33 | }
34 | }
35 | );
36 | }
37 | };
38 |
39 | async function tweetNow(text) {
40 | const tweet = {
41 | status: text
42 | };
43 |
44 | if (config.twitterConfig.enabled) {
45 | console.log('Going to try to tweet: ', text);
46 | bot.post('statuses/update', tweet, (err, data, response) => {
47 | if (err) {
48 | console.error('ERROR in tweeting!', err);
49 | } else {
50 | console.log('SUCCESS! tweeted: ', text);
51 | }
52 | });
53 | } else {
54 | console.log('Tweeting was disabled, but would have tweeted:', { text });
55 | }
56 | }
57 |
58 | module.exports = {
59 | setup: setup,
60 | tweetNow: tweetNow
61 | };
62 |
--------------------------------------------------------------------------------
/backend/index.js:
--------------------------------------------------------------------------------
1 | const cors = require('@koa/cors');
2 | const koa = require('koa');
3 | const bodyParser = require('koa-bodyparser');
4 | const route = require('koa-route');
5 | const app = new koa();
6 | const redis = require('redis');
7 | const cacheControl = require('koa-cache-control');
8 |
9 | const bot = require('./bot');
10 | const db = require('./db');
11 |
12 | require('dotenv').config();
13 |
14 | app.use(cors());
15 | app.use(bodyParser());
16 |
17 | const port = process.env.PORT || 8080;
18 | const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
19 | const cacheTtlSeconds = process.env.CACHE_TTL_SECONDS || 60;
20 | app.use(
21 | cacheControl({
22 | maxAge: cacheTtlSeconds
23 | })
24 | );
25 |
26 | console.log({ port, redisUrl, cacheTtlSeconds });
27 |
28 | // create a new redis client and connect to our local redis instance
29 | const redisClient = redis.createClient({ url: redisUrl });
30 |
31 | // if an error occurs, print it to the console
32 | redisClient.on('error', err => console.error('Error ' + err));
33 |
34 | async function setUpApp() {
35 | await db.setup();
36 | bot.setup();
37 |
38 | app.use(
39 | route.get('/api/aka/:screenName', async (ctx, screenName) => {
40 | try {
41 | await new Promise(resolve => {
42 | redisClient.get(screenName, async (error, result) => {
43 | if (result) {
44 | ctx.response.statusCode = 200;
45 | ctx.response.body = {
46 | success: true,
47 | data: JSON.parse(result)
48 | };
49 | resolve();
50 | } else {
51 | const data = await db.getResultsForScreenName(screenName);
52 | // with an expiry of 1 minute (60s)
53 | await redisClient.setex(
54 | screenName,
55 | cacheTtlSeconds,
56 | JSON.stringify(data)
57 | );
58 | ctx.response.statusCode = 200;
59 | ctx.response.body = { success: true, data };
60 | resolve();
61 | }
62 | });
63 | });
64 | } catch (e) {
65 | console.error(e);
66 | ctx.response.statusCode = 500;
67 | ctx.response.body = { success: false, error: e };
68 | }
69 | })
70 | );
71 |
72 | console.log('app listening on port ' + port);
73 | app.listen(port);
74 | }
75 |
76 | setUpApp();
77 |
--------------------------------------------------------------------------------
/src/Main.elm:
--------------------------------------------------------------------------------
1 | module Main exposing (ApiResponse, DisplayName, Model, Msg(..), decoder, displayNameDecoder, displayNameView, displayNamesView, errorView, footerView, handler, init, main, subscriptions, update, view)
2 |
3 | import Browser
4 | import Html exposing (Html, a, br, div, footer, h1, img, li, ol, span, text, ul)
5 | import Html.Attributes exposing (class, reversed)
6 | import Http
7 | import Json.Decode exposing (bool, list, string)
8 | import Markdown
9 | import Html.Attributes exposing (href)
10 |
11 |
12 | type Msg
13 | = GotDisplayNames (List DisplayName)
14 | | GotError Http.Error
15 |
16 |
17 | type alias Model =
18 | { displayNames : List DisplayName
19 | , error : Maybe Http.Error
20 | , loading : Bool
21 | }
22 |
23 |
24 | type alias ApiResponse =
25 | { success : Bool
26 | , data : List DisplayName
27 | }
28 |
29 |
30 | type alias DisplayName =
31 | { name : Maybe String }
32 |
33 |
34 | update : Msg -> Model -> ( Model, Cmd Msg )
35 | update msg model =
36 | case msg of
37 | GotDisplayNames displayNames ->
38 | ( { model | displayNames = displayNames, loading = False }, Cmd.none )
39 |
40 | GotError error ->
41 | ( { model | error = Just error, loading = False }, Cmd.none )
42 |
43 |
44 | view : Model -> Browser.Document Msg
45 | view model =
46 | { title = "Ken Wheeler's display names"
47 | , body =
48 | [ div []
49 | [ div [ class "main" ]
50 | [ h1 [] [ a [class "twitter-link", href "https://twitter.com/ken_wheeler"] [text "@ken_wheeler – also known as" ]]
51 | , div [ class "since" ] [ text "Since 2018-06-27 (note: missing data between 11/2020 and 06/2023)" ]
52 | , if model.loading == True then
53 | div [ class "loading" ] [ text "Loading..." ]
54 |
55 | else
56 | displayNamesView model.displayNames
57 | , errorView model.error
58 | ]
59 | , footer [] [ footerView ]
60 | ]
61 | ]
62 | }
63 |
64 |
65 | displayNamesView : List DisplayName -> Html msg
66 | displayNamesView displayNames =
67 | displayNames
68 | |> List.reverse
69 | |> List.map displayNameView
70 | |> ol [ class "display-names", reversed True ]
71 |
72 |
73 | displayNameView : DisplayName -> Html msg
74 | displayNameView displayName =
75 | case displayName.name of
76 | Just name ->
77 | li [] [ text name ]
78 |
79 | Nothing ->
80 | li [ class "error" ] [ text "Error: Invalid data – possibly \"undefined\" or \"null\"" ]
81 |
82 |
83 | errorView : Maybe Http.Error -> Html msg
84 | errorView err =
85 | case err of
86 | Just e ->
87 | case e of
88 | Http.NetworkError ->
89 | text "No connection, try again later."
90 |
91 | Http.Timeout ->
92 | text "Network timed out."
93 |
94 | Http.BadUrl _ ->
95 | text "It's not you, it's me. I have the server address wrong."
96 |
97 | Http.BadStatus _ ->
98 | text "The server didn't like the request (bad status)."
99 |
100 | Http.BadPayload _ _ ->
101 | text "Ouch, the server responded with strange contents."
102 |
103 | Nothing ->
104 | text ""
105 |
106 |
107 | footerView : Html msg
108 | footerView =
109 | Markdown.toHtml [] """
110 | * Built by [0lpeh](https://twitter.com/0lpeh)
111 | * Repository available at [GitHub](https://github.com/olpeh/ken-wheeler-aka)
112 | * Follow [@ken_wheeler_aka](https://twitter.com/ken_wheeler_aka)
113 | """
114 |
115 |
116 | type alias Flags =
117 | { apiBaseUrl : String }
118 |
119 |
120 | init : Flags -> ( Model, Cmd Msg )
121 | init flags =
122 | ( { displayNames = []
123 | , error = Nothing
124 | , loading = True
125 | }
126 | , Http.send handler (Http.get flags.apiBaseUrl decoder)
127 | )
128 |
129 |
130 | decoder : Json.Decode.Decoder ApiResponse
131 | decoder =
132 | Json.Decode.map2 ApiResponse
133 | (Json.Decode.at [ "success" ] bool)
134 | (Json.Decode.at [ "data" ] (list displayNameDecoder))
135 |
136 |
137 | displayNameDecoder : Json.Decode.Decoder DisplayName
138 | displayNameDecoder =
139 | Json.Decode.map DisplayName
140 | (Json.Decode.maybe (Json.Decode.field "name" Json.Decode.string))
141 |
142 |
143 | handler : Result Http.Error ApiResponse -> Msg
144 | handler result =
145 | case result of
146 | Ok apiResponse ->
147 | GotDisplayNames apiResponse.data
148 |
149 | Err error ->
150 | GotError error
151 |
152 |
153 | subscriptions : Model -> Sub Msg
154 | subscriptions model =
155 | Sub.none
156 |
157 |
158 | main : Program Flags Model Msg
159 | main =
160 | Browser.document
161 | { view = view
162 | , init = init
163 | , update = update
164 | , subscriptions = subscriptions
165 | }
166 |
--------------------------------------------------------------------------------
/dump/results_ken_wheeler.json:
--------------------------------------------------------------------------------
1 | {"_id":{"$oid":"5b33aa5b96c66fa3df51b584"},"name":"🇺🇸 UH MERICA 🇺🇲"}
2 | {"_id":{"$oid":"5b37729d96c66fa3df8ecc92"},"name":"🇺🇸 AMERIKANYE 🇺🇲"}
3 | {"_id":{"$oid":"5b38273b96c66fa3df971c0e"},"name":"🇺🇸 KANYEBRAHAM LINCOLN 🇺🇲"}
4 | {"_id":{"$oid":"5b38c06596c66fa3df9cf217"},"name":"🇺🇸 🗽🦅 UH MERICA 🍺🏹 🇺🇲"}
5 | {"_id":{"$oid":"5b3a9bb196c66fa3dfb3750f"},"name":"🇺🇸 🗽🦅 THE NAME YOU GO BY 🍺🏹 🇺🇲"}
6 | {"_id":{"$oid":"5b3aa9c296c66fa3dfb40a93"},"name":"🇺🇸 🗽🦅 AMERIKEN WHEELER 🍺🏹 🇺🇲"}
7 | {"_id":{"$oid":"5b3b995896c66fa3dfc1179c"},"name":"🇺🇸 🗽🦅 KEN WHEELER 🍺🏹 🇺🇲"}
8 | {"_id":{"$oid":"5b3c027a96c66fa3dfc75d59"},"name":"🇺🇸 🗽🦅 AMERICAN FLAG GUY 🍺🏹 🇺🇲"}
9 | {"_id":{"$oid":"5b3e6ed896c66fa3dfec8c23"},"name":"🇺🇸 OMG SO EXTRA 🇺🇲"}
10 | {"_id":{"$oid":"5b3ebfb496c66fa3dff005e8"},"name":"Ken Wheeler"}
11 | {"_id":{"$oid":"5b41639596c66fa3df217c86"},"name":"Ken's On Fuckin Vacation"}
12 | {"_id":{"$oid":"5b479dfd66e51e0014206611"},"name":"Ken Wheeler"}
13 | {"_id":{"$oid":"5b51183794074f001423a000"},"name":"HOT TAKE KENNETH"}
14 | {"_id":{"$oid":"5b5181ade2ac30001403abb3"},"name":"🔥 HOT TAKE KENNETH 🔥"}
15 | {"_id":{"$oid":"5b52807c45bfbe001434f3a5"},"name":"COLD STONE STEVE AUSTIN"}
16 | {"_id":{"$oid":"5b53554056b2b200140c5933"},"name":"GIRTH BROOKS"}
17 | {"_id":{"$oid":"5b53980e122f560014f7605a"},"name":"Ken Wheeler"}
18 | {"_id":{"$oid":"5b664c4aab769f0014740a48"},"name":"lil :wq AKA bread flintstone"}
19 | {"_id":{"$oid":"5b67cf070cdfbc0014a8f323"},"name":"ReasonML Glowing Laser Cobra"}
20 | {"_id":{"$oid":"5b683f8e34409b00144c0a52"},"name":"Notorious WIP"}
21 | {"_id":{"$oid":"5b69d05000391e001440ac46"},"name":"yung fortnite juul yeezy papi"}
22 | {"_id":{"$oid":"5b6a40cb5563c50014abd3e3"},"name":"NACHO MAN RANDY SAVAGE"}
23 | {"_id":{"$oid":"5b7016e5102b1c001479575f"},"name":"MR WORLDWIDE(WEB)"}
24 | {"_id":{"$oid":"5b793b0da37d5d0014d4a952"},"name":"SCRIPT GAME CHUCK NORRIS"}
25 | {"_id":{"$oid":"5b7f20ee4785810014127936"},"name":"FATRICK BATEMAN"}
26 | {"_id":{"$oid":"5b82185060c0ab001441b508"},"name":"K E R N W H E E L E R"}
27 | {"_id":{"$oid":"5b8369d3bc58e30014cff891"},"name":"LIVE ACTION HOMER SIMPSON"}
28 | {"_id":{"$oid":"5b84717a5400a00014ca4914"},"name":"Kenneth John"}
29 | {"_id":{"$oid":"5b8a014d83375f00140144a6"},"name":"POWDERED TOAST MAN"}
30 | {"_id":{"$oid":"5b8b1cebea309500148a3d7d"},"name":"🇺🇸 JavaScript Kanye 🇺🇸"}
31 | {"_id":{"$oid":"5b8ca44ed87c4200149ffb7d"},"name":"RACOON CITY PD"}
32 | {"_id":{"$oid":"5b91ea529971130014155c2c"},"name":"Perf Reynolds"}
33 | {"_id":{"$oid":"5b958a764858cb0014e37cc4"},"name":"WEYLAND YUTANI OFFICIAL"}
34 | {"_id":{"$oid":"5b9881d72dbe4b001489d70d"},"name":"DA BEARS"}
35 | {"_id":{"$oid":"5b991df3606dd80014477b67"},"name":"JUSTIN VIMBERLAKE"}
36 | {"_id":{"$oid":"5b99d3582dc88a0014918d1f"},"name":"Jimmy Juulpods"}
37 | {"_id":{"$oid":"5b9ab6d507c6ea001488a34d"},"name":"Totally Emily Freeman"}
38 | {"_id":{"$oid":"5b9bba2e04db270014c3870e"},"name":"One scripty boi"}
39 | {"_id":{"$oid":"5b9c12df3b5d1e0013d8866e"},"name":"LIVE ACTION LAUNCHPAD MCQUACK"}
40 | {"_id":{"$oid":"5b9c71e2a6878e00135ebea4"},"name":"Sean 'Protobuf Daddy' Combs"}
41 | {"_id":{"$oid":"5ba06ad92681c1001369f39e"},"name":"miller high life papi"}
42 | {"_id":{"$oid":"5ba368159f451d0013cc54ba"},"name":"wheezy f(baby)"}
43 | {"_id":{"$oid":"5ba6cec88b4b0800133fde2c"},"name":"GRANDMASTER FLASH PLAYER CS3"}
44 | {"_id":{"$oid":"5ba6d1208b4b0800133fde2d"},"name":"🇺🇸 DWAYNE ELIZONDO MOUNTAIN DEW CAMACHO 🇺🇸"}
45 | {"_id":{"$oid":"5baa219299dcfc00136c3466"},"name":"TIDE POD TOMMY"}
46 | {"_id":{"$oid":"5bac380b043f200013047cf8"},"name":"DAT BOI feat. DEM BOYZ"}
47 | {"_id":{"$oid":"5badb1447d55c200131680ec"},"name":"cool juul uncle"}
48 | {"_id":{"$oid":"5bb03cd8c8238700136c4285"},"name":"Ke"}
49 | {"_id":{"$oid":"5bb28b761e739b0013cc301f"},"name":"ke"}
50 | {"_id":{"$oid":"5bbbc5e0fe2f4c001330bbaa"},"name":"JAVASCRIPT KANYE"}
51 | {"_id":{"$oid":"5bc0cfd32ad67d00137c7326"},"name":"LOW INFORMATION CODER"}
52 | {"_id":{"$oid":"5bc5064f52ae5c00133dc377"},"name":"JERSEY DEV(il) aka COUNT HACKULA"}
53 | {"_id":{"$oid":"5bc508a752ae5c00133dc378"},"name":"DANKENSTEIN"}
54 | {"_id":{"$oid":"5bc67c83e137e90013ad4475"},"name":"JSON VORHEES"}
55 | {"_id":{"$oid":"5bc9bd578951a30013cd2d82"},"name":"HANNIBAL VECTOR"}
56 | {"_id":{"$oid":"5bca9ad7af8b1e0013375455"},"name":"COUNT SLACKULA"}
57 | {"_id":{"$oid":"5bcb522dc5f6810013703012"},"name":"ECTOCOOLER FLAVORED ZUULPODS"}
58 | {"_id":{"$oid":"5bcce977b3879b0013ef34bf"},"name":"JAVASCRIPT PETER VENKMAN"}
59 | {"_id":{"$oid":"5bcdbc685068720013228988"},"name":"LOCALHOSTBUSTERS"}
60 | {"_id":{"$oid":"5bce8f563833f6001378dd33"},"name":"ZUUL RUNNINGS"}
61 | {"_id":{"$oid":"5bcfba9a785dbf0013ac7f64"},"name":"SELECT * WHERE WOLF"}
62 | {"_id":{"$oid":"5bd380f4e0b2510013ea3f19"},"name":"{...}y Krueger"}
63 | {"_id":{"$oid":"5bd4d279b435cd001377d2e4"},"name":"A NIGHTMARE ON REASONML STREET"}
64 | {"_id":{"$oid":"5bdca56b873f5e00130418f5"},"name":"useKenWheeler"}
65 | {"_id":{"$oid":"5beb9e648f06d90014744d1e"},"name":"turducKEN 🦃🦆🇺🇸"}
66 | {"_id":{"$oid":"5bf54e1a0c23cb00142bd28d"},"name":"drunken thanksgiving uncle"}
67 | {"_id":{"$oid":"5bf77bea422d83001465c68b"},"name":"FANTA CLAUS"}
68 | {"_id":{"$oid":"5bf7d9aa422d83001465c68c"},"name":"Christmas Kenneth"}
69 | {"_id":{"$oid":"5bf7dad6422d83001465c68d"},"name":"yung beardless santa"}
70 | {"_id":{"$oid":"5c00d860bb7eca001406ed3f"},"name":"herr wheelenschnitzenheimer @ 🇩🇪"}
71 | {"_id":{"$oid":"5c04055b822e5e00147c0a7c"},"name":"naughty list kenneth"}
72 | {"_id":{"$oid":"5c05b74675f2f8001461a0e8"},"name":"mrs claus’ sidepiece"}
73 | {"_id":{"$oid":"5c099c886fb52a001483727f"},"name":"gnarcellus wallace"}
74 | {"_id":{"$oid":"5c14f347d4b78a001426d2b2"},"name":"colm o’driscoll"}
75 | {"_id":{"$oid":"5c1c118e80cb2d00146be566"},"name":"JAVASCRIPT HERC FROM THE WIRE"}
76 | {"_id":{"$oid":"5c1d630ca90e7800142c1b67"},"name":"WINSTON WOLF"}
77 | {"_id":{"$oid":"5c1e7280a114e40014fd7b00"},"name":"COTTON HEADED NINNYMUGGINS"}
78 | {"_id":{"$oid":"5c2373c9161c48001421027e"},"name":"2019 EDITION KENNETH"}
79 | {"_id":{"$oid":"5c26f1ed0ed6340014b74005"},"name":"GEORGE LIQUOR"}
80 | {"_id":{"$oid":"5c28436ca35ad2001490f8f9"},"name":"UNCLE DRUNKFACE"}
81 | {"_id":{"$oid":"5c2edaee81ad6f0014a14cc4"},"name":"BRAND NEW KENNETH"}
82 | {"_id":{"$oid":"5c3eace788d70d0014533514"},"name":"🇺🇸 Benjamin Danklin 🇺🇸"}
83 | {"_id":{"$oid":"5c3ffe6d4315b30014fb5625"},"name":"🇺🇸 Toxically Masculine Javascript 🇺🇸"}
84 | {"_id":{"$oid":"5c424d0bc57d830014ef74ae"},"name":"🇺🇸 BOBBY JAVASCRIPT 🇺🇸"}
85 | {"_id":{"$oid":"5c4695f47156df0014511044"},"name":"🇺🇸 YUNG WEBWORKER 🇺🇸"}
86 | {"_id":{"$oid":"5c4e458b75089600148f4917"},"name":"JS-JS-JS"}
87 | {"_id":{"$oid":"5c5badfe6b6d350014369966"},"name":"Kenny"}
88 | {"_id":{"$oid":"5c65382351a00c0014aa8e6a"},"name":"DR NIGEL CHANNING"}
89 | {"_id":{"$oid":"5c69c7ee26204c00144b7091"},"name":"THE WATERMELON WOLF"}
90 | {"_id":{"$oid":"5c6aa2799932e60014f9cd79"},"name":"DIGITAL GANGSTER"}
91 | {"_id":{"$oid":"5c6b47b38becc20015088107"},"name":"JAVASCRIPT WILLIE BEAMIN ASK YOUR BITCH"}
92 | {"_id":{"$oid":"5c6c14c3569fbb0015ac2e62"},"name":"CHITOWN KENNETH"}
93 | {"_id":{"$oid":"5c6d81a5a0b3860015c779e2"},"name":"EDGAR ALLEN DOUGH"}
94 | {"_id":{"$oid":"5c6f7476d196cc0015664783"},"name":"WHEELER BIRD RESEARCH"}
95 | {"_id":{"$oid":"5c72177ed1ea8000151d5f93"},"name":"🇺🇸 DRIP GAME CAT STEVENS 🇺🇸"}
96 | {"_id":{"$oid":"5c76ae97d591080015e7e381"},"name":"🇺🇸 PIPE GAME JOE NAMATH 🇺🇸"}
97 | {"_id":{"$oid":"5c787fc4594adf00156258a0"},"name":"GOON SHIT"}
98 | {"_id":{"$oid":"5c7c224dbb6b140015c81a50"},"name":"THICC BUNDLES"}
99 | {"_id":{"$oid":"5c7d6e93e669ae001507b036"},"name":"ITS RAINING KEN"}
100 | {"_id":{"$oid":"5c80439cf583740015ef21bd"},"name":"Ken Wheeler"}
101 | {"_id":{"$oid":"5c917055178e1f00152f40b3"},"name":"GRANDMASTER FLASH CS3"}
102 | {"_id":{"$oid":"5c9441b79c99da0015b285f1"},"name":"Dr Reginald Cox"}
103 | {"_id":{"$oid":"5c96b6779243a90015b40947"},"name":"UNPOPULAR OPINIONS"}
104 | {"_id":{"$oid":"5c9b7938cdef9d001577dc72"},"name":"SKRTT REYNOLDS"}
105 | {"_id":{"$oid":"5c9f9ffc73901200156f4440"},"name":"CRON MATTINGLY"}
106 | {"_id":{"$oid":"5ca579c0d8a13a0015923cd4"},"name":"SCRIPT GAME BRUCE WILLIS"}
107 | {"_id":{"$oid":"5cb3f46891c8150015b89354"},"name":"OL SPRINGTIME KENNETH"}
108 | {"_id":{"$oid":"5cb7b1e5a877690015b2090b"},"name":"KENNETH IN MISSISSIPPI"}
109 | {"_id":{"$oid":"5cb9e513e52ad80015b954a8"},"name":"KENNETH ON HIS WAY HOME"}
110 | {"_id":{"$oid":"5cbb891c60e8510015c86b4a"},"name":"SCRUMBAG STEVE"}
111 | {"_id":{"$oid":"5cc3c6784f899b00154a47a9"},"name":"RICH HOMIE QUANT"}
112 | {"_id":{"$oid":"5cc615165ad1c400157b5d27"},"name":"JUST KEN THINGS"}
113 | {"_id":{"$oid":"5cc623265ad1c400157b5d28"},"name":"PURPLE CITY NERD GANG"}
114 | {"_id":{"$oid":"5cd53ad2645f6900152e9ccd"},"name":"Docker, Texas Ranger"}
115 | {"_id":{"$oid":"5cde246e01c5c80015c1c4e3"},"name":"FATRICK BATEMAN"}
116 | {"_id":{"$oid":"5cdf3c5fca7aa7001544d287"},"name":"TURBOCHAD.TSX"}
117 | {"_id":{"$oid":"5ce144de6996f40015b62dae"},"name":"JONATHAN TAYLOR COMMAS"}
118 | {"_id":{"$oid":"5ce287228af63d0015e7cc43"},"name":"Ted NuGet"}
119 | {"_id":{"$oid":"5ce52c7be160a400155a222e"},"name":"JETHRO NULL"}
120 | {"_id":{"$oid":"5cebce838451f10015fa5773"},"name":"GRILL BEAST GANGSTER"}
121 | {"_id":{"$oid":"5cf09981fd71a4001568eada"},"name":"WHITE DEBO"}
122 | {"_id":{"$oid":"5cf1eaf941d1e0001523ab52"},"name":"XL TYRION LANNISTER"}
123 | {"_id":{"$oid":"5cf6e638474c1a0015848147"},"name":"THICC BUNDLES"}
124 | {"_id":{"$oid":"5d01bd0023bfef0015fee49a"},"name":"PATAGUCCI MANE"}
125 | {"_id":{"$oid":"5d1d079efa13a60015e7df14"},"name":"AMERIKEN WHEELER"}
126 | {"_id":{"$oid":"5d2a470d54175c0015759364"},"name":"kenX developer"}
127 | {"_id":{"$oid":"5d30d185071ff000151ce3c5"},"name":"ALMIGHTY BRO"}
128 | {"_id":{"$oid":"5d3677e81d36050015198a35"},"name":"UNCLE RICO BUT FOR REAL THO"}
129 | {"_id":{"$oid":"5d3a2f5276c1740015662df0"},"name":"🇺🇸 MR WHEELER 🇺🇸"}
130 | {"_id":{"$oid":"5d3e1ccd75c178001579b2f8"},"name":"🇺🇸 yung chief hopper 🇺🇸"}
131 | {"_id":{"$oid":"5d4ce26a5986c600151fa55d"},"name":"🇺🇸 pipe game emelio estevez 🇺🇸"}
132 | {"_id":{"$oid":"5d561ceab88e4100153da7df"},"name":"🇺🇸 ol saint thiccholas 🇺🇸"}
133 | {"_id":{"$oid":"5d59bd0b97fec700152963df"},"name":"🇺🇸 byte game dana white 🇺🇸"}
134 | {"_id":{"$oid":"5d5b62e8b280660015ca4428"},"name":"🇺🇸 yung al borland 🇺🇸"}
135 | {"_id":{"$oid":"5d5d2d209ce66a0015c6040a"},"name":"🇺🇸 lil SaaS x 🇺🇸"}
136 | {"_id":{"$oid":"5d601ac8975ee400153c3cab"},"name":"Ken Wheeler"}
137 | {"_id":{"$oid":"5d707aeac031690015eed22e"},"name":"ON A BREAK"}
138 | {"_id":{"$oid":"5d8835eae078e80015d5f147"},"name":"KENNETH"}
139 | {"_id":{"$oid":"5d9564e6e3931b001577549b"},"name":"LL GHOUL J"}
140 | {"_id":{"$oid":"5dacd62c3616c70015880f8b"},"name":"JSON VORHEES"}
141 | {"_id":{"$oid":"5db0c01d6523be00156dcf19"},"name":"ABSOLUTE UNIT TEST"}
142 | {"_id":{"$oid":"5dba8f6d61d0960015618db3"},"name":"HOLLA WEEN"}
143 | {"_id":{"$oid":"5dbd6d080a923f001561b1c9"},"name":"JIMMY FLAMINGO"}
144 | {"_id":{"$oid":"5dc63a894393c0001510a38a"},"name":"RICKY SMOOTH"}
145 | {"_id":{"$oid":"5dcb808893d27f00150cd118"},"name":"LAUNCHPAD MCQUACK"}
146 | {"_id":{"$oid":"5dcdea221a90ab001593a7cb"},"name":"CRON BON JOVI"}
147 | {"_id":{"$oid":"5dd33baf41a45f0015f31bbd"},"name":"THICC JAGGER"}
148 | {"_id":{"$oid":"5dd9aca9ca09e200154df4e2"},"name":"BREAD FLANDERS"}
149 | {"_id":{"$oid":"5de2e7233d52900015b27608"},"name":"Ol Saint Thicc"}
150 | {"_id":{"$oid":"5dee43ba6883870015f8ff10"},"name":"NAUGHTY LIST VETERAN"}
151 | {"_id":{"$oid":"5dfd34e27470c10015f4d600"},"name":"ELON HUSK"}
152 | {"_id":{"$oid":"5e018dfb050a0000150516ba"},"name":"LAMBDA CLAUS"}
153 | {"_id":{"$oid":"5e0d3212158046001545d6d2"},"name":"OL KENNY NOFUX"}
154 | {"_id":{"$oid":"5e1948fd0f4de600159c068c"},"name":"script game angela lansbury"}
155 | {"_id":{"$oid":"5e1cfd058ef4440015a72979"},"name":"pauly powershell"}
156 | {"_id":{"$oid":"5e23d4fc88934100151cb582"},"name":"ol dirty plastered"}
157 | {"_id":{"$oid":"5e2f5e192814a2001510bb2a"},"name":"HENNY KENNY"}
158 | {"_id":{"$oid":"5e39ad7f3bf3e200157365d1"},"name":"THE LAMBDELOREAN"}
159 | {"_id":{"$oid":"5e3dde99f27a9f0015e2a54a"},"name":"Javascript Happy Gilmore"}
160 | {"_id":{"$oid":"5e46290a45a552001501df97"},"name":"CRON STAMOS"}
161 | {"_id":{"$oid":"5e5af12cf75f6900152cdb37"},"name":"GIGACHAD DOT JPEG"}
162 | {"_id":{"$oid":"5e5ba7fe8c7bb3001512c369"},"name":"sophmore developer"}
163 | {"_id":{"$oid":"5e5c293996475400157f7b39"},"name":"PAULY PUFFBAR"}
164 | {"_id":{"$oid":"5e696018502c830015458e42"},"name":"LIL UZI PERF"}
165 | {"_id":{"$oid":"5e793222f86add0015cba0a0"},"name":"BROVID-19"}
166 | {"_id":{"$oid":"5e7fc970caa128001571b428"},"name":"JARDANI JAVASCRIPTOVICH"}
167 | {"_id":{"$oid":"5e88f5e17b14ca0015261f46"},"name":"PETER PANDEMIC"}
168 | {"_id":{"$oid":"5e8c5460b9ce860015a97347"},"name":"QUARANTEEN WOLF"}
169 | {"_id":{"$oid":"5e8fe1bf9bad4a0015d86f36"},"name":"john thicc"}
170 | {"_id":{"$oid":"5e924ee5d748f80015066b4c"},"name":null}
171 | {"_id":{"$oid":"5e92594959eaed0015b18d56"},"name":"john thicc"}
172 | {"_id":{"$oid":"5e97cac10adbf30015b4448c"},"name":"quaranteenage heartthrob"}
173 | {"_id":{"$oid":"5e9b6ae09f11a600151136d3"},"name":"Kentin Quarantino"}
174 | {"_id":{"$oid":"5eaab61c13d37700156ed420"},"name":"Lyin' Ken Wheeler"}
175 | {"_id":{"$oid":"5eaddfdf255f0c0015b93f44"},"name":"A$AP Schrader"}
176 | {"_id":{"$oid":"5ebb0edfb5bc640015521570"},"name":"yung gucci puter papi"}
177 | {"_id":{"$oid":"5ec83ddfe6464b00158f131a"},"name":"lil whiskey moufwash"}
178 | {"_id":{"$oid":"5eefd568f76cf70015383aac"},"name":"very sunburnt kenneth"}
179 | {"_id":{"$oid":"5f111cefaaa2cc001528afd1"},"name":"hot url summer"}
180 | {"_id":{"$oid":"5f384d56b9603f0015116cea"},"name":"wendy peffercorn fan club"}
181 | {"_id":{"$oid":"5f3ee4e22ec07200155a126a"},"name":"yung gucci tooth fairy"}
182 | {"_id":{"$oid":"5f447f36cee97b00156af288"},"name":"tim “the juul man” taylor"}
183 | {"_id":{"$oid":"5f592d4e4c954f00153bb48e"},"name":"patagucci papi"}
184 | {"_id":{"$oid":"5f7aa9132aaed70015d4bbf0"},"name":"halloween themed ken wheeler name"}
185 | {"_id":{"$oid":"5f8d1e14f9bcd100151fb8d1"},"name":"hotel cancelvania 3"}
186 | {"_id":{"$oid":"5f8d808106431800155355cc"},"name":"yung werewolf bite"}
187 | {"_id":{"$oid":"5f9a5b23b8e28f00154b4f42"},"name":"significantly less attractive drake"}
188 | {"_id":{"$oid":"5f9e99f8a0ddd80015865d4d"},"name":"dracula.count()"}
189 | {"_id":{"$oid":"5f9ff583d9b17a0015ed9bd0"},"name":"tech game rob corddry"}
190 | {"_id":{"$oid":"5faa2d25188baf0015884695"},"name":"holiday inn express total landscaping"}
191 |
--------------------------------------------------------------------------------