Compare commits
26 Commits
c1d58c838d
...
1.2
| Author | SHA1 | Date | |
|---|---|---|---|
| b646a06ec6 | |||
| 35a92e7661 | |||
| 8a5ad8a470 | |||
| f0cbc564f8 | |||
| 97d4325c02 | |||
| 4e57651370 | |||
| de3740741f | |||
| 452acf0cba | |||
| e97b04805d | |||
| 3627795fb9 | |||
| 42746bf347 | |||
| 7ca242c863 | |||
| 60dc3d6708 | |||
| 2f1a658235 | |||
| 450ff25c46 | |||
| e63352e7a8 | |||
| 88c6e93715 | |||
| be4ccf088f | |||
| 22bd6c766b | |||
| f77815ad7f | |||
| e75d9219e1 | |||
| 2c4f836829 | |||
| e9064f9b61 | |||
| 33410e68ed | |||
| 9cf756ffbd | |||
| 28720bfcd5 |
@@ -16,33 +16,45 @@ jobs:
|
|||||||
- name: Install java
|
- name: Install java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: "temurin"
|
||||||
java-version: '21'
|
java-version: "21"
|
||||||
|
|
||||||
|
# Install Leiningen
|
||||||
|
- name: Install Leiningen
|
||||||
|
run: |
|
||||||
|
curl https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein > lein
|
||||||
|
chmod +x lein
|
||||||
|
sudo mv lein /usr/local/bin/lein
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
- name: Install dependencies
|
||||||
|
run: lein deps
|
||||||
|
|
||||||
# Optional: cache dependencies
|
# Optional: cache dependencies
|
||||||
- name: Cache dependencias
|
#- name: Cache dependencias
|
||||||
uses: actions/cache@v4
|
# uses: actions/cache@v4
|
||||||
with:
|
# with:
|
||||||
path: ~/.m2/repository
|
# path: ~/.m2/repository
|
||||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/project.clj') }}
|
# key: ${{ runner.os }}-m2-${{ hashFiles('**/project.clj') }}
|
||||||
restore-keys: |
|
# restore-keys: |
|
||||||
${{ runner.os }}-m2-
|
# ${{ runner.os }}-m2-
|
||||||
|
|
||||||
# Get leiningen's version
|
# Get leiningen's version
|
||||||
- name: Get clojure version
|
- name: Get leiningen version
|
||||||
run: clojure --version
|
run: lein -v
|
||||||
|
|
||||||
# Test the code
|
# Test the code
|
||||||
#- name: Run tests
|
- name: Run tests
|
||||||
# env:
|
env:
|
||||||
# TFT_API: ${{ secrets.DEV_API }}
|
TFT_API: ${{ secrets.DEV_API }}
|
||||||
# run: lein test
|
run: lein test
|
||||||
|
|
||||||
# Send jar to repository
|
# Send jar to repository
|
||||||
#- name: Deploy on Gitea Maven
|
- name: Deploy on Gitea Maven
|
||||||
# if: github.ref == 'refs/heads/main'
|
# if: github.ref == 'refs/heads/main'
|
||||||
# env:
|
env:
|
||||||
# GITEA_USER: ${{ secrets.DEPLOY_USER }}
|
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||||
# GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
||||||
# run: |
|
run: |
|
||||||
# lein deploy gitea
|
lein deploy gitea
|
||||||
|
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -7,11 +7,14 @@ pom.xml.asc
|
|||||||
*.jar
|
*.jar
|
||||||
*.class
|
*.class
|
||||||
/.lein-*
|
/.lein-*
|
||||||
/**/.nrepl-port
|
/.nrepl-port
|
||||||
/.prepl-port
|
/.prepl-port
|
||||||
.cpcache
|
.hgignore
|
||||||
|
.hg/
|
||||||
.clj-kondo
|
.clj-kondo
|
||||||
.lsp
|
.lsp
|
||||||
.calva
|
.calva
|
||||||
*.svg
|
*.svg
|
||||||
/logs
|
/logs/*
|
||||||
|
log.txt
|
||||||
|
|
||||||
|
|||||||
184
README.md
184
README.md
@@ -1,39 +1,88 @@
|
|||||||
# riot-clojure
|
# riot-clojure
|
||||||
|
|
||||||
Riot Games provides a lot of nice endpoints with a lot of data for all their games.
|
This is a simple tool for using Riot Games public API. Those APIs are HUGE and
|
||||||
This projects provides a library to access those data from Clojure and a small
|
provide a lot of information, but this tool is focused in the time data of matches.
|
||||||
application to quick get some relevant data.
|
|
||||||
|
|
||||||
The library and the CLI application are separated, you can use the library independently.
|
The objetive of this tool is extract the playing time of one player. Taking the
|
||||||
|
starting and ending timestamps we can create a chronogram with the time spent
|
||||||
|
playing.
|
||||||
|
|
||||||
CLI application should be compatible with [babashka](https://babashka.org/), so
|
This simple program needs two API keys: one for LOL and another one for TFT. You
|
||||||
will use external libraries compatible with it.
|
can obtain a developer key from https://developer.riotgames.com/. Developer keys
|
||||||
|
are valid for 24 hours only, but you can refresh the key easily from the developer's
|
||||||
|
web site.
|
||||||
|
|
||||||
# The API and the tokens
|
By default, the application searchs for the API key in the environment variables
|
||||||
|
`LOL_API` and `TFT_API`. If you prefer to provide the key via parameter, you must use `--lol-api-key` or
|
||||||
|
`--tft-api-key` parameters.
|
||||||
|
|
||||||
Accessing the API requires one or more API tokens. Each videogame has their own
|
## Features
|
||||||
group of endpoints, secured with an API. Development APIs can access to all
|
|
||||||
andpoints, but they must be refreshed every 24 hours.
|
|
||||||
|
|
||||||
You can obtain a token for free in the [official developer web](https://developer.riotgames.com/).
|
Those features are implemented in v1.1
|
||||||
|
|
||||||
The library and the application will take the API token from environment. CLI application
|
* Call the API with development or production keys
|
||||||
has optional parameters that override the environment variables.
|
* Get a list of matches
|
||||||
|
* Get matches between two dates
|
||||||
|
* Check if player is currently playing
|
||||||
|
* Show the match's data in several formats:
|
||||||
|
* Fancy ASCII table (by default)
|
||||||
|
* Simple ASCII table
|
||||||
|
* Clojure native EDN format
|
||||||
|
* JSON
|
||||||
|
* CSV
|
||||||
|
* Select data fields to show
|
||||||
|
* Format dates and durations
|
||||||
|
* Show the result of the match (win/loss)
|
||||||
|
* Bulk request for hundred of matches
|
||||||
|
* Generate native executables
|
||||||
|
|
||||||
| Game | Environment variable | CLI parameter |
|
## Goals
|
||||||
| ---------- | -------------------- | ------------- |
|
|
||||||
| all | `RIOT_DEV_KEY` | `--dev-key` |
|
|
||||||
| LOL | `RIOT_LOL_KEY` | `--lol-key` |
|
|
||||||
| TFT | `RIOT_TFT_KEY` | `--tft-key` |
|
|
||||||
*more to came*
|
|
||||||
|
|
||||||
# Usage
|
Goals for version 1.1:
|
||||||
## CLI
|
|
||||||
### Installation
|
* [x] Simplify CLI options and parameters
|
||||||
|
* [x] Calculate simple statistics: win/loss rate, hours played every day, etc.
|
||||||
|
* [x] Get more data and delete some unused fields
|
||||||
|
* [x] Clean and refactor some code
|
||||||
|
* [x] Logging
|
||||||
|
* [x] Get only LOL or TFT data
|
||||||
|
* [x] Use Gitea Actions
|
||||||
|
|
||||||
|
Goals of the project, ordered from easiest to hardest:
|
||||||
|
|
||||||
|
* [x] Take API key from environment
|
||||||
|
* [x] Call the API to get basic player data
|
||||||
|
* [x] Make the call to the API flexible
|
||||||
|
* [x] Obtain list of past matches
|
||||||
|
* [x] Obtain list of current matches
|
||||||
|
* [x] Process app parameters
|
||||||
|
* [x] By default, search for player's name and tag
|
||||||
|
* [x] Take API key from parameter
|
||||||
|
* [x] Use PUUID instead of name and tag
|
||||||
|
* [x] Output as simple EDN data with timestamps
|
||||||
|
* [x] Output as simple EDN data with local datetimes
|
||||||
|
* [x] Output as JSON with timestamps
|
||||||
|
* [x] Output as JSON with local datetimes
|
||||||
|
* [x] Output as ASCII table with localdatetimes
|
||||||
|
* [x] By default, output timestamps and local datetimes, and provide parameters
|
||||||
|
to select the output data.
|
||||||
|
* [x] Only checks if it's playing just now
|
||||||
|
* [x] Win / Loss match
|
||||||
|
* [x] Distribution as uberjar
|
||||||
|
* [x] Distribution as native Linux executable
|
||||||
|
* [x] Unit tests: move "comment" manual tests to real unit tests
|
||||||
|
* [ ] Output as a graphical chronogram or calendar.
|
||||||
|
* [ ] Simple web server
|
||||||
|
* [ ] Universal player search
|
||||||
|
* [ ] Simple API to get JSON data
|
||||||
|
* [ ] Simple endpoint to tell if it's playing just now
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
Download release from https://git.rcorral.es/ruben/riot-clojure/releases.
|
Download release from https://git.rcorral.es/ruben/riot-clojure/releases.
|
||||||
|
|
||||||
### Example
|
## Usage
|
||||||
|
|
||||||
Using the Java uberjar
|
Using the Java uberjar
|
||||||
|
|
||||||
@@ -44,13 +93,13 @@ Using the linux native image:
|
|||||||
|
|
||||||
Both are equivalent. In the examples we will use the native image, because is shorter.
|
Both are equivalent. In the examples we will use the native image, because is shorter.
|
||||||
|
|
||||||
### Options
|
## Options
|
||||||
|
|
||||||
Run the application without params or wich `-?` param to show all options:
|
Run the application without params or wich `-?` param to show all options:
|
||||||
|
|
||||||
$ ./riot -?
|
$ ./riot -?
|
||||||
|
|
||||||
### Tipycal usages
|
## Examples
|
||||||
|
|
||||||
Show all matches from a march the 1st (in ISO format):
|
Show all matches from a march the 1st (in ISO format):
|
||||||
|
|
||||||
@@ -63,48 +112,71 @@ Get te same data in CSV and store it in a file:
|
|||||||
Don't format durations, show them in seconds
|
Don't format durations, show them in seconds
|
||||||
|
|
||||||
$ ./riot t <username> <tag> -s "2025-03-01" --no-format-durations
|
$ ./riot t <username> <tag> -s "2025-03-01" --no-format-durations
|
||||||
|
|
||||||
|
|
||||||
## Library
|
### Graphs
|
||||||
|
|
||||||
# Version
|
Posible ASCII art charts
|
||||||
|
|
||||||
**Notes about v2.0:** This is complete rewriting of the project, with all
|
#### Simple day
|
||||||
I have learn in the last months. The objetive for this version is to implement
|
|
||||||
all functionality provided by v1.1.
|
|
||||||
|
|
||||||
## Status
|
A one dimension diagram. Draw a tick each day the player has played
|
||||||
|
|
||||||
### General
|
```
|
||||||
|
####### ### ###### ###########
|
||||||
|
------------------------------------------------------------------------------------------
|
||||||
|
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|
||||||
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
||||||
|
|
||||||
|
june
|
||||||
|
```
|
||||||
|
|
||||||
* [x] Reestructure all project to use `deps.edn`, `tools.clj` and subprojects
|
#### Day and hour
|
||||||
* [ ] Use babashka compatible libraries and `bb.edn`
|
|
||||||
* [ ] Gitea actions for automatic test executing
|
|
||||||
* [ ] Distribute CLI as self executable (bb, jlink or graal)
|
|
||||||
|
|
||||||
### Library
|
The x axis represents days of month, while y axis represents hours of each day. A tick is drawed in the hour
|
||||||
|
when the player was playing.
|
||||||
|
|
||||||
* [x] Read tokens from environment and a function to override them manually
|
```
|
||||||
* [x] Fuctions for calling Riot API and read result as clojure data structures (EDN format)
|
23 |
|
||||||
* [x] Get [PUUID](https://developer.riotgames.com/docs/lol#summoner-names-to-riot-ids_obtaining-puuid-and-summonerid-from-riotid) for a player
|
22 |
|
||||||
* [x] Get basic player info
|
21 |
|
||||||
* [ ] Format raw data
|
20 |
|
||||||
|
19 | # #
|
||||||
|
18 | # # #
|
||||||
|
17 | # # # #
|
||||||
|
16 | #
|
||||||
|
15 | # # # #
|
||||||
|
14 | # # # #
|
||||||
|
13 | #
|
||||||
|
12 | # # # #
|
||||||
|
11 | # # # # #
|
||||||
|
10 | # # # #
|
||||||
|
09 | # #
|
||||||
|
08 |
|
||||||
|
07 |
|
||||||
|
06 |
|
||||||
|
05 |
|
||||||
|
04 |
|
||||||
|
03 |
|
||||||
|
02 |
|
||||||
|
01 |
|
||||||
|
00 |
|
||||||
|
------------------------------------------------------------------------------------------
|
||||||
|
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|
||||||
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
||||||
|
|
||||||
|
june
|
||||||
|
```
|
||||||
|
|
||||||
### CLI
|
## Interesting graphic and UI libraries
|
||||||
* [ ] Babashka compatible CLI
|
|
||||||
* [ ] Basic parameters for log level and override API tokens
|
|
||||||
* [ ] Obtain basic player info
|
|
||||||
* [ ] Last games for player (LOL, TFT and others)
|
|
||||||
* [ ] Is the player playing just now?
|
|
||||||
* [ ] Contiuosly check if the player es playing
|
|
||||||
* [ ] Write status check to a file, to save an activity log
|
|
||||||
* [ ] Format output as fancy table
|
|
||||||
* [ ] Format output as CSV
|
|
||||||
* [ ] Calculte basic statistics
|
|
||||||
* [ ] Calculate advanced statistics: win strike, adversaries, etc.
|
|
||||||
|
|
||||||
|
- Raster SVG: https://github.com/soulspace-org/cmp.batik
|
||||||
|
- Modify SVG graphics: https://github.com/stathissideris/dali
|
||||||
|
- Create Vega-lite SVG graphics: https://github.com/techascent/tech.viz
|
||||||
|
- Enhance CLI text: https://github.com/clj-commons/pretty?tab=readme-ov-file
|
||||||
|
- Universal UI: https://github.com/phronmophobic/membrane?tab=readme-ov-file
|
||||||
|
- Interesting libraries: https://www.clojure-toolbox.com/
|
||||||
|
|
||||||
# License
|
## License
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
|
|||||||
176
build.clj
176
build.clj
@@ -1,176 +0,0 @@
|
|||||||
(ns build
|
|
||||||
(:refer-clojure :exclude [test])
|
|
||||||
(:require [clojure.tools.build.api :as b]
|
|
||||||
[clojure.java.io :as io]
|
|
||||||
[clojure.pprint :as pp]
|
|
||||||
[clojure.java.basis :as basis]))
|
|
||||||
|
|
||||||
(def lib-group "es.rcorral")
|
|
||||||
(def artifact-prefix "clj-totp")
|
|
||||||
(def subprojs-base "projects")
|
|
||||||
(def curr-version (format "2.0.%s" (b/git-count-revs nil)))
|
|
||||||
|
|
||||||
|
|
||||||
;; Builds artifact's full descriptor for each subproject
|
|
||||||
(defn lib [subproj]
|
|
||||||
(symbol (str lib-group "/" artifact-prefix "-" subproj )))
|
|
||||||
|
|
||||||
|
|
||||||
;; Basis for each subproject, using their own deps.edn
|
|
||||||
;; Injects :extra-deps from :build as additional dependencies
|
|
||||||
(defn basis [subproj]
|
|
||||||
(delay (b/create-basis {:project (str subprojs-base "/" subproj "/deps.edn")
|
|
||||||
;; Inject extra deps as deps
|
|
||||||
:extra {:deps (get-in (basis/initial-basis) [:aliases :build :extra-deps])}
|
|
||||||
})))
|
|
||||||
|
|
||||||
|
|
||||||
;; Show basis generated for a subproject
|
|
||||||
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
|
||||||
(defn show-basis [subproj]
|
|
||||||
(println (with-out-str
|
|
||||||
(pp/pprint
|
|
||||||
@(basis subproj)
|
|
||||||
;(basis/initial-basis)
|
|
||||||
))))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(pp/pprint (keys (basis/initial-basis)))
|
|
||||||
(pp/pprint (:deps (basis/initial-basis)))
|
|
||||||
(pp/pprint (:libs (basis/initial-basis)))
|
|
||||||
(pp/pprint (sort (keys (:aliases (basis/initial-basis)))))
|
|
||||||
(get-in (basis/initial-basis) [:aliases :build :extra-deps])
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Target dir for each subproject
|
|
||||||
(defn target-dir [subproj]
|
|
||||||
(str "target/" subproj))
|
|
||||||
|
|
||||||
|
|
||||||
;; Path for compiled classes
|
|
||||||
(defn class-dir [subproj]
|
|
||||||
(str (target-dir subproj) "/" "classes"))
|
|
||||||
|
|
||||||
|
|
||||||
;; Jar file for each subproject. :uber type adds -standalone suffix
|
|
||||||
(defn jar-file [subproj version type]
|
|
||||||
(format "target/%s-%s-%s%s.jar" artifact-prefix subproj version
|
|
||||||
(if (= type :uber) "-standalone" "")))
|
|
||||||
|
|
||||||
|
|
||||||
;; Clean target dir for subproject
|
|
||||||
(defn clean [{:keys [subproj]}]
|
|
||||||
(b/delete {:path (target-dir subproj)})
|
|
||||||
(println "Project" subproj "cleaned"))
|
|
||||||
|
|
||||||
|
|
||||||
;; Compile java classes, only if java subdir exists
|
|
||||||
(defn compile-java [{:keys [subproj]}]
|
|
||||||
(let [java-dir (str subprojs-base "/" subproj "/java")]
|
|
||||||
(if (.exists (io/file java-dir))
|
|
||||||
(do
|
|
||||||
(println "Compiling java code for" subproj)
|
|
||||||
(b/javac {:src-dirs [java-dir]
|
|
||||||
:class-dir (class-dir subproj)
|
|
||||||
:basis @(basis subproj)
|
|
||||||
:javac-opts ["-source" "11" "--target" "11" "-proc:none"]}))
|
|
||||||
(println "Java dir" java-dir "not found"))))
|
|
||||||
|
|
||||||
|
|
||||||
;; Create a jar file
|
|
||||||
(defn jar
|
|
||||||
"Build a simple jar file, with no dependencies included."
|
|
||||||
[{:keys [subproj version]
|
|
||||||
:or {version curr-version}}]
|
|
||||||
(let [target-dir (target-dir subproj)
|
|
||||||
class-dir (class-dir subproj)
|
|
||||||
src-dir (str subprojs-base "/" subproj "/src")
|
|
||||||
resources-dir (str subprojs-base "/" subproj "/resources")
|
|
||||||
basis (basis subproj)
|
|
||||||
jar-file (jar-file subproj version :plain)]
|
|
||||||
;; Clean only class dir
|
|
||||||
(b/delete {:path class-dir})
|
|
||||||
;; Copy code
|
|
||||||
(b/copy-dir {:src-dirs [src-dir]
|
|
||||||
:target-dir class-dir})
|
|
||||||
;; Copy resources
|
|
||||||
(b/copy-dir {:src-dirs [resources-dir]
|
|
||||||
:target-dir target-dir})
|
|
||||||
;; Compile java code, if exists
|
|
||||||
(compile-java {:subproj subproj})
|
|
||||||
;; Build jar
|
|
||||||
(b/jar {:class-dir class-dir
|
|
||||||
:basis @basis
|
|
||||||
:jar-file jar-file
|
|
||||||
:lib (lib subproj)
|
|
||||||
:version version})
|
|
||||||
(println "Generated jar file:" jar-file)))
|
|
||||||
|
|
||||||
|
|
||||||
;; Create an uber jar, with all dependencies inside
|
|
||||||
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
|
|
||||||
(defn uber
|
|
||||||
"Build a uberjar with all dependencies included"
|
|
||||||
[{:keys [subproj version main-ns]
|
|
||||||
:or {version curr-version}}]
|
|
||||||
(let [target-dir (target-dir subproj)
|
|
||||||
basis (basis subproj)
|
|
||||||
class-dir (class-dir subproj)
|
|
||||||
src-dir (str subprojs-base "/" subproj "/src")
|
|
||||||
resources-dir (str subprojs-base "/" subproj "/resources")
|
|
||||||
uber-file (jar-file subproj version :uber)]
|
|
||||||
;(println "Using basis: ")(show-basis subproj)
|
|
||||||
(b/delete {:path class-dir})
|
|
||||||
(b/copy-dir {:src-dirs [src-dir]
|
|
||||||
:target-dir class-dir})
|
|
||||||
(b/copy-dir {:src-dirs [resources-dir]
|
|
||||||
:target-dir target-dir})
|
|
||||||
(compile-java {:subproj subproj})
|
|
||||||
(b/compile-clj {:basis @basis
|
|
||||||
:src-dirs [src-dir] :class-dir class-dir})
|
|
||||||
(b/uber {:class-dir class-dir
|
|
||||||
:uber-file uber-file
|
|
||||||
:basis @basis
|
|
||||||
:main main-ns})
|
|
||||||
(println "Generated uberjar executable:" uber-file)))
|
|
||||||
|
|
||||||
|
|
||||||
;; Multimethod to get the name of all subdirs in a dir.
|
|
||||||
;; Accepts strings or files
|
|
||||||
(defmulti get-subdirs type)
|
|
||||||
|
|
||||||
(defmethod get-subdirs
|
|
||||||
java.lang.String [dir]
|
|
||||||
(get-subdirs (io/file dir)))
|
|
||||||
|
|
||||||
(defmethod get-subdirs
|
|
||||||
java.io.File [dir]
|
|
||||||
(if (.isDirectory dir)
|
|
||||||
(filter #(.isDirectory %) (.listFiles dir))
|
|
||||||
(println "Directory" subprojs-base "doesn't exists!")))
|
|
||||||
|
|
||||||
|
|
||||||
;; Get the name of all subdir in a given directory
|
|
||||||
(defn get-subdir-names
|
|
||||||
"Get a list projects in the 'projects' directory"
|
|
||||||
[dir-name]
|
|
||||||
(map #(.getName %) (get-subdirs dir-name)))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(get-subdirs "projects")
|
|
||||||
(get-subdirs (io/file "projects"))
|
|
||||||
(get-subdir-names "projects")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
;; Generate jar files for all projects
|
|
||||||
(defn jar-all
|
|
||||||
"Build jar files for all projects"
|
|
||||||
[& {:keys [version]
|
|
||||||
:or {version curr-version}}]
|
|
||||||
(dorun (map #(jar {:subproj % :version version}) (get-subdir-names subprojs-base))))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(jar-all )
|
|
||||||
|
|
||||||
)
|
|
||||||
51
deps.edn
51
deps.edn
@@ -1,51 +0,0 @@
|
|||||||
{:paths ["projects/core/src"
|
|
||||||
"projects/core/resources"
|
|
||||||
"projects/cli/src"]
|
|
||||||
|
|
||||||
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
|
||||||
;; Local subprojects
|
|
||||||
clj-tmpl/core {:local/root "projects/core"}
|
|
||||||
clj-tmpl/cli {:local/root "projects/cli"}
|
|
||||||
}
|
|
||||||
|
|
||||||
:aliases {;; Execute the app.
|
|
||||||
:run {:main-opts ["-m" "riot.app"]}
|
|
||||||
;:run {:exec-fn totp.app/-main}
|
|
||||||
|
|
||||||
;; Execute the app (prepared for more subprojects)
|
|
||||||
:run/cli {:main-opts ["-m" "riot.app"]}
|
|
||||||
|
|
||||||
;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
|
||||||
;; Check test.edn for kaocha runner's config
|
|
||||||
:test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}
|
|
||||||
lambdaisland/kaocha-cloverage {:mvn/version "1.1.89"}}
|
|
||||||
:main-opts ["-m" "kaocha.runner"]}
|
|
||||||
|
|
||||||
;; Run with clj -T:build function-in-build
|
|
||||||
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
|
||||||
;; Used by all compilations
|
|
||||||
:extra-deps {clj-tmpl/core {:local/root "projects/core"}}
|
|
||||||
:ns-default build}
|
|
||||||
|
|
||||||
;; Aliases for easy building
|
|
||||||
:build/core {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
|
||||||
:ns-default build
|
|
||||||
:exec-fn jar
|
|
||||||
:exec-args {:subproj "core"}}
|
|
||||||
|
|
||||||
:build/cli {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
|
||||||
:ns-default build
|
|
||||||
:exec-fn jar
|
|
||||||
:exec-args {:subproj "cli"}}
|
|
||||||
|
|
||||||
;; Build all projects
|
|
||||||
:build/all {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
|
||||||
:ns-default build
|
|
||||||
:exec-fn jar-all}
|
|
||||||
|
|
||||||
;; Build uber jar for CLI app
|
|
||||||
:uber/cli {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
|
|
||||||
:ns-default build
|
|
||||||
:exec-fn uber
|
|
||||||
:exec-args {:subproj "cli" :main-ns "riot.app"}}}}
|
|
||||||
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
@startuml Load key
|
|
||||||
|
|
||||||
!pragma useVerticalIf on
|
|
||||||
|
|
||||||
start
|
|
||||||
if (key in map?) then (yes)
|
|
||||||
:return key;
|
|
||||||
elseif (dev key in map?) then (yes)
|
|
||||||
:return dev key;
|
|
||||||
elseif (key in env?) then (yes)
|
|
||||||
:store key in map;
|
|
||||||
:return key;
|
|
||||||
elseif (dev key in env?) then (yes)
|
|
||||||
:store dev key in map;
|
|
||||||
:return dev key;
|
|
||||||
else (nothing)
|
|
||||||
:nil key;
|
|
||||||
endif
|
|
||||||
stop
|
|
||||||
@enduml
|
|
||||||
3
doc/intro.md
Normal file
3
doc/intro.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Introduction to riot-clojure
|
||||||
|
|
||||||
|
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
70
project.clj
Normal file
70
project.clj
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
(defproject riot-clojure "1.2.0"
|
||||||
|
:description "Utility for getting data from Riot APIs in Clojure"
|
||||||
|
:url "https://git.rcorral.es/ruben/riot-clojure"
|
||||||
|
|
||||||
|
:license {:name "MIT"
|
||||||
|
:url "https://mit-license.org/"}
|
||||||
|
|
||||||
|
:dependencies [[org.clojure/clojure "1.11.1"]
|
||||||
|
[clj-http/clj-http "2.0.0"]
|
||||||
|
[cheshire/cheshire "6.0.0"]
|
||||||
|
[slingshot/slingshot "0.12.2"]
|
||||||
|
[org.clojure/tools.cli "1.1.230"]
|
||||||
|
[cli-matic/cli-matic "0.5.4"] ;; https://github.com/l3nz/cli-matic
|
||||||
|
[buddy/buddy-core "1.12.0-430"]
|
||||||
|
[org.clojure/tools.namespace "1.5.0"]
|
||||||
|
[lt.tokenmill/timewords "0.5.0"] ;; https://github.com/tokenmill/timewords
|
||||||
|
[org.clj-commons/pretty "3.5.0"] ;; https://github.com/clj-commons/pretty
|
||||||
|
[org.clojure/tools.logging "1.3.0"] ;; https://github.com/clojure/tools.logging
|
||||||
|
[org.slf4j/slf4j-api "2.0.17"] ;; https://www.slf4j.org/
|
||||||
|
[ch.qos.logback/logback-classic "1.5.18"]
|
||||||
|
|
||||||
|
;;; LIBS FOR GRAPHS, SOME OF THEM WILL BE REMOVED IN THE FUTURE
|
||||||
|
|
||||||
|
;[com.hypirion/clj-xchart "0.2.0"] ;; Graphs and charts
|
||||||
|
;[aerial.hanami "0.15.1"] ;; Parse vega-lite data and generate graphics
|
||||||
|
;[folcon/oz "1.6.0-alpha6.2"] ;; Parse vega and vega-lite
|
||||||
|
[metasoarous/oz "1.6.0-alpha36"] ;; Other version of Oz
|
||||||
|
;[metasoarous/oz "2.0.0-alpha5"] ;; Newer version of Oz
|
||||||
|
[incanter/incanter-charts "1.9.3"] ;; graphics with Incanter
|
||||||
|
;[hswick/jutsu "0.1.1"] ;; Wrapper para plotly
|
||||||
|
[techascent/tech.viz "6.00-beta-16-4"] ;; Vega-lite parser (https://github.com/techascent/tech.viz)
|
||||||
|
]
|
||||||
|
|
||||||
|
:plugins [[io.taylorwood/lein-native-image "0.3.1"]] ;; Compile to native using graal
|
||||||
|
|
||||||
|
:main ^:skip-aot riot.app
|
||||||
|
:target-path "target/%s"
|
||||||
|
|
||||||
|
:native-image {:name "riot" ;; name of output image, optional
|
||||||
|
:graal-bin "/home/ruben/.sdkman/candidates/java/21.0.2-graalce/" ;; path to GraalVM home, optional
|
||||||
|
:opts ["--verbose" "-H:+AllowDeprecatedBuilderClassesOnImageClasspath" "--no-fallback"]} ;; pass-thru args to GraalVM native-image, optional
|
||||||
|
|
||||||
|
:profiles {:uberjar {:aot :all
|
||||||
|
;:jvm-opts ["-Dclojure.compiler.direct-linking=true"]
|
||||||
|
}
|
||||||
|
:native-image {:jvm-opts ["-Dclojure.compiler.direct-linking=true" "--initialize-at-build-time"]
|
||||||
|
}
|
||||||
|
:dev {:dependencies [[org.clojure/test.check "1.1.1"]]
|
||||||
|
:plugins [[io.taylorwood/lein-native-image "0.3.1"]
|
||||||
|
[lein-binplus "0.6.8"]]}}
|
||||||
|
|
||||||
|
;; lein bin configuration
|
||||||
|
:bin {:name "riot_clj"
|
||||||
|
:bin-path "~/bin"
|
||||||
|
:jvm-opts ["-server" "-Dfile.encoding=utf-8" "$JVM_OPTS"]}
|
||||||
|
|
||||||
|
;; Deploy to repository
|
||||||
|
:repositories [["gitea" {:url "https://git.rcorral.es/api/packages/ruben/maven"
|
||||||
|
:username :env/DEPLOY_USER
|
||||||
|
:password :env/DEPLOY_TOKEN
|
||||||
|
:sign-releases false}]]
|
||||||
|
|
||||||
|
:deploy-repositories [["releases" :gitea]
|
||||||
|
["snapshots" :gitea]]
|
||||||
|
|
||||||
|
;; Test selectors
|
||||||
|
:test-selectors {:default (complement (some-fn :tft :timezone))
|
||||||
|
:tft :tft
|
||||||
|
:timezone :timezone})
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{:paths ["src" "resources" "target/classes"]
|
|
||||||
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
|
||||||
cli-matic/cli-matic {:mvn/version "0.5.4"} ;; https://github.com/l3nz/cli-matic
|
|
||||||
}
|
|
||||||
:aliases {;; Execute the app
|
|
||||||
;:run {:main-opts ["-m" "totp.app"]}
|
|
||||||
}}
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
(ns riot.app
|
|
||||||
#_{:clj-kondo/ignore [:refer-all]}
|
|
||||||
(:require [riot.core :refer :all])
|
|
||||||
(:gen-class))
|
|
||||||
|
|
||||||
(def cli-options {})
|
|
||||||
|
|
||||||
(defn -main [& args]
|
|
||||||
(greeting-emisor (generate-greeting :high "Juan")))
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{:paths ["src" "resources" "target/classes"]
|
|
||||||
:deps {org.clojure/clojure {:mvn/version "1.12.1"}
|
|
||||||
;; https://github.com/babashka/http-client
|
|
||||||
org.babashka/http-client {:mvn/version "0.4.22"}
|
|
||||||
;; https://github.com/dakrone/cheshire?tab=readme-ov-file
|
|
||||||
cheshire/cheshire {:mvn/version "6.1.0"}}
|
|
||||||
|
|
||||||
:aliases {;; Kaocha runner. You can use the 'kaocha' wrapper located in ~/bin/kaocha
|
|
||||||
:test {:extra-paths ["test"]
|
|
||||||
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
|
|
||||||
:main-opts ["-m" "kaocha.runner"]}
|
|
||||||
;; enable logging for java.net.http
|
|
||||||
:debug-full {:jvm-opts ["-Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all..],content,ssl,trace,channel"]}
|
|
||||||
:debug {:jvm-opts ["-Djdk.httpclient.HttpClient.log=errors,requests,headers,content"]}}}
|
|
||||||
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
(ns riot.core
|
|
||||||
(:require [babashka.http-client :as http]
|
|
||||||
[cheshire.core :as json]
|
|
||||||
[clojure.string :as s]
|
|
||||||
[clojure.pprint :as pp]))
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; API KEY MANAGEMENT
|
|
||||||
;;
|
|
||||||
|
|
||||||
(defn get-key-from-env
|
|
||||||
"Get api key from environment variable"
|
|
||||||
[key]
|
|
||||||
(System/getenv (name key)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-and-save-key-from-env
|
|
||||||
"Get api key from environment and store in the KEYS map. It key is not found in
|
|
||||||
the environment, it tries to load the development key as a fallback. If none api
|
|
||||||
key is found, return nil"
|
|
||||||
[KEYS default-key key]
|
|
||||||
(if-let [value (get-key-from-env key)]
|
|
||||||
(reset! (key KEYS) value)
|
|
||||||
(if-let [value_dev (get-key-from-env key)]
|
|
||||||
(reset! (default-key KEYS) value_dev)
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-api-key
|
|
||||||
"Get API key from KEYS map. If it is not found in the KEYS map, try to load it from
|
|
||||||
the environment. If the API key doesn't exists in the environment, try to load the
|
|
||||||
development key (the default key). If none found, print and error and return nil"
|
|
||||||
[KEYS default-key keytype]
|
|
||||||
(if (contains? KEYS keytype)
|
|
||||||
(if-let [key-value (deref (keytype KEYS))]
|
|
||||||
key-value
|
|
||||||
(if-let [def-key-value (deref (default-key KEYS))]
|
|
||||||
def-key-value
|
|
||||||
(get-and-save-key-from-env KEYS default-key keytype)))
|
|
||||||
(println "Invalid key type. Available keys:" (keys KEYS))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn set-api-key
|
|
||||||
"Force the value of an API key in the KEYS map"
|
|
||||||
[KEYS keytype value]
|
|
||||||
(when (contains? KEYS keytype)
|
|
||||||
(reset! (keytype KEYS) value)))
|
|
||||||
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; GENERATE URLS
|
|
||||||
;;
|
|
||||||
|
|
||||||
(defn get-url
|
|
||||||
"Get URL for using the API."
|
|
||||||
[path & parts]
|
|
||||||
(when path
|
|
||||||
(s/join "/" (concat [path] (vec parts)))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn url-encode-param
|
|
||||||
"Encode URL, replacing spaces with %20 and deleting invalid characters"
|
|
||||||
[value]
|
|
||||||
(when value
|
|
||||||
(s/escape value { \ "%20"})))
|
|
||||||
|
|
||||||
|
|
||||||
(defn replace-params
|
|
||||||
"Replace params in path"
|
|
||||||
[path params-map]
|
|
||||||
;(println "replacing" path "witn" params-map)
|
|
||||||
(if (and path params-map (not-empty params-map))
|
|
||||||
(reduce-kv
|
|
||||||
#(s/replace %1 ((comp re-pattern str) %2) (url-encode-param (str %3)))
|
|
||||||
(get-url path)
|
|
||||||
params-map)
|
|
||||||
path))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-known-url
|
|
||||||
"Get url from ENDPOINTS map."
|
|
||||||
[ENDPOINTS url-name params-map]
|
|
||||||
(if-let [url (get-in ENDPOINTS [url-name :uri])]
|
|
||||||
(replace-params url params-map)
|
|
||||||
(println "Unknown URL name" url-name)))
|
|
||||||
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; HELPERS
|
|
||||||
;;
|
|
||||||
|
|
||||||
(defn success
|
|
||||||
"Return a success that stores HTTP result code and parsed JSON data"
|
|
||||||
[status json-data]
|
|
||||||
{:success true :http-code status :value json-data})
|
|
||||||
|
|
||||||
(defn failure
|
|
||||||
"Return a success that stores HTTP result code and parsed JSON data"
|
|
||||||
[status error-data]
|
|
||||||
{:success false :http-code status :error error-data})
|
|
||||||
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; CALL GET ENDPOINTS
|
|
||||||
;;
|
|
||||||
|
|
||||||
(defn call-api
|
|
||||||
"Call the api using one of the supported URLs or a string. You can pass optional
|
|
||||||
maps for params:
|
|
||||||
- path-params: parameters in the path.
|
|
||||||
- query-params: parameters in the query part of the URL
|
|
||||||
- header-params: headers added to the default headers
|
|
||||||
|
|
||||||
The key-provider is a function that accepts the url as param and returns the api
|
|
||||||
key.
|
|
||||||
|
|
||||||
The api-key-header is the name of the header param that holds the API key. If
|
|
||||||
the endpoint don't requiere an API key, you can provide nil or empty string.
|
|
||||||
|
|
||||||
The URL can be a keyword of the map ENDPOINTS or a URL. Each path param must have
|
|
||||||
the same name as the key in the path-params map, including the ':' For example,
|
|
||||||
given this url:
|
|
||||||
http://example.com/api/get-user-by-id/:user
|
|
||||||
|
|
||||||
It has a path param called ':user'. If you provide this map:
|
|
||||||
:path-params {:user 2345}
|
|
||||||
|
|
||||||
it will expanded to:
|
|
||||||
http://example.com/api/get-user-by-id/2345
|
|
||||||
|
|
||||||
If you provide query-params, the will added after the question mark. For example, if
|
|
||||||
you call the function as follows:
|
|
||||||
(call-api \"http://example.com/api/get-user-by-id/:user\"
|
|
||||||
:path-params {:user 2345}
|
|
||||||
:query-params {:detail \"full\"})
|
|
||||||
|
|
||||||
this will output the following string:
|
|
||||||
\"http://example.com/api/get-user-by-id/2345?detail=full\"
|
|
||||||
|
|
||||||
This function returns a success if everythin goes well or a failure when http status
|
|
||||||
code is different from 200 or an exception is raised.
|
|
||||||
"
|
|
||||||
; Very simple arity to call a simple URL and the the JSON result
|
|
||||||
([url]
|
|
||||||
(call-api {} #(apply (constantly "") %) nil url))
|
|
||||||
; The complete call. You must personalize ENDPOINTS, key-provider and api-key-header
|
|
||||||
; for your API
|
|
||||||
([ENDPOINTS key-provider api-key-header url & {:keys [path-params query-params header-params debug]
|
|
||||||
:or {path-params {} query-params {} header-params {} debug false}}]
|
|
||||||
;(println "Endpoints:" (count ENDPOINTS) "Key provider:" key-provider "api key header:" api-key-header)
|
|
||||||
(let [; Process path-parameters
|
|
||||||
processed-url (if (keyword? url)
|
|
||||||
(get-known-url ENDPOINTS url path-params)
|
|
||||||
(replace-params url path-params))
|
|
||||||
; Try to get an api key (if required)
|
|
||||||
api-key (if (keyword? url) (key-provider url) (println "No api key for URL " url))
|
|
||||||
; Fixed headers, without api key
|
|
||||||
headers-no-key (merge {"Accept" "application/json"}
|
|
||||||
header-params)
|
|
||||||
; Headers with api key
|
|
||||||
headers (if (and (some? api-key-header) (string? api-key))
|
|
||||||
(merge headers-no-key {api-key-header api-key})
|
|
||||||
headers-no-key)]
|
|
||||||
;(println "URL:" processed-url "Query" query-params "headers: " headers)
|
|
||||||
(try
|
|
||||||
; Call the api and catch the result
|
|
||||||
(let [response (http/get processed-url {:headers headers
|
|
||||||
:query-params query-params
|
|
||||||
:throw false})]
|
|
||||||
; If debug activated, print all data from response
|
|
||||||
(when debug (println "RESPONSE:")(pp/pprint response))
|
|
||||||
; Check HTTP response code.
|
|
||||||
(if (= 200 (:status response))
|
|
||||||
(success 200 (json/parse-string (:body response) keyword))
|
|
||||||
(do
|
|
||||||
(println "Error response: HTTP code" (:status response))
|
|
||||||
(failure (:status response) (:body response)))))
|
|
||||||
(catch Exception exc
|
|
||||||
(prn "An exception has been thrown while calling the api" (.getMessage exc))
|
|
||||||
(failure nil (.getMessage exc)))))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(call-api "https://status.search.google.com/incidents.json")
|
|
||||||
|
|
||||||
(call-api "http://example.com")
|
|
||||||
|
|
||||||
)
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
(ns riot.data
|
|
||||||
(:require [riot.core :refer :all]
|
|
||||||
[riot.riot-api :refer :all]))
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; High functions to work with RIOT API.
|
|
||||||
;;
|
|
||||||
;; This file has a lot of utilities for working with data obtained with the help
|
|
||||||
;; of riot-api's functions
|
|
||||||
|
|
||||||
|
|
||||||
;; Get matches list
|
|
||||||
|
|
||||||
(defn get-lol-matches
|
|
||||||
"Get LOL matches for player"
|
|
||||||
[puuid & {:keys [startTime endTime queue type start count] :as params}]
|
|
||||||
;(println "Params:" params)
|
|
||||||
(let [response (call-riot-api :lol-matches-by-puuid
|
|
||||||
:path-params {:puuid puuid}
|
|
||||||
:query-params params)]
|
|
||||||
(if (:success response)
|
|
||||||
(:value response)
|
|
||||||
(do (println "No LOL matches found") response))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-tft-matches
|
|
||||||
"Get TFT matches for player"
|
|
||||||
[puuid & {:keys [startTime endTime queue type start count] :as params}]
|
|
||||||
(println "Params:" params)
|
|
||||||
(let [response (call-riot-api :tft-matches-by-puuid
|
|
||||||
:path-params {:puuid puuid}
|
|
||||||
:query-params params)]
|
|
||||||
(if (:success response)
|
|
||||||
(:value response)
|
|
||||||
(do (println "No TFT matches found") response))))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
|
|
||||||
(get-lol-matches (lol-get-uuid "Walid Georgey" "EUW") :count 5)
|
|
||||||
(get-lol-matches (lol-get-uuid "Errepunto" "4595") :count 5)
|
|
||||||
|
|
||||||
(get-tft-matches (tft-get-uuid "Walid Georgey" "EUW") :count 5)
|
|
||||||
(get-tft-matches (tft-get-uuid "Errepunto" "4595") :count 5)
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Get matches data
|
|
||||||
|
|
||||||
(defn create-match-data
|
|
||||||
[matchId participants gameStartTimestamp gameEndTimestamp gameDuration gameType endOfGameResult]
|
|
||||||
{:id matchId
|
|
||||||
:participants participants
|
|
||||||
:start gameStartTimestamp
|
|
||||||
:end gameEndTimestamp
|
|
||||||
:duration gameDuration
|
|
||||||
:type gameType
|
|
||||||
:result endOfGameResult})
|
|
||||||
|
|
||||||
(defn create-participant
|
|
||||||
[puuid championName deaths kills win]
|
|
||||||
{:puuid puuid
|
|
||||||
:champion championName
|
|
||||||
:deaths deaths
|
|
||||||
:kills kills
|
|
||||||
:win win})
|
|
||||||
|
|
||||||
(defn get-lol-match-data
|
|
||||||
"Get data from a LOL match"
|
|
||||||
[id]
|
|
||||||
(let [response (call-riot-api :lol-match-by-id :path-params {:match-id id})
|
|
||||||
info (get-in response [:value :info])
|
|
||||||
meta (get-in response [:value :metadata])]
|
|
||||||
;(println "Value:" info)
|
|
||||||
(if (:success response)
|
|
||||||
(create-match-data (:matchId meta)
|
|
||||||
(map #(create-participant (:puuid %)
|
|
||||||
(:championName %)
|
|
||||||
(:deaths %)
|
|
||||||
(:kills %)
|
|
||||||
(:win %))
|
|
||||||
(:participants info))
|
|
||||||
(:gameStartTimestamp info)
|
|
||||||
(:gameEndTimestamp info)
|
|
||||||
(:gameDuration info)
|
|
||||||
(:gameType info)
|
|
||||||
(:endOfGameResult info))
|
|
||||||
(do (println "No match data found") response))))
|
|
||||||
|
|
||||||
(defn get-tft-match-data
|
|
||||||
"Get data from a TFT match"
|
|
||||||
[id]
|
|
||||||
(let [response (call-riot-api :tft-match-by-id :path-params {:match-id id})
|
|
||||||
info (get-in response [:value :info])
|
|
||||||
meta (get-in response [:value :metadata])]
|
|
||||||
;(println "Value:" info)
|
|
||||||
(if (:success response)
|
|
||||||
(create-match-data (:match_id meta)
|
|
||||||
(map #(create-participant (:puuid %)
|
|
||||||
nil
|
|
||||||
nil
|
|
||||||
(:players_eliminated %)
|
|
||||||
(:win %))
|
|
||||||
(:participants info))
|
|
||||||
(:game_datetime info)
|
|
||||||
(+ (:game_datetime info) (int (* 1000 (:game_length info))))
|
|
||||||
(int (:game_length info))
|
|
||||||
(:tft_game_type info)
|
|
||||||
(:endOfGameResult info))
|
|
||||||
(do (println "No match data found") response))))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(get-lol-match-data "EUW1_7673677826")
|
|
||||||
(get-tft-match-data "EUW1_7674912532")
|
|
||||||
|
|
||||||
(System/currentTimeMillis)
|
|
||||||
1767819383643
|
|
||||||
1767819383643
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Check if it's playing
|
|
||||||
|
|
||||||
(defn is-playing-lol?
|
|
||||||
[puuid]
|
|
||||||
(let [response (call-riot-api :lol-spectator
|
|
||||||
:path-params {:puuid puuid})
|
|
||||||
http-code (:http-code response)]
|
|
||||||
(= 200 http-code)))
|
|
||||||
|
|
||||||
(defn is-playing-tft?
|
|
||||||
[puuid]
|
|
||||||
(let [response (call-riot-api :tft-spectator
|
|
||||||
:path-params {:puuid puuid})
|
|
||||||
http-code (:http-code response)]
|
|
||||||
(= 200 http-code)))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(is-playing-lol? (lol-get-uuid "Walid Georgey" "EUW"))
|
|
||||||
(is-playing-tft? (lol-get-uuid "Walid Georgey" "EUW"))
|
|
||||||
)
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
(ns riot.riot-api
|
|
||||||
(:require [riot.core :refer :all]
|
|
||||||
[riot.riot-config :refer :all]))
|
|
||||||
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; ADAPT API GENERIC METHODS TO RIOT API
|
|
||||||
;;
|
|
||||||
;; You need to adapt 3 things from the generic core:
|
|
||||||
;; - A method for setting (override) the API key
|
|
||||||
;; - A method for supply the API
|
|
||||||
;; - A wrapper for calling call-api with this API endpoint, and keyprovider
|
|
||||||
;;
|
|
||||||
;; Maps with keys and supported endpoints are in a separated file (riot-data.clj)
|
|
||||||
;;
|
|
||||||
|
|
||||||
; Not needed, but useful
|
|
||||||
(def get-riot-api-key (partial get-api-key RIOT-KEYS :RIOT_DEV_KEY))
|
|
||||||
|
|
||||||
; Not used by core, but useful for frontends
|
|
||||||
(def set-riot-api-key (partial set-api-key RIOT-KEYS))
|
|
||||||
|
|
||||||
; Key provider for this API
|
|
||||||
(defn riot-key-provider
|
|
||||||
[url]
|
|
||||||
(if-let [api-key (get-riot-api-key (get-in ENDPOINTS [url :api-key]))]
|
|
||||||
api-key
|
|
||||||
(println "No API key found")))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(get-in ENDPOINTS [:lol-status :api-key])
|
|
||||||
|
|
||||||
(riot-key-provider :lol-status)
|
|
||||||
)
|
|
||||||
|
|
||||||
; Wrapper for ease use of call-api
|
|
||||||
(def call-riot-api (partial call-api ENDPOINTS riot-key-provider "X-Riot-Token"))
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; HELPER FUNCTIONS
|
|
||||||
;;
|
|
||||||
(defn lol-get-uuid
|
|
||||||
"Get PUUID from player's name and tag for LOL endpoints"
|
|
||||||
[player-name player-tag]
|
|
||||||
(:puuid (:value (call-riot-api :lol-account-by-riot-id
|
|
||||||
:path-params {:player-name player-name :player-tag player-tag}))))
|
|
||||||
|
|
||||||
(defn tft-get-uuid
|
|
||||||
"Get PUUID from player's name and tag for TFT endpoints"
|
|
||||||
[player-name player-tag]
|
|
||||||
(:puuid (:value (call-riot-api :tft-account-by-riot-id
|
|
||||||
:path-params {:player-name player-name :player-tag player-tag}))))
|
|
||||||
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; USAGE EXAMPLES
|
|
||||||
;;
|
|
||||||
|
|
||||||
(comment
|
|
||||||
|
|
||||||
(set-riot-api-key :RIOT_DEV_KEY "RGAPI-a8d1f915-859b-4bad-99d8-0a163e349f77")
|
|
||||||
|
|
||||||
|
|
||||||
(lol-get-uuid "Walid Georgey" "EUW")
|
|
||||||
|
|
||||||
(tft-get-uuid "Walid Georgey" "EUW")
|
|
||||||
|
|
||||||
;; Examples calling the API
|
|
||||||
(call-riot-api :lol-account-by-puuid
|
|
||||||
:path-params {:puuid "Walid Georgey" :player-tag "EUW"})
|
|
||||||
|
|
||||||
(:value (call-riot-api :lol-status))
|
|
||||||
|
|
||||||
(call-riot-api :champion-mastery-by-puuid
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :champion-mastery-by-puuid-and-champion
|
|
||||||
:path-params {:championId 45
|
|
||||||
:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :champion-mastery-by-puuid-top
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")}
|
|
||||||
:query-params {:count 1})
|
|
||||||
|
|
||||||
(call-riot-api :champion-rotations)
|
|
||||||
|
|
||||||
(call-riot-api :summoner-by-puuid
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :league-all-by-puuid
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :league-by-id
|
|
||||||
:path-params {:league-id "35642608-b436-48a0-ac0f-b64eb6dfc14e"})
|
|
||||||
|
|
||||||
(call-riot-api :clash-by-puuid
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :clash-tournaments)
|
|
||||||
|
|
||||||
(call-riot-api :lol-matches-by-puuid
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")}
|
|
||||||
:query-params {:count 5})
|
|
||||||
|
|
||||||
(call-riot-api :lol-match-by-id
|
|
||||||
:path-params {:match-id "EUW1_7673677826"})
|
|
||||||
|
|
||||||
(call-riot-api :lol-match-timeline-by-id
|
|
||||||
:path-params {:match-id "EUW1_7673677826"})
|
|
||||||
|
|
||||||
(call-riot-api :lol-challenges-config)
|
|
||||||
|
|
||||||
(call-riot-api :lol-challenge-config-by-id
|
|
||||||
:path-params {:challenge-id 402109})
|
|
||||||
|
|
||||||
(call-riot-api :lol-challenges-percentiles)
|
|
||||||
|
|
||||||
(call-riot-api :lol-challenges-percentiles-by-id
|
|
||||||
:path-params {:challenge-id 402109})
|
|
||||||
|
|
||||||
(call-riot-api :lol-challenge-leaderboard
|
|
||||||
:path-params {:challenge-id 402109
|
|
||||||
:level "CHALLENGER"})
|
|
||||||
|
|
||||||
(call-riot-api :lol-challenge-by-puuid
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :lol-champion-mastery-by-puuid
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :lol-champion-mastery-by-puuid-champion
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")
|
|
||||||
:champion-id 777})
|
|
||||||
|
|
||||||
(call-riot-api :lol-champion-mastery-by-puuid-top
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")}
|
|
||||||
:query-params {:count 5})
|
|
||||||
|
|
||||||
(call-riot-api :lol-champion-mastery-scores-by-puuid
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :lol-spectator
|
|
||||||
:path-params {:puuid (lol-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
;; TFT
|
|
||||||
(call-riot-api :tft-summoner-by-puuid
|
|
||||||
:path-params {:puuid (tft-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :tft-status)
|
|
||||||
|
|
||||||
(call-riot-api :tft-matches-by-puuid
|
|
||||||
:path-params {:puuid (tft-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :tft-match-by-id
|
|
||||||
:path-params {:match-id "EUW1_7666482502"})
|
|
||||||
|
|
||||||
(call-riot-api :tft-spectator
|
|
||||||
:path-params {:puuid (tft-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :tft-league-by-puuid
|
|
||||||
:path-params {:puuid (tft-get-uuid "Walid Georgey" "EUW")})
|
|
||||||
|
|
||||||
(call-riot-api :tft-league-challenger)
|
|
||||||
|
|
||||||
(call-riot-api :tft-league-grandmaster-by-queue)
|
|
||||||
|
|
||||||
(call-riot-api :tft-league-master-by-queue)
|
|
||||||
|
|
||||||
(call-riot-api :tft-league-top-rated-ladders
|
|
||||||
:path-params {:queue "RANKED_TFT_TURBO"})
|
|
||||||
|
|
||||||
(call-riot-api :tft-league-by-league-id
|
|
||||||
:path-params {:league-id "abc"})
|
|
||||||
)
|
|
||||||
@@ -1,454 +0,0 @@
|
|||||||
(ns riot.riot-config)
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; API KEYS
|
|
||||||
;;
|
|
||||||
|
|
||||||
(def RIOT-KEYS {:RIOT_DEV_KEY (atom nil)
|
|
||||||
:RIOT_LOL_KEY (atom nil)
|
|
||||||
:RIOT_TFT_KEY (atom nil)})
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; KNOWN ENDPOINTS
|
|
||||||
;;
|
|
||||||
;; Each endpoint has a URL with path params (denoted as keywords, with ':') and
|
|
||||||
;; some informative metadata about parameters and some logical grouping.
|
|
||||||
;;
|
|
||||||
;; For now, metadata is optional and it is not used in the core, but is very
|
|
||||||
;; usefull for programmers like you.
|
|
||||||
|
|
||||||
(def ENDPOINTS
|
|
||||||
{;; account v1
|
|
||||||
:account-by-puuid
|
|
||||||
{:uri "https://europe.api.riotgames.com/riot/account/v1/accounts/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["account"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-account-by-riot-id
|
|
||||||
{:uri "https://europe.api.riotgames.com/riot/account/v1/accounts/by-riot-id/:player-name/:player-tag"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "account"]
|
|
||||||
:path-params {:player-name {:required true :type :string}
|
|
||||||
:player-tag {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:account-active-shards
|
|
||||||
{:uri "https://europe.api.riotgames.com/riot/account/v1/active-shards/by-game/:game/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["account"]
|
|
||||||
:path-params {:game {:required true :type :string}
|
|
||||||
:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; LOL status v4
|
|
||||||
:lol-status
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/status/v4/platform-data"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "status"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; champion-mastery-v4
|
|
||||||
:champion-mastery-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "champion-mastery"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:champion-mastery-by-puuid-and-champion
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/:puuid/by-champion/:championId"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "champion-mastery"]
|
|
||||||
:path-params {:puuid {:required true :type :string}
|
|
||||||
:championId {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:champion-mastery-by-puuid-top
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/:puuid/top"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "champion-mastery"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {:count {:required false :type :int :default 3}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; champion v3
|
|
||||||
:champion-rotations
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/platform/v3/champion-rotations"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "champion"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; summoner v4
|
|
||||||
:summoner-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "summoner"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; leage v4
|
|
||||||
:league-challenger-by-queue
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/league/v4/challengerleagues/by-queue/:queue"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "league"]
|
|
||||||
:path-params {:queue {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:league-all-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/league/v4/entries/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "league"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:league-all-queue-tier-division
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/league/v4/entries/:queue/:tier/:division"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "league"]
|
|
||||||
:path-params {:queue {:required true :type :string}
|
|
||||||
:tier {:required true :type :string}
|
|
||||||
:division {:required true :type :string}}
|
|
||||||
:query-params {:page {:required false :type :int :default 1}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:league-grandmaster-by-queue
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/league/v4/grandmasterleagues/by-queue/:queue"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "league"]
|
|
||||||
:path-params {:queue {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:league-by-id
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/league/v4/leagues/:league-id"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "league"]
|
|
||||||
:path-params {:league-id {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:league-by-queue
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/league/v4/masterleagues/by-queue/:queue"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "league"]
|
|
||||||
:path-params {:queue {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; league-exp v4
|
|
||||||
:league-exp-by-queue-tier-division
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/league-exp/v4/entries/:queue/:tier/:division"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "league"]
|
|
||||||
:path-params {:queue {:required true :type :string}
|
|
||||||
:tier {:required true :type :string}
|
|
||||||
:division {:required true :type :string}}
|
|
||||||
:query-params {:page {:required false :type :int :default 1}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; clash v1
|
|
||||||
:clash-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/clash/v1/players/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "clash"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:clash-by-team
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/clash/v1/teams/:team-id"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "clash"]
|
|
||||||
:path-params {:team-id {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:clash-tournaments
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/clash/v1/tournaments"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "clash"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:clash-tournaments-by-team
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/clash/v1/tournaments/by-team/:team-id"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "clash"]
|
|
||||||
:path-params {:team-id {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:clash-tournaments-by-tournament
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/clash/v1/tournaments/:tournament-id"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "clash"]
|
|
||||||
:path-params {:tournament-id {:required true :type :int}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; Match v5
|
|
||||||
:lol-matches-by-puuid
|
|
||||||
{:uri "https://europe.api.riotgames.com/lol/match/v5/matches/by-puuid/:puuid/ids"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "match"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {:startTime {:required false :type :long}
|
|
||||||
:endTime {:required false :type :long}
|
|
||||||
:queue {:required false :type :int}
|
|
||||||
:type {:required false :type :string}
|
|
||||||
:start {:required false :type :int :default 0}
|
|
||||||
:count {:required false :type :int :default 20 :min 0 :max 100}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-match-replays-by-puuid
|
|
||||||
{:uri "https://europe.api.riotgames.com/lol/match/v5/matches/by-puuid/:puuid/replays"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "match"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-match-by-id
|
|
||||||
{:uri "https://europe.api.riotgames.com/lol/match/v5/matches/:match-id"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "match"]
|
|
||||||
:path-params {:match-id {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-match-timeline-by-id
|
|
||||||
{:uri "https://europe.api.riotgames.com/lol/match/v5/matches/:match-id/timeline"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "match"]
|
|
||||||
:path-params {:match-id {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; LOL challenges v1
|
|
||||||
:lol-challenges-config
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/challenges/v1/challenges/config"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "challenges"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-challenge-config-by-id
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/challenges/v1/challenges/:challenge-id/config"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "challenges"]
|
|
||||||
:path-params {:challenge-id {:required true :type :long}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-challenges-percentiles
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/challenges/v1/challenges/percentiles"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "challenges"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-challenges-percentiles-by-id
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/challenges/v1/challenges/:challenge-id/percentiles"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "challenges"]
|
|
||||||
:path-params {:challenge-id {:required true :type :long}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-challenge-leaderboard
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/challenges/v1/challenges/:challenge-id/leaderboards/by-level/:level"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "challenges"]
|
|
||||||
:path-params {:challenge-id {:required true :type :long}
|
|
||||||
:level {:required true :type #{"NONE" "IRON" "BRONZE" "SILVER" "GOLD" "PLATINUM" "DIAMOND" "MASTER" "GRANDMASTER" "CHALLENGER" "HIGHEST_NOT_LEADERBOARD_ONLY" "HIGHEST" "LOWEST"}}}
|
|
||||||
:query-params {:limit {:required false :type :int}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-challenge-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/challenges/v1/player-data/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "challenges"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; LOL champion mastery v4
|
|
||||||
:lol-champion-mastery-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "champion-mastery"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-champion-mastery-by-puuid-champion
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/:puuid/by-champion/:champion-id"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "champion-mastery"]
|
|
||||||
:path-params {:puuid {:required true :type :string}
|
|
||||||
:champion-id {:required true :type :int}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-champion-mastery-by-puuid-top
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/:puuid/top"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "champion-mastery"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {:count {:required false :type :int :default 3}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:lol-champion-mastery-scores-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/champion-mastery/v4/scores/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "champion-mastery"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; spectator v5
|
|
||||||
|
|
||||||
:lol-spectator
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/spectator/v5/active-games/by-summoner/:puuid"
|
|
||||||
:api-key :RIOT_LOL_KEY
|
|
||||||
:groups ["lol" "spectator"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
|
|
||||||
;;
|
|
||||||
;; TFT
|
|
||||||
;;
|
|
||||||
|
|
||||||
;; account v1
|
|
||||||
:tft-account-by-riot-id
|
|
||||||
{:uri "https://europe.api.riotgames.com/riot/account/v1/accounts/by-riot-id/:player-name/:player-tag"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "account"]
|
|
||||||
:path-params {:player-name {:required true :type :string}
|
|
||||||
:player-tag {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; tft status v1
|
|
||||||
:tft-status
|
|
||||||
{:uri "https://euw1.api.riotgames.com/tft/status/v1/platform-data"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "status"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; TFT summoner v1
|
|
||||||
:tft-summoner-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/tft/summoner/v1/summoners/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "summoner"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; TFT match v1
|
|
||||||
:tft-matches-by-puuid
|
|
||||||
{:uri "https://europe.api.riotgames.com/tft/match/v1/matches/by-puuid/:puuid/ids"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "match"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {:startTime {:required false :type :long}
|
|
||||||
:endTime {:required false :type :long}
|
|
||||||
:queue {:required false :type :int}
|
|
||||||
:type {:required false :type :string}
|
|
||||||
:start {:required false :type :int :default 0}
|
|
||||||
:count {:required false :type :int :default 20 :min 0 :max 100}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
|
|
||||||
:tft-match-by-id
|
|
||||||
{:uri "https://europe.api.riotgames.com/tft/match/v1/matches/:match-id"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "match"]
|
|
||||||
:path-params {:match-id {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; TFT espectator v5
|
|
||||||
|
|
||||||
:tft-spectator
|
|
||||||
{:uri "https://euw1.api.riotgames.com/lol/spectator/tft/v5/active-games/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "spectator"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; TFT league v1
|
|
||||||
:tft-league-by-puuid
|
|
||||||
{:uri "https://euw1.api.riotgames.com/tft/league/v1/by-puuid/:puuid"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "spectator"]
|
|
||||||
:path-params {:puuid {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:tft-league-challenger
|
|
||||||
{:uri "https://euw1.api.riotgames.com/tft/league/v1/challenger"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "spectator"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {:ranked {:required false :type #{"RANKED_TFT" "RANKED_TFT_DOUBLE_UP"}}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:tft-league-by-tier-division
|
|
||||||
{:uri "https://euw1.api.riotgames.com/tft/league/v1/entries/:tier/:division"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "league"]
|
|
||||||
:path-params {:tier {:required true :type :string}
|
|
||||||
:division {:required true :type :string}}
|
|
||||||
:query-params {:queue {:required false :type #{"RANKED_TFT" "RANKED_TFT_DOUBLE_UP"} :default "RANKED_TFT"}
|
|
||||||
:page {:required false :type int :default 1}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:tft-league-grandmaster-by-queue
|
|
||||||
{:uri "https://euw1.api.riotgames.com/tft/league/v1/grandmaster"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "league"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {:queue {:required false :type #{"RANKED_TFT" "RANKED_TFT_DOUBLE_UP"} :default "RANKED_TFT"}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:tft-league-master-by-queue
|
|
||||||
{:uri "https://euw1.api.riotgames.com/tft/league/v1/master"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "league"]
|
|
||||||
:path-params {}
|
|
||||||
:query-params {:queue {:required false :type #{"RANKED_TFT" "RANKED_TFT_DOUBLE_UP"} :default "RANKED_TFT"}}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
:tft-league-top-rated-ladders
|
|
||||||
{:uri "https://euw1.api.riotgames.com/tft/league/v1/rated-ladders/:queue/top"
|
|
||||||
:api-key :RIOT_TFT_KEY
|
|
||||||
:groups ["tft" "league"]
|
|
||||||
:path-params {:queue {:required true :type :string}}
|
|
||||||
:query-params {}
|
|
||||||
:header-params {}}
|
|
||||||
|
|
||||||
;; END OF ENDPOINT DEFINITIONS
|
|
||||||
})
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
(ns riot.core-test
|
|
||||||
#_{:clj-kondo/ignore [:refer-all]}
|
|
||||||
(:require [clojure.test :refer :all]
|
|
||||||
[tmpl.core :refer :all])
|
|
||||||
(:import (java.util Arrays)))
|
|
||||||
|
|
||||||
(deftest generate-greeting-test
|
|
||||||
(testing "Some test cases"
|
|
||||||
(is (generate-greeting nil nil)) ;; Not nill
|
|
||||||
(is (= "Hello test" (generate-greeting :medium "test")))))
|
|
||||||
36
resources/logback.xml
Normal file
36
resources/logback.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<configuration>
|
||||||
|
|
||||||
|
<!-- Appender a consola -->
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>INFO</level>
|
||||||
|
</filter>
|
||||||
|
<encoder>
|
||||||
|
<!--<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
|
||||||
|
<pattern>[%-5level] %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- Appender a archivo con rotación -->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>logs/riot-app.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!-- rota cada día, conserva 7 días -->
|
||||||
|
<fileNamePattern>logs/riot-app.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<maxHistory>7</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>DEBUG</level>
|
||||||
|
</filter>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{ISO8601} %-5level %logger{36}:%L - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- Nivel de log raíz -->
|
||||||
|
<root level="DEBUG">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
260
src/riot/app.clj
Normal file
260
src/riot/app.clj
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
|
||||||
|
;; Main CLI application
|
||||||
|
|
||||||
|
(ns riot.app
|
||||||
|
(:require [cli-matic.core :refer [run-cmd]]
|
||||||
|
[cli-matic.utils :as U]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[timewords.core :refer [parse]]
|
||||||
|
[clojure.tools.logging :as log]
|
||||||
|
)
|
||||||
|
(:use [riot.core]
|
||||||
|
[riot.data]
|
||||||
|
[slingshot.slingshot :only [try+]])
|
||||||
|
(:gen-class))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-puuid-from-params
|
||||||
|
[api-key params & {:keys [puuid name tag]}]
|
||||||
|
;(println "get puuid API key" api-key)
|
||||||
|
(cond
|
||||||
|
(= 1 (count params)) (first params)
|
||||||
|
(= 2 (count params)) (get-puuid-from-name (first params) (second params) :api-key api-key)
|
||||||
|
(some? puuid) puuid
|
||||||
|
(and (some? name) (some? tag)) (get-puuid-from-name name tag :api-key api-key)))
|
||||||
|
|
||||||
|
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(defn cmd-active
|
||||||
|
"Checks if a player is online at the moment"
|
||||||
|
[& {:keys [lol-api-key tft-api-key debug-http lol tft _arguments]
|
||||||
|
:as opts}]
|
||||||
|
(try+
|
||||||
|
;(println "Check online" opts)
|
||||||
|
(let [lol-key (when lol (get-lol-api-key lol-api-key))
|
||||||
|
tft-key (when tft (get-tft-api-key tft-api-key))
|
||||||
|
lol-id (when lol (get-puuid-from-params lol-key _arguments opts))
|
||||||
|
tft-id (when tft (get-puuid-from-params tft-key _arguments opts))]
|
||||||
|
(if (and (nil? lol-id) (nil? tft-id))
|
||||||
|
(println "No games to check")
|
||||||
|
(if (is-playing? lol-id tft-id :lol-api-key lol-key :tft-api-key tft-key :debug debug-http)
|
||||||
|
(do (println "Yes, it's playing") true)
|
||||||
|
(do (println "No, it's not playing right now") false))))
|
||||||
|
(catch [:status 401] _
|
||||||
|
(U/exit! "Invalid API key" 3))
|
||||||
|
(catch [:status 404] _
|
||||||
|
(U/exit! "Unknown user or tag" 5))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn format-result
|
||||||
|
"Format result"
|
||||||
|
[output x]
|
||||||
|
(case output
|
||||||
|
"table" (as-ascii-table x)
|
||||||
|
"ptable" (as-pretty-table x)
|
||||||
|
"edn" (pp/pprint x)
|
||||||
|
"json" (println (as-json x))
|
||||||
|
"csv" (println (as-csv x))
|
||||||
|
(pp/pprint x))) ;edn as default
|
||||||
|
|
||||||
|
|
||||||
|
(defn format-dates-cond
|
||||||
|
"Format dates and durations if specified"
|
||||||
|
[format-dates pattern x]
|
||||||
|
(if format-dates
|
||||||
|
(if (some? pattern)
|
||||||
|
(with-parsed-dates x :datetime-format pattern)
|
||||||
|
(with-parsed-dates x))
|
||||||
|
x))
|
||||||
|
|
||||||
|
|
||||||
|
(defn format-duration-cond
|
||||||
|
"Format durations if specified"
|
||||||
|
[format-durations x]
|
||||||
|
(if format-durations
|
||||||
|
(with-parsed-durations x)
|
||||||
|
x))
|
||||||
|
|
||||||
|
|
||||||
|
(defn calculate-statistics-cond
|
||||||
|
"If true, calculate statistics and print them to console"
|
||||||
|
[calculate x]
|
||||||
|
(when calculate
|
||||||
|
(log/info "Calculating statistics")
|
||||||
|
(pp/pprint (calculate-statistics x)))
|
||||||
|
x)
|
||||||
|
|
||||||
|
(defn reverse-cond
|
||||||
|
"If true, data is ordered from nearest to fartest"
|
||||||
|
[rev x] (if rev x (reverse x)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn filter-columns
|
||||||
|
"Filter columns to show. With 'all' shows all columns"
|
||||||
|
[columns x] (if (= "all" columns)
|
||||||
|
x
|
||||||
|
(map #(select-keys % (map keyword (str/split columns #","))) x)))
|
||||||
|
|
||||||
|
(defn get-date
|
||||||
|
"Converts an string into a java.util.Date"
|
||||||
|
[x]
|
||||||
|
(if (nil? x) nil (parse x)))
|
||||||
|
|
||||||
|
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(defn cmd-timeline
|
||||||
|
"Get all matches"
|
||||||
|
[& {:keys [lol-api-key tft-api-key lol tft output format-dates format-durations date-format reverse order-by show-columns debug-http since until statistics _arguments]
|
||||||
|
:as opts}]
|
||||||
|
(try+
|
||||||
|
;(println "Get timeline" opts)
|
||||||
|
(let [lol-key (when lol (get-lol-api-key lol-api-key))
|
||||||
|
tft-key (when tft (get-tft-api-key tft-api-key))
|
||||||
|
lol-id (when lol (get-puuid-from-params lol-key _arguments opts))
|
||||||
|
tft-id (when tft (get-puuid-from-params tft-key _arguments opts))
|
||||||
|
since-date (get-date since)
|
||||||
|
until-date (get-date until)]
|
||||||
|
(log/info (str "Fetching data between " since-date " and " (if (some? until-date) until-date (java.util.Date.))))
|
||||||
|
(if (and (nil? lol-id) (nil? tft-id))
|
||||||
|
(U/exit! "No games selected" 2)
|
||||||
|
(->> (get-matches-info-batch lol-id tft-id :lol-api-key lol-key :tft-api-key tft-key :debug debug-http :order-by (keyword order-by) :since since-date :until until-date)
|
||||||
|
(calculate-statistics-cond statistics)
|
||||||
|
(reverse-cond reverse)
|
||||||
|
(format-dates-cond format-dates date-format)
|
||||||
|
(format-duration-cond format-durations)
|
||||||
|
(filter-columns show-columns)
|
||||||
|
(format-result output))))
|
||||||
|
(catch [:status 401] _
|
||||||
|
(U/exit! "Invalid API key" 3))
|
||||||
|
(catch [:status 404] _
|
||||||
|
(U/exit! "Unknown user or tag" 5))
|
||||||
|
(catch [:status 429] _
|
||||||
|
(U/exit! "Rate limit exceeded, please wait some minutes and try again" 4))
|
||||||
|
(catch [:status 502] _
|
||||||
|
(U/exit! "Server timeout, please wait some minutes and try again" 6))
|
||||||
|
(catch [:status 503] _
|
||||||
|
(U/exit! "Server timeout, please wait some minutes and try again" 6))))
|
||||||
|
|
||||||
|
|
||||||
|
;; More info: https://github.com/l3nz/cli-matic/blob/master/README.md
|
||||||
|
(def CONFIGURATION
|
||||||
|
{:app {:command "riot"
|
||||||
|
:version "1.2.0"
|
||||||
|
:description ["Get how much you play your favourite games"
|
||||||
|
""
|
||||||
|
"EXAMPLE USAGES:"
|
||||||
|
" Get all matches since March 1st 2025:"
|
||||||
|
" riot timeline <your_player_name> <your_tag> -s 2025-03-01"
|
||||||
|
""
|
||||||
|
" Get all matches as CSV, don't format durations:"
|
||||||
|
" riot timeline <your_player_name> <your_tag> -s 2025-03-01 -o csv --no-format-durations > my_matches.csv"
|
||||||
|
""
|
||||||
|
"ERROR CODES:"
|
||||||
|
" 0: OK, no error"
|
||||||
|
" 1: player not playing"
|
||||||
|
" 2: Invalid params"
|
||||||
|
" 3: Invalid API key, please, get and configure an updated one"
|
||||||
|
" 4: Rate limit exceeded, please wait some minutes and try again"
|
||||||
|
" 5: Unknown user and tag"
|
||||||
|
" 6: Server error. Please, try again"]}
|
||||||
|
|
||||||
|
:global-opts [{:option "lol-api-key"
|
||||||
|
:as "API key for LOL data"
|
||||||
|
:type :string :default nil
|
||||||
|
:env "LOL_API"}
|
||||||
|
{:option "tft-api-key"
|
||||||
|
:as "API key for TFT data"
|
||||||
|
:type :string :default nil
|
||||||
|
:env "TFT_API"}
|
||||||
|
{:option "debug-http"
|
||||||
|
:as "Show debug for HTTP connections. I'ts very verbose!"
|
||||||
|
:type :flag
|
||||||
|
:default false}
|
||||||
|
;; {:option "log-level"
|
||||||
|
;; :as "Log level (debug, info, warn, etc)"
|
||||||
|
;; :type :keyword
|
||||||
|
;; :default :info}
|
||||||
|
]
|
||||||
|
|
||||||
|
:commands [{:command "active" :short "a"
|
||||||
|
:description ["Shows if the player is currently playing"
|
||||||
|
"By default only shows LOL matches"]
|
||||||
|
:opts [{:option "lol"
|
||||||
|
:type :with-flag :default true
|
||||||
|
:as "Include LOL matches"}
|
||||||
|
{:option "tft"
|
||||||
|
:type :with-flag :default false
|
||||||
|
:as "Include TFT matches. Experimental"}]
|
||||||
|
:runs cmd-active}
|
||||||
|
|
||||||
|
{:command "timeline" :short "t"
|
||||||
|
:description ["Get info from LOL and TFT (experimental, disabled by default) matches in a time range."
|
||||||
|
""
|
||||||
|
"Warning: it can take several minutes to obtain all data, due to API restrictions."]
|
||||||
|
:opts [{:option "lol"
|
||||||
|
:type :with-flag :default true
|
||||||
|
:as "Include LOL matches"}
|
||||||
|
{:option "tft"
|
||||||
|
:type :with-flag :default false
|
||||||
|
:as "Include TFT matches. Experimental"}
|
||||||
|
{:option "since" :short "s"
|
||||||
|
:type :string :default "today"
|
||||||
|
:as ["Starting date (included) in 'yyyy-MM-dd' format."
|
||||||
|
"Other valid values: 'today', 'yesterday', 'last monday', etc"]}
|
||||||
|
{:option "until" :short "u"
|
||||||
|
:type :string :default nil
|
||||||
|
:as ["Ending date (not included) in 'yyyy-MM-dd' format."
|
||||||
|
"Other valid values: today, yesterday, tomorrow, etc"]}
|
||||||
|
{:option "output" :short "o"
|
||||||
|
:type #{"table" "ptable" "edn" "json" "csv"} :default "ptable"
|
||||||
|
:as ["Output data format. It can be one of:"
|
||||||
|
" - table: ASCII table"
|
||||||
|
" - ptable: Pretty ASCII table (default)"
|
||||||
|
" - edn: Clojure's internal EDN format"
|
||||||
|
" - json: Classic JSON"
|
||||||
|
" - csv: CSV data, using ',' as field separator"]}
|
||||||
|
{:option "order-by"
|
||||||
|
:type #{"start" "end" "duration"} :default "start"
|
||||||
|
:as "Order by field"}
|
||||||
|
{:option "reverse" :short "r"
|
||||||
|
:type :with-flag :default false
|
||||||
|
:as "If true, newer matches are shown last in list"}
|
||||||
|
{:option "format-dates"
|
||||||
|
:type :with-flag :default true
|
||||||
|
:as "Show formatted dates"}
|
||||||
|
{:option "format-durations"
|
||||||
|
:type :with-flag :default true
|
||||||
|
:as "Show formatted durations"}
|
||||||
|
{:option "date-format"
|
||||||
|
:type :string :default nil
|
||||||
|
:as "If format-dates is enabled, specifies the formatter's string"}
|
||||||
|
{:option "show-columns"
|
||||||
|
:type :string :default "all"
|
||||||
|
:as ["Show only selected columns, separated by comma. Supported columns:"
|
||||||
|
" - all: All columns"
|
||||||
|
" - start: Start timestamp"
|
||||||
|
" - end: End timestamp"
|
||||||
|
" - duration: Duration in seconds"
|
||||||
|
" - game-type: lol or tft"
|
||||||
|
" - id: Game unique id"
|
||||||
|
" - winner: Winner of the match"]}
|
||||||
|
{:option "statistics"
|
||||||
|
:type :with-flag :default true
|
||||||
|
:as "Show simple statistics"}]
|
||||||
|
:runs cmd-timeline}]})
|
||||||
|
|
||||||
|
|
||||||
|
(defn -main
|
||||||
|
"Main entry point"
|
||||||
|
[& args]
|
||||||
|
(log/debug "")
|
||||||
|
(log/debug "** Starting application **")
|
||||||
|
(log/debug "")
|
||||||
|
(run-cmd args CONFIGURATION))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
; Launch manually
|
||||||
|
(-main)
|
||||||
|
)
|
||||||
506
src/riot/core.clj
Normal file
506
src/riot/core.clj
Normal file
@@ -0,0 +1,506 @@
|
|||||||
|
|
||||||
|
;; Core functions to get data from server
|
||||||
|
|
||||||
|
(ns riot.core
|
||||||
|
(:require [clj-http.client :as client]
|
||||||
|
[buddy.core.crypto :as crypto]
|
||||||
|
[buddy.core.codecs :as codecs]
|
||||||
|
[clojure.tools.logging :as log]
|
||||||
|
)
|
||||||
|
(:use [slingshot.slingshot :only [try+]])
|
||||||
|
(:gen-class))
|
||||||
|
|
||||||
|
|
||||||
|
(def API-HOST "api.riotgames.com")
|
||||||
|
|
||||||
|
(def secret-key (codecs/b64->bytes "//EaEeKElZDJSLLJaydGQDKw5DiqUCZ5DdnLvpE6dbo="))
|
||||||
|
(def iv (codecs/b64->bytes "cMN8rAwUoYHk7YwJ7HawFw=="))
|
||||||
|
|
||||||
|
|
||||||
|
;; Keys aren't in plain
|
||||||
|
|
||||||
|
(defn encrypt-data
|
||||||
|
([text] (encrypt-data text secret-key iv))
|
||||||
|
([text key iv]
|
||||||
|
(-> (codecs/str->bytes text)
|
||||||
|
(crypto/encrypt key iv)
|
||||||
|
(codecs/bytes->b64-str))))
|
||||||
|
|
||||||
|
(defn decrypt-data
|
||||||
|
([b64] (decrypt-data b64 secret-key iv))
|
||||||
|
([b64 key iv]
|
||||||
|
(-> (codecs/b64->bytes b64)
|
||||||
|
(crypto/decrypt key iv)
|
||||||
|
(codecs/bytes->str))))
|
||||||
|
|
||||||
|
;;;; API KEYS
|
||||||
|
|
||||||
|
(def DEV_KEY "RGAPI-ec3779d1-bc61-4a2e-a071-34addcc6bd56")
|
||||||
|
(def LOL_KEY "RqFfnwRNwf2UkRNvxwr6tE11J7KSjzRMtOecqm1Aw+PWOXWSil9yaNiglVeKinEddQk4BQs7N/G1r42MeyMZHA==")
|
||||||
|
(def TFT_KEY (encrypt-data DEV_KEY secret-key iv))
|
||||||
|
|
||||||
|
(defn get-lol-api-key
|
||||||
|
"Get the API key from the LOL_API environment variable"
|
||||||
|
([key]
|
||||||
|
(if (some? key) key (get-lol-api-key)))
|
||||||
|
([]
|
||||||
|
(let [k (System/getenv "LOL_API")]
|
||||||
|
(if (some? k) k (decrypt-data LOL_KEY secret-key iv)))))
|
||||||
|
|
||||||
|
(defn get-tft-api-key
|
||||||
|
"Get the API key from the TFT_API environment variable"
|
||||||
|
([key]
|
||||||
|
(if (some? key) key (get-tft-api-key)))
|
||||||
|
([]
|
||||||
|
(let [k (System/getenv "TFT_API")]
|
||||||
|
(if (some? k) k (decrypt-data TFT_KEY secret-key iv)))))
|
||||||
|
|
||||||
|
|
||||||
|
;;;; MATCH DATA RESPONSE PARSER
|
||||||
|
|
||||||
|
;; Parsers takes data from a response, and builds a map with standardized keys
|
||||||
|
|
||||||
|
;; Builds a json-data object
|
||||||
|
(defn make-json-data
|
||||||
|
[json-key hash-key & [fn-value]]
|
||||||
|
(if (some? fn-value)
|
||||||
|
{:json-key json-key :hash-key hash-key :fn-value fn-value}
|
||||||
|
{:json-key json-key :hash-key hash-key :fn-value #(identity %)}))
|
||||||
|
|
||||||
|
;; Postprocess functions
|
||||||
|
(defn post-calculate-end
|
||||||
|
[match & _]
|
||||||
|
(assoc match :end
|
||||||
|
(+ (:start match) (* 1000 (int (:duration match))))))
|
||||||
|
|
||||||
|
; Winner?
|
||||||
|
|
||||||
|
(defn pos-in-col
|
||||||
|
"Find the indexes of an element inside a collection.
|
||||||
|
If there are multiple ocurrences, there will be multiple indexes in the result.
|
||||||
|
If the collection is a map, keys are returned"
|
||||||
|
[col x]
|
||||||
|
(reduce-kv (fn [m k v]
|
||||||
|
(if (= v x)
|
||||||
|
(conj m k)
|
||||||
|
m))
|
||||||
|
[] col))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-player-info
|
||||||
|
[match-resp puuid]
|
||||||
|
(let [participants (get-in match-resp [:metadata :participants])
|
||||||
|
idx (first (pos-in-col participants puuid))
|
||||||
|
player-info (get-in match-resp [:info :participants idx])]
|
||||||
|
player-info))
|
||||||
|
|
||||||
|
|
||||||
|
(defn winner?
|
||||||
|
[match-resp puuid]
|
||||||
|
(:win (get-player-info match-resp puuid)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn post-calculate-win
|
||||||
|
[data response puuid]
|
||||||
|
(assoc data :winner (winner? response puuid)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn with-winner-status
|
||||||
|
"Takes the original list of matches and adds winner info"
|
||||||
|
[matches puuid]
|
||||||
|
(map #(assoc % :winner (winner? % puuid)) matches))
|
||||||
|
|
||||||
|
|
||||||
|
;; json adapters
|
||||||
|
(def player-info-parser {:parser [(make-json-data [:puuid] :puuid)]})
|
||||||
|
|
||||||
|
(def lol-match-parser {:parser [(make-json-data [:info :gameCreation] :start)
|
||||||
|
(make-json-data [:info :gameEndTimestamp] :end)
|
||||||
|
(make-json-data [:info :gameDuration] :duration)
|
||||||
|
;; (make-json-data [:none] :active (constantly false))
|
||||||
|
(make-json-data [:none] :game-type (constantly "lol"))
|
||||||
|
(make-json-data [:metadata :matchId] :id)
|
||||||
|
(make-json-data [:none] :winner (constantly false))
|
||||||
|
(make-json-data [:info :endOfGameResult] :result)]
|
||||||
|
:post post-calculate-win})
|
||||||
|
|
||||||
|
(def tft-match-parser {:parser [(make-json-data [:info :gameCreation] :start)
|
||||||
|
(make-json-data [:none] :end (constantly -1))
|
||||||
|
(make-json-data [:info :game_length] :duration #(int %)) ; rounds to integer
|
||||||
|
;; (make-json-data [:none] :active (constantly false))
|
||||||
|
(make-json-data [:none] :game-type (constantly "tft"))
|
||||||
|
(make-json-data [:metadata :match_id] :id)
|
||||||
|
(make-json-data [:none] :winner (constantly false))
|
||||||
|
(make-json-data [:info :endOfGameResult] :result)]
|
||||||
|
:post #(post-calculate-win
|
||||||
|
(post-calculate-end %1 %2 %3) %2 %3)}) ; end = start + (duration * 1000)
|
||||||
|
|
||||||
|
(def lol-current-parser {:parser [(make-json-data [:gameStartTime] :start)
|
||||||
|
(make-json-data [:none] :end (constantly nil))
|
||||||
|
(make-json-data [:gameLength] :duration)
|
||||||
|
;; (make-json-data [:none] :active (constantly true))
|
||||||
|
(make-json-data [:none] :game-type (constantly "lol"))
|
||||||
|
(make-json-data [:gameId] :id)
|
||||||
|
(make-json-data [:none] :winner (constantly nil))
|
||||||
|
(make-json-data [:none] :result (constantly nil))]})
|
||||||
|
|
||||||
|
(def tft-current-parser {:parser [(make-json-data [:gameStartTime] :start)
|
||||||
|
(make-json-data [:none] :end (constantly nil))
|
||||||
|
(make-json-data [:gameLength] :duration)
|
||||||
|
;; (make-json-data [:none] :active (constantly true))
|
||||||
|
(make-json-data [:none] :game-type (constantly "tft"))
|
||||||
|
(make-json-data [:gameId] :id)
|
||||||
|
(make-json-data [:none] :winner (constantly nil))
|
||||||
|
(make-json-data [:none] :result (constantly nil))]})
|
||||||
|
|
||||||
|
|
||||||
|
(defn parse-response
|
||||||
|
"Takes a parser and a response and resturns a hasmap with known keys"
|
||||||
|
[parser response puuid]
|
||||||
|
(let [data (into {} (map
|
||||||
|
#(vector
|
||||||
|
(:hash-key %)
|
||||||
|
((:fn-value %) (get-in response (:json-key %))))
|
||||||
|
(:parser parser)))
|
||||||
|
postfn (:post parser)]
|
||||||
|
(if (some? postfn)
|
||||||
|
(postfn data response puuid)
|
||||||
|
data)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn create-endpoint
|
||||||
|
"Create a URL for the API"
|
||||||
|
[server & parts]
|
||||||
|
(apply str "https://" server "." API-HOST parts))
|
||||||
|
|
||||||
|
|
||||||
|
(defn raw-get-query
|
||||||
|
"Send a query to the API and parsers the result"
|
||||||
|
[url & {:keys [api-key params debug]
|
||||||
|
:or {params nil
|
||||||
|
debug false}}]
|
||||||
|
(when debug (println "** SENDING REQUEST **"))
|
||||||
|
(log/trace "Sending request. Params: " params)
|
||||||
|
;; (println "Params: " params)
|
||||||
|
(:body (client/get url
|
||||||
|
{:debug debug
|
||||||
|
:save-request? true
|
||||||
|
:headers {:X-Riot-Token api-key}
|
||||||
|
:query-params (when (map? params) params)
|
||||||
|
:as :json})))
|
||||||
|
|
||||||
|
|
||||||
|
(defn query
|
||||||
|
"Send a query to the API and parsers the result"
|
||||||
|
[url parser & {:keys [puuid api-key params debug]
|
||||||
|
:or {api-key nil
|
||||||
|
params nil
|
||||||
|
debug false}}]
|
||||||
|
(parse-response parser (raw-get-query url :api-key api-key :params params :debug debug) puuid))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;;;; MAIN QUERY FUNCTIONS
|
||||||
|
|
||||||
|
(defn get-puuid-from-name
|
||||||
|
"Get player's PUUID from its name and tag"
|
||||||
|
[playername tag & {:keys [api-key debug server]
|
||||||
|
:or {debug false
|
||||||
|
server "europe"}}]
|
||||||
|
(:puuid (raw-get-query
|
||||||
|
(create-endpoint server "/riot/account/v1/accounts/by-riot-id/" playername "/" tag)
|
||||||
|
:api-key api-key
|
||||||
|
:debug debug)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn date-to-seconds
|
||||||
|
([] (date-to-seconds (java.util.Date.)))
|
||||||
|
([d]
|
||||||
|
(when (some? d) (quot (.getTime d) 1000))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn query-params
|
||||||
|
"Calculate query params"
|
||||||
|
[& {:keys [count start since until]
|
||||||
|
:or {count 100}}]
|
||||||
|
(cond-> {"count" count}
|
||||||
|
(some? start) (assoc "start" start)
|
||||||
|
(some? since) (assoc "startTime" (date-to-seconds since))
|
||||||
|
(some? until) (assoc "endTime" (date-to-seconds until))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-lol-matches
|
||||||
|
"Get last LoL match-ids for a given PUUID"
|
||||||
|
[puuid & {:keys [api-key debug server count start since until]
|
||||||
|
:or {api-key (get-lol-api-key)
|
||||||
|
debug false
|
||||||
|
server "europe"
|
||||||
|
count 20
|
||||||
|
start 0}
|
||||||
|
:as params}]
|
||||||
|
;; (println "get lol start: " start)
|
||||||
|
(log/trace "Getting list of LOL matches. Params: " params)
|
||||||
|
(raw-get-query
|
||||||
|
(create-endpoint server "/lol/match/v5/matches/by-puuid/" puuid "/ids")
|
||||||
|
:api-key api-key
|
||||||
|
:params (query-params :count count :start start :since since :until until)
|
||||||
|
:debug debug))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-tft-matches
|
||||||
|
"Get last TFT match-ids for a given PUUID"
|
||||||
|
[puuid & {:keys [api-key debug server count start since until]
|
||||||
|
:or {api-key (get-tft-api-key)
|
||||||
|
debug false
|
||||||
|
server "europe"
|
||||||
|
count 20
|
||||||
|
start 0}
|
||||||
|
:as params}]
|
||||||
|
(log/trace "Getting list of TFT matches. Params: " params)
|
||||||
|
(raw-get-query
|
||||||
|
(create-endpoint server "/tft/match/v1/matches/by-puuid/" puuid "/ids")
|
||||||
|
:api-key api-key
|
||||||
|
:params (query-params :count count :start start :since since :until until)
|
||||||
|
:debug debug))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-lol-match-info
|
||||||
|
"Get info for a LoL match"
|
||||||
|
[match-id & {:keys [api-key debug server puuid]
|
||||||
|
:or {api-key (get-lol-api-key)
|
||||||
|
debug false
|
||||||
|
server "europe"}}]
|
||||||
|
;; (println "get-lol-match-info match:" match-id)
|
||||||
|
(log/debug "Getting data for LOL match " match-id)
|
||||||
|
(query
|
||||||
|
(create-endpoint server "/lol/match/v5/matches/" match-id)
|
||||||
|
lol-match-parser
|
||||||
|
:api-key api-key
|
||||||
|
:debug debug
|
||||||
|
:puuid puuid))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-tft-match-info
|
||||||
|
"Get info for a TFT match"
|
||||||
|
[match-id & {:keys [api-key debug server puuid]
|
||||||
|
:or {api-key (get-tft-api-key)
|
||||||
|
debug false
|
||||||
|
server "europe"}}]
|
||||||
|
;; (println "get-tft-match-info match:" match-id)
|
||||||
|
(log/debug "Getting data for TFT match " match-id)
|
||||||
|
(query
|
||||||
|
(create-endpoint server "/tft/match/v1/matches/" match-id)
|
||||||
|
tft-match-parser
|
||||||
|
:api-key api-key
|
||||||
|
:debug debug
|
||||||
|
:puuid puuid))
|
||||||
|
|
||||||
|
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(defn get-lol-current-info
|
||||||
|
"Get current LoL match, if any"
|
||||||
|
[puuid & {:keys [api-key debug print-not-active server]
|
||||||
|
:or {api-key (get-lol-api-key)
|
||||||
|
debug false
|
||||||
|
print-not-active false
|
||||||
|
server "euw1"}}]
|
||||||
|
(log/debug "Getting data for current LOL match")
|
||||||
|
(when (some? api-key)
|
||||||
|
(try+
|
||||||
|
(query
|
||||||
|
(create-endpoint server "/lol/spectator/v5/active-games/by-summoner/" puuid)
|
||||||
|
lol-current-parser
|
||||||
|
:api-key api-key
|
||||||
|
:debug debug)
|
||||||
|
(catch [:status 404] _
|
||||||
|
(when print-not-active (println "No active LoL match")))
|
||||||
|
(catch [:status 403] _
|
||||||
|
(println "Current status not available")))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(defn get-tft-current-info
|
||||||
|
"Get current TFT match, if any"
|
||||||
|
[puuid & {:keys [api-key debug print-not-active server]
|
||||||
|
:or {api-key (get-tft-api-key)
|
||||||
|
debug false
|
||||||
|
print-not-active false
|
||||||
|
server "euw1"}}]
|
||||||
|
(log/debug "Getting data for current TFT match")
|
||||||
|
(when (some? api-key)
|
||||||
|
(try+
|
||||||
|
(query
|
||||||
|
(create-endpoint server "/lol/spectator/tft/v5/active-games/by-puuid/" puuid)
|
||||||
|
tft-current-parser
|
||||||
|
:api-key api-key
|
||||||
|
:debug debug)
|
||||||
|
(catch [:status 404] _
|
||||||
|
(when print-not-active (println "No active tft match")))
|
||||||
|
(catch [:status 403] _
|
||||||
|
(println "Current status not available")))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn is-playing?
|
||||||
|
"Checks if player is in and active LoL or TFT match"
|
||||||
|
[lol-puuid tft-puuid & {:keys [lol-api-key tft-api-key debug server print-not-active]
|
||||||
|
:or {lol-api-key (get-lol-api-key)
|
||||||
|
tft-api-key (get-tft-api-key)
|
||||||
|
debug false
|
||||||
|
print-not-active false
|
||||||
|
server "euw1"}}]
|
||||||
|
(or (some? (get-lol-current-info lol-puuid :api-key lol-api-key :server server :debug debug :print-not-active print-not-active))
|
||||||
|
(some? (get-tft-current-info tft-puuid :api-key tft-api-key :server server :debug debug :print-not-active print-not-active))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-last-matches
|
||||||
|
"Get info for last LoL or tft matches"
|
||||||
|
[lol-puuid tft-puuid & {:keys [lol-api-key tft-api-key include-current print-not-active debug server1 server2 count start order-by since until]
|
||||||
|
:or {lol-api-key (get-lol-api-key)
|
||||||
|
tft-api-key (get-tft-api-key)
|
||||||
|
include-current true
|
||||||
|
print-not-active false
|
||||||
|
debug false
|
||||||
|
server1 "europe"
|
||||||
|
server2 "euw1"
|
||||||
|
count 20
|
||||||
|
start 0
|
||||||
|
order-by :start}}]
|
||||||
|
(sort-by order-by
|
||||||
|
(filter some?
|
||||||
|
(concat
|
||||||
|
(map
|
||||||
|
#(get-lol-match-info % :api-key lol-api-key :debug debug :server server1 :puuid lol-puuid)
|
||||||
|
(get-lol-matches lol-puuid :api-key lol-api-key :debug debug :server server1 :count count :start start :since since :until until))
|
||||||
|
(map
|
||||||
|
#(get-tft-match-info % :api-key tft-api-key :debug debug :server server1 :puuid tft-puuid)
|
||||||
|
(get-tft-matches tft-puuid :api-key tft-api-key :debug debug :server server1 :count count :start start :since since :until until))
|
||||||
|
(when include-current [(get-lol-current-info lol-puuid :api-key lol-api-key :server server2 :debug debug :print-not-active print-not-active)])
|
||||||
|
(when include-current [(get-tft-current-info tft-puuid :api-key tft-api-key :server server2 :debug debug :print-not-active print-not-active)])))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;;;; Get data bulk
|
||||||
|
#_{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
(defn get-with-wait
|
||||||
|
"Calls a getter and waits for response. If rate limit has been exceeded, waits for some
|
||||||
|
seconds and retries."
|
||||||
|
[getter & {:keys [wait-limit-exceeded]
|
||||||
|
:or {wait-limit-exceeded 15}}]
|
||||||
|
(try+
|
||||||
|
(getter) ;; Executes getter function
|
||||||
|
(catch [:status 429] _
|
||||||
|
(println "Rate limit exceeded, waiting for" wait-limit-exceeded "seconds")
|
||||||
|
(Thread/sleep (* 1000 wait-limit-exceeded))
|
||||||
|
(get-with-wait getter))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-batch-range
|
||||||
|
"Gets data in batches. Getter should be a function with one argument"
|
||||||
|
[getter & {:keys [wait-limit-exceeded first-batch max-batches]
|
||||||
|
:or {wait-limit-exceeded 15
|
||||||
|
first-batch 0
|
||||||
|
max-batches 10}}]
|
||||||
|
;; (println "Obtaining bulk data - range")
|
||||||
|
(log/debug "Getting data in " max-batches " batches")
|
||||||
|
(doall (flatten (concat
|
||||||
|
(map
|
||||||
|
#(get-with-wait (partial getter %) :wait-limit-exceeded wait-limit-exceeded)
|
||||||
|
(range first-batch max-batches))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-batch-seq
|
||||||
|
"Gets data from a secquence. The getter should be a function with one argument"
|
||||||
|
[getter seq & {:keys [wait-limit-exceeded]
|
||||||
|
:or {wait-limit-exceeded 15}}]
|
||||||
|
;; (println "Obtaining bulk data - seq")
|
||||||
|
(doall (flatten (concat
|
||||||
|
(map
|
||||||
|
#(get-with-wait (partial getter %) :wait-limit-exceeded wait-limit-exceeded)
|
||||||
|
seq)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-matches-batch-lol
|
||||||
|
"Get a lot of LOL match IDs"
|
||||||
|
[puuid api-key & {:keys [server since until wait-limit-exceeded first-batch max-batches count debug]
|
||||||
|
:or {server "europe"
|
||||||
|
wait-limit-exceeded 15
|
||||||
|
first-batch 0
|
||||||
|
max-batches 10
|
||||||
|
count 100
|
||||||
|
debug false}}]
|
||||||
|
(when (every? some? [puuid api-key])
|
||||||
|
(get-batch-range #(get-lol-matches puuid :api-key api-key :start (* count %) :since since :until until :server server :wait-limit-exceeded wait-limit-exceeded :debug debug :count count)
|
||||||
|
:wait-limit-exceeded wait-limit-exceeded
|
||||||
|
:first-batch first-batch
|
||||||
|
:max-batches max-batches)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-matches-batch-tft
|
||||||
|
"Get a lot of TFT match IDs"
|
||||||
|
[puuid api-key & {:keys [server since until wait-limit-exceeded first-batch max-batches count debug]
|
||||||
|
:or {server "europe"
|
||||||
|
wait-limit-exceeded 15
|
||||||
|
first-batch 0
|
||||||
|
max-batches 10
|
||||||
|
count 100
|
||||||
|
debug false}}]
|
||||||
|
(when (every? some? [puuid api-key])
|
||||||
|
(get-batch-range #(get-tft-matches puuid :api-key api-key :start (* count %) :since since :until until :server server :wait-limit-exceeded wait-limit-exceeded :debug debug :count count)
|
||||||
|
:wait-limit-exceeded wait-limit-exceeded
|
||||||
|
:first-batch first-batch
|
||||||
|
:max-batches max-batches)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-matches-info-batch-lol
|
||||||
|
"Get a lot of LOL matches info"
|
||||||
|
[matches-id puuid api-key & {:keys [server wait-limit-exceeded debug]
|
||||||
|
:or {server "europe"
|
||||||
|
wait-limit-exceeded 15
|
||||||
|
debug false}}]
|
||||||
|
;; (println "Matches: " matches-id)
|
||||||
|
(when (every? some? [matches-id puuid api-key])
|
||||||
|
(get-batch-seq #(get-lol-match-info % :puuid puuid :api-key api-key :server server :wait-limit-exceeded wait-limit-exceeded :debug debug)
|
||||||
|
matches-id
|
||||||
|
:wait-limit-exceeded wait-limit-exceeded)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-matches-info-batch-tft
|
||||||
|
"Get a lot of TFT matches info"
|
||||||
|
[matches-id puuid api-key & {:keys [server wait-limit-exceeded debug]
|
||||||
|
:or {server "europe"
|
||||||
|
wait-limit-exceeded 15
|
||||||
|
debug false}}]
|
||||||
|
(when (every? some? [matches-id puuid api-key])
|
||||||
|
(get-batch-seq #(get-tft-match-info % :puuid puuid :api-key api-key :server server :wait-limit-exceeded wait-limit-exceeded :debug debug)
|
||||||
|
matches-id
|
||||||
|
:wait-limit-exceeded wait-limit-exceeded)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-matches-info-batch
|
||||||
|
"Get a lot of LOL and TFT matches info"
|
||||||
|
[lol-puuid tft-puuid & {:keys [lol-api-key tft-api-key wait-limit-exceeded debug server server2 count order-by since until include-current]
|
||||||
|
:or {lol-api-key (get-lol-api-key)
|
||||||
|
tft-api-key (get-tft-api-key)
|
||||||
|
wait-limit-exceeded 15
|
||||||
|
debug false
|
||||||
|
server "europe"
|
||||||
|
server2 "euw1"
|
||||||
|
count 100
|
||||||
|
order-by :start
|
||||||
|
include-current true}}]
|
||||||
|
(log/debug "Fetching data in batches")
|
||||||
|
(sort-by order-by
|
||||||
|
(filter some?
|
||||||
|
(concat
|
||||||
|
(get-matches-info-batch-lol (get-matches-batch-lol lol-puuid lol-api-key :wait-limit-exceeded wait-limit-exceeded :debug debug :server server :count count :since since :until until)
|
||||||
|
lol-puuid lol-api-key :debug debug :server server :puuid lol-puuid)
|
||||||
|
(get-matches-info-batch-tft (get-matches-batch-tft tft-puuid tft-api-key :wait-limit-exceeded wait-limit-exceeded :debug debug :server server :count count :since since :until until)
|
||||||
|
tft-puuid tft-api-key :debug debug :server server :puuid lol-puuid)
|
||||||
|
(when include-current [(get-lol-current-info lol-puuid :api-key lol-api-key :server server2 :debug debug)])
|
||||||
|
(when include-current [(get-tft-current-info tft-puuid :api-key tft-api-key :server server2 :debug debug)])))))
|
||||||
253
src/riot/data.clj
Normal file
253
src/riot/data.clj
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
|
||||||
|
;; Parsing data
|
||||||
|
|
||||||
|
(ns riot.data
|
||||||
|
(:import [java.time Instant LocalDate ZoneId Duration]
|
||||||
|
[java.time.format DateTimeFormatter])
|
||||||
|
#_{:clj-kondo/ignore [:refer-all]}
|
||||||
|
(:require [clojure.pprint :as pp]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[cheshire.core :refer :all]
|
||||||
|
[riot.core :refer :all]
|
||||||
|
[clj-commons.format.table :refer [print-table] :as table])
|
||||||
|
(:gen-class))
|
||||||
|
|
||||||
|
|
||||||
|
(def DEFAULT_DATE_TIME_FORMAT "yyyy-MM-dd HH:mm:ss")
|
||||||
|
(def DEFAULT_DATE_FORMAT "yyyy-MM-dd")
|
||||||
|
|
||||||
|
|
||||||
|
;; Parse UNIX time
|
||||||
|
|
||||||
|
(defn unix->ZonedDateTime
|
||||||
|
"Convert from UNIX time in millis, to a ZonedDateTime"
|
||||||
|
[millis]
|
||||||
|
(when (some? millis)
|
||||||
|
(.atZone (Instant/ofEpochMilli millis) (ZoneId/systemDefault))))
|
||||||
|
|
||||||
|
(defn unix->LocalDate
|
||||||
|
"Convert from UNIX time in millis to LocalDate"
|
||||||
|
[millis]
|
||||||
|
(when (some? millis)
|
||||||
|
(.toLocalDate (unix->ZonedDateTime millis))))
|
||||||
|
|
||||||
|
(defn LocalDate->Date
|
||||||
|
"Converts a java.time.LocalDate to an ancient java.util.Date"
|
||||||
|
[localdate]
|
||||||
|
(when (some? localdate)
|
||||||
|
(java.util.Date/from (.toInstant (.atZone (.atStartOfDay localdate) (ZoneId/systemDefault))))))
|
||||||
|
|
||||||
|
(defn str->LocalDate
|
||||||
|
"Parse a yyyy-MM-dd date to java.time.LocalDate"
|
||||||
|
[s]
|
||||||
|
(java.time.LocalDate/parse s (DateTimeFormatter/ofPattern DEFAULT_DATE_FORMAT)))
|
||||||
|
|
||||||
|
(defn str->Date
|
||||||
|
[s]
|
||||||
|
(LocalDate->Date (str->LocalDate s)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn unix->Date
|
||||||
|
[millis]
|
||||||
|
(LocalDate->Date (unix->ZonedDateTime millis)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn format-datetime
|
||||||
|
"Converts a LocalDateTime to its string representation"
|
||||||
|
([dtime] (format-datetime dtime DEFAULT_DATE_TIME_FORMAT))
|
||||||
|
([dtime pattern]
|
||||||
|
(when (and (some? dtime) (some? pattern))
|
||||||
|
(.format (DateTimeFormatter/ofPattern pattern) dtime))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn format-datetime-millis
|
||||||
|
"Converts a UNIX time in millis to a string representation"
|
||||||
|
([millis] (format-datetime-millis millis DEFAULT_DATE_TIME_FORMAT))
|
||||||
|
([millis pattern]
|
||||||
|
(when (and (some? millis) (some? pattern))
|
||||||
|
(format-datetime (unix->ZonedDateTime millis) pattern))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; Parse durations
|
||||||
|
(defn format-duration-seconds
|
||||||
|
"Converts a duration in seconds to a string with hours, minutes and seconds"
|
||||||
|
[seconds]
|
||||||
|
(when (some? seconds)
|
||||||
|
(if (< seconds 3600)
|
||||||
|
(format "%02d:%02d" (quot seconds 60) (rem seconds 60))
|
||||||
|
(format "%02d:%02d:%02d"
|
||||||
|
(quot seconds 3600)
|
||||||
|
(- (quot seconds 60) (* 60 (quot seconds 3600)))
|
||||||
|
(rem seconds 60)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn today-millis
|
||||||
|
"Epoch time in millis for 00:00:00 of today"
|
||||||
|
[]
|
||||||
|
(* 1000 (. (. (LocalDate/now) atStartOfDay (ZoneId/systemDefault)) toEpochSecond)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn match-today?
|
||||||
|
"Predicate that is true if the match has been played today.
|
||||||
|
|
||||||
|
A match has been played today if its start date or end date is from today"
|
||||||
|
[match]
|
||||||
|
(or
|
||||||
|
(>= (or (:start match) 0) (today-millis))
|
||||||
|
(>= (or (:end match) 0) (today-millis))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; Parse hashmaps
|
||||||
|
|
||||||
|
(defn with-parsed-dates
|
||||||
|
"Takes the original list of matches and parses dates"
|
||||||
|
[matches & {:keys [datetime-format]
|
||||||
|
:or {datetime-format DEFAULT_DATE_TIME_FORMAT}}]
|
||||||
|
(->> matches
|
||||||
|
(map #(update % :start (fn [x] (format-datetime-millis x datetime-format))))
|
||||||
|
(map #(update % :end (fn [x] (format-datetime-millis x datetime-format))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn with-parsed-durations
|
||||||
|
"Takes the original list of matches and parses durations"
|
||||||
|
[matches]
|
||||||
|
(map #(update % :duration format-duration-seconds) matches))
|
||||||
|
|
||||||
|
|
||||||
|
(defn with-parsed-dates-durations
|
||||||
|
"Takes the original list of matches and parses dates and durations"
|
||||||
|
[matches & {:keys [datetime-format]
|
||||||
|
:or {datetime-format DEFAULT_DATE_TIME_FORMAT}}]
|
||||||
|
(-> matches
|
||||||
|
(with-parsed-durations)
|
||||||
|
(with-parsed-dates :datetime-format datetime-format)))
|
||||||
|
|
||||||
|
|
||||||
|
;;; Export to other formats
|
||||||
|
|
||||||
|
(def headers [[:game-type "Type"]
|
||||||
|
[:start "Start"]
|
||||||
|
[:end "End"]
|
||||||
|
[:duration "Duration"]
|
||||||
|
[:active "Now playing?"]])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn as-json
|
||||||
|
"Take a list with matches and parse it as a JSON string"
|
||||||
|
[matches & {:keys [pretty]
|
||||||
|
:or {pretty true}}]
|
||||||
|
|
||||||
|
(generate-string matches {:pretty pretty}))
|
||||||
|
|
||||||
|
|
||||||
|
(defn as-ascii-table
|
||||||
|
"Export as ascii table"
|
||||||
|
[matches]
|
||||||
|
(pp/print-table matches))
|
||||||
|
|
||||||
|
|
||||||
|
(defn as-pretty-table
|
||||||
|
"Export as an ANSI coloured text table"
|
||||||
|
[matches]
|
||||||
|
(if (and (some? matches) (< 0 (count matches)))
|
||||||
|
(let [columns {:columns (-> (first matches) keys vec)
|
||||||
|
:style table/default-style}]
|
||||||
|
(print-table columns matches))
|
||||||
|
(println "No data available")))
|
||||||
|
|
||||||
|
|
||||||
|
(defn as-csv
|
||||||
|
"Export as CSV"
|
||||||
|
([matches] (as-csv matches ","))
|
||||||
|
([matches separator]
|
||||||
|
(str/join (System/lineSeparator) ; Local EOL characteres
|
||||||
|
(concat
|
||||||
|
[(clojure.string/join separator (map name (keys (first matches))))] ; header
|
||||||
|
(map #(clojure.string/join separator %)
|
||||||
|
(map vals matches))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn calculate-num-days-played
|
||||||
|
[matches]
|
||||||
|
(count (partition-by #(unix->LocalDate (:start %)) matches))
|
||||||
|
)
|
||||||
|
|
||||||
|
(defn calculate-days-played
|
||||||
|
[matches]
|
||||||
|
(map #(.toString %) (sort (keys (group-by #(unix->LocalDate (:start %)) matches)))))
|
||||||
|
|
||||||
|
(defn calculate-total-seconds-played
|
||||||
|
[matches]
|
||||||
|
(reduce + (filter some? (map :duration matches))))
|
||||||
|
|
||||||
|
(defn calculate-seconds-played-per-day
|
||||||
|
[matches]
|
||||||
|
)
|
||||||
|
|
||||||
|
(defn calculate-statistics
|
||||||
|
"Calculate several statistics about the matches"
|
||||||
|
[matches]
|
||||||
|
(when (and (some? matches) (seq matches))
|
||||||
|
(let [matches-lol (filter #(= (:game-type %) "lol") matches)
|
||||||
|
matches-tft (filter #(= (:game-type %) "tft") matches)
|
||||||
|
total (count matches)
|
||||||
|
total-lol (count matches-lol)
|
||||||
|
total-tft (count matches-tft)
|
||||||
|
win (count (filter :winner matches))
|
||||||
|
win-lol (count (filter :winner matches-lol))
|
||||||
|
win-tft (count (filter :winner matches-tft))
|
||||||
|
loss (- total win)
|
||||||
|
loss-lol (- total-lol win-lol)
|
||||||
|
loss-tft (- total-tft win-tft)
|
||||||
|
win-percent (if (zero? total) 0 (* (/ win total) 100.0))
|
||||||
|
win-percent-lol (if (zero? total-lol) 0 (* (/ win-lol total-lol) 100.0))
|
||||||
|
win-percent-tft (if (zero? total-tft) 0 (* (/ win-tft total-tft) 100.0))
|
||||||
|
num-days-played (calculate-num-days-played matches)
|
||||||
|
num-days-played-lol (calculate-num-days-played matches-lol)
|
||||||
|
num-days-played-tft (calculate-num-days-played matches-tft)
|
||||||
|
days-played (calculate-days-played matches)
|
||||||
|
days-played-lol (calculate-days-played matches-lol)
|
||||||
|
days-played-tft (calculate-days-played matches-tft)
|
||||||
|
seconds-played (calculate-total-seconds-played matches)
|
||||||
|
seconds-played-lol (calculate-total-seconds-played matches-lol)
|
||||||
|
seconds-played-tft (calculate-total-seconds-played matches-tft)
|
||||||
|
seconds-per-day (if (zero? num-days-played) 0 (/ seconds-played num-days-played))
|
||||||
|
seconds-per-day-lol (if (zero? num-days-played-lol) 0 (/ seconds-played-lol num-days-played-lol))
|
||||||
|
seconds-per-day-tft (if (zero? num-days-played-tft) 0 (/ seconds-played-tft num-days-played-tft))]
|
||||||
|
{:total {:all total
|
||||||
|
:lol total-lol
|
||||||
|
:tft total-tft}
|
||||||
|
:win {:all win
|
||||||
|
:lol win-lol
|
||||||
|
:tft win-tft}
|
||||||
|
:loss {:all loss
|
||||||
|
:lol loss-lol
|
||||||
|
:tft loss-tft}
|
||||||
|
:win-percent {:all win-percent
|
||||||
|
:lol win-percent-lol
|
||||||
|
:tft win-percent-tft}
|
||||||
|
:num-days-played {:all num-days-played
|
||||||
|
:lol num-days-played-lol
|
||||||
|
:tft num-days-played-tft}
|
||||||
|
:days-played {:all days-played
|
||||||
|
:lol days-played-lol
|
||||||
|
:tft days-played-tft}
|
||||||
|
:time-played {:all (format-duration-seconds seconds-played)
|
||||||
|
:lol (format-duration-seconds seconds-played-lol)
|
||||||
|
:tft (format-duration-seconds seconds-played-tft)}
|
||||||
|
:played-per-day {:all (format-duration-seconds (int seconds-per-day))
|
||||||
|
:lol (format-duration-seconds (int seconds-per-day-lol))
|
||||||
|
:tft (format-duration-seconds (int seconds-per-day-tft))}})))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(calculate-statistics '({:winner true} {:winner false} {:winner true}))
|
||||||
|
|
||||||
|
(let [data '({:winner true} {:winner false} {:winner true})]
|
||||||
|
(pp/print-table [:name :all :lol :tft] (concat (for [k (keys data)] (assoc (k data) :name (name k))))))
|
||||||
|
)
|
||||||
323
src/riot/graph_experiments.clj
Normal file
323
src/riot/graph_experiments.clj
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
(ns riot.graph_experiments
|
||||||
|
(:use riot.data)
|
||||||
|
(:require ;[com.hypirion.clj-xchart :as c]
|
||||||
|
[clojure.pprint :as pprint]
|
||||||
|
[clojure.math :as m]
|
||||||
|
[clojure.core.matrix :as mtx])
|
||||||
|
(:require [incanter.core :as incanter]
|
||||||
|
[incanter.stats :as stats]
|
||||||
|
[incanter.charts :as charts]
|
||||||
|
[incanter.datasets :as datasets])
|
||||||
|
(:require [oz.core :as oz])
|
||||||
|
(:require [tech.viz.vega :as vega]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; ;;;; XCHART
|
||||||
|
|
||||||
|
;; ;; XY chart
|
||||||
|
;; (def xchart_xy
|
||||||
|
;; (c/xy-chart {"Expected rate" [(range 10) (range 10)]
|
||||||
|
;; "Actual rate" [(range 10) (map #(+ % (rand-int 5) -2) (range 10))]}))
|
||||||
|
;; (comment
|
||||||
|
;; (c/view xchart_xy))
|
||||||
|
|
||||||
|
|
||||||
|
;; ;;;; HANAMI
|
||||||
|
;; (comment
|
||||||
|
;; (hc/xform ht/point-chart
|
||||||
|
;; :UDATA "data/cars.json"
|
||||||
|
;; :X "Horsepower" :Y "Miles_per_Gallon" :COLOR "Origin"))
|
||||||
|
|
||||||
|
|
||||||
|
;;;; OZ
|
||||||
|
|
||||||
|
(def test-plot
|
||||||
|
{:data {:values
|
||||||
|
[{:time "18:00" :volume 10}
|
||||||
|
{:time "18:02" :volume 41}
|
||||||
|
{:time "18:07" :volume 192}
|
||||||
|
{:time "18:30" :volume 257}
|
||||||
|
{:time "19:00" :volume 300}]
|
||||||
|
:format {:parse {:time "date:'%H:%M'"}}}
|
||||||
|
:encoding {:x {:field "time" :type "temporal" :timeUnit "hoursminutes"}
|
||||||
|
:y {:field "volume" :type "quantitative"}}
|
||||||
|
:mark "point"})
|
||||||
|
|
||||||
|
;;; to compile and view in Clojure - Oz:
|
||||||
|
(comment
|
||||||
|
(do
|
||||||
|
(println "calling (oz/start-server!)")
|
||||||
|
(oz/start-server!)
|
||||||
|
|
||||||
|
(println "calling (oz/view!)")
|
||||||
|
(oz/view! test-plot)
|
||||||
|
|
||||||
|
(println "calling (Thread/sleep)")
|
||||||
|
(Thread/sleep 5000))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;;;; INCANTER
|
||||||
|
|
||||||
|
;; Simple histogram
|
||||||
|
(comment
|
||||||
|
(incanter/view (charts/histogram (stats/sample-normal 1000))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Boxplot demo
|
||||||
|
(defn create-box-plot [data]
|
||||||
|
(let [box-plot (charts/box-plot data
|
||||||
|
:title "Box Plot Example"
|
||||||
|
:y-label "Values"
|
||||||
|
:x-label "Dataset")]
|
||||||
|
(incanter/view box-plot)))
|
||||||
|
|
||||||
|
(def sample-data [5, 7, 8, 9, 10, 14, 15, 21, 23, 23, 24, 26, 28, 30, 37])
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(create-box-plot sample-data))
|
||||||
|
|
||||||
|
;; Scatter
|
||||||
|
(comment
|
||||||
|
(incanter/view (charts/scatter-plot :Sepal.Length :Sepal.Width
|
||||||
|
:data (datasets/get-dataset :iris)))
|
||||||
|
|
||||||
|
(clojure.pprint/pprint (datasets/get-dataset :iris)))
|
||||||
|
|
||||||
|
|
||||||
|
;; Scatter grouped
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(incanter/view (charts/scatter-plot :Sepal.Length :Sepal.Width
|
||||||
|
:group-by :Species
|
||||||
|
:data (datasets/get-dataset :iris)
|
||||||
|
:legend true)))
|
||||||
|
|
||||||
|
;; Scatter matrix (too complicated)
|
||||||
|
(comment
|
||||||
|
(incanter/view (charts/scatter-plot-matrix
|
||||||
|
(datasets/get-dataset :iris)
|
||||||
|
:nbins 20
|
||||||
|
:group-by :Species))
|
||||||
|
|
||||||
|
(incanter/with-data (datasets/get-dataset :iris) (incanter/view (charts/scatter-plot-matrix :nbins 20 :group-by :Species)))
|
||||||
|
|
||||||
|
(incanter/view (charts/scatter-plot-matrix
|
||||||
|
(datasets/get-dataset :chick-weight)
|
||||||
|
:group-by :Diet
|
||||||
|
:nbins 20)))
|
||||||
|
|
||||||
|
|
||||||
|
;; Heatmaps
|
||||||
|
(comment
|
||||||
|
(defn f [x y] (incanter/sin (incanter/sqrt (mtx/add (incanter/sq x) (incanter/sq y)))))
|
||||||
|
(incanter/view (charts/heat-map f -10 10 -15 15))
|
||||||
|
(incanter/view (charts/heat-map f -10 10 -10 10 :color? false))
|
||||||
|
(incanter/view (charts/heat-map f 5 10 5 10 :include-zero? false))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; Personalize a scatter
|
||||||
|
|
||||||
|
(def my-data '({:start 1751537581404,
|
||||||
|
:end 1751539178158,
|
||||||
|
:duration 1583,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7450441192",
|
||||||
|
:winner true}
|
||||||
|
{:start 1751489335800,
|
||||||
|
:end 1751491099813,
|
||||||
|
:duration 1748,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7450117155",
|
||||||
|
:winner true}
|
||||||
|
{:start 1751486177961,
|
||||||
|
:end 1751487764068,
|
||||||
|
:duration 1568,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7450048068",
|
||||||
|
:winner true}
|
||||||
|
{:start 1751484450321,
|
||||||
|
:end 1751485680510,
|
||||||
|
:duration 1201,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7450013510",
|
||||||
|
:winner true}
|
||||||
|
{:start 1751400380588,
|
||||||
|
:end 1751401545922,
|
||||||
|
:duration 1133,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7449129548",
|
||||||
|
:winner true}
|
||||||
|
{:start 1751398221584,
|
||||||
|
:end 1751399322722,
|
||||||
|
:duration 1020,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7449082553",
|
||||||
|
:winner false}
|
||||||
|
{:start 1751394541765,
|
||||||
|
:end 1751396611534,
|
||||||
|
:duration 2040,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7449005657",
|
||||||
|
:winner false}
|
||||||
|
{:start 1751388187077,
|
||||||
|
:end 1751390143357,
|
||||||
|
:duration 1937,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7448887620",
|
||||||
|
:winner true}
|
||||||
|
{:start 1751384103153,
|
||||||
|
:end 1751385903519,
|
||||||
|
:duration 1767,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7448818616",
|
||||||
|
:winner false}
|
||||||
|
{:start 1751363056183,
|
||||||
|
:end 1751364555063,
|
||||||
|
:duration 1462,
|
||||||
|
:active false,
|
||||||
|
:game-type "lol",
|
||||||
|
:id "EUW1_7448564681",
|
||||||
|
:winner false}))
|
||||||
|
|
||||||
|
(defn extract-date-millis
|
||||||
|
"Extract date from a long epoch timestamp in milliseconds"
|
||||||
|
[x]
|
||||||
|
;(java.util.Date. x)
|
||||||
|
(let [date (new java.util.Date x)]
|
||||||
|
(. (java.util.Date. (. date getYear) (. date getMonth) (. date getDay)) getTime)))
|
||||||
|
|
||||||
|
(defn extract-date
|
||||||
|
"Extract date from a long epoch timestamp in milliseconds"
|
||||||
|
[x]
|
||||||
|
;(java.util.Date. x)
|
||||||
|
(let [date (new java.util.Date x)]
|
||||||
|
(java.util.Date. (. date getYear) (. date getMonth) (. date getDay))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(extract-date-millis 1751363056183)
|
||||||
|
(. (extract-date-millis 1751363056183) getTime)
|
||||||
|
)
|
||||||
|
|
||||||
|
(defn extract-hour
|
||||||
|
"Extract hour from a long epoch timestamp in milliseconds"
|
||||||
|
[x]
|
||||||
|
;(java.util.Date. x)
|
||||||
|
(let [date (new java.util.Date x)]
|
||||||
|
(. date getHours)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(java.util.Date. 1751537581404)
|
||||||
|
(java.util.Date. 1751539178158)
|
||||||
|
(extract-hour 1751537581404)
|
||||||
|
(extract-hour 1751539178158)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn extract-active-hours-millis
|
||||||
|
[match]
|
||||||
|
(when (every? some? [ (:start match) (:end match)])
|
||||||
|
(let [d-ini (extract-date-millis (:start match))
|
||||||
|
d-end (extract-date-millis (:end match))
|
||||||
|
h-ini (extract-hour (:start match))
|
||||||
|
h-end (extract-hour (:end match))
|
||||||
|
game-type (:game-type match)
|
||||||
|
winner (:winner match)]
|
||||||
|
(if (= d-ini d-end)
|
||||||
|
(for [day [d-ini]
|
||||||
|
hour (range h-ini (inc h-end))]
|
||||||
|
[day hour game-type winner])
|
||||||
|
(concat
|
||||||
|
(for [day [d-ini]
|
||||||
|
hour (range h-ini 24)]
|
||||||
|
[day hour game-type winner])
|
||||||
|
(for [day [d-ini]
|
||||||
|
hour (range 0 (inc h-end))]
|
||||||
|
[day hour game-type winner]))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn extract-active-hours
|
||||||
|
[match]
|
||||||
|
(when (every? some? [(:start match) (:end match)])
|
||||||
|
(let [d-ini (extract-date (:start match))
|
||||||
|
d-end (extract-date (:end match))
|
||||||
|
h-ini (extract-hour (:start match))
|
||||||
|
h-end (extract-hour (:end match))
|
||||||
|
game-type (:game-type match)
|
||||||
|
winner (:winner match)]
|
||||||
|
(if (= d-ini d-end)
|
||||||
|
(for [day [d-ini]
|
||||||
|
hour (range h-ini (inc h-end))]
|
||||||
|
[day hour game-type winner])
|
||||||
|
(concat
|
||||||
|
(for [day [d-ini]
|
||||||
|
hour (range h-ini 24)]
|
||||||
|
[day hour game-type winner])
|
||||||
|
(for [day [d-ini]
|
||||||
|
hour (range 0 (inc h-end))]
|
||||||
|
[day hour game-type winner]))))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(extract-active-hours-millis (first my-data))
|
||||||
|
(partition 4 (flatten (map extract-active-hours-millis my-data)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(defn extract-dataset-millis
|
||||||
|
[matches]
|
||||||
|
(incanter/dataset
|
||||||
|
[:days :hours :game-type :winner]
|
||||||
|
(partition 4 (flatten (map extract-active-hours-millis matches)))))
|
||||||
|
|
||||||
|
(defn extract-dataset
|
||||||
|
[matches]
|
||||||
|
(incanter/dataset
|
||||||
|
[:days :hours :game-type :winner]
|
||||||
|
(partition 4 (flatten (map extract-active-hours matches)))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(extract-dataset-millis my-data)
|
||||||
|
(extract-dataset my-data)
|
||||||
|
|
||||||
|
(incanter/view (charts/scatter-plot :days :hours
|
||||||
|
:data (extract-dataset-millis my-data)
|
||||||
|
:group-by :game-type
|
||||||
|
:x-label "Day"
|
||||||
|
:y-label "Hour"
|
||||||
|
:legend false))
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(defn process-my-data [x y] (+ x y (m/random)))
|
||||||
|
|
||||||
|
(incanter/view (charts/heat-map process-my-data
|
||||||
|
-10 10 -15 15
|
||||||
|
:x-label "Day"
|
||||||
|
:y-label "Hour"
|
||||||
|
:z-label "Count"
|
||||||
|
:color? true
|
||||||
|
:include-zero? false))
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;;;; tech.viz
|
||||||
|
(comment
|
||||||
|
(let [ my-graph (vega/scatterplot [{:a 1 :b 2} {:a 2 :b 3}] :a :b)]
|
||||||
|
(vega/vega->svg-file my-graph "timeseries.svg"))
|
||||||
|
)
|
||||||
301
test/riot/core_test.clj
Normal file
301
test/riot/core_test.clj
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
(ns riot.core-test
|
||||||
|
{:clj-kondo/ignore [:unresolved-symbol]}
|
||||||
|
#_{:clj-kondo/ignore [:refer-all]}
|
||||||
|
(:require [clojure.test :refer :all]
|
||||||
|
[riot.test-examples :refer :all]
|
||||||
|
[riot.core :refer :all]
|
||||||
|
[timewords.core :refer [parse]]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
[slingshot.slingshot :refer [try+]]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; Tests encryption and decryption of data
|
||||||
|
(deftest test-encryption
|
||||||
|
(println "* Testing encryption *")
|
||||||
|
(let [orig "secret"
|
||||||
|
enc "Dbokabdn7We5iisgONMlfQZKCjqTaGKmuThA8PaNeO8="]
|
||||||
|
(testing "Cypher a text"
|
||||||
|
(is (= "Dbokabdn7We5iisgONMlfQZKCjqTaGKmuThA8PaNeO8=" (encrypt-data orig))))
|
||||||
|
(testing "Decypher a text"
|
||||||
|
(is (= "secret" (decrypt-data enc))))
|
||||||
|
(testing "Cypher and decypher"
|
||||||
|
(is (= orig (decrypt-data (encrypt-data orig)))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; NOTE: we can't test getting API from environment
|
||||||
|
;; Please, unset LOL_KEY or TFT_KEY from your environment before launch tests
|
||||||
|
(deftest test-get-apis
|
||||||
|
(println "* Testing how to get apis *")
|
||||||
|
(testing "Get LOL API key"
|
||||||
|
(is (= (decrypt-data LOL_KEY) (get-lol-api-key)))
|
||||||
|
(is (= (decrypt-data LOL_KEY) (get-lol-api-key nil)))
|
||||||
|
(is (= "abc" (get-lol-api-key "abc"))))
|
||||||
|
(testing "Get TFT API key"
|
||||||
|
;; (is (= (decrypt-data TFT_KEY) (get-tft-api-key)))
|
||||||
|
;; (is (= (decrypt-data TFT_KEY) (get-tft-api-key nil)))
|
||||||
|
(is (= "abc" (get-tft-api-key "abc")))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Make JSON data
|
||||||
|
(deftest test-make-json-data
|
||||||
|
(println "* Testing make-json-data *")
|
||||||
|
(testing "Default fn-value"
|
||||||
|
(are [exp res]
|
||||||
|
(= (select-keys [:json-key :hash-key] exp) (select-keys [:json-key :hash-key] res))
|
||||||
|
{:json-key "key1" :hash-key "key2"} (make-json-data "key1" "key2")
|
||||||
|
{:json-key :key1 :hash-key :key2} (make-json-data :key1 :key2)
|
||||||
|
{:json-key nil :hash-key nil} (make-json-data nil nil)))
|
||||||
|
(testing "With fn-value"
|
||||||
|
(are [exp res] (= exp res)
|
||||||
|
{:json-key "key1" :hash-key "key2" :fn-value +} (make-json-data "key1" "key2" +)
|
||||||
|
{:json-key :key1 :hash-key :key2 :fn-value +} (make-json-data :key1 :key2 +)
|
||||||
|
{:json-key nil :hash-key nil :fn-value +} (make-json-data nil nil +))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Parse a response
|
||||||
|
(deftest test-parse-response
|
||||||
|
(println "* Parse resonse from a query *")
|
||||||
|
(is (= (select-keys response-player-info [:puuid]) (parse-response player-info-parser response-player-info nil))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Find the index of an element inside a collection
|
||||||
|
(deftest test-pos-in-col
|
||||||
|
(println "* Testin index inside a collection *")
|
||||||
|
(testing "Nil values"
|
||||||
|
(is (= [] (pos-in-col nil "abc")))
|
||||||
|
(is (= [] (pos-in-col ["a" "b" "c"] nil))))
|
||||||
|
(testing "Vectors"
|
||||||
|
(is (= [2] (pos-in-col [3 1 2 4 1 5] 2)))
|
||||||
|
(is (= [1 4] (pos-in-col [3 1 2 4 1 5] 1)))
|
||||||
|
(is (= [] (pos-in-col [3 1 2 4 1 5] 9))))
|
||||||
|
(testing "Secuences"
|
||||||
|
(is (= [2] (pos-in-col (clojure.string/split "3 1 2 4 1 5" #" ") "2")))
|
||||||
|
(is (= [1 4] (pos-in-col (clojure.string/split "3 1 2 4 1 5" #" ") "1")))
|
||||||
|
(is (= [] (pos-in-col (clojure.string/split "3 1 2 4 1 5" #" ") "9"))))
|
||||||
|
(testing "Maps"
|
||||||
|
(is (= [:c] (pos-in-col {:a 3 :b 1 :c 2 :d 4 :e 1 :f 5} 2)))
|
||||||
|
(is (= [:b :e] (pos-in-col {:a 3 :b 1 :c 2 :d 4 :e 1 :f 5} 1)))
|
||||||
|
(is (= [] (pos-in-col {:a 3 :b 1 :c 2 :d 4 :e 1 :f 5} 9)))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Create a URL
|
||||||
|
(deftest test-create-endpoint
|
||||||
|
(println "* Testing create URL for a remote endpoint *")
|
||||||
|
(testing "Simple URL"
|
||||||
|
(are [exp res] (= exp res)
|
||||||
|
"https://test.api.riotgames.com" (create-endpoint "test")
|
||||||
|
"https://.api.riotgames.com" (create-endpoint "")
|
||||||
|
"https://.api.riotgames.com" (create-endpoint nil)))
|
||||||
|
(println "* Testing create URL for a remote endpoint *")
|
||||||
|
(testing "Complex URL"
|
||||||
|
(are [exp res] (= exp res)
|
||||||
|
"https://test.api.riotgames.compath" (create-endpoint "test" "path")
|
||||||
|
"https://test.api.riotgames.com/path" (create-endpoint "test" "/path")
|
||||||
|
"https://.api.riotgames.com" (create-endpoint "" "")
|
||||||
|
"https://.api.riotgames.com" (create-endpoint nil nil)
|
||||||
|
"https://test.api.riotgames.com/path/id?query" (create-endpoint "test" "/path/" "id" "?query"))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Make a raw HTML GET query
|
||||||
|
(deftest test-raw-get-query
|
||||||
|
(println "* Testing get raw data from Riot API *")
|
||||||
|
(testing "Get user data raw"
|
||||||
|
(is (= response-player-info (raw-get-query "https://europe.api.riotgames.com/riot/account/v1/accounts/by-riot-id/Errepunto/4595" :api-key (get-lol-api-key))))
|
||||||
|
(is (some? (raw-get-query "https://europe.api.riotgames.com/lol/match/v5/matches/EUW1_7434497430" :api-key (get-lol-api-key))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Make a query and parse result with a parser
|
||||||
|
(deftest test-query
|
||||||
|
(println "* Testing get parsed data from Riot API *")
|
||||||
|
(testing "Get user data parsed"
|
||||||
|
(is (= (select-keys response-player-info [:puuid]) (query "https://europe.api.riotgames.com/riot/account/v1/accounts/by-riot-id/Errepunto/4595" player-info-parser :api-key (get-lol-api-key))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get PUUID
|
||||||
|
(deftest test-get-puuid-from-name
|
||||||
|
(println "* Get PUUID from user name and tag *")
|
||||||
|
(testing "Get PUUID using lol api"
|
||||||
|
(is (= (:puuid response-player-info) (get-puuid-from-name "Errepunto" "4595" :api-key (get-lol-api-key))))
|
||||||
|
; THROWS EXCEPTION ;(is (= nil (get-puuid-from-name "Not_existing" "" :api-key (decrypt-data LOL_KEY))))
|
||||||
|
))
|
||||||
|
|
||||||
|
(comment (get-puuid-from-name example-name example-tag :api-key (get-tft-api-key)))
|
||||||
|
|
||||||
|
;; From java.util.Date to seconds since 1970
|
||||||
|
(deftest ^:timezone test-date-to-seconds
|
||||||
|
(println "* Date to epoch seconds *")
|
||||||
|
(testing "Some dates"
|
||||||
|
(is (nil? (date-to-seconds nil)))
|
||||||
|
(is (some? (date-to-seconds)))
|
||||||
|
(is (= 1738364400 (date-to-seconds (java.util.Date. 125 1 1))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get parameter for querying data
|
||||||
|
(deftest ^:timezone test-query-params
|
||||||
|
(println "* Generate query params *")
|
||||||
|
(testing "None params"
|
||||||
|
(is (= {"count" 100} (query-params))))
|
||||||
|
(testing "Start and count"
|
||||||
|
(is (= {"count" 5 "start" 2} (query-params :count 5 :start 2))))
|
||||||
|
(testing "Times"
|
||||||
|
(is (= {"count" 100 "startTime" 1743631200} (query-params :since (java.util.Date. 125 3 3))))
|
||||||
|
(is (= {"count" 100 "startTime" 1743631200 "endTime" 1744236000} (query-params :since (java.util.Date. 125 3 3) :until (java.util.Date. 125 3 10))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get LOL matches from PUUID
|
||||||
|
(deftest test-get-lol-matches
|
||||||
|
(println "* Getting LOL matches *")
|
||||||
|
(testing "Famous player"
|
||||||
|
(let [res (get-lol-matches example-lol-puuid :count 5)]
|
||||||
|
(is (seq? res))
|
||||||
|
(is (= 5 (count res)))
|
||||||
|
(is (some? (get-lol-matches example-lol-puuid :since (java.util.Date. 2025 1 1) :until (java.util.Date. 2025 1 31))))))
|
||||||
|
(testing "Never played"
|
||||||
|
(let [res (get-lol-matches example-lol-never-played :count 5)]
|
||||||
|
(is (seq? res))
|
||||||
|
(is (= 0 (count res))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get TFT matches from PUUID
|
||||||
|
;; I NEED A PRODUCTION KEY FIRST
|
||||||
|
(comment
|
||||||
|
(deftest test-get-tft-matches
|
||||||
|
(println "* Getting FTF matches *")
|
||||||
|
(testing "Famous player"
|
||||||
|
(let [res (get-tft-matches example-tft-puuid :count 5)]
|
||||||
|
(is (seq? res))
|
||||||
|
(is (= 5 (count res)))))
|
||||||
|
(testing "Never played"
|
||||||
|
(let [res (get-tft-matches example-tft-never-played :count 5)]
|
||||||
|
(is (seq? res))
|
||||||
|
(is (= 0 (count res)))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get info from one LOL match
|
||||||
|
(deftest test-get-lol-match-info
|
||||||
|
(println "* Getting one LOL match info *")
|
||||||
|
(let [res (get-lol-match-info "EUW1_7434497430" :puuid example-lol-puuid)]
|
||||||
|
(is (some? res))
|
||||||
|
(is (= example-lol-match res))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get info from one TFT match
|
||||||
|
;; I NEED A PRODUCTION KEY FIRST
|
||||||
|
(comment
|
||||||
|
(deftest test-get-tft-match-info
|
||||||
|
(println "* Getting one TFT match info *")
|
||||||
|
(let [res (get-tft-match-info "EUW1_7420994275")]
|
||||||
|
(is (some? res))
|
||||||
|
(is (= example-tft-match res)))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get current LOL playing info
|
||||||
|
;; RESULTS DEPENDS ON PLAYER IS ACTIVE OR NOT
|
||||||
|
(deftest test-get-lol-current-info
|
||||||
|
(println "* Get info from current LOL match *")
|
||||||
|
(let [res (get-lol-current-info example-lol-puuid)]
|
||||||
|
(is (nil? res) (str "Oh, well, the player is playing: " res))) ;; Data only when player is playing.
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; Get current LOL playing info
|
||||||
|
;; RESULTS DEPENDS ON PLAYER IS ACTIVE OR NOT
|
||||||
|
;; I NEED A PRODUCTION KEY FIRST
|
||||||
|
(comment
|
||||||
|
(deftest test-get-tft-current-info
|
||||||
|
(println "* Get info from current TFT match *")
|
||||||
|
(let [res (get-tft-current-info example-tft-puuid)]
|
||||||
|
(is (nil? res) (str "Oh, well, the player is playing: " res))) ;; Data only when player is playing.
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
;; Check if the player is currently in a match
|
||||||
|
;; RESULTS DEPENDS ON PLAYER IS ACTIVE OR NOT
|
||||||
|
;; I NEED A PRODUCTION KEY FIRST
|
||||||
|
(comment
|
||||||
|
(deftest test-is-playing?
|
||||||
|
(println "* Check player is playing *")
|
||||||
|
(let [res (is-playing? example-lol-puuid example-tft-puuid)]
|
||||||
|
(is (not res) (str "Oh, well, the player is playing: " res))) ;; Data only when player is playing.
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
;; Get last LOL and TFT matches
|
||||||
|
;; RESULTS DEPENDS ON PLAYER IS ACTIVE OR NOT
|
||||||
|
(comment
|
||||||
|
(deftest test-get-last-matches
|
||||||
|
(println "* Get last mateches *")
|
||||||
|
(testing "Don't include current"
|
||||||
|
(let [res (is-playing? example-lol-puuid example-tft-puuid :include-current false)]
|
||||||
|
(is (not res) (str "Oh, well, the player is playing: " res))))
|
||||||
|
(testing "Include current"
|
||||||
|
(let [res (is-playing? example-lol-puuid example-tft-puuid)]
|
||||||
|
(is (not res) (str "Oh, well, the player is playing: " res))))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest test-get-matches-batch-lol
|
||||||
|
(println "* Get lol matches batch *")
|
||||||
|
(testing "Invalid data"
|
||||||
|
(is (= nil (get-matches-batch-lol nil nil)))
|
||||||
|
(is (= nil (get-matches-batch-lol example-lol-puuid nil)))
|
||||||
|
(is (= nil (get-matches-batch-lol nil (get-lol-api-key)))))
|
||||||
|
(testing "Get LOL matches"
|
||||||
|
(is (= 10 (count (get-matches-batch-lol example-lol-puuid (get-lol-api-key) :count 5 :max-batches 2))))))
|
||||||
|
|
||||||
|
(deftest ^:tft test-get-matches-batch-tft
|
||||||
|
(println "* Get tft matches batch *")
|
||||||
|
(testing "Invalid data"
|
||||||
|
(is (= nil (get-matches-batch-tft nil nil)))
|
||||||
|
(is (= nil (get-matches-batch-tft example-tft-puuid nil)))
|
||||||
|
(is (= nil (get-matches-batch-tft nil (get-tft-api-key)))))
|
||||||
|
(testing "Get LOL matches"
|
||||||
|
(is (= 10 (count (get-matches-batch-tft example-tft-puuid (get-tft-api-key) :count 5 :max-batches 2))))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest test-get-matches-info-batch-lol
|
||||||
|
(println "* Get lol matches info batch *")
|
||||||
|
(testing "Invalid data"
|
||||||
|
(is (= nil (get-matches-info-batch-lol nil nil nil)))
|
||||||
|
(is (= nil (get-matches-info-batch-lol nil example-lol-puuid nil)))
|
||||||
|
(is (= nil (get-matches-info-batch-lol nil nil (get-lol-api-key)))))
|
||||||
|
(testing "Get LOL matches"
|
||||||
|
(is (= 10 (count (get-matches-info-batch-lol
|
||||||
|
(get-matches-batch-lol example-lol-puuid (get-lol-api-key) :count 5 :max-batches 2)
|
||||||
|
example-lol-puuid
|
||||||
|
(get-lol-api-key)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest ^:tft test-get-matches-info-batch-tft
|
||||||
|
(println "* Get tft matches info batch *")
|
||||||
|
(testing "Invalid data"
|
||||||
|
(is (= nil (get-matches-info-batch-tft nil nil nil)))
|
||||||
|
(is (= nil (get-matches-info-batch-tft nil example-tft-puuid nil)))
|
||||||
|
(is (= nil (get-matches-info-batch-tft nil nil (get-tft-api-key)))))
|
||||||
|
(testing "Get tft matches"
|
||||||
|
(is (= 10 (count (get-matches-info-batch-tft
|
||||||
|
(get-matches-batch-tft example-tft-puuid (get-tft-api-key) :count 5 :max-batches 2)
|
||||||
|
example-tft-puuid
|
||||||
|
(get-tft-api-key)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(def matches-lol (get-matches-batch-lol
|
||||||
|
example-lol-puuid
|
||||||
|
(get-lol-api-key)
|
||||||
|
:since (parse "since 5 days") :debug false :count 100 :max-batches 20))
|
||||||
|
|
||||||
|
(count matches-lol) ;; Todos los obtenidos
|
||||||
|
|
||||||
|
(count (set matches-lol)) ;; Esto cuenta sólo los únicos
|
||||||
|
|
||||||
|
(def matches-info (get-matches-info-batch-lol (take 200 matches-lol) example-lol-puuid (get-lol-api-key) :debug false))
|
||||||
|
|
||||||
|
(count matches-info)
|
||||||
|
(count (set matches-info))
|
||||||
|
|
||||||
|
(try+
|
||||||
|
(get-matches-info-batch example-lol-puuid example-tft-puuid)
|
||||||
|
(catch [:status 404] {:keys [request-time headers body] :as excp}
|
||||||
|
(println "NOT Found 404")
|
||||||
|
(pp/pprint excp))))
|
||||||
179
test/riot/data_test.clj
Normal file
179
test/riot/data_test.clj
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
(ns riot.data-test
|
||||||
|
(:require [clojure.test :refer :all]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
[riot.test-examples :refer :all]
|
||||||
|
[riot.data :refer :all]
|
||||||
|
[riot.core :refer :all])
|
||||||
|
(:import [java.time Instant LocalDate ZoneId Duration ZonedDateTime]
|
||||||
|
[java.time.format DateTimeFormatter]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; Convert from epoch in millis (1970-01-01 00:00:00) to ZonedDateTime
|
||||||
|
(deftest ^:timezone test-unix->ZonedDateTime
|
||||||
|
(println "* Epoch millis to java.util.Datetime *")
|
||||||
|
(testing "Synthetic data"
|
||||||
|
(is (= (ZonedDateTime/of 2025 4 3 0 0 0 0 (ZoneId/systemDefault))
|
||||||
|
(unix->ZonedDateTime 1743631200000)))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest ^:timezone test-unix->LocalDate
|
||||||
|
(println "* Epoch millis to java.time.LocalDate *")
|
||||||
|
(testing "Synthetic data"
|
||||||
|
(is (= (LocalDate/of 2025 4 3) (unix->LocalDate 1743631200000)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; Format a LocalDateTime o ZonedDateTime as a String
|
||||||
|
(deftest ^:timezone test-unix->ZonedDateTime
|
||||||
|
(println "* DateTime to String *")
|
||||||
|
(testing "Null"
|
||||||
|
(is (nil? (format-datetime-millis nil))))
|
||||||
|
(testing "Default format"
|
||||||
|
(is (= "2025-04-03 00:00:00"
|
||||||
|
(format-datetime (unix->ZonedDateTime 1743631200000)))))
|
||||||
|
(testing "Personalized time formats"
|
||||||
|
(is (= "2025-04-03"
|
||||||
|
(format-datetime (unix->ZonedDateTime 1743631200000) "yyyy-MM-dd")))
|
||||||
|
(is (= "00:00"
|
||||||
|
(format-datetime (unix->ZonedDateTime 1743631200000) "HH:00")))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Format a epoch in millis as a String
|
||||||
|
(deftest ^:timezone test-datetime-millis
|
||||||
|
(println "* DateTime to String *")
|
||||||
|
(testing "Null"
|
||||||
|
(is (nil? (format-datetime-millis nil))))
|
||||||
|
(testing "Default format"
|
||||||
|
(is (= "2025-04-03 00:00:00"
|
||||||
|
(format-datetime-millis 1743631200000))))
|
||||||
|
(testing "Personalized time formats"
|
||||||
|
(is (= "2025-04-03"
|
||||||
|
(format-datetime-millis 1743631200000 "yyyy-MM-dd")))
|
||||||
|
(is (= "00:00"
|
||||||
|
(format-datetime-millis 1743631200000 "HH:00")))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Format a duration in seconds to minutes and seconds
|
||||||
|
(deftest test-format-duration-seconds
|
||||||
|
(println "* Duration in seconds to string *")
|
||||||
|
(testing "Nil duration"
|
||||||
|
(is (nil? (format-duration-seconds nil))))
|
||||||
|
(testing "Short durations"
|
||||||
|
(is (= "00:01" (format-duration-seconds 1)))
|
||||||
|
(is (= "01:01" (format-duration-seconds 61)))
|
||||||
|
(is (= "59:59" (format-duration-seconds 3599))))
|
||||||
|
(testing "Long durations"
|
||||||
|
(is (= "01:00:00" (format-duration-seconds 3600)))
|
||||||
|
(is (= "01:00:01" (format-duration-seconds 3601)))
|
||||||
|
(is (= "01:01:01" (format-duration-seconds 3661)))
|
||||||
|
(is (= "05:53:09" (format-duration-seconds 21189)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; Epoch in millis for today at 00:00:00 hours. Difficult to test
|
||||||
|
(deftest ^:timezone test-today-millis
|
||||||
|
(println "* Epoch for today *")
|
||||||
|
(testing "Epoch for today"
|
||||||
|
(is (some? (today-millis)))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Match has been played today
|
||||||
|
(deftest test-match-today?
|
||||||
|
(println "* Has the match been played today? *")
|
||||||
|
(testing "Start date today"
|
||||||
|
(is (match-today? {:start (today-millis)}))
|
||||||
|
(is (match-today? {:start (+ 5000 (today-millis))})))
|
||||||
|
(testing "End date today"
|
||||||
|
(is (match-today? {:end (today-millis)}))
|
||||||
|
(is (match-today? {:end (+ 5000 (today-millis))}))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Parse dates inside a lis of matches
|
||||||
|
(deftest ^:timezone test-with-parsed-dates
|
||||||
|
(println "* Parse dates in a match list*")
|
||||||
|
(testing "Empty or invalid lists"
|
||||||
|
(is (= '() (with-parsed-dates '())))
|
||||||
|
(is (= '({:start nil, :end nil}) (with-parsed-dates '({}))))
|
||||||
|
(is (= '({:a "AAA" :b 2 :start nil, :end nil}) (with-parsed-dates '({:a "AAA" :b 2})))))
|
||||||
|
(testing "Valid list"
|
||||||
|
(is (= '({:start "2025-06-11 19:00:54", :end nil, :duration 1288, :active true, :game-type "lol", :id "EUW1_111111"})
|
||||||
|
(with-parsed-dates (take 1 matches-example))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Parse durations
|
||||||
|
(deftest ^:timezone test-with-parsed-durations
|
||||||
|
(println "* Parse durations in a match list *")
|
||||||
|
(testing "Empty or invalid lists"
|
||||||
|
(is (= '() (with-parsed-durations '())))
|
||||||
|
(is (= '({:duration nil}) (with-parsed-durations '({}))))
|
||||||
|
(is (= '({:a "AAA" :b 2 :duration nil}) (with-parsed-durations '({:a "AAA" :b 2})))))
|
||||||
|
(testing "Valid list"
|
||||||
|
(is (= '({:start 1749661254854, :end nil, :duration "21:28", :active true, :game-type "lol", :id "EUW1_111111"})
|
||||||
|
(with-parsed-durations (take 1 matches-example))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Parse dates and durations
|
||||||
|
(deftest ^:timezone test-with-parsed-dates-durations
|
||||||
|
(println "* Parse dates and durations in a match list *")
|
||||||
|
(testing "Empty or invalid lists"
|
||||||
|
(is (= '() (with-parsed-dates-durations '())))
|
||||||
|
(is (= '({:duration nil, :start nil, :end nil})
|
||||||
|
(with-parsed-dates-durations '({}))))
|
||||||
|
(is (= '({:a "AAA" :b 2 :duration nil, :start nil, :end nil})
|
||||||
|
(with-parsed-dates-durations '({:a "AAA" :b 2})))))
|
||||||
|
(testing "Valid list"
|
||||||
|
(is (= '({:start "2025-06-11 19:00:54", :end nil, :duration "21:28", :winner true, :game-type "lol", :id "EUW1_111111", :result "GameComplete"})
|
||||||
|
(with-parsed-dates-durations (take 1 matches-example))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Pretty table
|
||||||
|
(deftest test-as-pretty-table
|
||||||
|
(println "* Generates a pretty table *")
|
||||||
|
(testing "Some examples, printed to System.out"
|
||||||
|
(is (= nil (as-pretty-table [])))
|
||||||
|
(is (= nil (as-pretty-table nil)))
|
||||||
|
(is (= nil (as-pretty-table matches-example)))
|
||||||
|
(is (= nil (as-pretty-table matches-example2)))))
|
||||||
|
|
||||||
|
|
||||||
|
(deftest test-calculate-statistics
|
||||||
|
(println "* Calculate statistics *")
|
||||||
|
(testing "Simple check"
|
||||||
|
(is (nil? (calculate-statistics nil)))
|
||||||
|
(is (nil? (calculate-statistics '())))
|
||||||
|
(is (map? (calculate-statistics matches-example)))
|
||||||
|
(is (map? (calculate-statistics matches-example2))))
|
||||||
|
(let [data (calculate-statistics matches-example)]
|
||||||
|
(testing "With manual generated data"
|
||||||
|
(is (= 5 (get-in data [:total :all])))
|
||||||
|
(is (= 3 (get-in data [:total :lol])))
|
||||||
|
(is (= 2 (get-in data [:total :tft])))
|
||||||
|
(is (= 3 (get-in data [:win :all])))
|
||||||
|
(is (= 2 (get-in data [:loss :all])))
|
||||||
|
(is (= 60.0 (get-in data [:win-percent :all])))
|
||||||
|
(is (= 3 (get-in data [:num-days-played :all]))))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
matches-example
|
||||||
|
|
||||||
|
(.toLocalDate (unix->ZonedDateTime 1749661254854))
|
||||||
|
|
||||||
|
(partition-by #(unix->LocalDate (:start %)) matches-example)
|
||||||
|
(group-by #(unix->LocalDate (:start %)) matches-example)
|
||||||
|
|
||||||
|
(keys (group-by #(unix->LocalDate (:start %)) matches-example2))
|
||||||
|
|
||||||
|
(calculate-days-played matches-example2)
|
||||||
|
|
||||||
|
(reduce + (filter some? (map :duration matches-example)))
|
||||||
|
|
||||||
|
(calculate-statistics matches-example)
|
||||||
|
|
||||||
|
(calculate-statistics matches-example2)
|
||||||
|
|
||||||
|
(pp/pprint (calculate-statistics matches-example2))
|
||||||
|
)
|
||||||
3129
test/riot/test_examples.clj
Normal file
3129
test/riot/test_examples.clj
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
|||||||
#kaocha/v1
|
|
||||||
{:tests [{:test-paths ["projects/core/src" "projects/core/test"]}]
|
|
||||||
:plugins [:kaocha.plugin/cloverage]
|
|
||||||
:cloverage/opts {:src-ns-path ["projects/core/src" "projects/core/test"]
|
|
||||||
:ns-regex ["tmpl\\..*(?<!test)$"] ;; All starting with "totp" but not ending by "test"
|
|
||||||
}}
|
|
||||||
Reference in New Issue
Block a user