├── .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 |
promisegets player matches
7 |promisegets multiple matches data and returns array of matches objects
10 |promisegets match data
13 |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 | 
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 | arraygets Array of roster Objects in match, includes match data in each element
8 |arraygets Array of participants Objects in match
11 |arraygets Array of participants IDs in roster Object
14 |arraygets Array of Participant Objects in Roster by finding the participants with the roster participants id´ 17 | sorts Participants by kills
18 |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 |
--------------------------------------------------------------------------------