├── .gitignore ├── Client ├── Image Examples.txt ├── README.md ├── package.json ├── src │ ├── main │ │ ├── MainIcon.png │ │ └── main.ts │ └── renderer │ │ ├── ClientServer │ │ ├── ConnectionToServer.ts │ │ ├── LCUHelper.ts │ │ └── ServerRoutes.ts │ │ ├── ServerIndex.ts │ │ ├── index.ts │ │ ├── league │ │ ├── league.ts │ │ ├── proxy.ts │ │ └── util.ts │ │ └── merge-options.d.ts ├── tsconfig.json └── tslint.json ├── Data Generation ├── createPlayers.js └── words.txt ├── Documentation ├── Architecture.png ├── LCU Missions -_ Riot Implimentation.png ├── Riot API ER.png └── Riot Missions our Implimentation.png ├── Manager ├── clanwars │ └── index.ts ├── index.ts ├── package-lock.json ├── package.json └── routes │ ├── leaderboards.ts │ ├── missions.ts │ ├── player.ts │ └── wars.ts ├── Misc JSONS └── RIOTCONST.json ├── README.md ├── Server ├── globals.ts ├── index.ts ├── package-lock.json ├── package.json ├── riotApi │ ├── index.ts │ └── user.ts ├── routes │ ├── leaderboard.ts │ ├── missions.ts │ ├── player.ts │ └── wars.ts ├── sql │ ├── riotData.sql │ ├── riotStructure.sql │ ├── riotdb.sql │ └── usefulSQL.sql └── sql_functions │ ├── clan.ts │ ├── index.ts │ ├── leaderboard.ts │ ├── mission.ts │ ├── player.ts │ └── wars.ts └── Web ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── docs ├── 3rdpartylicenses.txt ├── assets │ ├── Person Icon.png │ ├── icon3.png │ └── score.png ├── favicon.ico ├── index.html ├── main.7ca38b7ce87d400f76be.js ├── polyfills.1ef83d22ada557f4a131.js ├── runtime.ec2944dd8b20ec099bf3.js └── styles.be4f5f281ebece82613f.css ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── server.js ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── challenge │ │ ├── challenge.component.css │ │ ├── challenge.component.html │ │ ├── challenge.component.spec.ts │ │ └── challenge.component.ts │ ├── clan-leaderboard │ │ ├── clan-leaderboard.component.css │ │ ├── clan-leaderboard.component.html │ │ ├── clan-leaderboard.component.spec.ts │ │ └── clan-leaderboard.component.ts │ ├── download │ │ ├── download.component.css │ │ ├── download.component.html │ │ ├── download.component.spec.ts │ │ └── download.component.ts │ ├── home │ │ ├── home.component.css │ │ ├── home.component.html │ │ ├── home.component.spec.ts │ │ └── home.component.ts │ ├── leaderboard │ │ ├── leaderboard.component.css │ │ ├── leaderboard.component.html │ │ ├── leaderboard.component.spec.ts │ │ └── leaderboard.component.ts │ ├── player-leaderboard │ │ ├── player-leaderboard.component.css │ │ ├── player-leaderboard.component.html │ │ ├── player-leaderboard.component.spec.ts │ │ └── player-leaderboard.component.ts │ ├── register │ │ ├── register.component.css │ │ ├── register.component.html │ │ ├── register.component.spec.ts │ │ └── register.component.ts │ ├── self-defined │ │ └── enums.ts │ ├── store.service.spec.ts │ └── store.service.ts ├── assets │ ├── .gitkeep │ ├── Person Icon.png │ ├── icon3.png │ ├── info-icon.png │ └── score.png ├── browserslist ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | /Client/LCUProxy/node_modules 2 | /Client/LCUProxy/dist 3 | node_modules 4 | /Client/node_modules 5 | /Client/dist 6 | .env 7 | package-lock.json 8 | *.ppk 9 | *.pem -------------------------------------------------------------------------------- /Client/Image Examples.txt: -------------------------------------------------------------------------------- 1 | /fe/lol-new-player-experience/tutorial_summoners_rift_mid_arrow.png 2 | /fe/lol-new-player-experience/reward-xp.png 3 | /fe/lol-missions/events/images/missions/leveling/MissionIcon-WOTD.png 4 | /fe/lol-missions/events/images/eog/rewards/leveling/features/XP.png 5 | /fe/lol-new-player-experience/tutorial_summoners_rift_base_arrows.png 6 | /fe/lol-new-player-experience/reward-xp.png 7 | /lol-game-data/assets/ASSETS/Loot/NB-Fire-Poro-490px-small.png 8 | /fe/lol-new-player-experience/tutorial_summoners_rift_shop.png 9 | /lol-game-data/assets/ASSETS/Missions/preseason2018-thumbnail.png 10 | /lol-game-data/assets/v1/profile-icons/3897.jpg 11 | /lol-game-data/assets/v1/profile-icons/3897.jpg -------------------------------------------------------------------------------- /Client/README.md: -------------------------------------------------------------------------------- 1 | 2 | # The Client 3 | #### Technologies 4 | ![alt-text](https://d2eip9sf3oo6c2.cloudfront.net/tags/images/000/000/256/square_256/nodejslogo.png) ![alt-text](https://d2eip9sf3oo6c2.cloudfront.net/tags/images/000/000/377/square_256/typescriptlang.png) 5 | ![alt-text](http://icons.iconarchive.com/icons/papirus-team/papirus-apps/256/electron-icon.png) 6 | 7 | The main technologies that are used in the client are Electron, Typescript, and Node. We use electron to host the main application and run connections to our server and the LCU. We also use electron because it allows for us to compile our project to an .exe for a demo. We use Node for general compiling and websocket/request support. Finally we use typescript since its a nice language to use when writing large pieces of code and compiles down to javascript. 8 | 9 | #### Setting Up Missions 10 | We realized very early into development that the LCU doesn’t load missions on the fly. It only loads/updates them at two points, after a user has logged in and after a match has completed. 11 | A diagram of how the LCU **normally** handles missions on startup can be found below: 12 | ![alt-text](https://raw.githubusercontent.com/Earleking/2018-Riot-API-Challenge/master/Documentation/LCU%20Missions%20-_%20Riot%20Implimentation.png) 13 | 14 | In order to have the missions appear on the server we go through a couple steps: 15 | 1. User starts our program. 16 | 2. Program starts the league client. 17 | 3. Program wait for user to log in. 18 | 4. If user is not registered on site, register them and generate missions. 19 | 5. Program logs the command line arguments and kills the render process. 20 | 6. Program sets up a middleman server that connects to the LCU. 21 | 7. Render process is restarted with command line arguments that map the renderer to the middleman proxy. 22 | 8. When the program sees the /lol-missions/* endpoint being called it requests missions from our server and adds them to the missions from riots server. 23 | 9. Missions go to the render and the user sees both riot and our missions. 24 | 25 | These steps can be represented by this diagram: 26 | 27 | ![](https://raw.githubusercontent.com/Earleking/2018-Riot-API-Challenge/master/Documentation/Riot%20Missions%20our%20Implimentation.png) 28 | 29 | When you log in the home screen will have missions similar to the ones in the image below or at the following **youtube** link: 30 | http://www.youtube.com/watch?v=nxp2FtBHDmY 31 | 32 | [![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/nxp2FtBHDmY/0.jpg)](http://www.youtube.com/watch?v=nxp2FtBHDmY) 33 | 34 | 35 | 36 | Endpoints used: 37 | * /lol-summoner/v1/current-summoner 38 | * /lol-missions/v1/missions 39 | * /missions/user/${puuid} 40 | * /lol-clubs/v1/clubs 41 | 42 | ### Setting Up Infrastructure for 1v1 and Clan Wars 43 | players have the ability to challenge other players from other teams to a 1v1 for points. In order to do this we run a client side endpoint with 3 main endpoints which the server can call: 44 | * endpoint for creating lobby 45 | * endpoint for sending invites 46 | * endpoint for registering on the 1v1 47 | When the client initially launches it creates a tunnel for the server to communicate with the client. Then whenever a 1v1 is initiated the server calls the endpoints on the client to send out the invites. If the user wants to accept the invite then they can 1v1. The match is setup with the following json 48 | 49 | ``` 50 | { 51 | "customGameLobby": 52 | { 53 | "configuration": 54 | { 55 | "gameMode":"ARAM", 56 | "gameMutator":"", 57 | "gameServerRegion":"", 58 | "mapId":12, 59 | "mutators":{"id":6}, 60 | "spectatorPolicy":"AllAllowed", 61 | "teamSize":1 62 | }, 63 | "lobbyName":Lobbyname, 64 | "lobbyPassword":"PATRICKIN2018" 65 | }, 66 | "isCustom":true 67 | } 68 | ``` 69 | Gamemode is aram which maps to the aram map but he draft strategy is tournament so that you can ban out some of the opponents champions or some of the OP champions. We decided to use a static password because in reality these lobbies are going to be filling up extremely quickly with our auto accept invite. In the future this would be set to a random hash. 70 | 71 | You can see a demo of the feature over here: 72 | [![IMAGE ALT TEXT HERE](https://i.imgur.com/SY1yqci.png)](https://www.youtube.com/watch?v=zkY1zZsIDiE) 73 | 74 | Endpoints used: 75 | * /lol-summoner/v1/current-summoner 76 | * /lol-lobby/v2/lobby 77 | * /lol-lobby/v2/lobby/invitations 78 | * /lol-lobby/v2/received-invitations/ 79 | 80 | ### Technical Challenges In The Client 81 | The main issue with this client was setting up the proxy, the LCU makes setting up the client a nightmare with multiple hoops that need to be jumped through in order to have your own custom missions appear. Another issue is that the client seems to re-instantiate the LCU on champ select so if we want to keep the UI clean we have to manually manage the render processes. Since the RiotClient endpoint isn’t allowed we used manual process killing and starting for a large part of the application which made the behaviour a lot more flakey than we would like. If you would like to see the less flakey behaviour feel free to enable the riot client calls and disable the line below them. The proxy that we set up also adds a lot of overhead in terms of load time to UI components, this slows the client down quite a bit. 82 | 83 | Another smaller issue that we experienced was a timing issue, we needed to delay sending the invite for 5000ms because sometimes the lobby wouldn’t be ready by the time our code hit the invite to lobby line. 84 | 85 | ### Closing Remarks 86 | Overall the client was decently hard to work with, learning how to set up the proxy so that we could see missions through the client was hard but rewarding. I would like to see more official support from riot to better document the client for us developers to work with. I think that we could make a lot more awesome stuff if we better understood how the client worked. 87 | 88 | ## How To Install 89 | ``` 90 | git clone https://github.com/Earleking/2018-Riot-API-Challenge.git 91 | goto $clonedir/Client 92 | npm install 93 | npm run dev 94 | ``` -------------------------------------------------------------------------------- /Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "riot-API-Challenge", 3 | "version": "0.1.0", 4 | "description": "Electron app that hooks into the new LoL client (LCU)", 5 | "private": true, 6 | "keywords": [ 7 | "lcu", 8 | "league", 9 | "lol" 10 | ], 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/Earleking/2018-Riot-API-Challenge" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/Earleking/2018-Riot-API-Challenge/issues" 18 | }, 19 | "dependencies": { 20 | "@types/express": "^4.16.0", 21 | "body-parser": "^1.18.3", 22 | "electron-updater": "^3.1.6", 23 | "express": "^4.16.4", 24 | "localtunnel": "^1.9.1", 25 | "ngrok": "^3.1.0", 26 | "node-fetch": "^1.7.3", 27 | "request": "^2.88.0", 28 | "request-promise-native": "^1.0.5", 29 | "source-map-support": "^0.5.9", 30 | "ws": "^6.1.0" 31 | }, 32 | "devDependencies": { 33 | "electron": "^4.0.0-beta6", 34 | "electron-builder": "^20.31.2", 35 | "electron-webpack": "2.3.1", 36 | "electron-webpack-ts": "^3.0.0", 37 | "electron-webpack-vue": "^2.2.2", 38 | "element-ui": "^2.4.9", 39 | "node-sass": "^4.10.0", 40 | "sass-loader": "^7.1.0", 41 | "tslint": "^5.11.0", 42 | "typescript": "^3.1.6", 43 | "vuex": "^3.0.1", 44 | "vue": "^2.5.17", 45 | "webpack": "^4.25.0", 46 | "@types/node": "^10.12.2", 47 | "@types/request": "^2.48.1", 48 | "@types/request-promise-native": "^1.0.15", 49 | "@types/webpack": "^4.4.17", 50 | "@types/ws": "^6.0.1", 51 | "express": "^4.16.4", 52 | "body-parser": "^1.18.3", 53 | "@types/node-fetch": "^1.6.7" 54 | }, 55 | "main": "lib/index.js", 56 | "typings": "lib/index", 57 | "scripts": { 58 | "dev": "electron-webpack dev", 59 | "compile": "electron-webpack", 60 | "dist": "npm run compile && electron-builder", 61 | "dist:dir": "npm run dist --dir -c.compression=store -c.mac.identity=null" 62 | }, 63 | "build": { 64 | "appId": "com.lolhack.app", 65 | "productName": "LolAPIChallenge", 66 | "mac": { 67 | "category": "public.app-category.utilities" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Client/src/main/MainIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Client/src/main/MainIcon.png -------------------------------------------------------------------------------- /Client/src/main/main.ts: -------------------------------------------------------------------------------- 1 | import {RunProxy} from "../renderer"; 2 | import {RunServerWatcher} from "../renderer/ServerIndex"; 3 | 4 | const ngrok = require('ngrok'); 5 | //const url = require("url"); 6 | const path = require("path"); 7 | const PORT = 49000 + (100 * Math.random())|0; 8 | const REPLACE_PORT = 49100 + (100 * Math.random())|0; 9 | // const SERVER_PORT = 49100 + (100 * Math.random())|0; 10 | const SERVER_PORT = 4800; 11 | 12 | const PWD = "dankmemes"; 13 | 14 | 15 | const { app, Tray } = require('electron'); 16 | const iconPath = path.join(__dirname, 'MainIcon.png'); 17 | 18 | let tray = null; 19 | app.on('ready', async () => { 20 | tray = new Tray(iconPath); 21 | tray.setToolTip('Make Clubs Great Again'); 22 | const url = await ngrok.connect(4800); 23 | console.log("TUNNEL CREATED!!!!"); 24 | RunProxy(PORT, REPLACE_PORT, PWD, url); 25 | RunServerWatcher(PORT, PWD, SERVER_PORT); 26 | // var mytunnel = localtunnel(SERVER_PORT, {"subdomin": "loldomain"} ,(err:any, tunnel:any) => { 27 | // if(err) throw err; 28 | // console.log("TUNNEL CREATED!!!!"); 29 | // RunProxy(PORT, REPLACE_PORT, PWD, tunnel.url); 30 | // RunServerWatcher(PORT, PWD, SERVER_PORT); 31 | // }); 32 | 33 | }); 34 | 35 | -------------------------------------------------------------------------------- /Client/src/renderer/ClientServer/ConnectionToServer.ts: -------------------------------------------------------------------------------- 1 | import fetch, { Response } from "node-fetch"; 2 | 3 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 4 | 5 | export default class ConnectionToServer { 6 | constructor(private ip: string, ) {} 7 | 8 | public async request(url: string, method = "GET", body?: string | object): Promise { 9 | console.log(`${this.ip}${url}`); 10 | return fetch(`${this.ip}${url}`, { 11 | headers: { 12 | Authorization: "Basic", 13 | "Content-Type": "application/json" 14 | }, 15 | method, 16 | body: typeof body === "string" ? body : JSON.stringify(body) 17 | }); 18 | } 19 | } -------------------------------------------------------------------------------- /Client/src/renderer/ClientServer/LCUHelper.ts: -------------------------------------------------------------------------------- 1 | import LeagueConnection from "../league/league"; 2 | 3 | export class LCUHelper 4 | { 5 | constructor(private leagueconnection: LeagueConnection){} 6 | 7 | public async CreateCustomLobby(Lobbyname: string = "its a lobby", players:any) 8 | { 9 | console.log("TRYING TO CREATE LOBBY"); 10 | const data = 11 | { 12 | "customGameLobby": 13 | { 14 | "configuration": 15 | { 16 | "gameMode":"ARAM", 17 | "gameMutator":"", 18 | "gameServerRegion":"", 19 | "mapId":12, 20 | "mutators":{"id":6}, 21 | "spectatorPolicy":"AllAllowed", 22 | "teamSize":1 23 | }, 24 | "lobbyName":Lobbyname, 25 | "lobbyPassword":"PATRICKIN2018" 26 | }, 27 | "isCustom":true 28 | }; 29 | 30 | await this.leagueconnection.request("/lol-lobby/v2/lobby", "POST", data); 31 | 32 | this.InviteToLobby(players); 33 | } 34 | 35 | public async InviteToLobby(players: any) 36 | { 37 | 38 | delay(5000); //timing hack 39 | const data = [ 40 | { 41 | "state": "Requested", 42 | "toSummonerId": players["body"]["toSummonerId"] 43 | } 44 | ]; 45 | 46 | await this.leagueconnection.request("/lol-lobby/v2/lobby/invitations", "POST", data); 47 | 48 | } 49 | 50 | public async AcceptLobbyInvite(playerjson: any) 51 | { 52 | //accept the invite 53 | const recievedinvites = JSON.parse((await this.leagueconnection.request("/lol-lobby/v2/received-invitations/", "GET")).body.read().toString()); 54 | for(let i = 0; i < recievedinvites.length; i++) 55 | { 56 | if(recievedinvites[i]["fromSummonerId"] === playerjson["summonerID"]) 57 | { 58 | const inviteID = recievedinvites[i]["invitationId"]; 59 | this.leagueconnection.request("/lol-lobby/v2/received-invitations/" + inviteID + "/accept", "POST"); 60 | } 61 | } 62 | } 63 | } 64 | 65 | async function delay(ms: number) { 66 | return new Promise( resolve => setTimeout(resolve, ms) ); 67 | } -------------------------------------------------------------------------------- /Client/src/renderer/ClientServer/ServerRoutes.ts: -------------------------------------------------------------------------------- 1 | import LeagueConnection from "../league/league"; 2 | import {Express} from "express"; 3 | import {LCUHelper} from "./LCUHelper"; 4 | 5 | export function setupRoutes(app: Express, leagueconnection: LeagueConnection) 6 | { 7 | const lcuhelper = new LCUHelper(leagueconnection); 8 | app.route('/lol/create-lobby').put((req: any, res:any) => { 9 | console.log(req.body); 10 | lcuhelper.CreateCustomLobby("1v1 Clan War", JSON.parse(req.body.toString())); 11 | res.send(""); 12 | }); 13 | app.route('/lol/accept-lobby').put((req: any, res:any) => { 14 | lcuhelper.AcceptLobbyInvite(JSON.parse(req.body)); 15 | res.send(""); 16 | }); 17 | app.route("/lol/me").get((req, res) => { 18 | leagueconnection.request("/lol-summoner/v1/current-summoner").then((resp) => { 19 | console.log(req); 20 | const currentsummoner = JSON.parse(resp.body.read().toString()); 21 | res.send(currentsummoner); 22 | }); 23 | }); 24 | 25 | } -------------------------------------------------------------------------------- /Client/src/renderer/ServerIndex.ts: -------------------------------------------------------------------------------- 1 | import LeagueConnection from "./league/league"; 2 | 3 | import bodyParser = require("body-parser"); 4 | import express = require("express"); 5 | 6 | import {setupRoutes} from "./ClientServer/ServerRoutes"; 7 | //import {stopLeagueRenderProccess} from "./util"; 8 | 9 | class ServerProxy { 10 | private app = express(); 11 | 12 | 13 | constructor(private leagueconnection: LeagueConnection) { 14 | // Parse every body as text, regardless of actual type. 15 | this.app.use(bodyParser.text({type: () => true})); 16 | this.app.use(function (request:any, response:any, next:any) { 17 | // response.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200'); 18 | response.setHeader('Access-Control-Allow-Origin', '*'); 19 | response.header("Access-Control-Allow-Headers", "Origin, XRequested-With, Content-Type, Accept "); 20 | response.header('Access-Control-Allow-Methods', 'POST, PATCH, GET, PUT, DELETE, OPTIONS '); 21 | console.log(request); 22 | next(); 23 | }); 24 | // Listen to every HTTP request. 25 | // this.app.all("*", this.onWebRequest); 26 | this.app.route("/").get((req, res) => { 27 | res.json({"message": "hello world"}); 28 | console.log(req); 29 | }); 30 | // Listen to WS connections. 31 | // this.wss.on("connection", this.onWebsocketRequest); 32 | setupRoutes(this.app, this.leagueconnection); 33 | } 34 | 35 | listen(port: number) { 36 | // this.server.listen(port); 37 | this.app.listen(port); 38 | console.log("[+] Listening on 0.0.0.0:" + port + "... ^C to exit."); 39 | } 40 | 41 | 42 | 43 | 44 | 45 | } 46 | 47 | 48 | 49 | export async function RunServerWatcher(PORT: number, PWD: string, SERVER_PORT: number) 50 | { 51 | 52 | const league = new LeagueConnection(PORT, PWD); 53 | const server = new ServerProxy(league); 54 | server.listen(SERVER_PORT); 55 | console.log("server being watched"); 56 | } 57 | -------------------------------------------------------------------------------- /Client/src/renderer/index.ts: -------------------------------------------------------------------------------- 1 | import LeagueConnection from "./league/league"; 2 | import { 3 | getUxArguments, startFoundation, startLeagueRenderNoArgs, startUx, stopLeague, stopLeagueRenderProccess, 4 | 5 | } from "./league/util"; 6 | import LCUProxy from "./league/proxy"; 7 | import fs = require("fs"); 8 | import ConnectionToServer from "./ClientServer/ConnectionToServer"; 9 | 10 | const LEAGUE_PATH = 'C:/Riot Games/League of Legends/'; 11 | 12 | const SERVER_IP ='http://ec2-35-182-253-71.ca-central-1.compute.amazonaws.com:8080'; 13 | 14 | export async function RunProxy(PORT: number, REPLACE_PORT: number, PWD: string, URL:string) 15 | { 16 | // LOCKFILE: LeagueClient:20904:63769:3UMgPMIfav6dqRUHowD0Aw:https 17 | console.log(`LOCKFILE: LeagueClient:1:${PORT}:${PWD}:https`); 18 | console.log(`PROXY PORT: ${REPLACE_PORT}`); 19 | (async () => { 20 | try { 21 | process.chdir('C:/Riot Games/League of Legends/'); 22 | fs.writeFileSync(LEAGUE_PATH + "lockfile", `LeagueClient:1:${PORT}:${PWD}:https`); 23 | 24 | // Stop an existing league process, if needed. Then start league and find the arguments. 25 | stopLeague(); 26 | startFoundation(PORT, PWD); 27 | 28 | await new Promise(x => setTimeout(x, 5000)); 29 | 30 | // Connect to league and load the normal window. 31 | const league = new LeagueConnection(PORT, PWD); 32 | const connectionToServer = new ConnectionToServer(SERVER_IP); 33 | 34 | /** 35 | * NOTE TO GENE: 36 | * use await league.request("/riotclient/launch-ux", "POST"); for good version 37 | * startLeagueRenderNoArgs(); //more flakey version 38 | * 39 | */ 40 | //await league.request("/riotclient/launch-ux", "POST"); //good version 41 | startLeagueRenderNoArgs(); //more flakey version 42 | 43 | let args = null; 44 | while (!args) { 45 | args = await getUxArguments(); 46 | await new Promise(resolve => setTimeout(resolve, 1000)); 47 | } 48 | 49 | // Wait for user to log in. 50 | while (true) { 51 | let resp = await league.request("/lol-summoner/v1/current-summoner"); 52 | if (resp.status !== 404) 53 | break; 54 | await new Promise(resolve => setTimeout(resolve, 1000)); 55 | } 56 | 57 | //register summoner on server 58 | await RegisterSummoner(league, connectionToServer, URL); 59 | 60 | // Kill UX 61 | /** 62 | * NOTE TO GENE: 63 | * USE await league.request("/riotclient/kill-ux", "POST"); for good version 64 | * stopLeagueRenderProccess(); //more flakey version 65 | */ 66 | //await league.request("/riotclient/kill-ux", "POST"); //good 67 | stopLeagueRenderProccess(); //more flakey version 68 | 69 | 70 | // Configure the proxy. 71 | const proxy = new LCUProxy(league); 72 | proxy.listen(REPLACE_PORT); 73 | 74 | //get missions from server 75 | let missionsData: [] = await GetMissionData(league, connectionToServer); 76 | 77 | proxy.adjust(/lol-missions\/v1\/missions/, data => { 78 | let json: Array = JSON.parse(data); 79 | for(var i = 0; i < missionsData.length; i++) 80 | { 81 | json.unshift(missionsData[i]); 82 | } 83 | return JSON.stringify(json); 84 | }); 85 | 86 | // Start a new league window that refers to the proxy. 87 | startUx(args.replace("" + PORT, "" + REPLACE_PORT) + ` "--use-http"`); 88 | } 89 | catch(exception) 90 | { 91 | console.log("Exception Hit"); 92 | console.log(exception) 93 | } 94 | 95 | })().catch(console.error); 96 | } 97 | 98 | async function GetMissionData(league: LeagueConnection, connectionToServer: ConnectionToServer) : Promise 99 | { 100 | 101 | let resp = await league.request("/lol-summoner/v1/current-summoner"); 102 | const puuid = JSON.parse(resp.body.read().toString())["puuid"]; 103 | // const puuid = "021yeCQS9RfiDeTOXSIj3vruMFua5lQ2GM0DI5E6xqWY7iBdPh"; //testing puuid 104 | const missionsdataresponse = await connectionToServer.request(`/missions/user/${puuid}`); 105 | const missiondata = JSON.parse(missionsdataresponse.body.read().toString()); 106 | console.log(missiondata); 107 | var arrayOfMissions = []; 108 | for(var i = 0; i < missiondata.length; i++) 109 | { 110 | var tempJSON = 111 | { 112 | "backgroundImageUrl": "", 113 | "celebrationType": "VIGNETTE", 114 | "clientNotifyLevel": "ALWAYS", 115 | "completedDate": -1, 116 | "completionExpression": "", 117 | "cooldownTimeMillis": -1, 118 | "description": missiondata[i]["description"], 119 | "display": { 120 | "attributes": [] 121 | }, 122 | "displayType": "ALWAYS", 123 | "endTime": Date.now() + 24 * 3600 * 1000, 124 | "expiringWarnings": [ 125 | { 126 | "alertTime": 1546675140000, 127 | "message": "", 128 | "type": "" 129 | } 130 | ], 131 | "helperText": missiondata[i]["type"] + " Mission for: " + missiondata[i]["reward"] + " Points" , 132 | "iconImageUrl": missiondata[i]["icon_path"], 133 | "id": "ef810430-f81d-11e8-81fc-02cbe124d5e4", 134 | "internalName": "neeko18_intro", 135 | "isNew": false, 136 | "lastUpdatedTimestamp": Date.now(), 137 | "locale": "en_US", 138 | "metadata": { 139 | "tutorial": { 140 | "displayRewards": {}, 141 | "queueId": "", 142 | "stepNumber": -1, 143 | "useChosenChampion": false, 144 | "useQuickSearchMatchmaking": false 145 | } 146 | }, 147 | "missionType": "ONETIME", 148 | "objectives": [ 149 | { 150 | "description": "Kill 7 champions", 151 | "hasObjectiveBasedReward": false, 152 | "progress": { 153 | "currentProgress": missiondata[i]["curent_progress"], 154 | "lastViewedProgress": 0, 155 | "totalCount": missiondata[i]["max_progress"] 156 | }, 157 | "rewardGroups": [], 158 | "sequence": 1, 159 | "type": "LEGS" 160 | } 161 | ], 162 | "requirements": [], 163 | "rewardStrategy": { 164 | "groupStrategy": "ALL_GROUPS", 165 | "selectMaxGroupCount": 0, 166 | "selectMinGroupCount": 0 167 | }, 168 | "rewards": [ 169 | { 170 | "description": "", 171 | "iconUrl": "/fe/lol-loot/assets/loot_item_icons/currency_champion.png", 172 | "isObjectiveBasedReward": false, 173 | "itemId": "", 174 | "quantity": missiondata[i]["reward"], 175 | "rewardFulfilled": false, 176 | "rewardGroup": "0", 177 | "rewardGroupSelected": false, 178 | "rewardType": "SUMMONER_ICON", 179 | "sequence": 1, 180 | "uniqueName": "" 181 | } 182 | ], 183 | "seriesName": "", 184 | "startTime": 1544025600000, 185 | "status": "PENDING", 186 | "title": missiondata[i]["title"], 187 | "viewed": true 188 | }; 189 | arrayOfMissions.push(tempJSON) 190 | } 191 | return arrayOfMissions 192 | } 193 | 194 | async function RegisterSummoner(league: LeagueConnection, connectionToServer: ConnectionToServer, URL:string) 195 | { 196 | console.log("registering summoner"); 197 | let resp = await league.request("/lol-summoner/v1/current-summoner"); 198 | const currentsummoner = JSON.parse(resp.body.read().toString()); 199 | let clubresp = await league.request("/lol-clubs/v1/clubs"); 200 | clubresp.json().then((clubdata) => { 201 | // console.log(clubdata); 202 | const registerJSON = { 203 | "display_name": currentsummoner["displayName"], 204 | "puuid": currentsummoner["puuid"], 205 | "id": currentsummoner["summonerId"], 206 | "accountId": currentsummoner["accountId"], 207 | "clan_name": clubdata[0]["name"], 208 | "clan_tag": clubdata[0]["tag"], 209 | "ip": URL, 210 | }; 211 | console.log(registerJSON); 212 | connectionToServer.request(`/player/register`, "PUT", registerJSON); 213 | }); 214 | 215 | } -------------------------------------------------------------------------------- /Client/src/renderer/league/league.ts: -------------------------------------------------------------------------------- 1 | import WebSocket = require("ws"); 2 | import fetch, { Response } from "node-fetch"; 3 | 4 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 5 | 6 | export default class LeagueConnection { 7 | constructor(private port: number, private password: string) {} 8 | 9 | public async request(url: string, method = "GET", body?: string | object): Promise { 10 | return fetch(`https://127.0.0.1:${this.port}${url}`, { 11 | headers: { 12 | Authorization: "Basic " + new Buffer("riot:" + this.password).toString("base64"), 13 | "Content-Type": "application/json" 14 | }, 15 | method, 16 | body: typeof body === "string" ? body : JSON.stringify(body) 17 | }); 18 | } 19 | 20 | public connectWebsocket(url: string, handler: (msg: WebSocket.Data) => any, closeHandler?: () => void): Promise<{ send: (args: any) => void, close: () => void }> { 21 | const socket = new WebSocket(`wss://riot:${this.password}@127.0.0.1:${this.port}${url}`, "wamp"); 22 | 23 | return new Promise(resolve => { 24 | socket.onopen = () => { 25 | resolve({ 26 | send: msg => { 27 | if (socket.readyState !== 1) console.error("SERVER NO LONGER IN ACTION RIP RIP"); 28 | socket.readyState === 1 && socket.send(msg) 29 | }, 30 | close() { 31 | socket.close(); 32 | } 33 | }); 34 | }; 35 | socket.onmessage = d => { /*console.log(d.data);*/ handler(d.data); }; 36 | socket.onclose = () => { 37 | console.log("[-] Closed upstream connection to " + url); 38 | closeHandler && closeHandler(); 39 | }; 40 | }); 41 | } 42 | } -------------------------------------------------------------------------------- /Client/src/renderer/league/proxy.ts: -------------------------------------------------------------------------------- 1 | import { createServer, IncomingMessage } from "http"; 2 | import bodyParser = require("body-parser"); 3 | import express = require("express"); 4 | 5 | import { Server } from "ws"; 6 | import WebSocket = require("ws"); 7 | import LeagueConnection from "./league"; 8 | //import {stopLeagueRenderProccess} from "./util"; 9 | 10 | export default class LCUProxy { 11 | private app = express(); 12 | private server = createServer(this.app); 13 | private wss = new Server({ server: this.server }); 14 | 15 | private downstreamEdits: [RegExp, (body: string) => string][] = []; 16 | 17 | constructor(private league: LeagueConnection) { 18 | // Parse every body as text, regardless of actual type. 19 | this.app.use(bodyParser.text({ type: () => true })); 20 | 21 | // Listen to every HTTP request. 22 | this.app.all("*", this.onWebRequest); 23 | 24 | // Listen to WS connections. 25 | this.wss.on("connection", this.onWebsocketRequest); 26 | } 27 | 28 | adjust(regexp: RegExp, handler: (body: string) => string) { 29 | this.downstreamEdits.push([regexp, handler]); 30 | } 31 | 32 | listen(port: number) { 33 | this.server.listen(port); 34 | console.log("[+] Listening on 0.0.0.0:" + port + "... ^C to exit."); 35 | } 36 | 37 | private onWebRequest = async (req: express.Request, res: express.Response) => { 38 | // Request response from league. 39 | const resp = await this.league.request(req.url, req.method, typeof req.body === "string" ? req.body : ""); 40 | 41 | // Forward headers. 42 | resp.headers.forEach((val: any, name: any) => { 43 | // Don't forward content-length, since we might end up modifying it. 44 | if (name === "Content-Length") return; 45 | res.header(name, val); 46 | }); 47 | 48 | // Maybe adjust the response, depending on what we registered. 49 | let toRet = await resp.text(); 50 | for (const [match, fn] of this.downstreamEdits) { 51 | if (!match.test(req.url)) continue; 52 | toRet = fn(toRet); 53 | } 54 | 55 | // Forward body. 56 | res.status(resp.status === 204 ? 200 : resp.status).send(toRet); 57 | }; 58 | 59 | private onWebsocketRequest = async (client: WebSocket, request: IncomingMessage) => { 60 | console.log("[+] Got new websocket connection to " + request.url); 61 | 62 | // Buffer any messages that arrive before we establish the forwarding connection. 63 | const pendingMessages: any[] = []; 64 | client.on("message", msg => pendingMessages.push(msg)); 65 | 66 | // Establish a forwarding connection. 67 | const forwarding = await this.league.connectWebsocket(request.url!, msg => { 68 | // If this is a plaintext socket, maybe change it's contents. 69 | if (request.url === "/") { 70 | let [ op, ev, payload ] = JSON.parse(msg); 71 | if (op === 8 && ev === "OnJsonApiEvent" && (payload.eventType === "Update" || payload.eventType === "Create")) { 72 | // if(payload.uri.includes("/lol-champ-select-legacy/v1/implementation-active")) //signals the start of champ select 73 | // { 74 | // console.log(payload.uri); 75 | // stopLeagueRenderProccess(); 76 | // this.league.request("/riotclient/launch-ux", "POST"); 77 | // } 78 | for (const [ match, fn ] of this.downstreamEdits) { 79 | if (!match.test(payload.uri)) continue; 80 | payload.data = JSON.parse(fn(JSON.stringify(payload.data))); 81 | } 82 | msg = JSON.stringify([op, ev, payload]); 83 | } 84 | } 85 | 86 | if (client.readyState === 1) client.send(msg); 87 | }, () => { 88 | client.close(); 89 | }); 90 | 91 | // Send the messages we buffered, then remove the listener. 92 | for (const msg of pendingMessages) forwarding.send(msg); 93 | client.removeAllListeners("message"); 94 | 95 | // Close upstream when downstream is closed. 96 | client.on("close", () => { 97 | console.log("closing client"); 98 | forwarding.close() 99 | }); 100 | 101 | // Handle messages. 102 | client.on("message", msg => { 103 | forwarding.send(msg); 104 | }); 105 | }; 106 | } -------------------------------------------------------------------------------- /Client/src/renderer/league/util.ts: -------------------------------------------------------------------------------- 1 | import * as child_process from "child_process"; 2 | 3 | /** 4 | * Tries to find a running LeagueClientUx process and returns it's command line. 5 | */ 6 | export function getUxArguments(): Promise { 7 | return new Promise(resolve => { 8 | child_process.exec( 9 | "WMIC PROCESS WHERE name='LeagueClientUx.exe' GET commandline", 10 | (err, stdout, stderr) => { 11 | // If there was an error or stderr isn't empty, abort. 12 | if (err || !stdout || stderr) return resolve(); 13 | if (!stdout.includes("CommandLine")) return resolve(); 14 | 15 | resolve(stdout.replace("CommandLine", "").replace(/\n/g, "").trim()); 16 | } 17 | ); 18 | }); 19 | } 20 | 21 | /** 22 | * Stops all league processes, if it is currently running. 23 | */ 24 | export function stopLeague() { 25 | child_process.execSync(`WMIC PROCESS WHERE name='LeagueClient.exe' DELETE`); 26 | } 27 | 28 | /** 29 | * Starts a new league instance with the specified port and remoting password. 30 | */ 31 | export function startFoundation(port: number, password: string) { 32 | child_process.execSync(`"C:/Riot Games/League of Legends/LeagueClient.exe" --remoting-auth-token=${password} --app-port=${port} --allow-multiple-clients`);// --headless`); 33 | } 34 | 35 | /** 36 | * Starts the UX process with the provided commandline. 37 | */ 38 | export function startUx(commandLine: string) { 39 | const [ , cwd ] = /"(.*)?LeagueClientUx\.exe"/.exec(commandLine)!; 40 | console.log("Starting " + commandLine + " in " + cwd); 41 | child_process.exec(commandLine, { 42 | cwd 43 | }, () => { 44 | // League exited, so can we. 45 | process.exit(0); 46 | }); 47 | } 48 | export function stopLeagueRenderProccess() { 49 | child_process.exec(`WMIC PROCESS WHERE name='LeagueClientUxRender.exe' DELETE`); 50 | } 51 | 52 | export function startLeagueRenderNoArgs(){ 53 | child_process.execSync(`"C:\\Riot Games\\League of Legends\\RADS\\projects\\league_client\\releases\\0.0.0.181\\deploy\\LeagueClientUxRender.exe"`) 54 | } -------------------------------------------------------------------------------- /Client/src/renderer/merge-options.d.ts: -------------------------------------------------------------------------------- 1 | declare module "merge-options" { 2 | function merge(a: object, b: object): object; 3 | export = merge; 4 | } -------------------------------------------------------------------------------- /Client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "allowUnreachableCode": false, 5 | "allowUnusedLabels": false, 6 | "alwaysStrict": true, 7 | "declaration": false, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "importHelpers": true, 12 | "lib": ["dom", "dom.iterable", "es2017"], 13 | "module": "commonjs", 14 | "moduleResolution": "node", 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": false, 19 | "noImplicitUseStrict": false, 20 | "noLib": false, 21 | "noResolve": false, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "removeComments": true, 25 | "rootDir": ".", 26 | "strictNullChecks": true, 27 | "target": "es2017" 28 | }, 29 | "exclude": [ 30 | ".git", 31 | "js", 32 | "lib", 33 | "node_modules" 34 | ], 35 | "extends": "./node_modules/electron-webpack/tsconfig-base.json", 36 | "filesGlob": [ 37 | "src/**/*.ts", 38 | "test/**/*.ts" 39 | ], 40 | "formatCodeOptions": { 41 | "convertTabsToSpaces": true, 42 | "indentSize": 2, 43 | "insertSpaceAfterCommaDelimiter": true, 44 | "insertSpaceAfterSemicolonInForStatements": true, 45 | "insertSpaceBeforeAndAfterBinaryOperators": true, 46 | "insertSpaceAfterKeywordsInControlFlowStatements": true, 47 | "insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, 48 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, 49 | "newLineCharacter": "\n", 50 | "placeOpenBraceOnNewLineForFunctions": false, 51 | "placeOpenBraceOnNewLineForControlBlocks": false, 52 | "tabSize": 2 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "adjacent-overload-signatures": true, 4 | "align": [true, 5 | "parameters", 6 | "arguments", 7 | "statements" 8 | ], 9 | "array-type": false, 10 | "arrow-parens": [true, "ban-single-arg-parens"], 11 | "arrow-return-shorthand": true, 12 | "await-promise": true, 13 | "ban": [true, 14 | ["_", "isDefined"], 15 | ["_", "isEqual"] 16 | ], 17 | "ban-comma-operator": true, 18 | "ban-types": false, 19 | "binary-expression-operand-order": true, 20 | "callable-types": true, 21 | "class-name": true, 22 | "comment-format": [true, 23 | "check-space" 24 | ], 25 | "completed-docs": false, 26 | "curly": true, 27 | "cyclomatic-complexity": false, 28 | "deprecation": false, 29 | "encoding": true, 30 | "eofline": true, 31 | "file-header": false, 32 | "file-name-casing": false, 33 | "forin": true, 34 | "import-blacklist": false, 35 | "import-spacing": false, 36 | "indent": [true, 37 | "spaces" 38 | ], 39 | "interface-name": [true, 40 | "never-prefix" 41 | ], 42 | "interface-over-type-literal": true, 43 | "jsdoc-format": true, 44 | "label-position": true, 45 | "linebreak-style": [true, 46 | "LF" 47 | ], 48 | "match-default-export-name": true, 49 | "max-classes-per-file": false, 50 | "max-file-line-count": false, 51 | "max-line-length": [true, 52 | 79 53 | ], 54 | "member-access": true, 55 | "member-ordering": [true, 56 | { "order": "fields-first" } 57 | ], 58 | "newline-before-return": false, 59 | "newline-per-chained-call": false, 60 | "new-parens": true, 61 | "no-angle-bracket-type-assertion": true, 62 | "no-any": false, 63 | "no-arg": true, 64 | "no-bitwise": false, 65 | "no-boolean-literal-compare": false, 66 | "no-conditional-assignment": true, 67 | "no-consecutive-blank-lines": false, 68 | "no-console": [true, 69 | "debug", 70 | "info", 71 | "time", 72 | "timeEnd", 73 | "trace" 74 | ], 75 | "no-construct": true, 76 | "no-debugger": true, 77 | "no-default-export": true, 78 | "no-duplicate-imports": true, 79 | "no-duplicate-switch-case": true, 80 | "no-duplicate-super": true, 81 | "no-duplicate-variable": true, 82 | "no-dynamic-delete": false, 83 | "no-empty": true, 84 | "no-empty-interface": true, 85 | "no-eval": true, 86 | "no-floating-promises": true, 87 | "no-for-in-array": true, 88 | "no-implicit-dependencies": false, 89 | "no-import-side-effect": true, 90 | "no-inferred-empty-object-type": true, 91 | "no-inferrable-types": false, 92 | "no-internal-module": true, 93 | "no-invalid-template-strings": true, 94 | "no-invalid-this": [true, 95 | "check-function-in-method" 96 | ], 97 | "no-irregular-whitespace": true, 98 | "no-magic-numbers": false, 99 | "no-mergeable-namespace": true, 100 | "no-misused-new": true, 101 | "no-namespace": true, 102 | "no-non-null-assertion": true, 103 | "no-null-keyword": false, 104 | "no-object-literal-type-assertion": true, 105 | "no-parameter-properties": true, 106 | "no-parameter-reassignment": false, 107 | "no-redundant-jsdoc": true, 108 | "no-reference": false, 109 | "no-reference-import": true, 110 | "no-return-await": false, 111 | "no-require-imports": false, 112 | "no-shadowed-variable": true, 113 | "no-sparse-arrays": true, 114 | "no-string-literal": true, 115 | "no-string-throw": true, 116 | "no-submodule-imports": false, 117 | "no-switch-case-fall-through": true, 118 | "no-this-assignment": true, 119 | "no-trailing-whitespace": true, 120 | "no-unbound-method": false, 121 | "no-unsafe-any": true, 122 | "no-unsafe-finally": true, 123 | "no-unnecessary-class": false, 124 | "no-unnecessary-callback-wrapper": true, 125 | "no-unnecessary-initializer": false, 126 | "no-unnecessary-qualifier": true, 127 | "no-unnecessary-type-assertion": true, 128 | "no-unused-expression": true, 129 | "no-use-before-declare": true, 130 | "no-var-keyword": true, 131 | "no-var-requires": true, 132 | "no-void-expression": true, 133 | "number-literal-format": true, 134 | "object-literal-key-quotes": [true, 135 | "as-needed" 136 | ], 137 | "object-literal-shorthand": true, 138 | "object-literal-sort-keys": true, 139 | "one-line": [true, 140 | "check-catch", 141 | "check-else", 142 | "check-finally", 143 | "check-open-brace", 144 | "check-whitespace" 145 | ], 146 | "one-variable-per-declaration": [true, 147 | "ignore-for-loop" 148 | ], 149 | "only-arrow-functions": false, 150 | "ordered-imports": true, 151 | "quotemark": [true, 152 | "single", 153 | "avoid-escape" 154 | ], 155 | "prefer-conditional-expression": false, 156 | "prefer-const": true, 157 | "prefer-for-of": true, 158 | "prefer-function-over-method": false, 159 | "prefer-method-signature": true, 160 | "prefer-object-spread": true, 161 | "prefer-switch": true, 162 | "prefer-readonly": true, 163 | "prefer-template": false, 164 | "prefer-while": true, 165 | "promise-function-async": false, 166 | "radix": true, 167 | "restrict-plus-operands": true, 168 | "return-undefined": true, 169 | "semicolon": [true, 170 | "always" 171 | ], 172 | "space-before-function-paren": [true, 173 | { 174 | "anonymous": "always", 175 | "asyncArrow": "always", 176 | "constructor": "never", 177 | "method:": "never", 178 | "named": "never" 179 | } 180 | ], 181 | "space-within-parens": true, 182 | "strict-boolean-expressions": false, 183 | "strict-type-predicates": true, 184 | "switch-default": true, 185 | "switch-final-break": true, 186 | "trailing-comma": [true, 187 | { 188 | "multiline": "always", 189 | "singleline": "never" 190 | } 191 | ], 192 | "triple-equals": true, 193 | "type-literal-delimiter": true, 194 | "typedef": [true, 195 | "call-signature", 196 | "parameter", 197 | "property-declaration", 198 | "member-variable-declaration" 199 | ], 200 | "typedef-whitespace": [true, 201 | { 202 | "call-signature": "nospace", 203 | "index-signature": "nospace", 204 | "parameter": "nospace", 205 | "property-declaration": "nospace", 206 | "variable-declaration": "nospace" 207 | } 208 | ], 209 | "use-default-type-parameter": false, 210 | "use-isnan": true, 211 | "unified-signatures": true, 212 | "variable-name": [true, 213 | "check-format", 214 | "allow-leading-underscore", 215 | "ban-keywords" 216 | ], 217 | "whitespace": [true, 218 | "check-branch", 219 | "check-decl", 220 | "check-operator", 221 | "check-separator", 222 | "check-type" 223 | ] 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /Data Generation/createPlayers.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var request = require('request'); 3 | var clans = []; 4 | 5 | for(var i = 1; i <= 50; i ++) { 6 | clans.push({ 7 | "tag": "CLN" + i, 8 | "name": "Clan " + i 9 | }); 10 | } 11 | 12 | fs.readFile("words.txt", 'utf8', (err, data) => { 13 | data = data.split('\n'); 14 | var max = data.length; 15 | for(var i = 0; i < 1000; i ++) { 16 | var clan = rand(clans.length); 17 | var options = { 18 | form: { 19 | "display_name": data[rand(max)] + " " + data[rand(max)], 20 | "puuid": makepuuid(), 21 | "id": makeid(), 22 | "accountId": makeid(), 23 | "clan_name": clans[clan]["name"], 24 | "clan_tag": clans[clan]["tag"] 25 | } 26 | } 27 | request.put("http://localhost:1000/player", options, (err, res, data) => { 28 | console.log(data); 29 | }); 30 | } 31 | }); 32 | 33 | function rand(max) { 34 | return Math.floor(Math.random() * Math.floor(max)); 35 | } 36 | 37 | function makepuuid() { 38 | var text = ""; 39 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 40 | 41 | for (var i = 0; i < 50; i++) 42 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 43 | 44 | return text; 45 | } 46 | 47 | function makeid() { 48 | var text = ""; 49 | var possible = "123456789"; 50 | 51 | for (var i = 0; i < 6; i++) 52 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 53 | 54 | return text; 55 | } -------------------------------------------------------------------------------- /Documentation/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Documentation/Architecture.png -------------------------------------------------------------------------------- /Documentation/LCU Missions -_ Riot Implimentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Documentation/LCU Missions -_ Riot Implimentation.png -------------------------------------------------------------------------------- /Documentation/Riot API ER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Documentation/Riot API ER.png -------------------------------------------------------------------------------- /Documentation/Riot Missions our Implimentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Documentation/Riot Missions our Implimentation.png -------------------------------------------------------------------------------- /Manager/clanwars/index.ts: -------------------------------------------------------------------------------- 1 | var request = require("request"); 2 | 3 | export function genereateWar() { 4 | // set war to setup 5 | request.put("http://localhost:1000/war/setup/start", (err, res, data) => { 6 | generateTeams(); 7 | }); 8 | } 9 | 10 | export function startWar() { 11 | request.put("http://localhost:1000/war/games/start", (err, res, data) => { 12 | getMatches((matches:object) => { 13 | for(var key in matches) { 14 | var game = matches[key]; 15 | for(var team of game) { 16 | for(var player of team) { 17 | // Send some data to players here 18 | } 19 | } 20 | } 21 | }); 22 | }); 23 | } 24 | 25 | function generateTeams() { 26 | getPlayers((players:Object) => { 27 | var teamsArray:Array> = generateArray(players); 28 | 29 | var matches = []; 30 | var teamsTeams = createTeams(teamsArray); 31 | var onevones = matchPlayers(teamsTeams[0]); 32 | var fivevfives = matchPlayers(teamsTeams[1]); 33 | // JSON.stringify(onevones); 34 | var options = { 35 | form: { 36 | "players": JSON.stringify(fivevfives) 37 | } 38 | } 39 | // Add 5v5s 40 | request.post("http://localhost:1000/war/game/add", options, (err, res, data) => { 41 | console.log(data); 42 | }); 43 | // Add 1v1s 44 | options = { 45 | form: { 46 | "players": JSON.stringify(onevones) 47 | } 48 | } 49 | request.post("http://localhost:1000/war/game/add", options, (err, res, data) => { 50 | console.log(data); 51 | }); 52 | }); 53 | } 54 | 55 | function getPlayers(callback:Function) { 56 | request.get("http://localhost:1000/war/players/get/1", (err, res, data) => { 57 | callback(JSON.parse(data)); 58 | }); 59 | } 60 | 61 | function generateArray(players:object):Array> { 62 | var teams:object = {}; 63 | var teamsArray:Array> = []; 64 | for(var i in players) { 65 | if(teams[players[i].clan_id] == undefined) 66 | teams[players[i].clan_id] = []; 67 | teams[players[i].clan_id].push(players[i]); 68 | } 69 | var added:boolean = false; 70 | for(var team in teams) { 71 | if(teamsArray.length == 0) { 72 | teamsArray.push(teams[team]); 73 | delete teams[team]; 74 | continue; 75 | } 76 | added = false; 77 | for(var index in teamsArray) { 78 | if(teams[team].length > teamsArray[index].length) { 79 | teamsArray.splice(parseInt(index), 0, teams[team]); 80 | delete teams[team]; 81 | added = true; 82 | break; 83 | } 84 | } 85 | if(!added) { 86 | teamsArray.push(teams[team]); 87 | delete teams[team]; 88 | } 89 | } 90 | return teamsArray; 91 | } 92 | 93 | function createTeams(clans:Array>):Array { 94 | var soloPlayers:object = {}; 95 | var fivesPlayers:object = {}; 96 | for(var players of clans) { 97 | var currentClan:string = players[0]["clan_id"]; 98 | var numberOfExcessPlayers = players.length % 5; 99 | for(var t = 0; t < numberOfExcessPlayers; t ++) { 100 | if(soloPlayers[currentClan] == undefined) 101 | soloPlayers[currentClan] = []; 102 | var rand = Math.floor(Math.random() * players.length); 103 | soloPlayers[currentClan].push([players[rand]]); 104 | players.splice(rand, 1); 105 | } 106 | var numberOfTeams = players.length / 5; 107 | 108 | for(var x = 0; x < numberOfTeams; x ++) { 109 | if(fivesPlayers[currentClan] == undefined) 110 | fivesPlayers[currentClan] = []; 111 | fivesPlayers[currentClan].push(players.splice(0, 5)); 112 | } 113 | } 114 | 115 | 116 | return [soloPlayers, fivesPlayers]; 117 | } 118 | 119 | function matchPlayers(clans:object):Array> { 120 | var matches = []; 121 | for(var clan in clans) { 122 | for(var team in clans[clan]) { 123 | var randomClan = clan; 124 | var randomTeam = 0; 125 | // Get other team 126 | while(randomClan == clan && Object.keys(clans).length > 1 || clans[randomClan].length == 0) { 127 | randomClan = Object.keys(clans)[(Math.floor(Math.random() * Object.keys(clans).length)).toString()]; 128 | randomTeam = Math.floor(Math.random() * clans[randomClan].length); 129 | } 130 | 131 | // Add them to a match 132 | matches.push([clans[clan][team], clans[randomClan][randomTeam]]); 133 | clans[clan].splice(team, 1); 134 | clans[randomClan].splice(randomTeam, 1); 135 | } 136 | } 137 | return matches 138 | } 139 | 140 | function getMatches(callback:Function) { 141 | request.get('http://localhost:1000/war/game/get', (err, response, data) => { 142 | callback(JSON.parse(data)); 143 | }); 144 | } 145 | 146 | // genereateWar(); 147 | getMatches((data) => { 148 | 149 | }); -------------------------------------------------------------------------------- /Manager/index.ts: -------------------------------------------------------------------------------- 1 | // This will actually manage the backend stuff like assigning missions and creating clan wars 2 | 3 | import * as bodyParser from 'body-parser'; 4 | import * as express from 'express'; 5 | import { setup as missionSetup } from './routes/missions'; 6 | import { setup as playerSetup} from "./routes/player"; 7 | import { setup as warsSetup} from "./routes/wars"; 8 | import { setup as leaderboardSetup} from "./routes/leaderboards"; 9 | 10 | var app = express(); 11 | // Configure app 12 | app.use(bodyParser.urlencoded({ extended: false })); 13 | app.use(bodyParser.json()) 14 | app.use(function (request, response, next) { 15 | // response.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200'); 16 | response.setHeader('Access-Control-Allow-Origin', '*'); 17 | response.header("Access-Control-Allow-Headers", "Origin, XRequested-With, Content-Type, Accept "); 18 | response.header('Access-Control-Allow-Methods', 'POST, PATCH, GET, PUT, DELETE, OPTIONS '); 19 | next(); 20 | }); 21 | 22 | 23 | // Add routes 24 | missionSetup(app); 25 | playerSetup(app); 26 | warsSetup(app); 27 | leaderboardSetup(app); 28 | 29 | app.listen(8000); 30 | console.log("App listening on port 8000"); -------------------------------------------------------------------------------- /Manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manager", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "ts-node index.ts" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@types/node": "^10.12.18", 14 | "express": "^4.16.4", 15 | "request": "^2.88.0", 16 | "ts-node": "^7.0.1", 17 | "typescript": "^3.2.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Manager/routes/leaderboards.ts: -------------------------------------------------------------------------------- 1 | const request = require("request"); 2 | const host = "http://localhost:1000"; 3 | export function setup(app) { 4 | console.log("Leaderboards endpoint setup"); 5 | app.route('/leaderboard/current/player/:number').get((req, res) => { 6 | request.get(`${host}/leaderboard/current/player/${req.params.number}`, (err, response, data) => { 7 | res.send(data); 8 | }); 9 | }); 10 | 11 | app.route('/leaderboard/current/clan/:number').get((req, res) => { 12 | request.get(`${host}/leaderboard/current/clan/${req.params.number}`, (err, response, data) => { 13 | if(err) console.log(err); 14 | // console.log(data); 15 | res.send(data); 16 | }); 17 | }); 18 | // app.route("/war/setup") 19 | } -------------------------------------------------------------------------------- /Manager/routes/missions.ts: -------------------------------------------------------------------------------- 1 | const request = require("request"); 2 | const host = "http://localhost:1000"; 3 | export function setup(app) { 4 | console.log("Mission endpoint setup"); 5 | app.route("/missions/user/:user").get((req, res) => { 6 | var user:string = req.params.user; 7 | request.get(`${host}/missions/puuid/${user}`, (err, response, data) => { 8 | // console.log(data); 9 | res.send(data); 10 | }); 11 | }); 12 | } -------------------------------------------------------------------------------- /Manager/routes/player.ts: -------------------------------------------------------------------------------- 1 | const request = require("request"); 2 | const host = "http://localhost:1000"; 3 | export function setup(app) { 4 | console.log("Player endpoint setup"); 5 | app.route("/player/register").put((req, res) => { 6 | var user:string = req.body; 7 | var options = { 8 | form: user 9 | } 10 | request.put(`${host}/player`, options, (err, response, data) => { 11 | // console.log(data); 12 | res.send(data); 13 | }); 14 | }); 15 | 16 | app.route("/player/disconnect").put((req, res) => { 17 | res.send("not yet implemented"); 18 | }); 19 | 20 | app.route("/player/challenge/:id").get((req, res) => { 21 | request.get(`${host}/player/challenge/${req.params.id}`, (err, response, data) => { 22 | if(err) console.log(err); 23 | res.send(data); 24 | }); 25 | }); 26 | } -------------------------------------------------------------------------------- /Manager/routes/wars.ts: -------------------------------------------------------------------------------- 1 | const request = require("request"); 2 | const host = "http://localhost:1000"; 3 | export function setup(app) { 4 | console.log("Wars endpoint setup"); 5 | app.route("/war/register/:puuid").put((req, res) => { 6 | var user:string = req.params.puuid; 7 | request.put(`${host}/war/register/player/${user}`, (err, response, data) => { 8 | if(err) { 9 | res.send(err); 10 | return; 11 | } 12 | res.json(data); 13 | }); 14 | }); 15 | app.route("/war/status/:puuid").get((req, res) => { 16 | request.get(`${host}/war/current/setup`, (err, response, data) => { 17 | var data = JSON.parse(data); 18 | if(data.length == 0) { 19 | res.json({"code": 1}); 20 | return; 21 | } 22 | else { 23 | request.get(`${host}/war/player/${req.params.puuid}/${data[0]["war_id"]}`, (err, response, data) => { 24 | var data = JSON.parse(data); 25 | if(data.length == 0) { 26 | res.json({"code": 2}); 27 | } 28 | else { 29 | res.json({"code": 3}); 30 | } 31 | }); 32 | } 33 | }); 34 | }); 35 | } -------------------------------------------------------------------------------- /Misc JSONS/RIOTCONST.json: -------------------------------------------------------------------------------- 1 | { 2 | "pre_translated_title": "{{title}}", 3 | "pre_translated_details": "{{details}}", 4 | "notifications_dismiss_all_confirm": "You want to dismiss all notifications?", 5 | "notifications_dismiss_all_confirm_yes": "Yes", 6 | "notifications_dismiss_all_confirm_no": "No", 7 | "notifications_tray_title": "Notifications", 8 | "rental_expired_title": "Rental update", 9 | "rental_expired_detail": "Your rental of {{expiredContentName}} has expired.", 10 | "notifications_store_chest_title": "Mystery Gift", 11 | "notifications_store_chest_details": "{{player}} sent you a gift", 12 | "notifications_store_gift_title": "Gift Received", 13 | "notifications_store_gift_details": "You received a gift from {{sender}}!", 14 | "notifications_store_gift_opened_title": "Gift Opened", 15 | "notifications_store_gift_opened_details": "{{receiver}} has opened their gift!", 16 | "notifications_store_reward_title": "Reward Received", 17 | "notifications_store_reward_details": "You received a gift from Riot Games!", 18 | "esports": "Esports Notification", 19 | "esports_toast_watch_game_between": "Watch {{teamA}} vs {{teamB}} LIVE!", 20 | "watch_game_between": "Watch {{teamA}} vs {{teamB}} LIVE!", 21 | "notifications_rgm_header": "Now Live", 22 | "notifications_rgm_title_DARKSTAR": "Dark Star: Singularity", 23 | "notifications_rgm_title_KINGPORO": "Legend of the Poro King", 24 | "notifications_rgm_title_ASSASSINATE": "Blood Moon", 25 | "notifications_rgm_title_ASCENSION": "Ascension", 26 | "notifications_rgm_title_DOOMBOTSTEEMO": "Doom Bots of Doom", 27 | "notifications_rgm_title_URF": "AR URF", 28 | "notifications_rgm_title_STARGUARDIAN": "Invasion", 29 | "notifications_rgm_details": "Click to play!", 30 | "clubs_toast": "Clubs", 31 | "clubs_nominate_sent": "You have successfully nominated {{summonerName}} to {{clubName}}.", 32 | "clubs_invite_sent": "You have successfully invited {{summonerName}} to {{clubName}}.", 33 | "clubs_invite_accept": "{{summonerName}} wants you to join up with {{clubName}}", 34 | "club_promote_member_success_title": "Promotion Success", 35 | "club_promote_member_success_body": "You successfully promoted {{summonerName}} in the club {{clubName}} from a member to an officer.", 36 | "club_demote_member_success_title": "Demotion Success", 37 | "club_demote_member_success_body": "You successfully demoted {{summonerName}} in the club {{clubName}} from an officer to a member.", 38 | "club_button_accept": "ACCEPT", 39 | "club_button_reject": "REJECT", 40 | "clubs_removed": "You have been removed from the club {{clubName}}.", 41 | "clubs_leave": "You have successfully left {{clubName}}", 42 | "clubs_player_nominated": "{{summonerName}} has been nominated to the club {{clubName}}", 43 | "clubs_player_joined": "{{summonerName}} has joined the club {{clubName}}.", 44 | "clubs_player_left": "{{summonerName}} has left the club {{clubName}}.", 45 | "clubs_kick_player": "You have successfully removed {{summonerName}} from the club {{clubName}}.", 46 | "clubs_promote_player": "{{summonerName}} has been promoted to {{roleName}} in the club {{clubName}}", 47 | "clubs_promote_you": "You have been promoted to {{roleName}} in the club {{clubName}}", 48 | "clubs_demote_player": "{{summonerName}} has been demoted to {{roleName}} in the club {{clubName}}", 49 | "clubs_demote_you": "You have been demoted to {{roleName}} in the club {{clubName}}", 50 | "clubs_transfer_ownership": "Ownership of the club {{clubName}} has been transferred to {{summonerName}}.", 51 | "clubs_transfer_ownership_you": "Ownership of the club {{clubName}} has been transferred to you.", 52 | "clubs_new_invite": "{{summonerName}} has invited you to the club {{clubName}}", 53 | "loot_adventure_arcade_event_notification_title": "EVENT NOTIFICATION", 54 | "loot_adventure_arcade_first_event_notification_description": "Play missions and earn rewards during Arcade Boss World | {{startDate}} - {{endDate}}", 55 | "loot_adventure_arcade_second_event_notification_description": "Arcade Boss World ends soon. Complete missions before time’s up on {{endDate}}!", 56 | "loot_adventure_arcade_third_event_notification_description": "Arcade Boss World has ended. Redeem your tickets before they disappear on {{redeemDate}}. Thanks for playing!", 57 | "new_mission_title": "NEW MISSION", 58 | "new_mission_details": "{{mission_title}} has been added to your missions.", 59 | "new_mission_title_multi": "NEW MISSIONS", 60 | "new_mission_details_multi": "{{quantity}} New Missions have been added.", 61 | "player_behavior_chat_restriction_notification_header_plural": "Chat is restricted for {{gamesRemaining}} games", 62 | "player_behavior_chat_restriction_notification_header_singular": "Chat is restricted for {{gamesRemaining}} game", 63 | "player_behavior_chat_restriction_notification_body": "Because of unsportsmanlike conduct, your in-game chat is being limited. Once you've completed the required number of games, the restriction will go away.", 64 | "player_behavior_chat_restriction_notification_finished_header": "Chat restriction complete", 65 | "player_behavior_chat_restriction_notification_finished_body": "You completed your chat restriction. Keep it sportsmanlike from here on out!", 66 | "leaver_buster_punishment_header": "LeaverBuster", 67 | "leaver_buster_punishment_body_plural": "Because you either left or AFK'd during a game, you're being placed in low priority queues for {{gamesRemaining}} matchmade games.", 68 | "leaver_buster_punishment_body_singular": "Because you either left or AFK'd during a game, you're being placed in low priority queues for {{gamesRemaining}} matchmade game.", 69 | "leaver_buster_reforming_header": "LeaverBuster", 70 | "leaver_buster_reforming_body": "You've been leaving or AFKing in fewer games recently, and your account status has improved. Thanks for making an effort to be a better teammate.", 71 | "tooltip_dismiss": "Dismiss", 72 | "tooltip_dismiss_all": "Dismiss All", 73 | "UNKNOWN": "Unknown", 74 | "MEMBER": "Member", 75 | "OFFICER": "Officer", 76 | "INVITEE": "Invitee", 77 | "OWNER": "Owner", 78 | "clash_notification_title_DISMISS": "Clash Team Disbanded", 79 | "clash_notification_title_SUGGESTION": "Suggested Clash Invite", 80 | "clash_notification_title_ACCEPT_INVITE": "Clash Invite Accepted", 81 | "clash_notification_title_DECLINE_INVITE": "Clash Invite Declined", 82 | "clash_notification_title_REVOKE_INVITE": "Clash Invite Revoked", 83 | "clash_notification_title_LEAVE": "Player Abandoned Clash Team", 84 | "clash_notification_title_CAPTAIN_LEAVE": "Player Abandoned Clash Team", 85 | "clash_notification_title_CAPTAIN_LEAVE_SELF_PROMOTED": "Team Captain Update", 86 | "clash_notification_title_ACCEPT_SUGGESTION": "Clash Invite Suggestion Accepted", 87 | "clash_notification_title_DECLINE_SUGGESTION": "Clash Invite Suggestion Declined", 88 | "clash_notification_title_ACCEPT_TICKET": "Clash Ticket Offer Accepted", 89 | "clash_notification_title_DECLINE_TICKET": "Clash Ticket Offer Declined", 90 | "clash_notification_title_ROSTER_ACCEPT_TICKET": "Clash Ticket Offer Accepted", 91 | "clash_notification_title_ROSTER_DECLINE_TICKET": "Clash Ticket Offer Declined", 92 | "clash_notification_title_OWNER_CLOSE": "Clash Team Disbanded", 93 | "clash_notification_title_OWNER_TRANSFER": "Clash Team Captain Update", 94 | "clash_notification_title_KICK_SELF": "Kicked from Clash Team", 95 | "clash_notification_title_KICK": "Clash Teammate Kicked", 96 | "clash_notification_title_INVITE": "Invited to Join Clash Team", 97 | "clash_notification_title_RESENT_INVITE": "Invited to Join Clash Team", 98 | "clash_notification_title_REGISTERED": "Clash Team Registered!", 99 | "clash_notification_title_PHASE_CHECKIN": "Clash Team Locked In!", 100 | "clash_notification_title_QUEUE_DODGE": "A Player has Exited the Match", 101 | "clash_notification_title_BYE_AUTO_WIN": "Clash Game Won via Bye", 102 | "clash_notification_title_RESTRICTION_AUTO_WIN": "Clash Game Won by Default", 103 | "clash_notification_title_GAME_COMPLETED": "Clash Game Completed", 104 | "clash_notification_title_GAME_STARTED_ERROR": "Error Starting Clash Game", 105 | "clash_notification_title_WITHDRAW_SELF": "Withdrawn from Clash", 106 | "clash_notification_title_WITHDRAW_OPPONENT": "Opponent has Withdrawn", 107 | "clash_notification_title_ROUND_COMPLETE": "Clash Round Complete", 108 | "clash_notification_title_SUB_INVITE": "Invited to Substitute for Clash", 109 | "clash_notification_title_SUB_INVITE_SELF": "Substituted Out for Clash", 110 | "clash_notification_title_SUB_ACCEPT": "Substitute Invite Accepted", 111 | "clash_notification_title_SUB_DECLINE": "Substitute Invite Declined", 112 | "clash_notification_title_SUB_RECLAIM": "Substitute Position Reclaimed", 113 | "clash_notification_title_SUB_REVOKE": "Substitute Position Revoked", 114 | "clash_notification_title_SUB_LEAVE": "Substitute Abandoned Clash Team", 115 | "clash_notification_title_MEMBER_SUBBED": "Teammate Unable to Compete", 116 | "clash_notification_title_REWARD_GRANT_FAILED": "Clash Rewards Error", 117 | "clash_notification_title_REWARD_GRANT_RETRY": "Clash Rewards Granted", 118 | "clash_notification_title_GAME_START_RETRY": "Clash Start Error - Retry {{currentRetry}} of {{maxRetry}}", 119 | "clash_notification_title_GAME_START_FAILED": "Clash Game Start Failure", 120 | "clash_notification_description_DISMISS": "Your Clash team has disbanded. All tickets have been refunded.", 121 | "clash_notification_description_SUGGESTION": "A player has been suggested to your team.", 122 | "clash_notification_description_ACCEPT_INVITE": "{{sourcePlayer}} accepted your invite and is locking in.", 123 | "clash_notification_description_DECLINE_INVITE": "{{sourcePlayer}} has declined to join your team.", 124 | "clash_notification_description_REVOKE_INVITE": "{{sourcePlayer}} revoked an invite.", 125 | "clash_notification_description_LEAVE": "{{sourcePlayer}} has left the team.", 126 | "clash_notification_description_CAPTAIN_LEAVE": "The captain of your Clash team has left. A new captain has been picked.", 127 | "clash_notification_description_CAPTAIN_LEAVE_SELF_PROMOTED": "The captain of your Clash team has left. You have been promoted to Captain.", 128 | "clash_notification_description_ACCEPT_SUGGESTION": "The team captain has sent an invite to your suggested player.", 129 | "clash_notification_description_DECLINE_SUGGESTION": "The team captain declined your invite suggestion.", 130 | "clash_notification_description_ACCEPT_TICKET": "{{sourcePlayer}} has accepted your offer of a ticket for this tournament.", 131 | "clash_notification_description_DECLINE_TICKET": "{{sourcePlayer}} has declined your offer of a ticket for this tournament.", 132 | "clash_notification_description_ROSTER_ACCEPT_TICKET": "{{sourcePlayer}} has accepted your offer of a ticket for this tournament.", 133 | "clash_notification_description_ROSTER_DECLINE_TICKET": "{{sourcePlayer}} has declined your offer of a ticket for this tournament.", 134 | "clash_notification_description_OWNER_CLOSE": "The captain disbanded the team. All tickets have been refunded.", 135 | "clash_notification_description_OWNER_TRANSFER": "You've been promoted to captain.", 136 | "clash_notification_description_KICK_SELF": "The captain has removed you from the team.", 137 | "clash_notification_description_KICK": "The captain has removed {{sourcePlayer}} from the team.", 138 | "clash_notification_description_INVITE": "You're invited to join {{rosterName}} for Clash.", 139 | "clash_notification_description_RESENT_INVITE": "You're invited to join {{rosterName}} for Clash.", 140 | "clash_notification_description_REGISTERED": "Your team is registered for Clash!", 141 | "clash_notification_description_PHASE_CHECKIN": "Your Clash team is locked in.", 142 | "clash_notification_description_QUEUE_DODGE": "A player has dodged champion select or did not show for your match. Your match has been completed.", 143 | "clash_notification_description_BYE_AUTO_WIN": "Congratulations! Your team has completed a match.", 144 | "clash_notification_description_RESTRICTION_AUTO_WIN": "Congratulations! Your team has completed a match.", 145 | "clash_notification_description_GAME_COMPLETED": "Your team has completed a Clash match!", 146 | "clash_notification_description_GAME_STARTED_ERROR": "Trouble starting Match. Please wait.", 147 | "clash_notification_description_WITHDRAW_SELF": "Your team has been removed from the bracket. You'll receive any prizes earned up until this point.", 148 | "clash_notification_description_WITHDRAW_OPPONENT": "The opposing team has forfeited and withdrawn from Clash.", 149 | "clash_notification_description_ROUND_COMPLETE": "The current round has been completed.", 150 | "clash_notification_description_SUB_INVITE": "You have been invited to substitute on a Clash team for the upcoming phase.", 151 | "clash_notification_description_SUB_INVITE_SELF": "Your team has chosen a substitute for you. You may still reclaim your spot.", 152 | "clash_notification_description_SUB_ACCEPT": "{{sourcePlayer}} has accepted the invite to substitute for the upcoming phase.", 153 | "clash_notification_description_SUB_DECLINE": "{{sourcePlayer}} has declined the invite to substitute for the upcoming phase.", 154 | "clash_notification_description_SUB_RECLAIM": "The substituted player has returned to the Clash roster. You have been removed.", 155 | "clash_notification_description_SUB_REVOKE": "Your subsitute position has been revoked by the team captain.", 156 | "clash_notification_description_SUB_LEAVE": "{{sourcePlayer}} (substitute) has left the team.", 157 | "clash_notification_description_MEMBER_SUBBED": "{{sourcePlayer}} is subbing for another team. You’ll need to find a sub to compete.", 158 | "clash_notification_description_REWARD_GRANT_FAILED": "Due to an error with Clash, we could not deliver your rewards. We’ll be trying again shortly!", 159 | "clash_notification_description_REWARD_GRANT_RETRY": "Clash rewards delivered! Check your Profile and Loot to see what you got!", 160 | "clash_notification_description_GAME_START_RETRY": "Your Clash match could not be started because a summoner could not be reached. Retrying in {{retrySeconds}} seconds.", 161 | "clash_notification_description_GAME_START_FAILED": "We were unable to reach a summoner. Your Clash match has been completed.", 162 | "clash_notification_title_BRACKET_STATUS_COMPLETE": "Bracket Complete", 163 | "clash_notification_title_BRACKET_STATUS_COMPLETE_WIN": "Congratulations on Your Victory!", 164 | "clash_notification_title_BRACKET_STATUS_COMPLETE_ELIMINATED": "You've been eliminated from Clash.", 165 | "clash_notification_title_BRACKET_STATUS_COMPLETE_TOURNAMENT_OVER": "Thanks For Playing in Clash!", 166 | "clash_notification_title_BRACKET_STATUS_NEXT_MATCH_LOSERS": "You're In the Consolation Bracket", 167 | "clash_notification_title_BRACKET_STATUS_NEXT_MATCH": "Next Match Coming Up. Scouting is Open.", 168 | "clash_notification_title_BRACKET_STATUS_NEXT_MATCH_BYE": "You Have a Bye (Free Win!)", 169 | "clash_notification_title_BRACKET_READY": "Scouting is Open", 170 | "clash_notification_description_BRACKET_STATUS_COMPLETE": "Your bracket is over.", 171 | "clash_notification_description_BRACKET_STATUS_COMPLETE_WIN": "Congratulations! Come back on the next day for a bigger bracket and fight for another Trophy!", 172 | "clash_notification_description_BRACKET_STATUS_COMPLETE_ELIMINATED": "You're eliminated from this bracket. Buy back in for a shot at cool prizes!", 173 | "clash_notification_description_BRACKET_STATUS_COMPLETE_TOURNAMENT_OVER": "Clash has ended. Check the Clash Hub for upcoming events!", 174 | "clash_notification_description_BRACKET_STATUS_NEXT_MATCH_LOSERS": "You've lost the bracket. Keep playing for more prizes.", 175 | "clash_notification_description_BRACKET_STATUS_NEXT_MATCH": "The next round starts soon. Scout your opponents!", 176 | "clash_notification_description_BRACKET_STATUS_NEXT_MATCH_BYE": "You have a free win! You have no opponent for the current match.", 177 | "clash_notification_description_BRACKET_READY": "You've been placed into a bracket. Scout your opponents now!", 178 | "clash_notification_title_UPDATE_PHASE": "Clash: Schedule Changed", 179 | "clash_notification_description_UPDATE_PHASE": "The schedule for Clash has been updated! See the Clash page for more information.", 180 | "clash_notification_title_CR_TOURNAMENT_REGISTRATION_OPEN": "Clash Team Creation Available", 181 | "clash_notification_description_CR_TOURNAMENT_REGISTRATION_OPEN": "Head to the Clash tab and form your team!", 182 | "clash_notification_title_CR_TOURNAMENT_LOCK_IN_OPEN": "Clash Lock In Available", 183 | "clash_notification_description_CR_TOURNAMENT_LOCK_IN_OPEN": "The Clash tournament starts soon. Head to the Clash tab to Lock In!", 184 | "ranked_notification_eos_last_day_of_season_title": "RANKED 2018 ENDS SOON!", 185 | "ranked_notification_eos_last_day_of_season_detail": "Last day of the ranked season is November 12", 186 | "ranked_notification_eos_ended": "THE RANKED 2018 SEASON HAS ENDED", 187 | "ranked_notification_eos_BRONZE": "Congrats on completing the Ranked season! Here are your rewards!", 188 | "ranked_notification_eos_SILVER": "Congrats on completing the Ranked season! Keep climbing, we believe.", 189 | "ranked_notification_eos_GOLD": "You are victorious! Congratulations on finishing in Gold.", 190 | "ranked_notification_eos_PLATINUM": "Congratulations! You’re in the top 11.3% of League players.", 191 | "ranked_notification_eos_DIAMOND": "Few can call themselves Diamond players. Specifically, only 0.6% can. Congratulations on completing the Ranked season.", 192 | "ranked_notification_eos_MASTER": "Congratulations! You’ve completed this Ranked season at the highest levels of competitive play.", 193 | "ranked_notification_eos_CHALLENGER": "Congratulations! You’ve completed this Ranked season at the highest levels of competitive play.", 194 | "merch_notification_general_reward_header": "MERCH REWARD UNLOCKED", 195 | "merch_notification_space_odyssey_2018_complete_body": "Go to the merch website by 10/08/18 to pre-order the Morning Star Crew Jacket!" 196 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Insert MissionsHere 2 | [Winner of the Creativity Category of the 2018 Riot API Challenge](https://www.riotgames.com/en/DevRel/api-challenge-recap-winter-2019) 3 | 4 | Created by Earleking and Vexrax 5 | 6 | [Leaderboard Website](http://riot-api-2018.herokuapp.com/) 7 | 8 | [Client](https://github.com/Earleking/2018-Riot-API-Challenge/tree/master/Client) 9 | 10 | # Introduction 11 | Hello, this is our project, Insert Mission Here. The goal of this project was to help foster a stronger communal environment in League of Legends. While you play with 9 other people each game there is often very little connecting any of you before or after the game. With Insert Mission Here we try to achieve this goal by using what we feel is an underdeveloped feature that Riot has previously implemented, Clubs. 12 | 13 | # What it Does 14 | There are 2 main parts to this project - missions for clubs and individuals along with what we have dubbed Club Wars. When a user completes a mission they earn points towards their respective leaderboard, either their club leaderboard or their individual leaderboard. Players can accumulate Leaderboard Points and the top scorers of these points are displayed on the leaderboards page on our website. In addition to regular missions you can earn points by challenging you fellow players and clubs, a club war will put you into a winner take all 5v5 summoner's rift match where you will take points from the other team if you win. A solo war will put you into a howling abyss match. We feel like our project will not only give players a reason to be in a club but also give them extra incentive to complete missions. 15 | ## Missions 16 | 17 | ![Image of Missions](https://i.imgur.com/5KqLV9F.png) 18 | 19 | In Insert Mission Here we are able to assign custom missions. There are two main types of these missions - club missions and solo missions. The solo missions are assigned to each player and they must be completed individually. Club missions on the other hand are assigned to a Club. When logged into the client every Player in the Club will be assigned this mission and every player is able to contribute to the missions success. 20 | 21 | We hope that these missions give people a reason to engage with their club members to complete the Club missions. Ultimately we hope this will both help foster engagement in current players as well as try to get existing players to help their friends get into the game. 22 | 23 | ## Club Wars 24 | 25 | ![Image of Client Showing A 1v1](https://i.imgur.com/vdp15lN.png) 26 | 27 | While group missions are great, we wanted to create a singular event that would bring entire clubs together to play, cheer and win together. To this end we created Club Wars. These would be auto generated custom games between Clubs. You would register for a club war and then when the time came you would be automatically inserted into a game along with your teammates to face off against another clan for honor, glory, and some leaderboard points. 28 | 29 | ## Club Challenges 30 | 31 | While the Club Wars allow larger for larger Club events, we also wanted to allow for players who might not be able to partake in these due to real world constraints to participate in Club conflicts. These are 1v1s that can take place between members of different Clubs at any time. You can go onto the web portal and challenge any of the available players to a 1v1. You can’t challenge anyone from the same Club because friends shouldn’t fight. 32 | 33 | # The Client 34 | #### Technologies 35 | ![alt-text](https://d2eip9sf3oo6c2.cloudfront.net/tags/images/000/000/256/square_256/nodejslogo.png) ![alt-text](https://d2eip9sf3oo6c2.cloudfront.net/tags/images/000/000/377/square_256/typescriptlang.png) 36 | ![alt-text](http://icons.iconarchive.com/icons/papirus-team/papirus-apps/256/electron-icon.png) 37 | 38 | The main technologies that are used in the client are Electron, Typescript, and Node. We use electron to host the main application and run connections to our server and the LCU. We also use electron because it allows for us to compile our project to an .exe for a demo. We use Node for general compiling and websocket/request support. Finally we use typescript since its a nice language to use when writing large pieces of code and compiles down to javascript. 39 | 40 | #### Setting Up Missions 41 | We realized very early into development that the LCU doesn’t load missions on the fly. It only loads/updates them at two points, after a user has logged in and after a match has completed. 42 | A diagram of how the LCU **normally** handles missions on startup can be found below: 43 | ![alt-text](https://raw.githubusercontent.com/Earleking/2018-Riot-API-Challenge/master/Documentation/LCU%20Missions%20-_%20Riot%20Implimentation.png) 44 | 45 | In order to have the missions appear on the server we go through a couple steps: 46 | 1. User starts our program. 47 | 2. Program starts the league client. 48 | 3. Program wait for user to log in. 49 | 4. If user is not registered on site, register them and generate missions. 50 | 5. Program logs the command line arguments and kills the render process. 51 | 6. Program sets up a middleman server that connects to the LCU. 52 | 7. Render process is restarted with command line arguments that map the renderer to the middleman proxy. 53 | 8. When the program sees the /lol-missions/* endpoint being called it requests missions from our server and adds them to the missions from riots server. 54 | 9. Missions go to the render and the user sees both riot and our missions. 55 | 56 | These steps can be represented by this diagram: 57 | 58 | ![](https://raw.githubusercontent.com/Earleking/2018-Riot-API-Challenge/master/Documentation/Riot%20Missions%20our%20Implimentation.png) 59 | 60 | When you log in the home screen will have missions similar to the ones in the image below or at the following **youtube** link: 61 | http://www.youtube.com/watch?v=nxp2FtBHDmY 62 | 63 | [![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/nxp2FtBHDmY/0.jpg)](http://www.youtube.com/watch?v=nxp2FtBHDmY) 64 | 65 | 66 | 67 | Endpoints used: 68 | * /lol-summoner/v1/current-summoner 69 | * /lol-missions/v1/missions 70 | * /missions/user/${puuid} 71 | * /lol-clubs/v1/clubs 72 | 73 | ### Setting Up Infrastructure for 1v1 and Clan Wars 74 | players have the ability to challenge other players from other teams to a 1v1 for points. In order to do this we run a client side endpoint with 3 main endpoints which the server can call: 75 | * endpoint for creating lobby 76 | * endpoint for sending invites 77 | * endpoint for registering on the 1v1 78 | When the client initially launches it creates a tunnel for the server to communicate with the client. Then whenever a 1v1 is initiated the server calls the endpoints on the client to send out the invites. If the user wants to accept the invite then they can 1v1. The match is setup with the following json 79 | 80 | ``` 81 | { 82 | "customGameLobby": 83 | { 84 | "configuration": 85 | { 86 | "gameMode":"ARAM", 87 | "gameMutator":"", 88 | "gameServerRegion":"", 89 | "mapId":12, 90 | "mutators":{"id":6}, 91 | "spectatorPolicy":"AllAllowed", 92 | "teamSize":1 93 | }, 94 | "lobbyName":Lobbyname, 95 | "lobbyPassword":"PATRICKIN2018" 96 | }, 97 | "isCustom":true 98 | } 99 | ``` 100 | Gamemode is aram which maps to the aram map but he draft strategy is tournament so that you can ban out some of the opponents champions or some of the OP champions. We decided to use a static password because in reality these lobbies are going to be filling up extremely quickly with our auto accept invite. In the future this would be set to a random hash. 101 | 102 | You can see a demo of the feature over here: 103 | https://youtu.be/zkY1zZsIDiE?t=28 104 | [![IMAGE ALT TEXT HERE](https://i.imgur.com/SY1yqci.png)](https://youtu.be/zkY1zZsIDiE?t=28) 105 | 106 | Endpoints used: 107 | * /lol-summoner/v1/current-summoner 108 | * /lol-lobby/v2/lobby 109 | * /lol-lobby/v2/lobby/invitations 110 | * /lol-lobby/v2/received-invitations/ 111 | 112 | ### Technical Challenges In The Client 113 | The main issue with this client was setting up the proxy, the LCU makes setting up the client a nightmare with multiple hoops that need to be jumped through in order to have your own custom missions appear. Another issue is that the client seems to re-instantiate the LCU on champ select so if we want to keep the UI clean we have to manually manage the render processes. Since the RiotClient endpoint isn’t allowed we used manual process killing and starting for a large part of the application which made the behaviour a lot more flakey than we would like. If you would like to see the less flakey behaviour feel free to enable the Riot client calls and disable the line below them. The proxy that we set up also adds a lot of overhead in terms of load time to UI components, this slows the client down quite a bit. 114 | 115 | Another smaller issue that we experienced was a timing issue, we needed to delay sending the invite for 5000ms because sometimes the lobby wouldn’t be ready by the time our code hit the invite to lobby line. 116 | 117 | ### Closing Remarks 118 | Overall the client was fairly hard to work with, especially learning how to set up the proxy so that we could see missions through the client was hard but rewarding. I would like to see more official support from Riot to better document the client for us developers to work with. I think that we could make a lot more awesome stuff if we better understood how the client worked. 119 | 120 | 121 | ### How To Install 122 | As we use a proxy for displaying the missions in the client this application unfortuantly is unsuitable for public distribution. 123 | 124 | 125 | 126 | # Web Development 127 | The server was divided up into 4 main sections. These are the database itself (Database), a server to access the database (Database Server) and a management server (Management Server) that contains all the business logic and finally the Website. 128 | 129 | ![Image of Overall Architecture](https://i.imgur.com/FnAtuMf.png) 130 | 131 | ## Management Server 132 | The management server is the center of Insert Mission Here. It is what connects all of the individual pieces together turning it into one functioning body. The website and the client both make calls for data and functionality to this server. After querying the Database Server and transforming any necessary data it returns them where they are displayed. This server is created using TypeScript along with an Express server. Along with the Database Server it is served on an EC2 Amazon Web Services instance. 133 | 134 | ## Database and Database Server 135 | 136 | ![Image of ER Diagram](https://i.imgur.com/1B7k3rn.png) 137 | 138 | Image above is the diagram of our database Schema. We needed to be able to extensively store data so that we can record mission progress as well as any club wars that are occuring. The database uses MySQL. We decided that because of how tightly linked a lot of this data is a relational database would be the best choice. From their we decided to use MySQL as it was one we both were familiar with. The final database is currently being hosted by Amazon Web Services. 139 | 140 | The Database Server is coded in TypeScript and is very barebones. It is only able to access and return data from the database. No data transformation occurs here and this server can only be accessed locally by the Management Server which is running on the same machine as it. 141 | 142 | ## Website 143 | ![Image of Angular Logo](https://angular.io/assets/images/logos/angularjs/AngularJS-Shield.svg) 144 | 145 | Keeping in line with the TypeScript theme, Angular 7 was used to create the web portal for Insert Mission Here. We both wanted to use Angular in particular for this project as neither of us had extensive experience with Single-Page Websites and they seemed extremely powerful. Using Angular’s powerful templating engine and dynamic http calls made the entire development process much easier. 146 | 147 | ## Issues 148 | Throughout this development process we encountered many issues while developing our web applications. One of the main issues was communication. To ease the integration of our many components we used HTTP methods to communicate. While this was easy at first it became much more difficult near the end of the project when more data needed to be passed quickly. It was a major problem when we were trying to determine when someone disconnected from our application as if it is force closed there is no time to send a closing HTTP request. If we were to do a project like this we would used sockets to communicate between our Client and our Management Server. 149 | 150 | Another issue that we encountered was recording mission progress. Because each mission needs to be updated individually with different code multiple implementations would need to be used. This is quite time consuming even for the small number of missions that we created for the purpose of the project. At the moment we are unsure of how to fix this in the future while maintaining diversity in the types missions. 151 | 152 | # Future Plans 153 | Unfortunately there are likely not to be many future for this project in its’ entirety. To display missions we had to setup a proxy that intercepts and alters mission data before it gets to your client. While allowed for this API Challenge, this is not acceptable for a publicly distributed application. Due to this there it is likely that even if we continue this project, it will be in a different form. 154 | 155 | However that is not to say we have no regrets with this project. There were a lot of things that we wanted to include but simply didn’t have the time for. For example we wanted to create personalized missions for players. For example, if we noticed that you were a bad warder in past games we would give you missions such as, “Achieve x vision score” in a game. Another idea for the future was creating pools of clans. We would try and match up clans of similar size into smaller pools. The goal of this would be to eventually create a group of people that you get to know by playing against them consistently. 156 | 157 | # Conclusion 158 | 159 | Expanding upon Clubs is something that we both feel is something that should be done. This project is simply our image of that future. Whether it takes this form or not, if Riot does end up expanding upon the vision of Clubs it is something that we think everyone should be excited about. 160 | 161 | Thank you for taking the time to look through our project! 162 | 163 | ![Poro waving image](https://vignette.wikia.nocookie.net/leagueoflegends/images/a/ab/Peace_Poro_Emote.png/revision/latest?cb=20181207233837) 164 | 165 | 166 | Insert Mission Here was created under Riot Games' "Legal Jibber Jabber" policy and uses assets owned by Riot Games. Riot Games does not endorse or sponsor this project. 167 | -------------------------------------------------------------------------------- /Server/globals.ts: -------------------------------------------------------------------------------- 1 | import { RiotApi } from "./riotApi/index"; 2 | export var riotApi = new RiotApi(process.env.RIOT_KEY); -------------------------------------------------------------------------------- /Server/index.ts: -------------------------------------------------------------------------------- 1 | // var express = require('express') 2 | // var app = express() 3 | // var mysql = require('mysql'); 4 | 5 | import * as mysql from 'mysql'; 6 | import * as bodyParser from 'body-parser'; 7 | import * as express from 'express'; 8 | import * as dotenv from 'dotenv'; 9 | // TODO Refactor database to use more fucking logical ids 10 | dotenv.config({path: `${__dirname}/.env`}); 11 | var https = require('https'); 12 | import { setup as missionSetup } from './routes/missions'; 13 | import { setup as playerSetup } from './routes/player'; 14 | import { setup as leaderboardSetup } from './routes/leaderboard'; 15 | import { setup as warSetup } from './routes/wars'; 16 | 17 | import { SQL } from './sql_functions'; 18 | 19 | var app = express(); 20 | // Configure app 21 | app.use(bodyParser.urlencoded({ extended: false, limit: '50mb' })); 22 | app.use(bodyParser.json()) 23 | // Connect to mysql 24 | var conn = mysql.createConnection({ 25 | host: "riot-2018.chwqb24onmqi.ca-central-1.rds.amazonaws.com", 26 | user: "root", 27 | password: "password", 28 | database: "riot-2018" 29 | }); 30 | 31 | app.use(function (request, response, next) { 32 | // response.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200'); 33 | response.setHeader('Access-Control-Allow-Origin', '*'); 34 | response.header("Access-Control-Allow-Headers", "Origin, XRequested-With, Content-Type, Accept "); 35 | response.header('Access-Control-Allow-Methods', 'POST, PATCH, GET, PUT, DELETE, OPTIONS '); 36 | next(); 37 | }); 38 | 39 | // respond with "hello world" when a GET request is made to the homepage 40 | app.route("/").get((req, res) => { 41 | res.send('hello world'); 42 | }); 43 | 44 | // Connect to database 45 | conn.connect((err) => { 46 | if(err) throw err; 47 | console.log("Connection to database established"); 48 | 49 | var SQLData:SQL = createSQL(); 50 | initializeRoutes(SQLData); 51 | 52 | app.listen(1000); 53 | // var server = https.createServer(app).listen(1000, function() { 54 | // console.log('Https App started'); 55 | // }); 56 | console.log("Listening on port 1000"); 57 | }); 58 | 59 | function initializeRoutes(SQLData:SQL) { 60 | // Setup paths 61 | missionSetup(app, SQLData); 62 | playerSetup(app, SQLData); 63 | leaderboardSetup(app, SQLData); 64 | warSetup(app, SQLData); 65 | } 66 | 67 | function createSQL():SQL { 68 | return new SQL(conn); 69 | } -------------------------------------------------------------------------------- /Server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "ts-node index.ts" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@types/node": "^10.12.18", 14 | "body-parser": "^1.18.3", 15 | "dotenv": "^6.2.0", 16 | "express": "^4.16.4", 17 | "mysql": "^2.16.0", 18 | "request": "^2.88.0", 19 | "request-ip": "^2.1.3", 20 | "ts-node": "^7.0.1", 21 | "typescript": "^3.2.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Server/riotApi/index.ts: -------------------------------------------------------------------------------- 1 | import { userAPI } from "./user"; 2 | 3 | export class RiotApi { 4 | private host = "https://na1.api.riotgames.com"; 5 | 6 | public user:userAPI; 7 | constructor(private key:string) { 8 | // Create the header access 9 | var headers = { 10 | "X-Riot-Token": key 11 | } 12 | 13 | // Setup child classes 14 | this.user = new userAPI(this.host, headers); 15 | } 16 | } -------------------------------------------------------------------------------- /Server/riotApi/user.ts: -------------------------------------------------------------------------------- 1 | const request = require("request"); 2 | export class userAPI { 3 | constructor(private host:string, private headers:object) { 4 | 5 | } 6 | getUser(summonerName:string, callback:Function) { 7 | var options = { 8 | url: this.host + `/lol/summoner/v4/summoners/by-name/${summonerName}`, 9 | headers: this.headers 10 | } 11 | request.get(options, (err, res, data) => { 12 | callback(JSON.parse(data)); 13 | }); 14 | } 15 | } -------------------------------------------------------------------------------- /Server/routes/leaderboard.ts: -------------------------------------------------------------------------------- 1 | import { SQL } from "../sql_functions"; 2 | 3 | export function setup(app, sql:SQL) { 4 | app.route('/leaderboard/current/player/:number').get((req, res) => { 5 | sql.leaderboard.getCurrentPlayerLeaderboard(req.params.number, (results) => { 6 | res.send(results); 7 | }); 8 | }); 9 | 10 | app.route('/leaderboard/current/clan/:number').get((req, res) => { 11 | sql.leaderboard.getCurrentClanLeaderboard(req.params.number, (results) => { 12 | res.send(results); 13 | }); 14 | }); 15 | 16 | // Start new leaderboard for the current month 17 | app.route('/leaderboard/new').put((req, res) => { 18 | sql.leaderboard.addNewLeaderboards(() => { 19 | res.send("New Leaderboards created"); 20 | }); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /Server/routes/missions.ts: -------------------------------------------------------------------------------- 1 | import { SQL } from "../sql_functions"; 2 | 3 | export function setup(app, sql:SQL) { 4 | // Id is SummonerID 5 | app.route("/missions/summoner/:id").get((req, res) => { 6 | sql.mission.getMissionsBySummonerId(req.params.id, (results) => { 7 | res.send(results); 8 | }); 9 | }) 10 | .put((req, res) => { 11 | res.json({"message": "not yet implemented"}); 12 | }); 13 | 14 | app.route("/missions/account/:id").get((req, res) => { 15 | sql.mission.getMissionsByAccountId(req.params.id, (results) => { 16 | res.send(results); 17 | }); 18 | }) 19 | .put((req, res) => { 20 | res.json({"message": "not yet implemented"}); 21 | }); 22 | 23 | app.route("/missions/puuid/:id").get((req, res) => { 24 | sql.mission.getMissionsByPuuid(req.params.id, (results) => { 25 | res.send(results); 26 | }); 27 | }) 28 | .put((req, res) => { 29 | res.json({"message": "not yet implemented"}); 30 | }); 31 | 32 | app.route("/missions/update/:id").put((req, res) => { 33 | // Only accepts an update to the quantity 34 | if(req.body.current_progress == undefined) { 35 | res.json({"message": "Please supply the new progress as current_progress"}); 36 | } 37 | sql.mission.updateMissionProgress(req.params.id, req.body.current_progress, (results) => { 38 | res.send(results); 39 | }); 40 | }); 41 | 42 | } 43 | 44 | // module.exports = missionsRoute; 45 | -------------------------------------------------------------------------------- /Server/routes/player.ts: -------------------------------------------------------------------------------- 1 | import {} from "../sql_functions/player"; 2 | import { SQL } from "../sql_functions"; 3 | import { riotApi } from "../globals"; 4 | var requestIp = require('request-ip'); 5 | 6 | export function setup(app, sql:SQL) { 7 | // Adding players 8 | app.route("/player").put((req, res) => { 9 | var body:Object = req.body; 10 | if(body["display_name"] == undefined) { 11 | res.json({"message": "A display_name is required"}); 12 | return; 13 | } 14 | console.log(requestIp.getClientIp(req)); 15 | if(body["clan_tag"] != undefined && body["clan_tag"].length >= 1 && body["clan_name"] != undefined && body["clan_name"].length >= 1) { 16 | // They have a clan 17 | sql.clan.addClan(body, () => { 18 | 19 | // Add player 20 | sql.player.addPlayer(body, () => { 21 | sql.player.setIp(body["puuid"], body["ip"], () => { 22 | // Add player to clan 23 | sql.clan.addPlayer(body["clan_tag"], body["puuid"], () => { 24 | console.log("Player added"); 25 | res.json({"message": "Player added"}); 26 | return; 27 | }); 28 | }); 29 | }); 30 | }); 31 | } 32 | else { 33 | // They don't have a clan 34 | sql.player.addPlayer(body, () => { 35 | sql.player.setIp(body["puuid"], body["ip"], () => { 36 | console.log("Player added"); 37 | res.json({"message": "Player added"}); 38 | return; 39 | }); 40 | }); 41 | } 42 | 43 | 44 | }); 45 | 46 | // Summoner ID 47 | app.route("/player/summoner/:id").get((req, res) => { 48 | sql.player.selectPlayerBySummonerId(req.params.id, (results) => { 49 | res.send(results); 50 | }); 51 | }); 52 | 53 | // Account ID 54 | app.route("/player/account/:id").get((req, res) => { 55 | sql.player.selectPlayerByAccountId(req.params.id, (results) => { 56 | res.send(results); 57 | }); 58 | }); 59 | 60 | // Account ID 61 | app.route("/player/puuid/:id").get((req, res) => { 62 | sql.player.selectPlayerByPuuid(req.params.id, (results) => { 63 | res.send(results); 64 | }); 65 | }); 66 | 67 | app.route("/player/challenge/:id").get((req, res) => { 68 | sql.player.getChallengePlayers(req.params.id, (results) => { 69 | res.send(results); 70 | }) 71 | }); 72 | } -------------------------------------------------------------------------------- /Server/routes/wars.ts: -------------------------------------------------------------------------------- 1 | import { SQL } from "../sql_functions"; 2 | 3 | export function setup(app, sql:SQL) { 4 | app.route("/war/new").put((req, res) => { 5 | 6 | }); 7 | app.route("/war/register/player/:puuid").put((req, res) => { 8 | sql.wars.registerPlayer(req.params["puuid"], (msg) => { 9 | res.json(msg); 10 | }); 11 | }); 12 | app.route("/war/signup/start").put((req, res) => { 13 | sql.wars.openSignUp((msg) => { 14 | res.json(msg); 15 | }); 16 | }); 17 | app.route("/war/setup/start").put((req, res) => { 18 | sql.wars.setUpWar((response) => { 19 | res.send(response); 20 | }); 21 | }); 22 | app.route("/war/games/start").put((req, res) => { 23 | sql.wars.startWar((response) => { 24 | res.send(response); 25 | }); 26 | }); 27 | app.route("/war/players/get/:id").get((req, res) => { 28 | sql.wars.getPlayers(req.params.id, (results) => { 29 | res.send(results); 30 | }); 31 | }); 32 | app.route("/war/game/add").post((req, res) => { 33 | var matches = JSON.parse(req.body.players); 34 | for(var match in matches) { 35 | var players = matches[match]; 36 | sql.wars.createGame(players[0][0]["clan_id"], players[1][0]["clan_id"], (game_id) => { 37 | for(var team in players) { 38 | for (var player in players[team]) { 39 | sql.wars.addPLayerToGame(players[team][player]["entity_id"], game_id, team, () => { 40 | 41 | }); 42 | } 43 | } 44 | }); 45 | } 46 | res.send("Added game") 47 | }); 48 | app.route("/war/game/get").get((req, res) => { 49 | sql.wars.getGames((games) => { 50 | var object = {}; 51 | for(var i in games) { 52 | var player = games[i]; 53 | if(object[player["clan_war_game"]] == undefined) { 54 | object[player["clan_war_game"]] = []; 55 | object[player["clan_war_game"]].push([]); 56 | object[player["clan_war_game"]].push([]); 57 | } 58 | object[player["clan_war_game"]][player["team"]].push(player); 59 | } 60 | res.send(object); 61 | }); 62 | }); 63 | app.route("/war/current/setup").get((req, res) => { 64 | sql.wars.getSignUpWar((data) => { 65 | res.send(data); 66 | }); 67 | }); 68 | 69 | app.route("/war/player/:player/:war").get((req, res) => { 70 | sql.wars.getPlayerInWar(req.params.war, req.params.player, (data) => { 71 | res.send(data); 72 | }); 73 | }); 74 | } -------------------------------------------------------------------------------- /Server/sql/riotStructure.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.7.17, for Win64 (x86_64) 2 | -- 3 | -- Host: 127.0.0.1 Database: riot-2018 4 | -- ------------------------------------------------------ 5 | -- Server version 5.7.21-log 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `assigned_mission` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `assigned_mission`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `assigned_mission` ( 26 | `mission_id` int(11) NOT NULL AUTO_INCREMENT, 27 | `curent_progress` int(11) DEFAULT '0', 28 | `entity` varchar(80) NOT NULL, 29 | `date_assigned` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 30 | `date_completed` timestamp NULL DEFAULT NULL, 31 | `active` enum('ACTIVE','COMPLETED','FAILED') NOT NULL DEFAULT 'ACTIVE', 32 | PRIMARY KEY (`mission_id`,`entity`), 33 | KEY `mission_entity_idx` (`entity`), 34 | CONSTRAINT `mission_entity` FOREIGN KEY (`entity`) REFERENCES `entity` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, 35 | CONSTRAINT `mission_key` FOREIGN KEY (`mission_id`) REFERENCES `mission` (`mission_id`) ON DELETE CASCADE ON UPDATE CASCADE 36 | ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; 37 | /*!40101 SET character_set_client = @saved_cs_client */; 38 | 39 | -- 40 | -- Table structure for table `available_mission` 41 | -- 42 | 43 | DROP TABLE IF EXISTS `available_mission`; 44 | /*!40101 SET @saved_cs_client = @@character_set_client */; 45 | /*!40101 SET character_set_client = utf8 */; 46 | CREATE TABLE `available_mission` ( 47 | `mission_id` int(11) NOT NULL, 48 | `date_assigned` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 49 | `date_expired` timestamp NULL DEFAULT NULL, 50 | PRIMARY KEY (`mission_id`), 51 | CONSTRAINT `to_mission` FOREIGN KEY (`mission_id`) REFERENCES `mission` (`mission_id`) ON DELETE NO ACTION ON UPDATE NO ACTION 52 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 53 | /*!40101 SET character_set_client = @saved_cs_client */; 54 | 55 | -- 56 | -- Table structure for table `clan` 57 | -- 58 | 59 | DROP TABLE IF EXISTS `clan`; 60 | /*!40101 SET @saved_cs_client = @@character_set_client */; 61 | /*!40101 SET character_set_client = utf8 */; 62 | CREATE TABLE `clan` ( 63 | `entity_id` varchar(80) NOT NULL, 64 | `points` int(11) NOT NULL DEFAULT '0', 65 | PRIMARY KEY (`entity_id`), 66 | UNIQUE KEY `entity_id_UNIQUE` (`entity_id`), 67 | CONSTRAINT `clan_entity` FOREIGN KEY (`entity_id`) REFERENCES `entity` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE 68 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 69 | /*!40101 SET character_set_client = @saved_cs_client */; 70 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 71 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 72 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 73 | /*!50003 SET character_set_client = utf8 */ ; 74 | /*!50003 SET character_set_results = utf8 */ ; 75 | /*!50003 SET collation_connection = utf8_general_ci */ ; 76 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 77 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; 78 | DELIMITER ;; 79 | /*!50003 CREATE*/ /*!50017 DEFINER=CURRENT_USER*/ /*!50003 TRIGGER `riot-2018`.`clan_AFTER_INSERT` AFTER INSERT ON `clan` FOR EACH ROW 80 | BEGIN 81 | INSERT INTO `riot-2018`.assigned_mission (assigned_mission.mission_id, assigned_mission.entity) SELECT available_mission.mission_id, NEW.entity_id FROM available_mission join mission on (available_mission.mission_id = mission.mission_id) WHERE mission.type = 'CLAN'; 82 | INSERT INTO `riot-2018`.leaderboard_entry (leaderboard_entry.leaderboard_id, leaderboard_entry.entity) SELECT leaderboard.leaderboard_id, NEW.entity_id FROM leaderboard WHERE active = 1 AND leaderboard.type='CLAN'; 83 | END */;; 84 | DELIMITER ; 85 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 86 | /*!50003 SET character_set_client = @saved_cs_client */ ; 87 | /*!50003 SET character_set_results = @saved_cs_results */ ; 88 | /*!50003 SET collation_connection = @saved_col_connection */ ; 89 | 90 | -- 91 | -- Table structure for table `clan_member` 92 | -- 93 | 94 | DROP TABLE IF EXISTS `clan_member`; 95 | /*!40101 SET @saved_cs_client = @@character_set_client */; 96 | /*!40101 SET character_set_client = utf8 */; 97 | CREATE TABLE `clan_member` ( 98 | `clan_id` varchar(80) NOT NULL, 99 | `player_id` varchar(80) NOT NULL, 100 | PRIMARY KEY (`clan_id`,`player_id`), 101 | KEY `player_i_idx` (`player_id`), 102 | CONSTRAINT `clan_key` FOREIGN KEY (`clan_id`) REFERENCES `clan` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, 103 | CONSTRAINT `player_i` FOREIGN KEY (`player_id`) REFERENCES `player` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE 104 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 105 | /*!40101 SET character_set_client = @saved_cs_client */; 106 | 107 | -- 108 | -- Table structure for table `clan_war` 109 | -- 110 | 111 | DROP TABLE IF EXISTS `clan_war`; 112 | /*!40101 SET @saved_cs_client = @@character_set_client */; 113 | /*!40101 SET character_set_client = utf8 */; 114 | CREATE TABLE `clan_war` ( 115 | `war_id` int(11) NOT NULL AUTO_INCREMENT, 116 | `title` varchar(50) NOT NULL DEFAULT 'CLAN WAR', 117 | `status` enum('FINISHED','IN_PROGRESS','SIGN_UP','NOT_STARTED','SET_UP') DEFAULT NULL, 118 | PRIMARY KEY (`war_id`), 119 | UNIQUE KEY `war_id_UNIQUE` (`war_id`) 120 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; 121 | /*!40101 SET character_set_client = @saved_cs_client */; 122 | 123 | -- 124 | -- Table structure for table `clan_war_entry` 125 | -- 126 | 127 | DROP TABLE IF EXISTS `clan_war_entry`; 128 | /*!40101 SET @saved_cs_client = @@character_set_client */; 129 | /*!40101 SET character_set_client = utf8 */; 130 | CREATE TABLE `clan_war_entry` ( 131 | `war` int(11) NOT NULL, 132 | `player` varchar(80) NOT NULL, 133 | PRIMARY KEY (`war`,`player`), 134 | KEY `player_idx` (`player`), 135 | CONSTRAINT `player` FOREIGN KEY (`player`) REFERENCES `clan_member` (`player_id`) ON DELETE CASCADE ON UPDATE CASCADE, 136 | CONSTRAINT `war` FOREIGN KEY (`war`) REFERENCES `clan_war` (`war_id`) ON DELETE CASCADE ON UPDATE CASCADE 137 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 138 | /*!40101 SET character_set_client = @saved_cs_client */; 139 | 140 | -- 141 | -- Table structure for table `clan_war_game` 142 | -- 143 | 144 | DROP TABLE IF EXISTS `clan_war_game`; 145 | /*!40101 SET @saved_cs_client = @@character_set_client */; 146 | /*!40101 SET character_set_client = utf8 */; 147 | CREATE TABLE `clan_war_game` ( 148 | `id` int(11) NOT NULL AUTO_INCREMENT, 149 | `team_1` varchar(80) NOT NULL, 150 | `team_2` varchar(80) NOT NULL DEFAULT '0', 151 | `war` int(11) NOT NULL, 152 | `result` tinyint(4) NOT NULL DEFAULT '0', 153 | PRIMARY KEY (`id`), 154 | KEY `gameff_idx` (`war`), 155 | KEY `team_1_idx` (`team_1`), 156 | KEY `team_2_idx` (`team_2`), 157 | CONSTRAINT `clan_game` FOREIGN KEY (`war`) REFERENCES `clan_war` (`war_id`) ON DELETE CASCADE ON UPDATE CASCADE, 158 | CONSTRAINT `team_1` FOREIGN KEY (`team_1`) REFERENCES `clan` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, 159 | CONSTRAINT `team_2` FOREIGN KEY (`team_2`) REFERENCES `clan` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE 160 | ) ENGINE=InnoDB AUTO_INCREMENT=459 DEFAULT CHARSET=utf8; 161 | /*!40101 SET character_set_client = @saved_cs_client */; 162 | 163 | -- 164 | -- Table structure for table `clan_war_member` 165 | -- 166 | 167 | DROP TABLE IF EXISTS `clan_war_member`; 168 | /*!40101 SET @saved_cs_client = @@character_set_client */; 169 | /*!40101 SET character_set_client = utf8 */; 170 | CREATE TABLE `clan_war_member` ( 171 | `clan_war_game` int(11) NOT NULL, 172 | `player` varchar(80) NOT NULL, 173 | `team` enum('0','1') DEFAULT NULL, 174 | PRIMARY KEY (`clan_war_game`,`player`), 175 | KEY `player_in_game_idx` (`player`), 176 | CONSTRAINT `is_clan_game` FOREIGN KEY (`clan_war_game`) REFERENCES `clan_war_game` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 177 | CONSTRAINT `player_in_game` FOREIGN KEY (`player`) REFERENCES `clan_war_entry` (`player`) ON DELETE CASCADE ON UPDATE CASCADE 178 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 179 | /*!40101 SET character_set_client = @saved_cs_client */; 180 | 181 | -- 182 | -- Table structure for table `entity` 183 | -- 184 | 185 | DROP TABLE IF EXISTS `entity`; 186 | /*!40101 SET @saved_cs_client = @@character_set_client */; 187 | /*!40101 SET character_set_client = utf8 */; 188 | CREATE TABLE `entity` ( 189 | `entity_id` varchar(80) NOT NULL, 190 | `display_name` varchar(45) NOT NULL, 191 | PRIMARY KEY (`entity_id`) 192 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 193 | /*!40101 SET character_set_client = @saved_cs_client */; 194 | 195 | -- 196 | -- Table structure for table `leaderboard` 197 | -- 198 | 199 | DROP TABLE IF EXISTS `leaderboard`; 200 | /*!40101 SET @saved_cs_client = @@character_set_client */; 201 | /*!40101 SET character_set_client = utf8 */; 202 | CREATE TABLE `leaderboard` ( 203 | `leaderboard_id` int(11) NOT NULL AUTO_INCREMENT, 204 | `leaderboard_title` varchar(45) DEFAULT NULL, 205 | `month` int(11) DEFAULT NULL, 206 | `year` int(11) DEFAULT NULL, 207 | `active` tinyint(4) DEFAULT NULL, 208 | `type` enum('PLAYER','CLAN') DEFAULT NULL, 209 | PRIMARY KEY (`leaderboard_id`) 210 | ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; 211 | /*!40101 SET character_set_client = @saved_cs_client */; 212 | 213 | -- 214 | -- Table structure for table `leaderboard_entry` 215 | -- 216 | 217 | DROP TABLE IF EXISTS `leaderboard_entry`; 218 | /*!40101 SET @saved_cs_client = @@character_set_client */; 219 | /*!40101 SET character_set_client = utf8 */; 220 | CREATE TABLE `leaderboard_entry` ( 221 | `leaderboard_id` int(11) NOT NULL, 222 | `entity` varchar(80) NOT NULL, 223 | PRIMARY KEY (`leaderboard_id`,`entity`), 224 | KEY `leaderboard_key_idx` (`leaderboard_id`), 225 | KEY `leaderboard_entity_idx` (`entity`), 226 | CONSTRAINT `leaderboard_entity` FOREIGN KEY (`entity`) REFERENCES `entity` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, 227 | CONSTRAINT `leaderboard_key` FOREIGN KEY (`leaderboard_id`) REFERENCES `leaderboard` (`leaderboard_id`) ON DELETE CASCADE ON UPDATE CASCADE 228 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 229 | /*!40101 SET character_set_client = @saved_cs_client */; 230 | 231 | -- 232 | -- Table structure for table `mission` 233 | -- 234 | 235 | DROP TABLE IF EXISTS `mission`; 236 | /*!40101 SET @saved_cs_client = @@character_set_client */; 237 | /*!40101 SET character_set_client = utf8 */; 238 | CREATE TABLE `mission` ( 239 | `mission_id` int(11) NOT NULL AUTO_INCREMENT, 240 | `type` enum('PLAYER','CLAN') NOT NULL, 241 | `reward` int(11) NOT NULL DEFAULT '0', 242 | `repeatable` enum('SINGLE','DAILY','CUSTOM') NOT NULL DEFAULT 'SINGLE', 243 | `title` varchar(45) DEFAULT NULL, 244 | `description` longtext, 245 | `max_progress` int(11) DEFAULT NULL, 246 | `icon_path` varchar(45) DEFAULT NULL, 247 | `duration_hours` int(11) DEFAULT NULL, 248 | `cooldown_time` int(11) NOT NULL DEFAULT '0', 249 | PRIMARY KEY (`mission_id`) 250 | ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; 251 | /*!40101 SET character_set_client = @saved_cs_client */; 252 | 253 | -- 254 | -- Table structure for table `player` 255 | -- 256 | 257 | DROP TABLE IF EXISTS `player`; 258 | /*!40101 SET @saved_cs_client = @@character_set_client */; 259 | /*!40101 SET character_set_client = utf8 */; 260 | CREATE TABLE `player` ( 261 | `entity_id` varchar(80) NOT NULL, 262 | `account_id` varchar(50) NOT NULL, 263 | `summoner_id` varchar(50) NOT NULL, 264 | `points` int(11) DEFAULT '0', 265 | PRIMARY KEY (`entity_id`), 266 | CONSTRAINT `entity` FOREIGN KEY (`entity_id`) REFERENCES `entity` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE 267 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 268 | /*!40101 SET character_set_client = @saved_cs_client */; 269 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 270 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 271 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 272 | /*!50003 SET character_set_client = utf8 */ ; 273 | /*!50003 SET character_set_results = utf8 */ ; 274 | /*!50003 SET collation_connection = utf8_general_ci */ ; 275 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 276 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; 277 | DELIMITER ;; 278 | /*!50003 CREATE*/ /*!50017 DEFINER=CURRENT_USER*/ /*!50003 TRIGGER `riot-2018`.`player_AFTER_INSERT` AFTER INSERT ON `player` FOR EACH ROW 279 | BEGIN 280 | INSERT INTO `riot-2018`.assigned_mission (assigned_mission.mission_id, assigned_mission.entity) SELECT available_mission.mission_id, NEW.entity_id FROM available_mission join mission on (available_mission.mission_id = mission.mission_id) WHERE TYPE = 'PLAYER'; 281 | INSERT INTO `riot-2018`.leaderboard_entry (leaderboard_entry.leaderboard_id, leaderboard_entry.entity) SELECT leaderboard.leaderboard_id, NEW.entity_id FROM leaderboard WHERE active = 1 AND type='PLAYER'; 282 | END */;; 283 | DELIMITER ; 284 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 285 | /*!50003 SET character_set_client = @saved_cs_client */ ; 286 | /*!50003 SET character_set_results = @saved_cs_results */ ; 287 | /*!50003 SET collation_connection = @saved_col_connection */ ; 288 | 289 | -- 290 | -- Dumping events for database 'riot-2018' 291 | -- 292 | 293 | -- 294 | -- Dumping routines for database 'riot-2018' 295 | -- 296 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 297 | 298 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 299 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 300 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 301 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 302 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 303 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 304 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 305 | 306 | -- Dump completed on 2019-01-22 18:15:57 307 | -------------------------------------------------------------------------------- /Server/sql/usefulSQL.sql: -------------------------------------------------------------------------------- 1 | -- Used to add all missions to availble missions 2 | INSERT INTO `riot-2018`.available_mission (mission_id, date_expired) select mission_id, DATE_ADD(NOW(), INTERVAL duration_hours HOUR) from mission; 3 | 4 | -- Used to add all possible players to the first clan war 5 | INSERT INTO clan_war_entry SELECT 1, entity_id FROM player -------------------------------------------------------------------------------- /Server/sql_functions/clan.ts: -------------------------------------------------------------------------------- 1 | export class ClanSQL { 2 | constructor(private sql) { 3 | 4 | } 5 | addClan(data:Object, callback:Function) { 6 | // First check if the clan already exists 7 | var query:string = "SELECT 1 FROM clan WHERE entity_id=?"; 8 | this.sql.query(query, [data["clan_tag"]], (err, results:any[], fields) => { 9 | if(err) { 10 | throw err; 11 | } 12 | if(results.length != 0) { 13 | callback(); 14 | return; 15 | } 16 | // If clan does not exist, create new one 17 | // Add clan to entity 18 | query = "INSERT INTO entity VALUES (?, ?)"; 19 | this.sql.query(query, [data["clan_tag"], data["clan_name"]], (err, results:any[], fields) => { 20 | if(err) { 21 | if(err.code != "ER_DUP_ENTRY") { 22 | console.log(err); 23 | } 24 | } 25 | 26 | // Add clan to clan table 27 | query = "INSERT INTO clan (entity_id) VALUES (?)"; 28 | console.log("addng clan") 29 | this.sql.query(query, [data["clan_tag"]], (err, results, fields) => { 30 | if(err) { 31 | console.log(err); 32 | if(err.code != "ER_DUP_ENTRY") { 33 | console.log(err); 34 | } 35 | } 36 | callback(); 37 | }); 38 | }); 39 | }); 40 | 41 | } 42 | getAllClans(callback:Function) { 43 | var query:string = "SELECT * FROM clan"; 44 | this.sql.query(query, [], (err, results, fields) => { 45 | if(err) throw err; 46 | callback(results); 47 | }); 48 | } 49 | 50 | addPlayer(clanTag:string, puuid:string, callback:Function) { 51 | console.log(clanTag + ":" + puuid); 52 | var query:string = "INSERT INTO clan_member(clan_id, player_id) VALUES(?, ?)"; 53 | this.sql.query(query, [clanTag, puuid], (err, results, fields) => { 54 | if(err) console.log(err); 55 | callback(); 56 | }); 57 | } 58 | } -------------------------------------------------------------------------------- /Server/sql_functions/index.ts: -------------------------------------------------------------------------------- 1 | import { PlayerSQL } from "./player"; 2 | import { MissionSQL } from "./mission"; 3 | import { LeaderboardSQL } from "./leaderboard"; 4 | import { ClanSQL } from "./clan"; 5 | import { WarsSQL } from "./wars"; 6 | 7 | // This class will be called by external functions 8 | export class SQL { 9 | // Define all the sub methods 10 | player:PlayerSQL; 11 | clan:ClanSQL; 12 | mission:MissionSQL; 13 | leaderboard:LeaderboardSQL; 14 | wars:WarsSQL; 15 | constructor(sqlConnection:any) { 16 | // Create all the sub stuff 17 | this.player = new PlayerSQL(sqlConnection); 18 | this.mission = new MissionSQL(sqlConnection); 19 | this.leaderboard = new LeaderboardSQL(sqlConnection); 20 | this.clan = new ClanSQL(sqlConnection); 21 | this.wars = new WarsSQL(sqlConnection); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Server/sql_functions/leaderboard.ts: -------------------------------------------------------------------------------- 1 | export class LeaderboardSQL { 2 | constructor(private sql) { 3 | 4 | } 5 | 6 | getCurrentPlayerLeaderboard(max:number, callback:Function) { 7 | var query:string = `SELECT * FROM leaderboard l, leaderboard_entry le, player p, entity e where l.leaderboard_id = le.leaderboard_id and le.entity = p.entity_id and e.entity_id = p.entity_id and type = 'PLAYER' and active = 1 ORDER BY points desc LIMIT ${max}`; 8 | this.sql.query(query, [], (err, results, fields) => { 9 | if(err) { 10 | console.log(err); 11 | callback({"message": "Sorry there was an unexpected error"}); 12 | return; 13 | } 14 | callback(results); 15 | }); 16 | } 17 | 18 | getCurrentClanLeaderboard(max:number, callback:Function) { 19 | var query:string = `SELECT * FROM leaderboard l, leaderboard_entry le, clan c, entity e where l.leaderboard_id = le.leaderboard_id and le.entity = c.entity_id and e.entity_id = c.entity_id and type = 'CLAN' and active = 1 ORDER BY points desc LIMIT ${max}`; 20 | this.sql.query(query, [], (err, results, fields) => { 21 | if(err) { 22 | console.log(err); 23 | callback({"message": "Sorry there was an unexpected error"}); 24 | return; 25 | } 26 | callback(results); 27 | }); 28 | } 29 | 30 | addNewLeaderboards(callback:Function) { 31 | // Deactive past leaderboards if they are out of date 32 | this.deactivatePastLeaderboards(() => { 33 | // Try to create a new clan leaderboard if needed 34 | this.tryToCreateNewLeaderboard("CLAN", () => { 35 | // Try to create a new clan leaderboard if needed 36 | this.tryToCreateNewLeaderboard("PLAYER", () => { 37 | callback(); 38 | }) 39 | }); 40 | }); 41 | 42 | } 43 | 44 | 45 | private tryToCreateNewLeaderboard(type:string, callback:Function) { 46 | var date = new Date(); 47 | var query:string = "SELECT 1 FROM leaderboard WHERE month=? AND year=? AND type=?"; 48 | this.sql.query(query, [date.getMonth(), date.getFullYear(), type], (err, results:any[], fields) => { 49 | if(results.length == 0) { 50 | // There is no one for the current month! 51 | var newQuery:string = "INSERT INTO leaderboard (leaderboard_title, month, year, active, type) VALUES (?, ?, ?, ?, ?)"; 52 | var sqlValues:any[] = [`${type} Leaderboard for Year ${date.getFullYear()}, Month ${date.getMonth()}`, date.getMonth(), date.getFullYear(), 1, type]; 53 | // Create new leaderboard 54 | this.sql.query(newQuery, sqlValues, (err, results:any[], fields) => { 55 | if(err) 56 | throw err; 57 | // Get new leaderboard ID 58 | this.getLeaderboardId(type, (row) => { 59 | this.populateLeaderboardEntries(row.leaderboard_id, type, (back) => { 60 | console.log("created all"); 61 | callback(); 62 | }); 63 | }); 64 | }); 65 | } 66 | else 67 | callback(); 68 | }); 69 | } 70 | 71 | getLeaderboardId(type:string, callback:Function) { 72 | var query:string = "SELECT * from leaderboard WHERE type=? AND active=1"; 73 | this.sql.query(query, [type], (err, results, fields) => { 74 | if(results.length != 1) { 75 | throw "No Leaderboard found for type" 76 | } 77 | callback(results[0]); 78 | }); 79 | } 80 | 81 | private deactivatePastLeaderboards(callback:Function) { 82 | var query:string = "UPDATE leaderboard SET active=0 WHERE month <> ? AND year <> ? AND active=1" 83 | var date = new Date(); 84 | this.sql.query(query, [date.getMonth(), date.getFullYear()], (err, results:any[], fields) => { 85 | if(err) { 86 | throw err; 87 | } 88 | callback(); 89 | }); 90 | } 91 | 92 | private populateLeaderboardEntries(leaderboard_id:number, type:string, callback:Function) { 93 | var query:string = `INSERT INTO leaderboard_entry (leaderboard_id, entity) SELECT ?, entity_id FROM ${type.toLowerCase()}`; 94 | this.sql.query(query, [leaderboard_id], (err, results, fields) => { 95 | if(err) throw err; 96 | callback(results); 97 | }); 98 | } 99 | } -------------------------------------------------------------------------------- /Server/sql_functions/mission.ts: -------------------------------------------------------------------------------- 1 | export class MissionSQL { 2 | constructor(private sql) { 3 | 4 | } 5 | getMissionsByAccountId(accountId:number, callback:Function) { 6 | var query:string = "SELECT * FROM assigned_mission LEFT JOIN player ON assigned_mission.entity = player.entity_id WHERE summoner_id = ?"; 7 | this.sql.query(query, [accountId], (err, results, fields) => { 8 | if(err) { 9 | console.log(err); 10 | callback({"message": "Sorry there was an unexpected error"}); 11 | return; 12 | } 13 | callback(results); 14 | }); 15 | } 16 | 17 | getMissionsBySummonerId(summoner_id:number, callback:Function) { 18 | var query:string = "SELECT * FROM assigned_mission LEFT JOIN player ON assigned_mission.entity = player.entity_id WHERE account_id = ?"; 19 | this.sql.query(query, [summoner_id], (err, results, fields) => { 20 | if(err) { 21 | console.log(err); 22 | callback({"message": "Sorry there was an unexpected error"}); 23 | return; 24 | } 25 | callback(results); 26 | }); 27 | } 28 | 29 | getMissionsByPuuid(puuid:number, callback:Function) { 30 | var query:string = `SELECT title, description, reward, type, curent_progress, max_progress, date_assigned, duration_hours, icon_path 31 | FROM assigned_mission am 32 | LEFT JOIN mission m ON am.mission_id = m.mission_id 33 | LEFT JOIN clan_member cm ON am.entity = cm.player_id or cm.clan_id = am.entity 34 | WHERE cm.player_id = ? 35 | AND active = 'ACTIVE'`; 36 | this.sql.query(query, [puuid], (err, results, fields) => { 37 | if(err) { 38 | console.log(err); 39 | callback({"message": "Sorry there was an unexpected error"}); 40 | return; 41 | } 42 | callback(results); 43 | }); 44 | } 45 | 46 | updateMissionProgress(mission_id:number, new_progress:number, callback:Function) { 47 | var query:string = "UPDATE assigned_mission SET current_progress = ? WHERE mission_id = ?"; 48 | this.sql.query(query, [new_progress, mission_id], (err, results, fields) => { 49 | if(err) { 50 | console.log(err); 51 | callback({"message": "Sorry there was an unexpected error"}); 52 | return; 53 | } 54 | callback(results); 55 | }); 56 | } 57 | assignToAllPlayers(mission_id:number, callback:Function) { 58 | var query:string = "INSERT INTO assigned_mission () SELECT mission_id, 0, max_progress, "; 59 | } 60 | } -------------------------------------------------------------------------------- /Server/sql_functions/player.ts: -------------------------------------------------------------------------------- 1 | export class PlayerSQL { 2 | constructor(private sql) { 3 | 4 | } 5 | addPlayer(data:Object, callback:Function) { 6 | var query:string = "INSERT INTO entity (entity_id, display_name) VALUES (?, ?)"; 7 | // Create general entity 8 | this.sql.query(query, [data["puuid"], data["display_name"]], (err, results, fields) => { 9 | if(err){ 10 | console.log(err); 11 | } 12 | 13 | // Now add player 14 | query = "INSERT INTO player (entity_id, summoner_id, account_id) VALUES (?, ?, ?)"; 15 | this.sql.query(query, [data["puuid"], data["id"], data["accountId"]], (err, results, fields) => { 16 | if(err) console.log(err); 17 | callback(); 18 | return; 19 | }); 20 | }); 21 | } 22 | 23 | setIp(player:string, ip:string, callback:Function) { 24 | var query:string = "UPDATE player SET ip = ? WHERE entity_id = ?"; 25 | this.sql.query(query, [ip, player], (err, results, fields) => { 26 | if(err) console.log(err); 27 | callback(); 28 | return; 29 | }); 30 | } 31 | addToClan(player_id:string, clan_id:string, callback:Function) { 32 | var query:string = "INSERT INTO clan_member (clan_id, player_id) VALUES (?, ?)"; 33 | this.sql.query(query, [clan_id, player_id], (err, results, fields) => { 34 | if(err) console.log(err); 35 | callback(); 36 | return; 37 | }); 38 | } 39 | selectPlayerByAccountId(accountId:Number, callback:Function) { 40 | var query:string = "SELECT * FROM player LEFT JOIN entity ON (entity.entity_id = player.entity_id) WHERE account_id=?"; 41 | this.sql.query(query, [accountId], (err, results, fields) => { 42 | if(err) { 43 | console.log(err); 44 | callback({"message": "Sorry there was an unexpected error"}); 45 | return; 46 | } 47 | callback(results); 48 | return; 49 | }); 50 | } 51 | selectPlayerBySummonerId(summoner_id:Number, callback:Function) { 52 | var query:string = "SELECT * FROM player LEFT JOIN entity ON (entity.entity_id = player.entity_id) WHERE summoner_id=?"; 53 | this.sql.query(query, [summoner_id], (err, results, fields) => { 54 | if(err) { 55 | console.log(err); 56 | callback({"message": "Sorry there was an unexpected error"}); 57 | return; 58 | } 59 | callback(results); 60 | }); 61 | } 62 | selectPlayerByPuuid(puuid:String, callback:Function) { 63 | var query:string = "SELECT * FROM player LEFT JOIN entity ON (entity.entity_id = player.entity_id) WHERE player.entity_id=?"; 64 | this.sql.query(query, [puuid], (err, results, fields) => { 65 | if(err) { 66 | console.log(err); 67 | callback({"message": "Sorry there was an unexpected error"}); 68 | return; 69 | } 70 | callback(results); 71 | }); 72 | } 73 | 74 | getChallengePlayers(player1:string, callback:Function) { 75 | var query = `SELECT e.display_name as player_name, z.display_name as clan_name, z.entity_id as clan_tag, player.summoner_id, player.ip as ip from player JOIN 76 | (SELECT player_id, clan_id FROM clan_member JOIN entity ee ON ee.entity_id = clan_member.player_id WHERE player_id <> ? AND ee.display_name <> ?) AS c1 77 | ON c1.player_id = player.entity_id 78 | JOIN entity e 79 | ON player.entity_id = e.entity_id 80 | JOIN entity z 81 | ON z.entity_id = c1.clan_id 82 | WHERE player.ip IS NOT NULL 83 | ` 84 | this.sql.query(query, [player1, player1], (err, results, fields) => { 85 | if(err) { 86 | console.log(err); 87 | callback({"message": "Sorry there was an unexpected error"}); 88 | return; 89 | } 90 | callback(results); 91 | }); 92 | } 93 | } -------------------------------------------------------------------------------- /Server/sql_functions/wars.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export class WarsSQL { 4 | constructor(private sql) { 5 | 6 | } 7 | 8 | registerPlayer(puuid:string, callback:Function) { 9 | var query:string = "INSERT INTO clan_war_entry SELECT war_id, ? FROM clan_war WHERE status = 'SIGN_UP' LIMIT 1"; 10 | this.sql.query(query, [puuid], (err, results, fields) => { 11 | if(err) { 12 | console.log(err); 13 | callback({"message": "Something went wrong"}); 14 | return; 15 | } 16 | callback({"message": "User registered"}); 17 | }); 18 | } 19 | 20 | createClanWar(callback:Function) { 21 | var query:string = "INSERT INTO clan_war (status) VALUES ('NOT_STARTED')"; 22 | this.sql.query(query, [], (err, results, fields) => { 23 | if(err) { 24 | callback({"message": "Something went wrong"}); 25 | } 26 | else { 27 | callback({"message": "New clan war created"}); 28 | } 29 | }); 30 | } 31 | 32 | openSignUp(callback:Function) { 33 | var query:string = "UPDATE `riot-2018`.`clan_war` SET `status`='SIGN_UP' WHERE `status`='NOT_STARTED'"; 34 | this.sql.query(query, [], (err, results, fields) => { 35 | if(err) { 36 | callback({"message": "Something went wrong"}); 37 | } 38 | else { 39 | callback({"message": "Sign Up has been opened"}); 40 | } 41 | }); 42 | } 43 | 44 | setUpWar(callback:Function) { 45 | var query:string = "UPDATE `riot-2018`.`clan_war` SET `status`='SET_UP' WHERE `status`='SIGN_UP'"; 46 | this.sql.query(query, [], (err, results, fields) => { 47 | if(err) { 48 | callback({"message": "Something went wrong"}); 49 | } 50 | else { 51 | callback({"message": "War is being setup"}); 52 | } 53 | }); 54 | } 55 | 56 | startWar(callback:Function) { 57 | var query:string = "UPDATE `riot-2018`.`clan_war` SET `status`='IN_PROGRESS' WHERE `status`='SET_UP'"; 58 | this.sql.query(query, [], (err, results, fields) => { 59 | if(err) { 60 | callback({"message": "Something went wrong"}); 61 | } 62 | else { 63 | callback({"message": "Clan War was started"}); 64 | } 65 | }); 66 | } 67 | 68 | endWar(callback:Function) { 69 | var query:string = "UPDATE `riot-2018`.`clan_war` SET `status`='FINISHED' WHERE `status`='IN_PROGRESS'"; 70 | this.sql.query(query, [], (err, results, fields) => { 71 | if(err) { 72 | callback({"message": "Something went wrong"}); 73 | } 74 | else { 75 | callback({"message": "Clan War has finished"}); 76 | } 77 | }); 78 | } 79 | 80 | createGame(team1:string, team2:string, callback:Function) { 81 | var query:string = "INSERT INTO `riot-2018`.`clan_war_game` (team_1, team_2, war, result) SELECT ?, ?, war_id, 0 FROM clan_war WHERE status='SET_UP'"; 82 | this.sql.query(query, [team1, team2], (err, results, fields) => { 83 | if(err) { 84 | console.log(err); 85 | callback({"message": "Something went wrong"}); 86 | } 87 | else { 88 | callback(results.insertId); 89 | } 90 | }); 91 | } 92 | 93 | getGames(callback:Function) { 94 | var query:string = "SELECT * FROM `riot-2018`.clan_war_member c join player p on c.player = p.entity_id"; 95 | this.sql.query(query, [], (err, results, fields) => { 96 | if(err) { 97 | console.log(err); 98 | callback({"message": "Something went wrong"}); 99 | } 100 | else { 101 | callback(results); 102 | } 103 | }); 104 | } 105 | 106 | getSignUpWar(callback:Function) { 107 | var query:string = "SELECT * FROM clan_war WHERE status = 'SIGN_UP'"; 108 | this.sql.query(query, [], (err, results, fields) => { 109 | if(err) { 110 | console.log(err); 111 | callback({"message": "Something went wrong"}); 112 | } 113 | else { 114 | callback(results); 115 | } 116 | }); 117 | } 118 | 119 | getPlayerInWar(warId, player, callback:Function) { 120 | var query:string = "SELECT * FROM clan_war_entry WHERE war = ? AND player = ?"; 121 | this.sql.query(query, [warId, player], (err, results, fields) => { 122 | if(err) { 123 | console.log(err); 124 | callback({"message": "Something went wrong"}); 125 | } 126 | else { 127 | callback(results); 128 | } 129 | }); 130 | } 131 | 132 | addPLayerToGame(player:object, game:number, team:string, callback:Function) { 133 | var query:string = "INSERT INTO clan_war_member (clan_war_game, player, team) VALUES (?, ?, ?)"; 134 | this.sql.query(query, [game, player, team], (err, results, fields) => { 135 | if(err) { 136 | // console.log(err); 137 | callback({"message": "Something went wrong"}); 138 | } 139 | else { 140 | callback({"message": "Player added to game"}); 141 | } 142 | }); 143 | } 144 | 145 | resolveGame(team1:string, team2:string, war:number, winner:number, callback:Function) { 146 | if(winner > 2 || winner < 0) { 147 | callback({"message": "Invalid winner"}); 148 | return; 149 | } 150 | var query:string = "UPDATE `riot-2018`.`clan_war_game` SET result = ? WHERE team_1=? AND team_2=? AND war=?" 151 | this.sql.query(query, [winner, team1, team2, war], (err, results, fields) => { 152 | if(err) { 153 | callback({"message": "Something went wrong"}); 154 | } 155 | else { 156 | callback({"message": "Game was resolved"}); 157 | } 158 | }); 159 | } 160 | 161 | getPlayers(war:number, callback:Function) { 162 | var query:string = ` 163 | SELECT entity_id, summoner_id, clan_id FROM clan_war_entry c 164 | JOIN player p ON c.player = p.entity_id 165 | JOIN clan_war w ON w.war_id = c.war 166 | JOIN clan_member cm ON cm.player_id = c.player 167 | 168 | WHERE w.war_id = ?` 169 | 170 | this.sql.query(query, [war], (err, results, fields) => { 171 | if(err) { 172 | callback({"message": "Something went wrong"}); 173 | } 174 | else { 175 | callback(results); 176 | } 177 | }); 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /Web/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /Web/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | 31 | # misc 32 | /.sass-cache 33 | /connect.lock 34 | /coverage 35 | /libpeerconnection.log 36 | npm-debug.log 37 | yarn-error.log 38 | testem.log 39 | /typings 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | -------------------------------------------------------------------------------- /Web/README.md: -------------------------------------------------------------------------------- 1 | # Web 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.1.4. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /Web/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "Web": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/Web", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css" 27 | ], 28 | "scripts": [] 29 | }, 30 | "configurations": { 31 | "production": { 32 | "fileReplacements": [ 33 | { 34 | "replace": "src/environments/environment.ts", 35 | "with": "src/environments/environment.prod.ts" 36 | } 37 | ], 38 | "optimization": true, 39 | "outputHashing": "all", 40 | "sourceMap": false, 41 | "extractCss": true, 42 | "namedChunks": false, 43 | "aot": true, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true, 47 | "budgets": [ 48 | { 49 | "type": "initial", 50 | "maximumWarning": "2mb", 51 | "maximumError": "5mb" 52 | } 53 | ] 54 | } 55 | } 56 | }, 57 | "serve": { 58 | "builder": "@angular-devkit/build-angular:dev-server", 59 | "options": { 60 | "browserTarget": "Web:build" 61 | }, 62 | "configurations": { 63 | "production": { 64 | "browserTarget": "Web:build:production" 65 | } 66 | } 67 | }, 68 | "extract-i18n": { 69 | "builder": "@angular-devkit/build-angular:extract-i18n", 70 | "options": { 71 | "browserTarget": "Web:build" 72 | } 73 | }, 74 | "test": { 75 | "builder": "@angular-devkit/build-angular:karma", 76 | "options": { 77 | "main": "src/test.ts", 78 | "polyfills": "src/polyfills.ts", 79 | "tsConfig": "src/tsconfig.spec.json", 80 | "karmaConfig": "src/karma.conf.js", 81 | "styles": [ 82 | "src/styles.css" 83 | ], 84 | "scripts": [], 85 | "assets": [ 86 | "src/favicon.ico", 87 | "src/assets" 88 | ] 89 | } 90 | }, 91 | "lint": { 92 | "builder": "@angular-devkit/build-angular:tslint", 93 | "options": { 94 | "tsConfig": [ 95 | "src/tsconfig.app.json", 96 | "src/tsconfig.spec.json" 97 | ], 98 | "exclude": [ 99 | "**/node_modules/**" 100 | ] 101 | } 102 | } 103 | } 104 | }, 105 | "Web-e2e": { 106 | "root": "e2e/", 107 | "projectType": "application", 108 | "prefix": "", 109 | "architect": { 110 | "e2e": { 111 | "builder": "@angular-devkit/build-angular:protractor", 112 | "options": { 113 | "protractorConfig": "e2e/protractor.conf.js", 114 | "devServerTarget": "Web:serve" 115 | }, 116 | "configurations": { 117 | "production": { 118 | "devServerTarget": "Web:serve:production" 119 | } 120 | } 121 | }, 122 | "lint": { 123 | "builder": "@angular-devkit/build-angular:tslint", 124 | "options": { 125 | "tsConfig": "e2e/tsconfig.e2e.json", 126 | "exclude": [ 127 | "**/node_modules/**" 128 | ] 129 | } 130 | } 131 | } 132 | } 133 | }, 134 | "defaultProject": "Web" 135 | } -------------------------------------------------------------------------------- /Web/docs/assets/Person Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/docs/assets/Person Icon.png -------------------------------------------------------------------------------- /Web/docs/assets/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/docs/assets/icon3.png -------------------------------------------------------------------------------- /Web/docs/assets/score.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/docs/assets/score.png -------------------------------------------------------------------------------- /Web/docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/docs/favicon.ico -------------------------------------------------------------------------------- /Web/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /Web/docs/runtime.ec2944dd8b20ec099bf3.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getTitleText()).toEqual('Welcome to Web!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /Web/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Web/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /Web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "NODE_ENV=production node server.js", 7 | "public": "ng serve --disable-host-check true", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e", 12 | "postinstall": "ng build --prod" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "~7.1.0", 17 | "@angular/cli": "^7.2.3", 18 | "@angular/common": "~7.1.0", 19 | "@angular/compiler": "~7.1.0", 20 | "@angular/compiler-cli": "^7.1.4", 21 | "@angular/core": "~7.1.0", 22 | "@angular/forms": "~7.1.0", 23 | "@angular/platform-browser": "~7.1.0", 24 | "@angular/platform-browser-dynamic": "~7.1.0", 25 | "@angular/router": "~7.1.0", 26 | "angular-cli-ghpages": "^0.5.3", 27 | "core-js": "^2.5.4", 28 | "express": "^4.16.4", 29 | "path": "^0.12.7", 30 | "rxjs": "~6.3.3", 31 | "tslib": "^1.9.0", 32 | "typescript": "~3.1.6", 33 | "zone.js": "~0.8.26" 34 | }, 35 | "devDependencies": { 36 | "@angular-devkit/build-angular": "~0.11.0", 37 | "@angular/cli": "^7.2.3", 38 | "@angular/compiler-cli": "^7.1.4", 39 | "@angular/language-service": "~7.1.0", 40 | "@types/jasmine": "~2.8.8", 41 | "@types/jasminewd2": "~2.0.3", 42 | "@types/node": "~8.9.4", 43 | "codelyzer": "~4.5.0", 44 | "jasmine-core": "~2.99.1", 45 | "jasmine-spec-reporter": "~4.2.1", 46 | "karma": "~3.1.1", 47 | "karma-chrome-launcher": "~2.2.0", 48 | "karma-coverage-istanbul-reporter": "~2.0.1", 49 | "karma-jasmine": "~1.1.2", 50 | "karma-jasmine-html-reporter": "^0.2.2", 51 | "protractor": "~5.4.0", 52 | "ts-node": "~7.0.0", 53 | "tslint": "~5.11.0", 54 | "typescript": "~3.1.6" 55 | }, 56 | "engines": { 57 | "node": "10.10.0", 58 | "npm": "6.5.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Web/server.js: -------------------------------------------------------------------------------- 1 | //Install express server 2 | const express = require('express'); 3 | const path = require('path'); 4 | 5 | const app = express(); 6 | 7 | // Serve only the static files form the dist directory 8 | app.use(express.static(__dirname + '/dist/Web')); 9 | 10 | app.get('/*', function(req,res) { 11 | 12 | res.sendFile(path.join(__dirname+'/dist/Web/index.html')); 13 | }); 14 | 15 | // Start the app by listening on the default Heroku port 16 | app.listen(process.env.PORT || 8080); -------------------------------------------------------------------------------- /Web/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { HomeComponent } from './home/home.component'; 4 | import { PlayerLeaderboardComponent } from './player-leaderboard/player-leaderboard.component'; 5 | import { ClanLeaderboardComponent } from './clan-leaderboard/clan-leaderboard.component'; 6 | import { DownloadComponent } from './download/download.component'; 7 | import { ChallengeComponent } from './challenge/challenge.component'; 8 | import { RegisterComponent } from './register/register.component'; 9 | 10 | const routes: Routes = [ 11 | {path: "", component: HomeComponent}, 12 | {path: "player", component: PlayerLeaderboardComponent}, 13 | {path: "clan", component: ClanLeaderboardComponent}, 14 | {path: "download", component: DownloadComponent}, 15 | {path: "challenge", component: ChallengeComponent}, 16 | {path: "signup", component: RegisterComponent} 17 | ]; 18 | 19 | @NgModule({ 20 | imports: [RouterModule.forRoot(routes)], 21 | exports: [RouterModule] 22 | }) 23 | export class AppRoutingModule { } 24 | -------------------------------------------------------------------------------- /Web/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | 2 | .nav-items-left { 3 | list-style: none; 4 | padding: 0; 5 | display: inline; 6 | } 7 | 8 | .nav-item { 9 | display: inline-block; 10 | min-width: 90px; 11 | padding: 0 10px; 12 | height: 100%; 13 | line-height: 50px; 14 | text-align: center; 15 | alignment: center; 16 | transition: .5s; 17 | color: white; 18 | font-family: 'Oswald', sans-serif; 19 | font-size: large; 20 | 21 | } 22 | 23 | .nav-img { 24 | display: inline-block; 25 | min-width: 70px; 26 | max-height: 70px; 27 | padding: 0 10px; 28 | height: 100%; 29 | line-height: 50px; 30 | text-align: center; 31 | color: white; 32 | font-family: 'Oswald', sans-serif; 33 | vertical-align: middle; 34 | 35 | } 36 | 37 | .logo-icon { 38 | min-width: 70px; 39 | max-height: 70px; 40 | 41 | } 42 | 43 | .nav-item:hover { 44 | cursor: pointer; 45 | box-shadow: inset 0 -3px 0 0 white; 46 | 47 | } 48 | 49 | .nav-bar { 50 | padding: 0; 51 | position: static; 52 | left: 0; 53 | top: 0; 54 | height: 50px; 55 | background-color: #372549; 56 | } 57 | -------------------------------------------------------------------------------- /Web/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 33 | 34 | -------------------------------------------------------------------------------- /Web/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'Web'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('Web'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to Web!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /Web/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { Router } from '../../node_modules/@angular/router'; 3 | 4 | @Component({ 5 | selector: "app-root", 6 | templateUrl: "./app.component.html", 7 | styleUrls: ["./app.component.css"] 8 | }) 9 | export class AppComponent implements OnInit{ 10 | title = "Club Leaderboard"; 11 | constructor(private router:Router) { 12 | 13 | } 14 | ngOnInit() { } 15 | 16 | toPlayer() { 17 | this.router.navigate(["/player"]); 18 | } 19 | 20 | toClan() { 21 | this.router.navigate(["/clan"]); 22 | } 23 | 24 | toHome() { 25 | this.router.navigate([""]); 26 | } 27 | 28 | toChallenge() { 29 | this.router.navigate(["challenge"]); 30 | } 31 | 32 | toDownload() { 33 | this.router.navigate(["download"]); 34 | } 35 | 36 | toSignUp() { 37 | this.router.navigate(["signup"]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Web/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from "@angular/platform-browser"; 2 | import { NgModule } from "@angular/core"; 3 | 4 | import { AppRoutingModule } from "./app-routing.module"; 5 | import { AppComponent } from "./app.component"; 6 | import { HomeComponent } from "./home/home.component"; 7 | import { LeaderboardComponent } from "./leaderboard/leaderboard.component"; 8 | import { HttpClientModule } from '../../node_modules/@angular/common/http'; 9 | import { PlayerLeaderboardComponent } from './player-leaderboard/player-leaderboard.component'; 10 | import { ClanLeaderboardComponent } from './clan-leaderboard/clan-leaderboard.component'; 11 | import { ChallengeComponent } from './challenge/challenge.component'; 12 | import { DownloadComponent } from './download/download.component'; 13 | import { RegisterComponent } from './register/register.component'; 14 | 15 | @NgModule({ 16 | declarations: [ 17 | AppComponent, 18 | HomeComponent, 19 | LeaderboardComponent, 20 | PlayerLeaderboardComponent, 21 | ClanLeaderboardComponent, 22 | ChallengeComponent, 23 | DownloadComponent, 24 | RegisterComponent 25 | ], 26 | imports: [BrowserModule, 27 | AppRoutingModule, 28 | HttpClientModule], 29 | providers: [], 30 | bootstrap: [AppComponent] 31 | }) 32 | export class AppModule {} 33 | -------------------------------------------------------------------------------- /Web/src/app/challenge/challenge.component.css: -------------------------------------------------------------------------------- 1 | .table-title { 2 | font-family: 'Oswald', sans-serif; 3 | font-size: 2.5rem; 4 | color: white; 5 | margin-bottom: 10px; 6 | display: inline; 7 | } 8 | 9 | .help { 10 | display: inline; 11 | } 12 | table { 13 | font-family: 'Oswald', sans-serif; 14 | width: 100%; 15 | alignment: center; 16 | margin-left:auto; 17 | margin-right:auto; 18 | opacity: .9; 19 | } 20 | 21 | td, 22 | th { 23 | font-family: 'Oswald', sans-serif; 24 | font-size: 1.4rem; 25 | display: inline-block; 26 | min-height: 50px; 27 | padding-left: 5%; 28 | padding-right: 5%; 29 | text-align: center; 30 | width: 33%; 31 | } 32 | 33 | tr:nth-child(even){ 34 | background-color: white; 35 | } 36 | tr:nth-child(odd) { 37 | background-color: #cacaca; 38 | } 39 | b{ 40 | margin-top: 10px; 41 | display: inline-block; 42 | } 43 | .icon-img{ 44 | max-width: 30px; 45 | max-height: 30px; 46 | margin-left: 10px; 47 | margin-right: 10px; 48 | margin-bottom: 7px; 49 | } 50 | .info-img { 51 | max-height: 50px; 52 | height: 2.5em; 53 | width: 2.5em; 54 | max-width: 50px; 55 | top: -10px; 56 | position: relative; 57 | filter: invert(100%); 58 | } 59 | 60 | .info-img:hover { 61 | cursor: pointer; 62 | } 63 | .main-container{ 64 | width: 60%; 65 | margin:0 auto; 66 | alignment: center; 67 | } 68 | 69 | .overlay-back { 70 | height: 100vh; 71 | width: 100vw; 72 | position: absolute; 73 | background-color: rgba(0, 0, 0, 0.7); 74 | top: 0; 75 | } 76 | 77 | .overlay { 78 | width: 70%; 79 | left: 15%; 80 | background-color: white; 81 | top: 0; 82 | position: relative; 83 | top: 10%; 84 | opacity: .9; 85 | padding: 10px 2%; 86 | } 87 | 88 | .overlay:hover { 89 | cursor: pointer; 90 | } 91 | 92 | .overlay-title { 93 | color: black; 94 | text-align: center; 95 | font-family: 'Oswald', sans-serif; 96 | 97 | } -------------------------------------------------------------------------------- /Web/src/app/challenge/challenge.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{title}}
3 | 4 | 5 | 6 | 10 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
7 | 8 | player 9 | 11 | 12 | Clan 13 | 15 | Fight Button 16 |
{{fighter.player_name}}{{fighter.clan_name}}
27 |
28 |
29 |
30 |
What is this page?
31 |
32 |

This is a page where you can challenge players from other clans 33 | If you are unable to see the "FIGHT ME" button, it means that the website was unable to 34 | find out client running on your computer. 35 |

36 |

If this is a mistake and the client is actually running, try either refreshing the web page, 37 | restarting your client or both. If you do not have the client you will not be able to challenge 38 | other users. Sorry! 39 |

40 |

To challenge another user just click the button next to their name. You will be taken to a lobby screen 41 | on your client. You will not be able to challenge another opponent until you refresh the page. 42 |

43 |

Anyway thats about all. Happy questing!

44 |
45 |
46 | -------------------------------------------------------------------------------- /Web/src/app/challenge/challenge.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ChallengeComponent } from './challenge.component'; 4 | 5 | describe('ChallengeComponent', () => { 6 | let component: ChallengeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ChallengeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ChallengeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Web/src/app/challenge/challenge.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { StoreService } from "../store.service"; 3 | 4 | @Component({ 5 | selector: "app-challenge", 6 | templateUrl: "./challenge.component.html", 7 | styleUrls: ["./challenge.component.css"] 8 | }) 9 | export class ChallengeComponent implements OnInit { 10 | title: string = "WELCOME TO THE THUNDERDOME"; 11 | board: Array; 12 | overlay:string = "hidden"; 13 | currentSummoner:object = undefined; 14 | name:string = "!"; 15 | inchallenge:boolean = true; 16 | constructor(private store: StoreService) {} 17 | 18 | ngOnInit() { 19 | this.store.checkClient((data) => { 20 | console.log(data); 21 | if(data.error || data.errorCode) { 22 | if(data.httpStatus == 404) { 23 | alert("Please Log in"); 24 | } 25 | else if(data.error == 400) { 26 | alert("Couldn't find app"); 27 | } 28 | else { 29 | alert("Unknown error"); 30 | } 31 | } 32 | else { 33 | this.currentSummoner = data; 34 | this.name = data.displayName; 35 | this.title = `WELCOME TO THE THUNDERDOME ${data.displayName.toUpperCase()}`; 36 | this.inchallenge = false; 37 | } 38 | this.store.getFighters(this.name, fighters => { 39 | console.log(fighters); 40 | this.board = fighters; 41 | }); 42 | 43 | }); 44 | 45 | } 46 | 47 | fightPlayer(otherPlayer:object) { 48 | console.log(otherPlayer); 49 | this.inchallenge = true; 50 | this.store.setupLobby("http://localhost:4800/lol/create-lobby", otherPlayer["summoner_id"], (res) => { 51 | this.store.acceptLobby(otherPlayer["ip"] + "/lol/accept-lobby", this.currentSummoner["summonerId"], () => { 52 | 53 | }); 54 | }); 55 | 56 | } 57 | 58 | showOverlay() { 59 | console.log('hh') 60 | this.overlay = "visible"; 61 | } 62 | 63 | hideOverlay() { 64 | this.overlay = "hidden"; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Web/src/app/clan-leaderboard/clan-leaderboard.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/src/app/clan-leaderboard/clan-leaderboard.component.css -------------------------------------------------------------------------------- /Web/src/app/clan-leaderboard/clan-leaderboard.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 |
8 | 9 | -------------------------------------------------------------------------------- /Web/src/app/clan-leaderboard/clan-leaderboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ClanLeaderboardComponent } from './clan-leaderboard.component'; 4 | 5 | describe('ClanLeaderboardComponent', () => { 6 | let component: ClanLeaderboardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ClanLeaderboardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ClanLeaderboardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Web/src/app/clan-leaderboard/clan-leaderboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { LeaderboardType } from '../self-defined/enums'; 3 | 4 | @Component({ 5 | selector: "app-clan-leaderboard", 6 | templateUrl: "./clan-leaderboard.component.html", 7 | styleUrls: ["./clan-leaderboard.component.css"] 8 | }) 9 | export class ClanLeaderboardComponent implements OnInit { 10 | clan = LeaderboardType.CLAN; 11 | constructor() {} 12 | 13 | ngOnInit() {} 14 | } 15 | -------------------------------------------------------------------------------- /Web/src/app/download/download.component.css: -------------------------------------------------------------------------------- 1 | .main { 2 | position: relative; 3 | width: 80%; 4 | left: 10%; 5 | opacity: .85; 6 | padding: 20px 5%; 7 | background-color: rgb(21, 21, 26); 8 | color: whitesmoke; 9 | border-radius: 10px; 10 | } 11 | 12 | .title { 13 | width: 100%; 14 | text-align: center; 15 | font-size: 2em; 16 | font-family: 'Oswald', sans-serif; 17 | } 18 | 19 | .sub-title { 20 | font-size: 1.7em; 21 | font-family: 'Oswald', sans-serif; 22 | /* text-align: center; */ 23 | } 24 | 25 | .download-link { 26 | text-align: center; 27 | font-size: 1.5em; 28 | width: 100%; 29 | display: block; 30 | } 31 | 32 | p { 33 | margin-bottom: 0; 34 | /* font-family: 'Courier New', Courier, monospace; */ 35 | } -------------------------------------------------------------------------------- /Web/src/app/download/download.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Download the App Here
3 |

Hello, thanks for checking out our app!

4 |

Before actually downloading our app we need to add a disclaimer:

5 |

If you are not part of the 2018 Riot API Challenge judging comittee, DO NOT DOWNLOAD THIS

6 |
7 |

Now if you are one of the judges, click on the link below to download our app

8 | 9 | DOWNLOAD 10 | 11 |
Instructions for Use
12 |
13 |

TODO

14 |
-------------------------------------------------------------------------------- /Web/src/app/download/download.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DownloadComponent } from './download.component'; 4 | 5 | describe('DownloadComponent', () => { 6 | let component: DownloadComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DownloadComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DownloadComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Web/src/app/download/download.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-download', 5 | templateUrl: './download.component.html', 6 | styleUrls: ['./download.component.css'] 7 | }) 8 | export class DownloadComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Web/src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | .leaderboards-home { 2 | display: grid; 3 | grid-template-columns: auto auto; 4 | width: 90%; 5 | left: 5%; 6 | position: relative; 7 | column-gap: 50px; 8 | } 9 | 10 | .home-leaderboard { 11 | border: 1px black solid; 12 | border-radius: 10px 10px 0 0; 13 | padding: 5px 0 0 0; 14 | } 15 | 16 | .home-header{ 17 | font-family: 'Oswald', sans-serif; 18 | font-size: 4.5rem; 19 | color: white; 20 | font-weight: 800; 21 | grid-area: a; 22 | } 23 | 24 | .home-subtitle{ 25 | font-family: 'Oswald', sans-serif; 26 | font-size: 1.5rem; 27 | color: white; 28 | font-weight: 900; 29 | grid-area: b; 30 | } 31 | 32 | .main-container{ 33 | width: 50%; 34 | height: 60%; 35 | margin:0 auto; 36 | margin-top: 12%; 37 | } 38 | .title-container{ 39 | width: 80%; 40 | height: 30%; 41 | margin:0 auto; 42 | margin-top: 50px; 43 | display: grid; 44 | grid-template-areas: 'a a' 'b b' 'c d'; 45 | column-gap: 10px; 46 | } 47 | 48 | .button { 49 | background-color: #08a6ff; 50 | border: none; 51 | color: white; 52 | font-family: 'Oswald', sans-serif; 53 | padding: 15px 0; 54 | text-align: center; 55 | text-decoration: none; 56 | display: inline-block; 57 | font-size: 16px; 58 | margin:0 auto; 59 | margin-right: 10px; 60 | width: 100% 61 | } 62 | 63 | .button:hover 64 | { 65 | cursor: pointer; 66 | background-color: #0cabff; 67 | } 68 | -------------------------------------------------------------------------------- /Web/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | INSERT MISSIONS HERE 5 |

6 |

7 | Making Clubs Great Again 8 |

9 | 10 | 15 |
16 | SOLO LEADERBOARDS 17 |
18 |
19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Web/src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Web/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { LeaderboardType } from "../self-defined/enums"; 3 | import {Router} from "@angular/router"; 4 | 5 | @Component({ 6 | selector: "app-home", 7 | templateUrl: "./home.component.html", 8 | styleUrls: ["./home.component.css"] 9 | }) 10 | export class HomeComponent implements OnInit { 11 | player = LeaderboardType.PLAYER; 12 | clan = LeaderboardType.CLAN; 13 | 14 | ngOnInit() {} 15 | 16 | constructor(private router:Router) { 17 | 18 | } 19 | 20 | toPlayer() { 21 | this.router.navigate(["/player"]); 22 | } 23 | 24 | toClan() { 25 | this.router.navigate(["/clan"]); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Web/src/app/leaderboard/leaderboard.component.css: -------------------------------------------------------------------------------- 1 | .table-title { 2 | width: 100%; 3 | font-family: 'Oswald', sans-serif; 4 | font-size: 2.5rem; 5 | color: white; 6 | margin-bottom: 10px; 7 | } 8 | table { 9 | font-family: 'Oswald', sans-serif; 10 | width: 100%; 11 | alignment: center; 12 | margin-left:auto; 13 | margin-right:auto; 14 | opacity: .9; 15 | } 16 | 17 | td, 18 | th { 19 | font-family: 'Oswald', sans-serif; 20 | font-size: 1.4rem; 21 | display: inline-block; 22 | min-height: 50px; 23 | padding-left: 5%; 24 | padding-right: 5%; 25 | text-align: center; 26 | width: 50%; 27 | } 28 | 29 | tr:nth-child(even){ 30 | background-color: white; 31 | } 32 | tr:nth-child(odd) { 33 | background-color: #cacaca; 34 | } 35 | b{ 36 | margin-top: 10px; 37 | display: inline-block; 38 | } 39 | .icon-img{ 40 | max-width: 30px; 41 | max-height: 30px; 42 | margin-left: 10px; 43 | margin-right: 10px; 44 | margin-bottom: 7px; 45 | } 46 | .main-container{ 47 | width: 60%; 48 | margin:0 auto; 49 | alignment: center; 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /Web/src/app/leaderboard/leaderboard.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{title}}
3 | 4 | 5 | 6 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
7 | 8 | {{type}} 9 | 11 | 12 | SCORE 13 |
{{entry.display_name}}{{entry.points}}
23 |
24 | 25 | -------------------------------------------------------------------------------- /Web/src/app/leaderboard/leaderboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LeaderboardComponent } from './leaderboard.component'; 4 | 5 | describe('LeaderboardComponent', () => { 6 | let component: LeaderboardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LeaderboardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LeaderboardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Web/src/app/leaderboard/leaderboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from "@angular/core"; 2 | import { LeaderboardType } from '../self-defined/enums'; 3 | import { StoreService } from '../store.service'; 4 | 5 | @Component({ 6 | selector: "app-leaderboard", 7 | templateUrl: "./leaderboard.component.html", 8 | styleUrls: ["./leaderboard.component.css"] 9 | }) 10 | export class LeaderboardComponent implements OnInit { 11 | @Input() type:LeaderboardType; 12 | title:string = ""; 13 | leaderboard:Array; 14 | constructor(private store:StoreService) { 15 | } 16 | 17 | ngOnInit() { 18 | console.log(this.type); 19 | this.title = this.type + " LEADERBOARD"; 20 | console.log(this.type); 21 | this.store.getLeaderboard(this.type, 25, (leaderboard) => { 22 | console.log(leaderboard); 23 | this.leaderboard = leaderboard; 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Web/src/app/player-leaderboard/player-leaderboard.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/src/app/player-leaderboard/player-leaderboard.component.css -------------------------------------------------------------------------------- /Web/src/app/player-leaderboard/player-leaderboard.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /Web/src/app/player-leaderboard/player-leaderboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PlayerLeaderboardComponent } from './player-leaderboard.component'; 4 | 5 | describe('PlayerLeaderboardComponent', () => { 6 | let component: PlayerLeaderboardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ PlayerLeaderboardComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PlayerLeaderboardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Web/src/app/player-leaderboard/player-leaderboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { LeaderboardType } from '../self-defined/enums'; 3 | 4 | @Component({ 5 | selector: "app-player-leaderboard", 6 | templateUrl: "./player-leaderboard.component.html", 7 | styleUrls: ["./player-leaderboard.component.css"] 8 | }) 9 | export class PlayerLeaderboardComponent implements OnInit { 10 | player = LeaderboardType.PLAYER; 11 | constructor() {} 12 | 13 | ngOnInit() {} 14 | } 15 | -------------------------------------------------------------------------------- /Web/src/app/register/register.component.css: -------------------------------------------------------------------------------- 1 | .main-section { 2 | position: relative; 3 | width: 80%; 4 | left: 10%; 5 | background-color: rgba(0, 0, 0, 0.6); 6 | border-radius: 10px; 7 | color: white; 8 | padding: 25px 10%; 9 | } 10 | 11 | .title { 12 | text-align: center; 13 | display: block; 14 | font-size: 2.5em; 15 | font-family: 'Oswald', sans-serif; 16 | } 17 | 18 | .status-message { 19 | text-align: center; 20 | } 21 | 22 | button { 23 | position: relative; 24 | left: 50%; 25 | transform: translateX(-50%); 26 | width: 200px; 27 | } -------------------------------------------------------------------------------- /Web/src/app/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Sign Up for Clan War
3 |
4 |

Checking status of clan-war sign-up

5 | 6 |

Sorry, there isn't a clan war in the sign-up phase right now. Please check back later

7 | 8 |
9 |

Click the button below to sign-up

10 | 11 |
12 | 13 |

You have already signed-up for the upcoming clan war (It probably will never happen. Sorry!)

14 | 15 |

Sorry couldn't find you. Are you sure your client is open?

16 |
-------------------------------------------------------------------------------- /Web/src/app/register/register.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RegisterComponent } from './register.component'; 4 | 5 | describe('RegisterComponent', () => { 6 | let component: RegisterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RegisterComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RegisterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Web/src/app/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { StoreService } from '../store.service'; 3 | 4 | @Component({ 5 | selector: "app-register", 6 | templateUrl: "./register.component.html", 7 | styleUrls: ["./register.component.css"] 8 | }) 9 | export class RegisterComponent implements OnInit { 10 | status:number = 0; 11 | constructor(private store:StoreService) {} 12 | 13 | ngOnInit() { 14 | this.store.checkStatus((status) => { 15 | console.log(status); 16 | this.status = status; 17 | }); 18 | } 19 | 20 | register() { 21 | this.store.registerForClanWar(() => { 22 | this.status = 3; 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Web/src/app/self-defined/enums.ts: -------------------------------------------------------------------------------- 1 | export enum LeaderboardType { 2 | PLAYER = "PLAYER", 3 | CLAN = "CLAN" 4 | } -------------------------------------------------------------------------------- /Web/src/app/store.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { StoreService } from './store.service'; 4 | 5 | describe('StoreService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: StoreService = TestBed.get(StoreService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /Web/src/app/store.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { HttpClient } from "../../node_modules/@angular/common/http"; 3 | import { LeaderboardType } from './self-defined/enums'; 4 | 5 | @Injectable({ 6 | providedIn: "root" 7 | }) 8 | export class StoreService { 9 | host = "http://ec2-35-182-253-71.ca-central-1.compute.amazonaws.com:8000"; 10 | constructor(private http: HttpClient) {} 11 | 12 | getLeaderboard(type:LeaderboardType, numb:Number = 50, callback:Function) { 13 | var uri = this.host + "/leaderboard/current/" + type.toLowerCase() + '/' + numb; 14 | this.http.get(uri).subscribe((data) => { 15 | callback(data); 16 | }); 17 | } 18 | 19 | getClanGames(war:number=1, callback:Function) { 20 | var uri = this.host + "/leaderboard/current/" + war; 21 | this.http.get(uri).subscribe((data) => { 22 | callback(data); 23 | }); 24 | } 25 | 26 | getFighters(user:string = "Earleking", callback:Function) { 27 | var uri = this.host + "/player/challenge/" + user; 28 | this.http.get(uri).subscribe((data) => { 29 | callback(data); 30 | }); 31 | } 32 | 33 | checkClient(callback:Function) { 34 | var uri = "http://localhost:4800/lol/me" 35 | this.http.get(uri).subscribe((data) => { 36 | callback(data); 37 | }, (err) => { 38 | callback({"error": 400}); 39 | }); 40 | } 41 | 42 | setupLobby(url:string, otherUser:object, callback:Function) { 43 | var options = { 44 | body: { 45 | "toSummonerId": otherUser 46 | } 47 | } 48 | this.http.put(url, options).subscribe((res) => { 49 | callback(res); 50 | }); 51 | } 52 | acceptLobby(url:string, from:string, callback:Function) { 53 | this.http.put(url, {"acceptSummonerId": from}).subscribe((res) => { 54 | callback(res); 55 | }); 56 | } 57 | 58 | checkStatus(callback:Function) { 59 | this.checkClient((data) => { 60 | if(data.error || data.errorCode) { 61 | callback(-1); 62 | return; 63 | } 64 | var url = `${this.host}/war/status/` + data.puuid; 65 | this.http.get(url).subscribe((res) => { 66 | callback(res["code"]); 67 | }); 68 | }); 69 | } 70 | 71 | registerForClanWar(callback:Function) { 72 | this.checkClient((data) => { 73 | if(data.error || data.errorCode) { 74 | callback(-1); 75 | return; 76 | } 77 | var url = `${this.host}/war/register/` + data.puuid; 78 | // var url = "http://localhost:8000/war/register/" + data.puuid; 79 | this.http.put(url, {}).subscribe((res) => { 80 | callback(); 81 | }); 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Web/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/src/assets/.gitkeep -------------------------------------------------------------------------------- /Web/src/assets/Person Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/src/assets/Person Icon.png -------------------------------------------------------------------------------- /Web/src/assets/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/src/assets/icon3.png -------------------------------------------------------------------------------- /Web/src/assets/info-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/src/assets/info-icon.png -------------------------------------------------------------------------------- /Web/src/assets/score.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/src/assets/score.png -------------------------------------------------------------------------------- /Web/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /Web/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /Web/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /Web/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afieldi/2018-Riot-API-Challenge/e609734bb5509ed59f0497d4685eed6f44334fcd/Web/src/favicon.ico -------------------------------------------------------------------------------- /Web/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /Web/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /Web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /Web/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. 22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot 23 | */ 24 | 25 | // import 'core-js/es6/symbol'; 26 | // import 'core-js/es6/object'; 27 | // import 'core-js/es6/function'; 28 | // import 'core-js/es6/parse-int'; 29 | // import 'core-js/es6/parse-float'; 30 | // import 'core-js/es6/number'; 31 | // import 'core-js/es6/math'; 32 | // import 'core-js/es6/string'; 33 | // import 'core-js/es6/date'; 34 | // import 'core-js/es6/array'; 35 | // import 'core-js/es6/regexp'; 36 | // import 'core-js/es6/map'; 37 | // import 'core-js/es6/weak-map'; 38 | // import 'core-js/es6/set'; 39 | 40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 41 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 42 | 43 | /** IE10 and IE11 requires the following for the Reflect API. */ 44 | // import 'core-js/es6/reflect'; 45 | 46 | /** 47 | * Web Animations `@angular/platform-browser/animations` 48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 50 | */ 51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 52 | 53 | /** 54 | * By default, zone.js will patch all possible macroTask and DomEvents 55 | * user can disable parts of macroTask/DomEvents patch by setting following flags 56 | * because those flags need to be set before `zone.js` being loaded, and webpack 57 | * will put import in the top of bundle, so user need to create a separate file 58 | * in this directory (for example: zone-flags.ts), and put the following flags 59 | * into that file, and then add the following code before importing zone.js. 60 | * import './zone-flags.ts'; 61 | * 62 | * The flags allowed in zone-flags.ts are listed here. 63 | * 64 | * The following flags will work for all browsers. 65 | * 66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 69 | * 70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 72 | * 73 | * (window as any).__Zone_enable_cross_context_check = true; 74 | * 75 | */ 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import 'zone.js/dist/zone'; // Included with Angular CLI. 81 | 82 | 83 | /*************************************************************************************************** 84 | * APPLICATION IMPORTS 85 | */ 86 | -------------------------------------------------------------------------------- /Web/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | body { 3 | margin: 0; 4 | } 5 | .background { 6 | background-repeat: no-repeat; 7 | background-size: 100vw 100vh; 8 | background-attachment: fixed; 9 | } 10 | -------------------------------------------------------------------------------- /Web/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /Web/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Web/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Web/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Web/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "no-output-on-prefix": true, 121 | "use-input-property-decorator": true, 122 | "use-output-property-decorator": true, 123 | "use-host-property-decorator": true, 124 | "no-input-rename": true, 125 | "no-output-rename": true, 126 | "use-life-cycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "component-class-suffix": true, 129 | "directive-class-suffix": true 130 | } 131 | } 132 | --------------------------------------------------------------------------------