Compare commits
26 Commits
0c2c5954ba
...
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,9 +16,9 @@ jobs:
|
||||
- name: Install java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
distribution: "temurin"
|
||||
java-version: "21"
|
||||
|
||||
# Install Leiningen
|
||||
- name: Install Leiningen
|
||||
run: |
|
||||
@@ -29,15 +29,15 @@ jobs:
|
||||
# Install dependencies
|
||||
- name: Install dependencies
|
||||
run: lein deps
|
||||
|
||||
|
||||
# Optional: cache dependencies
|
||||
- name: Cache dependencias
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/project.clj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-m2-
|
||||
#- name: Cache dependencias
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: ~/.m2/repository
|
||||
# key: ${{ runner.os }}-m2-${{ hashFiles('**/project.clj') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-m2-
|
||||
|
||||
# Get leiningen's version
|
||||
- name: Get leiningen version
|
||||
@@ -46,14 +46,15 @@ jobs:
|
||||
# Test the code
|
||||
- name: Run tests
|
||||
env:
|
||||
TFT_API: ${{ secrets.DEV_API }}
|
||||
TFT_API: ${{ secrets.DEV_API }}
|
||||
run: lein test
|
||||
|
||||
# Send jar to repository
|
||||
- name: Deploy on Gitea Maven
|
||||
if: github.ref == 'refs/heads/main'
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
env:
|
||||
GITEA_USER: ${{ secrets.DEPLOY_USER }}
|
||||
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
|
||||
run: |
|
||||
lein deploy gitea
|
||||
lein deploy gitea
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -15,4 +15,6 @@ pom.xml.asc
|
||||
.lsp
|
||||
.calva
|
||||
*.svg
|
||||
/logs
|
||||
/logs/*
|
||||
log.txt
|
||||
|
||||
|
||||
181
README.md
181
README.md
@@ -1,39 +1,88 @@
|
||||
# riot-clojure
|
||||
|
||||
Riot Games provides a lot of nice endpoints with a lot of data for all their games.
|
||||
This projects provides a library to access those data from Clojure and a small
|
||||
application to quick get some relevant data.
|
||||
This is a simple tool for using Riot Games public API. Those APIs are HUGE and
|
||||
provide a lot of information, but this tool is focused in the time data of matches.
|
||||
|
||||
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
|
||||
will use external libraries compatible with it.
|
||||
This simple program needs two API keys: one for LOL and another one for TFT. You
|
||||
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
|
||||
group of endpoints, secured with an API. Development APIs can access to all
|
||||
andpoints, but they must be refreshed every 24 hours.
|
||||
## Features
|
||||
|
||||
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
|
||||
has optional parameters that override the environment variables.
|
||||
* Call the API with development or production keys
|
||||
* 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 |
|
||||
| ---------- | -------------------- | ------------- |
|
||||
| all | `RIOT_DEV_KEY` | `--dev-key` |
|
||||
| LOL | `RIOT_LOL_KEY` | `--lol-key` |
|
||||
| TFT | `RIOT_TFT_KEY` | `--tft-key` |
|
||||
*more to came*
|
||||
## Goals
|
||||
|
||||
# Usage
|
||||
## CLI
|
||||
### Installation
|
||||
Goals for version 1.1:
|
||||
|
||||
* [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.
|
||||
|
||||
### Example
|
||||
## Usage
|
||||
|
||||
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.
|
||||
|
||||
### Options
|
||||
## Options
|
||||
|
||||
Run the application without params or wich `-?` param to show all options:
|
||||
|
||||
$ ./riot -?
|
||||
|
||||
### Tipycal usages
|
||||
## Examples
|
||||
|
||||
Show all matches from a march the 1st (in ISO format):
|
||||
|
||||
@@ -63,45 +112,71 @@ Get te same data in CSV and store it in a file:
|
||||
Don't format durations, show them in seconds
|
||||
|
||||
$ ./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
|
||||
I have learn in the last months. The objetive for this version is to implement
|
||||
all functionality provided by v1.1.
|
||||
#### Simple day
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
* [ ] Reestructure all project to use `deps.edn`, `tools.clj` and subprojects
|
||||
* [ ] Use babashka compatible libraries and `bb.edn`
|
||||
* [ ] Gitea actions for automatic test executing
|
||||
* [ ] Distribute CLI as self executable (bb, jlink or graal)
|
||||
#### Day and hour
|
||||
|
||||
### 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.
|
||||
|
||||
* [ ] Read tokens from environment and a function to override them manually
|
||||
* [ ] Fuctions for calling Riot API and read result as clojure data structures (EDN format)
|
||||
* [ ] Get [PUUID](https://developer.riotgames.com/docs/lol#summoner-names-to-riot-ids_obtaining-puuid-and-summonerid-from-riotid) for a player
|
||||
* [ ] Get basic player info
|
||||
* [ ] Format raw data
|
||||
```
|
||||
23 |
|
||||
22 |
|
||||
21 |
|
||||
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
|
||||
* [ ] 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?
|
||||
* [ ] Format output as fancy table
|
||||
* [ ] Format output as CSV
|
||||
* [ ] Calculte statistics
|
||||
## Interesting graphic and UI libraries
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
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/)
|
||||
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})
|
||||
|
||||
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
Reference in New Issue
Block a user