Skip to content

Instantly share code, notes, and snippets.

@sneakyness
Last active July 6, 2019 20:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sneakyness/e14965c3cd171a5c9fa8f74769a69895 to your computer and use it in GitHub Desktop.
Save sneakyness/e14965c3cd171a5c9fa8f74769a69895 to your computer and use it in GitHub Desktop.
Used http://app.quicktype.io to generate Swift Models for stats.quake.com API. All 3 routes (Leaderboards/Player Stats/Match History) work and serialize into codable model objects. Quad Thanks to Sponge for being very helpful and prompt with the questions I had while piecing this together. Twitter thread of progress here: https://twitter.com/Sne…
import Foundation
enum ChampionString: String, Codable {
case ranger = "RANGER"
case scalebearer = "SCALEBEARER"
case visor = "VISOR"
case anarki = "ANARKI"
case nyx = "NYX"
case sorlag = "SORLAG"
case clutch = "CLUTCH"
case galena = "GALENA"
case slash = "SLASH"
case doomslayer = "DOOM_SLAYER"
case bjblazkowicz = "BJ_BLAZKOWICZ"
case keel = "KEEL"
case strogg = "STROGG"
case deathknight = "DEATH_KNIGHT"
}
struct PlayerStats: Codable { // top level object for …/player/Stats
let name: String
let playerRatings: PlayerRatings
let playerLoadOut: PlayerLoadOut
let playerProfileStats: PlayerProfileStats
let playerLevelState: PlayerLevelState
let matches: [MatchStats]
}
struct MatchStats: Codable {
let id, playedDateTime: String
let gameMode, map, score: JSONNull?
let scoreLimit, timeLimit: Int
}
struct GamesSummary: Codable { // top level object for …/player/GamesSummary
let matches: [MatchSummary]
}
struct MatchSummary: Codable {
let id, time, mapName: String
let rank: Int
let score: [Int]?
let gameMode: GameModeString
let won: Bool
let xp: Int
let kdr: Double
let totalDamage: Int
let weaponAccuracy: [String: Double]
}
struct Leaderboards: Codable { // top level object for …/Leaderboard
let boardType: String
let entries: [LeaderboardEntry]
let totalEntries: Int
}
struct LeaderboardEntry: Codable {
let userName: String
let eloRating: Int
let profileIconID, namePlateID: String
enum CodingKeys: String, CodingKey {
case userName, eloRating
case profileIconID = "profileIconId"
case namePlateID = "namePlateId"
}
}
typealias PlayerSearch = [PlayerSearchElement] // top level object for player search
struct PlayerSearchElement: Codable {
let entityID, entityName: String
enum CodingKeys: String, CodingKey {
case entityID = "entityId"
case entityName
}
}
struct PlayerLevelState: Codable {
let level, exp: Int
}
struct PlayerLoadOut: Codable {
let namePlateID, iconID: String
enum CodingKeys: String, CodingKey {
case namePlateID = "namePlateId"
case iconID = "iconId"
}
}
struct PlayerProfileStats: Codable {
let champions: [String: ChampionStats]
}
struct ChampionStats: Codable {
let gameModes: GameMode
let damageStatusList: [String: DamageStatusList]
// let medals: JSONNull? // sponge said this is dead, safe to remove
}
struct DamageStatusList: Codable {
let hits, shots, kills, headshots: Int
let damage: Int
}
enum GameModeString: String, Codable {
case ffa = "FFA"
case tdm = "TDM"
case duel = "DUEL"
case sacrifice = "SACRIFICE"
case sacrificePro = "SACRIFICE_PRO"
case tdm2Vs2 = "TDM_2VS2"
case instagib = "INSTAGIB"
case duelPro = "DUEL_PRO"
}
struct GameMode: Codable {
let ffa, tdm, duel, sacrifice: GameModeStats
let sacrificePro, tdm2Vs2, instagib, duelPro: GameModeStats
enum CodingKeys: String, CodingKey {
case ffa = "FFA"
case tdm = "TDM"
case duel = "DUEL"
case sacrifice = "SACRIFICE"
case sacrificePro = "SACRIFICE_PRO"
case tdm2Vs2 = "TDM_2VS2"
case instagib = "INSTAGIB"
case duelPro = "DUEL_PRO"
}
}
struct GameModeStats: Codable {
let won, lost, tie, lifeTime: Int
let timePlayed, kills, deaths, powerPickups: Int
let megaHealthPickups, heavyArmorPickups, tacticalPickups, score: Int
let captured, defended: Int
let scoringEvents: [String: Int]
let healed, smallArmorPickups, rankedWon, rankedLost: Int
let rankedTied, rankedTimePlayed: Int
}
struct PlayerRatings: Codable {
let duel, tdm: GameModeRating
}
struct GameModeRating: Codable {
let rating, deviation: Int
let volitility: Double
let lastUpdated, gamesCount, lastChange: Int
let history: [RatingHistory]
}
struct RatingHistory: Codable {
let gamesPlayed, eloRating: Int
let time: String
let result: Int
let sessionID: String
enum CodingKeys: String, CodingKey {
case gamesPlayed, eloRating, time, result
case sessionID = "sessionId"
}
}
struct GameSearch: Codable { // top level item for game search
let summary: GameSummary // null if no player provided
let id: String
let teamScores: [JSONAny]
let playedDateTime, mapName: String
let timeLimit, scoreLimit: Int
let gameMode: String
let battleReportPersonalStatistics: [BattleReportPersonalStatistic]
}
struct BattleReportPersonalStatistic: Codable {
let isCustomGame: Bool
let userID, nickname: String
let teamIndex, averageLifetime, score, totalDamage: Int
let kills, deaths, previousProfileXP, megaHealthPickups: Int
let heavyArmorPickups, smallArmorPickups, powerPickups, hpHealed: Int
let bestWeapon: String
let bestWeaponAccuracyPercent, bestWeaponDamage, bestWeaponKills: Int
let lastSelectedChampion: String
let completedChallenges: [Int]
let completedStaticChallenges: [JSONAny]
let scoringEvents: [String: Int]
let championsTime: ChampionsTime
let frag, duelScore: Int
let squadChampionIndexArray: [JSONAny]
let damageStatsWithDamageTypeIndexArray: [String: SumAllDamageStats]
let sumAllDamageStats: SumAllDamageStats
let earnedXP, bonusXP, earnedFavor, bonusFavor: Int
enum CodingKeys: String, CodingKey {
case isCustomGame
case userID = "userId"
case nickname, teamIndex, averageLifetime, score, totalDamage, kills, deaths
case previousProfileXP = "previousProfileXp"
case megaHealthPickups, heavyArmorPickups, smallArmorPickups, powerPickups, hpHealed, bestWeapon, bestWeaponAccuracyPercent, bestWeaponDamage, bestWeaponKills, lastSelectedChampion, completedChallenges, completedStaticChallenges, scoringEvents, championsTime, frag, duelScore, squadChampionIndexArray, damageStatsWithDamageTypeIndexArray, sumAllDamageStats
case earnedXP = "earnedXp"
case bonusXP = "bonusXp"
case earnedFavor, bonusFavor
}
}
struct ChampionsTime: Codable {
let galena, sorlag, scalebearer, anarki: Int?
let nyx, keel, deathknight, visor: Int?
let ranger, clutch, slash, doomslayer: Int?
let bjblazkowicz, strogg: Int?
enum CodingKeys: String, CodingKey {
case ranger = "RANGER"
case scalebearer = "SCALEBEARER"
case visor = "VISOR"
case anarki = "ANARKI"
case nyx = "NYX"
case sorlag = "SORLAG"
case clutch = "CLUTCH"
case galena = "GALENA"
case slash = "SLASH"
case doomslayer = "DOOM_SLAYER"
case bjblazkowicz = "BJ_BLAZKOWICZ"
case keel = "KEEL"
case strogg = "STROGG"
case deathknight = "DEATH_KNIGHT"
}
}
struct SumAllDamageStats: Codable {
let shots, hits, damage, kills: Int
}
struct GameSummary: Codable {
let id, time, mapName: String
let rank: Int
let score: JSONNull?
let gameMode: String
let won: Bool
let xp: Int
let kdr: Double
let totalDamage: Int
let weaponAccuracy: [String: Double]
}
// MARK: - URLSession response handlers
extension URLSession {
fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
return self.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completionHandler(nil, response, error)
return
}
completionHandler(try? JSONDecoder().decode(T.self, from: data), response, nil)
}
}
func playerStatsTask(with url: URL, completionHandler: @escaping (PlayerStats?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
return self.codableTask(with: url, completionHandler: completionHandler)
}
func gamesSummaryTask(with url: URL, completionHandler: @escaping (GamesSummary?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
return self.codableTask(with: url, completionHandler: completionHandler)
}
func leaderboardsTask(with url: URL, completionHandler: @escaping (Leaderboards?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
return self.codableTask(with: url, completionHandler: completionHandler)
}
func playerSearchTask(with url: URL, completionHandler: @escaping (PlayerSearch?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
return self.codableTask(with: url, completionHandler: completionHandler)
}
func gameSearchTask(with url: URL, completionHandler: @escaping (GameSearch?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
return self.codableTask(with: url, completionHandler: completionHandler)
}
}
// MARK: Encode/decode helpers
class JSONNull: Codable {
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
class JSONCodingKey: CodingKey {
let key: String
required init?(intValue: Int) {
return nil
}
required init?(stringValue: String) {
key = stringValue
}
var intValue: Int? {
return nil
}
var stringValue: String {
return key
}
}
class JSONAny: Codable {
let value: Any
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = JSONCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}
public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: JSONCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}
// TODO: scoring event constants
//"SCORING_EVENT_KILL": 2094,
//"SCORING_EVENT_ASSIST": 0,
//"SCORING_EVENT_HEADSHOT": 28,
//"SCORING_EVENT_MIDAIRKILL": 48,
//"SCORING_EVENT_IMPRESSIVE": 81,
//"SCORING_EVENT_DIEHARD": 7,
//"SCORING_EVENT_FIRSTBLOOD": 10,
//"SCORING_EVENT_ABILITYKILL": 218,
//"SCORING_EVENT_EXCELENT": 164,
//"SCORING_EVENT_TRIPLEKILL": 13,
//"SCORING_EVENT_OVERKILL": 2,
//"SCORING_EVENT_EXTIRPATOR": 1,
//"SCORING_EVENT_CRUSHER": 1,
//"SCORING_EVENT_EXTERMINATOR": 1,
//"SCORING_EVENT_DESTROYER": 0,
//"SCORING_EVENT_REAPER": 0,
//"SCORING_EVENT_GOD_OF_DEATH": 0,
//"SCORING_EVENT_HITMAN": 0,
//"SCORING_EVENT_GAUNTLET": 56,
//"SCORING_EVENT_COMBO_KILL": 3,
//"SCORING_EVENT_RING_OUT": 17,
//"SCORING_EVENT_PERFORATION": 0,
//"SCORING_EVENT_SERIAL_KILLER_1": 62,
//"SCORING_EVENT_SERIAL_KILLER_2": 2,
//"SCORING_EVENT_SERIAL_KILLER_3": 0,
//"SCORING_EVENT_SERIAL_KILLER_4": 0,
//"SCORING_EVENT_SERIAL_KILLER_5": 0,
//"SCORING_EVENT_SERIAL_KILLER_6": 0,
//"SCORING_EVENT_SERIAL_KILLER_7": 0,
//"SCORING_EVENT_SERIAL_KILLER_8": 0,
//"SCORING_EVENT_SERIAL_KILLER_9": 0,
//"SCORING_EVENT_SERIAL_KILLER_10": 0,
//"SCORING_EVENT_LAST_SHOT": 0,
//"SCORING_EVENT_ASSISTANT": 0,
//"SCORING_EVENT_STOP": 0,
//"SCORING_EVENT_GUARDIAN": 0,
//"SCORING_EVENT_SNIPER": 0,
//"SCORING_EVENT_REVENGE": 103,
//"SCORING_EVENT_CLOWN": 0,
//"SCORING_EVENT_FROM_THE_GRAVE": 30,
//"SCORING_EVENT_BAD_LUCK": 0,
//"SCORING_EVENT_COUNTER_ABILITY": 0,
//"SCORING_EVENT_MELEE_MASTERY": 0,
//"SCORING_EVENT_MELEE_GOD": 0,
//"SCORING_EVENT_MACHINEGUN_MASTERY": 0,
//"SCORING_EVENT_MACHINEGUN_GOD": 0,
//"SCORING_EVENT_SHOTGUN_MASTERY": 0,
//"SCORING_EVENT_SHOTGUN_GOD": 1,
//"SCORING_EVENT_ROCKET_LAUNCHER_MASTERY": 0,
//"SCORING_EVENT_ROCKET_LAUNCHER_GOD": 11,
//"SCORING_EVENT_LIGHTNING_GUN_MASTERY": 0,
//"SCORING_EVENT_LIGHTNING_GUN_GOD": 0,
//"SCORING_EVENT_RAILGUN_MASTERY": 0,
//"SCORING_EVENT_RAILGUN_GOD": 0,
//"SCORING_EVENT_NAILGUN_MASTERY": 0,
//"SCORING_EVENT_NAILGUN_GOD": 0,
//"SCORING_EVENT_FIGHTER": 0,
//"SCORING_EVENT_TERRORIST": 0,
//"SCORING_EVENT_AVENGER": 0,
//"SCORING_EVENT_QUAD_KILLER": 0,
//"SCORING_EVENT_RAGE": 0,
//"SCORING_EVENT_FLOCK": 0,
//"SCORING_EVENT_DAMAGE_DEALER": 51,
//"SCORING_EVENT_TELEFRAG": 2,
//"SCORING_EVENT_OBELISK_CAPTURE": 0,
//"SCORING_EVENT_OBELISK_CAPTURE_ASSIST": 0,
//"SCORING_EVENT_OBELISK_DEFEND": 0,
//"SCORING_EVENT_POWERUP": 102,
//"SCORING_EVENT_FFA_PERFECT": 0,
//"SCORING_EVENT_OBELISK_MASTERY": 0,
//"SCORING_EVENT_OBELISK_OFFENCE": 0,
//"SCORING_EVENT_MATCH_WIN": 20,
//"SCORING_EVENT_MATCH_COMPLETE": 61,
//"SCORING_EVENT_TEAM_WIN": 0,
//"SCORING_EVENT_TEAM_TIE": 0,
//"SCORING_EVENT_1_PLACE": 20,
//"SCORING_EVENT_2_PLACE": 14,
//"SCORING_EVENT_3_PLACE": 12,
//"SCORING_EVENT_MVP": 0,
//"SCORING_EVENT_DAMAGE": 3863,
//"SCORING_EVENT_ABILITY_HEAL": 0,
//"SCORING_EVENT_BLOCKING": 0,
//"SCORING_EVENT_PICKUP_MAJOR": 385,
//"SCORING_EVENT_PICKUP_WEAPON": 3154,
//"SCORING_EVENT_TOTEM_DESTROY": 39,
//"SCORING_EVENT_ABILITY_USE_RANGER": 1113,
//"SCORING_EVENT_FIRST_GAME": 0,
//"SCORING_EVENT_FIRST_WIN": 0,
//"SCORING_EVENT_ABILITY_USE_VISOR": 0,
//"SCORING_EVENT_ABILITY_USE_SCALEBEARER": 0,
//"SCORING_EVENT_ABILITY_USE_NYX": 0,
//"SCORING_EVENT_ABILITY_USE_GALENA": 0,
//"SCORING_EVENT_ABILITY_USE_ANARKI": 0,
//"SCORING_EVENT_ABILITY_USE_CLUTCH": 0,
//"SCORING_EVENT_ABILITY_USE_SLASH": 0,
//"SCORING_EVENT_ABILITY_USE_SORLAG": 0,
//"SCORING_EVENT_ABILITY_USE_KEEL": 0,
//"SCORING_EVENT_ABILITY_USE_STROGG": 0,
//"SCORING_EVENT_ABILITY_USE_DOOM": 0,
//"SCORING_EVENT_ABILITY_USE_BJ": 0,
//"SCORING_EVENT_HELPING_HAND": 0,
//"SCORING_EVENT_QUATERBACK": 0,
//"SCORING_EVENT_DEFENDER": 0,
//"SCORING_EVENT_SURVIVOR": 10,
//"SCORING_EVENT_SCAVENGER": 20,
//"SCORING_EVENT_MOST_DAMAGE": 27,
//"SCORING_EVENT_MOST_ACCURATE": 10,
//"SCORING_EVENT_PARTY_BREAKER": 43,
//"SCORING_EVENT_DENIED": 98,
//"SCORING_EVENT_POWERUP_MASSACRE": 6,
//"SCORING_EVENT_POINT_BLANK": 83,
//"SCORING_EVENT_NET_MASTER": 20,
//"SCORING_EVENT_TANK": 125,
//"SCORING_EVENT_SHOWSTOPPER": 44,
//"SCORING_EVENT_OT": 0,
//"SCORING_EVENT_COMEBACK": 0,
//"SCORING_EVENT_PRECISE": 4,
//"SCORING_EVENT_COLLATERAL_DAMAGE": 1,
//"SCORING_EVENT_DP": 0,
//"SCORING_EVENT_STOPWATCH": 72,
//"SCORING_EVENT_NEMESIS": 0,
//"SCORING_EVENT_AIRBORNE": 27,
//"SCORING_EVENT_BRING_EM_DOWN": 0,
//"SCORING_EVENT_DOMINATING": 3,
//"SCORING_EVENT_PROVACATEUR": 0,
//"SCORING_EVENT_KAMIKAZE": 10,
//"SCORING_EVENT_DISINTEGRATOR": 2,
//"SCORING_EVENT_SHUB_SLAYER": 55,
//"SCORING_EVENT_DEADLY_SLIPGATE": 43,
//"SCORING_EVENT_SHOOTING_RANGE": 0,
//"SCORING_EVENT_CONTROLLER": 0,
//"SCORING_EVENT_SEER": 0,
//"SCORING_EVENT_STOMPER": 0,
//"SCORING_EVENT_CHOOCHOO": 0,
//"SCORING_EVENT_ROADKILL": 0,
//"SCORING_EVENT_EVASIVE": 0,
//"SCORING_EVENT_INSIDEOUT": 0,
//"SCORING_EVENT_FROM_THE_SHADOW": 0,
//"SCORING_EVENT_HOVERTANK": 0,
//"SCORING_EVENT_REANIMATION": 0,
//"SCORING_EVENT_JUNKIE": 0,
//"SCORING_EVENT_PLAGUE": 0,
//"SCORING_EVENT_MELTDOWN": 0,
//"SCORING_EVENT_VENOMOUS": 0,
//"SCORING_EVENT_THEROCK": 0,
//"SCORING_EVENT_THEDRILL": 0,
//"SCORING_EVENT_THESHOCKER": 0,
//"SCORING_EVENT_DAMNED": 0,
//"SCORING_EVENT_BLESSED": 0,
//"SCORING_EVENT_CURSED": 0,
//"SCORING_EVENT_NAZI_SLAYER": 0,
//"SCORING_EVENT_MEIN_LEBEN": 0,
//"SCORING_EVENT_GET_PSYCHED": 0,
//"SCORING_EVENT_SLIDER": 0,
//"SCORING_EVENT_SLALOM": 0,
//"SCORING_EVENT_BLACK_SWAN": 0,
//"SCORING_EVENT_BERSERKER": 0,
//"SCORING_EVENT_DAISY": 0,
//"SCORING_EVENT_SLAYER": 0,
//"SCORING_EVENT_TRIBOLT_GOD": 0,
//"SCORING_EVENT_PINEAPPLE": 0,
//"SCORING_EVENT_NADE_SPAM": 0,
//"SCORING_EVENT_MORTAR": 0,
//"SCORING_EVENT_BOMBARDMENT": 1,
//"SCORING_EVENT_PSI_RADAR": 10,
//"SCORING_EVENT_AIR_ASSAULT": 0,
//"SPECIAL_DAMAGE_DEALER_EVENT": 0,
//"SPECIAL_HEALER_EVENT": 1,
//"SPECIAL_DAMAGE_ABSORBED_EVENT": 0,
//"CHAMPION_MATCH_WIN": 20,
//"CHAMPION_MATCH_COMPLETE": 50,
//"ABILLITY_KILLS_GOLD": 0,
//"ABILLITY_KILLS_SILVER": 0,
//"ABILLITY_KILLS_BRONZE": 0,
//"DAMAGE_DEALT_GOLD": 0,
//"DAMAGE_DEALT_SILVER": 0,
//"DAMAGE_DEALT_BRONZE": 0,
//"ASSIST_GOLD": 0,
//"ASSIST_SILVER": 0,
//"ASSIST_BRONZE": 0,
//"MACHINEGUN_KILLS_GOLD": 0,
//"MACHINEGUN_KILLS_SILVER": 0,
//"MACHINEGUN_KILLS_BRONZE": 0,
//"RAILGUN_KILLS_GOLD": 0,
//"RAILGUN_KILLS_SILVER": 0,
//"RAILGUN_KILLS_BRONZE": 0,
//"ROCKET_KILLS_GOLD": 0,
//"ROCKET_KILLS_SILVER": 0,
//"ROCKET_KILLS_BRONZE": 0,
//"NAILGUN_KILLS_GOLD": 0,
//"NAILGUN_KILLS_SILVER": 0,
//"NAILGUN_KILLS_BRONZE": 0,
//"TRIBOLT_KIILS_GOLD": 0,
//"TRIBOLT_KIILS_SILVER": 0,
//"TRIBOLT_KIILS_BRONZE": 0,
//"SHOTGUN_KILLS_GOLD": 0,
//"SHOTGUN_KILLS_SILVER": 0,
//"SHOTGUN_KILLS_BRONZE": 0,
//"LIGHTNING_GUN_KILLS_GOLD": 0,
//"LIGHTNING_GUN_KILLS_SILVER": 0,
//"LIGHTNING_GUN_KILLS_BRONZE": 0,
//"ACCURACY_GOLD": 0,
//"ACCURACY_SILVER": 0,
//"ACCURACY_BRONZE": 0,
//"GAUNTLET_KILLS_GOLD": 0,
//"GAUNTLET_KILLS_SILVER": 0,
//"GAUNTLET_KILLS_BRONZE": 0,
//"KILLS_BASIC": 0,
//"DAMAGE_DEALT_BASIC": 0,
//"ASSIST_BASIC": 0,
//"HEAL_BASIC": 0,
//"ARMOR_PICKUP_BASIC": 0,
//"MEGA_PICKUP_BASIC": 0,
//"POWER_PICKUP_BASIC": 0,
//"SCORING_EVENT_INFERNO": 0,
//"SCORING_EVENT_TRIDENT": 0,
//"SCORING_EVENT_SMELTER": 0,
//"SCORING_EVENT_PICKUP_MEGA_HEALTH": 124,
//"SCORING_EVENT_PICKUP_HEAVY_ARMOR": 141,
//"SCORING_EVENT_ABILITY_USE_ANY": 743
// Routes
//https://stats.quake.com/api/v2/Leaderboard?from=0&board={duel,tdm}&season={current,preseason1}
//https://stats.quake.com/api/v2/Player/Stats?name=sneakyness
//https://stats.quake.com/api/v2/Player/GamesSummary?name=sneakyness
//https://stats.quake.com/api/v2/Player/Search?term=sneakyness
//https://stats.quake.com/api/v2/Player/Games?playerName=sneakyness&id=43291c80-9aaa-11e8-bce6-0003ffb6d7a2, where id is the id of a MatchSummary (playerName optional)
guard let url = URL(string: "https://stats.quake.com/api/v2/Leaderboard?from=0&board=tdm&season=current") else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
do {
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
dataResponse, options: [])
// debugPrint(jsonResponse)
// print("\n ---- \n")
// let playerStats = try? JSONDecoder().decode(PlayerStats.self, from: jsonData)
// let gamesSummary = try? JSONDecoder().decode(GamesSummary.self, from: jsonData)
// let leaderboards = try? JSONDecoder().decode(Leaderboards.self, from: jsonData)
let leaderboards = try JSONDecoder().decode(Leaderboards.self, from: dataResponse)
// debugPrint(summary)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
@aleab
Copy link

aleab commented Aug 8, 2018

In case you missed them, there are also these two endpoints in the API:

  • Player/Search?term=
  • Player/Games?playerName=&id=, where id is the id of a MatchSummary

@sneakyness
Copy link
Author

Thank you so much @aleab! I'll toss those in shortly

@aleab
Copy link

aleab commented Aug 8, 2018

Oh– forgot to specify that the playerName parameter is actually optional.

@sneakyness
Copy link
Author

sneakyness commented Aug 8, 2018

Player name search and game search have been added to the gist. I've only tested name search so far, game search is thicc so I'm gonna loop back around to that tomorrow. Thanks again 👍

⭐ Don't forget to star this gist if you find it useful ⭐

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment