├── .env.example ├── docs ├── logo.png ├── utils_pubg-api.md └── utils_filter.md ├── .gitignore ├── package.json ├── graphql ├── schema.graphql └── resolvers.js ├── server.js ├── utils ├── filter │ ├── player.js │ └── match.js └── requests │ └── pubg-api.js ├── LICENSE └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | PUBG_KEY=YOURVERYLONGINDEEDAWESOMEPUBGDEV360LEETNOSCOPEAPIKEY -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoniojps/graphql-pubg/HEAD/docs/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | data/ 4 | .env 5 | composer.lock 6 | node_modules/ 7 | dist/ 8 | npm-debug.log 9 | yarn-error.log 10 | .vscode/ 11 | .DS_Store -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "job-board-server", 3 | "private": true, 4 | "license": "MIT", 5 | "scripts": { 6 | "watch": "nodemon server.js", 7 | "start": "node server.js", 8 | "debug": "nodemon --inspect-brk server.js" 9 | }, 10 | "dependencies": { 11 | "apollo-server-express": "^1.3.6", 12 | "axios": "^0.18.0", 13 | "body-parser": "1.18.2", 14 | "cors": "2.8.4", 15 | "dotenv": "^6.0.0", 16 | "express": "4.16.3", 17 | "express-jwt": "5.3.1", 18 | "graphql": "^0.13.2", 19 | "graphql-tools": "^3.0.2", 20 | "jsonwebtoken": "8.2.0", 21 | "mongoose": "^5.1.5" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^1.17.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /graphql/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | player(name: String!,matchesLimit: Int,shards:String) : Player 3 | match(id: ID!, shards: String!) : Match 4 | } 5 | 6 | type Player { 7 | id: ID! 8 | name: String 9 | matches: [Match] 10 | } 11 | 12 | type Match { 13 | id: String 14 | gameMode: String 15 | createdAt: String 16 | map: String 17 | isCustomMatch: Boolean 18 | duration: Int 19 | server: String 20 | totalParticipants: Int 21 | rosters: [Roster] 22 | } 23 | 24 | type Roster { 25 | id: ID! 26 | slot: Int 27 | stats: RosterStats 28 | participants: [Participant] 29 | } 30 | 31 | type RosterStats { 32 | rank: Int 33 | won: Boolean 34 | kills: Int 35 | damage: Int 36 | dbnos: Int 37 | } 38 | 39 | type Participant { 40 | id: ID! 41 | name: String 42 | kills: Int 43 | damage: Float 44 | dbnos: Int 45 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const bodyParser = require('body-parser') 3 | const cors = require('cors') 4 | const express = require('express') 5 | 6 | const {makeExecutableSchema} = require('graphql-tools') 7 | const {graphqlExpress,graphiqlExpress} = require('apollo-server-express') 8 | const fs = require('fs') 9 | 10 | const port = process.env.PORT || 9000 11 | 12 | const typeDefs = fs.readFileSync('./graphql/schema.graphql',{encoding:'utf-8'}) 13 | const resolvers = require('./graphql/resolvers') 14 | const schema = makeExecutableSchema({typeDefs,resolvers}) 15 | 16 | const app = express() 17 | app.use(cors(), bodyParser.json()) 18 | app.use('/graphql',graphqlExpress({schema})) 19 | app.use('/graphiql',graphiqlExpress({endpointURL:'/graphql'})) 20 | 21 | app.listen(port, () => console.info(`Server started on port ${port}`)) 22 | -------------------------------------------------------------------------------- /utils/filter/player.js: -------------------------------------------------------------------------------- 1 | /* 2 | Utils for player data filtering 3 | const _player = require('./utils/filter/player') 4 | 5 | */ 6 | 7 | /** 8 | * gets Array of Player matches 9 | * sorts by more recent 10 | * @param {array} - Array of participants 11 | * @param {array} - Array of roster participants IDs 12 | * @returns {array} 13 | */ 14 | function getPlayerMatchesArr(playerObj,limit = 1) { 15 | const max = 5 16 | let matchesLimit = limit 17 | 18 | if (matchesLimit > max) matchesLimit = max 19 | 20 | const matches = playerObj.data[0].relationships.matches.data.map(match => match.id) 21 | const length = matches.length; 22 | if(length>matchesLimit){ 23 | const itemsToRemove = length-matchesLimit; 24 | return matches.reverse().splice(itemsToRemove,matchesLimit).reverse() 25 | } 26 | return matches; 27 | } 28 | 29 | 30 | 31 | 32 | module.exports = { 33 | getPlayerMatchesArr 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 António Pires dos Santos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/utils_pubg-api.md: -------------------------------------------------------------------------------- 1 | ## pubg-api Util 2 | Deals with requests to the pubg-api 3 | 4 |
5 |
getPlayerMatches(player, shards)promise
6 |

gets player matches

7 |
8 |
getMatchListData(matchesIdArr, shards)promise
9 |

gets multiple matches data and returns array of matches objects

10 |
11 |
getMatchData(matchID, shards)promise
12 |

gets match data

13 |
14 |
15 | 16 | 17 | 18 | ## getPlayerMatches(player, shards) ⇒ promise 19 | gets player matches 20 | 21 | **Kind**: global function 22 | 23 | | Param | Type | Default | Description | 24 | | --- | --- | --- | --- | 25 | | player | string | | player name - case sensitive | 26 | | shards | string | "pc-eu" | platform-server (pc-eu/pc-na...) | 27 | 28 | 29 | 30 | ## getMatchListData(matchesIdArr, shards) ⇒ promise 31 | gets multiple matches data and returns array of matches objects 32 | 33 | **Kind**: global function 34 | 35 | | Param | Type | Default | Description | 36 | | --- | --- | --- | --- | 37 | | matchesIdArr | array | | [matchesIDs] | 38 | | shards | string | "pc-eu" | platform-server (pc-eu/pc-na...) | 39 | 40 | 41 | 42 | ## getMatchData(matchID, shards) ⇒ promise 43 | gets match data 44 | 45 | **Kind**: global function 46 | 47 | | Param | Type | Default | Description | 48 | | --- | --- | --- | --- | 49 | | matchID | string | | match id | 50 | | shards | string | "pc-eu" | platform-server (pc-eu/pc-na...) | -------------------------------------------------------------------------------- /utils/requests/pubg-api.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | // config 3 | const ax = axios.create({ 4 | baseURL: 'https://api.playbattlegrounds.com/shards/', 5 | timeout: 10000, 6 | headers: { 7 | 'Authorization': `Bearer ${process.env.PUBG_KEY}`, 8 | 'Accept': 'application/vnd.api+json', 9 | 'Accept-Encoding': 'gzip' 10 | } 11 | }) 12 | 13 | /** 14 | * gets player matches 15 | * @param {string} - player name - case sensitive 16 | * @param {string} - platform-server (pc-eu/pc-na...) 17 | * @returns {promise} 18 | */ 19 | async function getPlayerMatches(player, shards = "pc-eu") { 20 | const url = `${encodeURI(shards)}/players?filter[playerNames]=${encodeURI(player)}` 21 | console.log(url) 22 | const playerMatches = await ax.get(url) 23 | .then(res => res.data) 24 | .catch(err => { 25 | console.log(err) 26 | return err 27 | }) 28 | return playerMatches 29 | } 30 | 31 | /** 32 | * gets multiple matches data and returns array of matches objects 33 | * @param {array} - [matchesIDs] 34 | * @param {string} - platform-server (pc-eu/pc-na...) 35 | * @returns {promise} 36 | */ 37 | async function getMatchListData(matchesIdArr, shards = 'pc-eu') { 38 | const matchesDataObj = await Promise.all( 39 | matchesIdArr.map((matchID) => getMatchData(matchID, shards)) 40 | ) 41 | return matchesDataObj 42 | } 43 | 44 | /** 45 | * gets match data 46 | * @param {string} - match id 47 | * @param {string} - platform-server (pc-eu/pc-na...) 48 | * @returns {promise} 49 | */ 50 | async function getMatchData(matchID, shards = 'pc-eu') { 51 | const url = `${encodeURI(shards)}/matches/${encodeURI(matchID)}` 52 | console.log(url) 53 | 54 | const match = await ax.get(url) 55 | .then(res => res.data) 56 | .catch(err => { 57 | console.log(err) 58 | return err 59 | }) 60 | return match; 61 | } 62 | 63 | module.exports = { 64 | getPlayerMatches, 65 | getMatchData, 66 | getMatchListData 67 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```diff 2 | - Warning: this project is not being maintained and it's usage is not recommended. 3 | ``` 4 | 5 | ![graphql-pubg](./docs/logo.png) 6 | # GraphQL-pubg 7 | PUBG API aggregator on top of GraphQL 8 | 9 | ## How to start the server? 10 | 11 | 1. Clone the repository 12 | ``` 13 | $ git clone https://github.com/antoniojps/graphql-pubg.git 14 | $ cd graphql-pubg 15 | ``` 16 | 2. Paste your **API KEY to .env** 17 | 18 | - create .env file from .env.example (just remove .example) and add your API KEY 19 | 3. Install dependencies and start the server 20 | 21 | ``` 22 | $ npm install 23 | 24 | // Starts both the webpack server as well as the graphQL server on 2 different ports 25 | $ npm start 26 | ``` 27 | 28 | ### GraphQL API 29 | http://localhost:9000/graphql 30 | . 31 | 32 | ### Access GraphiQL 33 | http://localhost:9000/graphiql 34 | 35 | ## Example Queries: 36 | The last 2 games of the player "shroud" in the North American server 37 | 38 | ```graphql 39 | { 40 | player(name:"shroud",shards:"pc-na",matchesLimit:2){ 41 | matches{ 42 | id 43 | gameMode 44 | createdAt 45 | map 46 | isCustomMatch 47 | duration 48 | totalParticipants 49 | rosters{ 50 | id 51 | slot 52 | stats{ 53 | rank 54 | kills 55 | damage 56 | dbnos 57 | } 58 | participants{ 59 | id 60 | name 61 | kills 62 | damage 63 | dbnos 64 | } 65 | } 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | Get single match data 72 | 73 | ```graphql 74 | { 75 | match(id:"47529205-1e29-4149-ac67-90907027c5f0",shards:"pc-eu"){ 76 | id 77 | map 78 | isCustomMatch 79 | rosters{ 80 | stats{ 81 | rank 82 | kills 83 | damage 84 | } 85 | participants{ 86 | name 87 | kills 88 | damage 89 | } 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | ### Notes 96 | 97 | - Shards is defaulted to "pc-eu" on all queries 98 | - matchesLimit in the Player query is capped at 5 as of now (you can change this at Utils/filter/player.js on the function getPlayerMatchesArr 99 | - Error handling is really limited 100 | - No telemetry data 101 | 102 | ### Docs for the Utils 103 | - [Filter](./docs/utils_filter.md) 104 | - [Pubg-Api](./docs/utils_pubg-api.md) 105 | -------------------------------------------------------------------------------- /utils/filter/match.js: -------------------------------------------------------------------------------- 1 | /* 2 | Utils for match data filtering 3 | const _match = require('./utils/filter/match') 4 | 5 | */ 6 | 7 | /** 8 | * gets Array of roster Objects in match, includes match data in each element 9 | * @param {object} - match data 10 | * @returns {array} 11 | */ 12 | function getRosters(obj) { 13 | const rosters = obj.included.filter(includedObj => includedObj.type === 'roster') 14 | .sort((obj1, obj2) => obj1.attributes.stats.rank - obj2.attributes.stats.rank) 15 | return rosters.map(roster => { 16 | return { 17 | roster, 18 | match: obj 19 | } 20 | }) 21 | } 22 | 23 | /** 24 | * gets Array of participants Objects in match 25 | * @param {object} - match data 26 | * @returns {array} 27 | */ 28 | function getParticipants(obj) { 29 | return obj.included.filter(includedObj => includedObj.type === 'participant') 30 | } 31 | 32 | /** 33 | * gets Array of participants IDs in roster Object 34 | * @param {object} - roster type 35 | * @returns {array} 36 | */ 37 | function getRosterParticipantsArr(rosterObj) { 38 | return rosterObj.relationships.participants.data.map(participant => participant.id) 39 | } 40 | 41 | /** 42 | * gets Array of Participant Objects in Roster by finding the participants with the roster participants id´ 43 | * sorts Participants by kills 44 | * @param {array} - Array of participants 45 | * @param {array} - Array of roster participants IDs 46 | * @returns {array} 47 | */ 48 | function getRosterParticipants(matchParticipants, rosterParticipantsArr) { 49 | return rosterParticipantsArr.map((id) => { 50 | return matchParticipants.find((participant => participant.id === id)) 51 | }).sort((obj1, obj2) => obj2.attributes.stats.kills - obj1.attributes.stats.kills) 52 | } 53 | 54 | function _getRosterStats(rosterParticipantsObj, key = 'kills') { 55 | return rosterParticipantsObj.map(participant => participant.attributes.stats[key]) 56 | .reduce((accumulator, current) => accumulator + current) 57 | } 58 | 59 | function getRosterKills(rosterParticipantsObj) { 60 | return _getRosterStats(rosterParticipantsObj, 'kills') 61 | } 62 | 63 | function getRosterDamage(rosterParticipantsObj) { 64 | return Math.round(_getRosterStats(rosterParticipantsObj, 'damageDealt')) 65 | } 66 | 67 | function getRosterDbnos(rosterParticipantsObj) { 68 | return _getRosterStats(rosterParticipantsObj, 'DBNOs') 69 | 70 | } 71 | 72 | module.exports = { 73 | getRosters, 74 | getParticipants, 75 | getRosterParticipants, 76 | getRosterParticipantsArr, 77 | getRosterKills, 78 | getRosterDamage, 79 | getRosterDbnos 80 | } 81 | -------------------------------------------------------------------------------- /graphql/resolvers.js: -------------------------------------------------------------------------------- 1 | const _match = require('./../utils/filter/match') 2 | const _player = require('./../utils/filter/player') 3 | const _pubg = require('./../utils/requests/pubg-api') 4 | 5 | 6 | const Query = { 7 | player: (root,arg) => { 8 | return _pubg.getPlayerMatches(arg.name, arg.shards).then(player => { 9 | return { 10 | id: player.data[0].id, 11 | name: player.data[0].attributes.name, 12 | matches: () => { 13 | arg.matchesLimit = arg.matchesLimit || 1; 14 | const matchesIDs = _player.getPlayerMatchesArr(player, arg.matchesLimit) 15 | return _pubg.getMatchListData(matchesIDs, arg.shards).then(res => res) 16 | } 17 | } 18 | }).catch(err => { 19 | console.log(err) 20 | }) 21 | }, 22 | match: (obj, arg) => _pubg.getMatchData(arg.id).then(res => res) 23 | } 24 | 25 | const Match = { 26 | id: obj => obj.data.id, 27 | gameMode: obj => obj.data.attributes.gameMode, 28 | createdAt: obj => obj.data.attributes.createdAt, 29 | map: obj => obj.data.attributes.mapName, 30 | isCustomMatch: obj => obj.data.attributes.isCustomMatch, 31 | duration: obj => obj.data.attributes.duration, 32 | server: obj => obj.data.attributes.shardId, 33 | totalParticipants: obj => _match.getParticipants(obj).length, 34 | rosters: obj => _match.getRosters(obj) 35 | } 36 | 37 | const Roster = { 38 | id: obj => obj.roster.id, 39 | slot: obj => obj.roster.attributes.stats.teamId, 40 | stats: obj => { 41 | const rosterParticipants = _match.getRosterParticipantsArr(obj.roster) 42 | const matchParticipants = _match.getParticipants(obj.match) 43 | const rosterParticipantsData = _match.getRosterParticipants(matchParticipants, rosterParticipants); 44 | 45 | return { 46 | rosters: obj.roster, 47 | participants: rosterParticipantsData 48 | } 49 | }, 50 | participants: (obj) => { 51 | const rosterParticipants = _match.getRosterParticipantsArr(obj.roster) 52 | const matchParticipants = _match.getParticipants(obj.match) 53 | 54 | return _match.getRosterParticipants(matchParticipants, rosterParticipants) 55 | // make request if data not available 56 | } 57 | } 58 | 59 | const RosterStats = { 60 | won: obj => obj.rosters.attributes.won === "true" ? true : false, 61 | rank: obj => obj.rosters.attributes.stats.rank, 62 | kills: obj => _match.getRosterKills(obj.participants), 63 | damage: obj => _match.getRosterDamage(obj.participants), 64 | dbnos: obj => _match.getRosterDbnos(obj.participants) 65 | } 66 | 67 | const Participant = { 68 | name: obj => obj.attributes.stats.name, 69 | kills: obj => obj.attributes.stats.kills, 70 | damage: obj => obj.attributes.stats.damageDealt, 71 | dbnos: obj => obj.attributes.stats.DBNOs 72 | } 73 | 74 | module.exports = { 75 | Query, 76 | Match, 77 | Roster, 78 | RosterStats, 79 | Participant 80 | } 81 | -------------------------------------------------------------------------------- /docs/utils_filter.md: -------------------------------------------------------------------------------- 1 | ## Filters the boring data 2 | Basically a lot of loops, filtering and maps on arrays and objects (boring stuff) 3 | 4 | ### Match 5 |
6 |
getRosters(obj)array
7 |

gets Array of roster Objects in match, includes match data in each element

8 |
9 |
getParticipants(obj)array
10 |

gets Array of participants Objects in match

11 |
12 |
getRosterParticipantsArr(rosterObj)array
13 |

gets Array of participants IDs in roster Object

14 |
15 |
getRosterParticipants(matchParticipants, rosterParticipantsArr)array
16 |

gets Array of Participant Objects in Roster by finding the participants with the roster participants id´ 17 | sorts Participants by kills

18 |
19 |
20 | 21 | 22 | 23 | ## getRosters(obj) ⇒ array 24 | gets Array of roster Objects in match, includes match data in each element 25 | 26 | **Kind**: global function 27 | 28 | | Param | Type | Description | 29 | | --- | --- | --- | 30 | | obj | object | match data | 31 | 32 | 33 | 34 | ## getParticipants(obj) ⇒ array 35 | gets Array of participants Objects in match 36 | 37 | **Kind**: global function 38 | 39 | | Param | Type | Description | 40 | | --- | --- | --- | 41 | | obj | object | match data | 42 | 43 | 44 | 45 | ## getRosterParticipantsArr(rosterObj) ⇒ array 46 | gets Array of participants IDs in roster Object 47 | 48 | **Kind**: global function 49 | 50 | | Param | Type | Description | 51 | | --- | --- | --- | 52 | | rosterObj | object | roster type | 53 | 54 | 55 | 56 | ## getRosterParticipants(matchParticipants, rosterParticipantsArr) ⇒ array 57 | gets Array of Participant Objects in Roster by finding the participants with the roster participants id´ 58 | sorts Participants by kills 59 | 60 | **Kind**: global function 61 | 62 | | Param | Type | Description | 63 | | --- | --- | --- | 64 | | matchParticipants | array | Array of participants | 65 | | rosterParticipantsArr | array | Array of roster participants IDs | 66 | 67 | ### Player 68 | 69 | 70 | 71 | ## getPlayerMatchesArr(playerObj, limit) ⇒ array 72 | gets Array of Player matches 73 | sorts by more recent 74 | 75 | **Kind**: global function 76 | 77 | | Param | Type | Default | Description | 78 | | --- | --- | --- | --- | 79 | | playerObj | array | | Array of participants | 80 | | limit | array | 1 | Array of roster participants IDs | --------------------------------------------------------------------------------