Compare commits

..

4 Commits

Author SHA1 Message Date
e9064f9b61 Update README.md 2025-07-15 19:41:28 +02:00
33410e68ed Reestrcture and new graph library 2025-07-15 19:17:51 +02:00
9cf756ffbd Update README.md
Added idea for charts
2025-07-08 15:46:23 +02:00
28720bfcd5 graph tests 2025-07-07 21:55:07 +02:00
12 changed files with 879 additions and 689 deletions

View File

@@ -1,59 +0,0 @@
name: Compile and test using leiningen
run-name: ${{ gitea.actor }} testing the code
on: [push]
jobs:
clojure:
name: Run tests
runs-on: ubuntu-latest
steps:
# Checkouts code
- name: Checkout
uses: actions/checkout@v3
# Install Java
- name: Install java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
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
- 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
run: lein -v
# Test the code
- name: Run tests
env:
TFT_API: ${{ secrets.DEV_API }}
run: lein test
# Send jar to repository
- name: Deploy on Gitea Maven
if: github.ref == 'refs/heads/main'
env:
GITEA_USER: ${{ secrets.DEPLOY_USER }}
GITEA_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: |
lein deploy gitea

2
.gitignore vendored
View File

@@ -14,5 +14,3 @@ pom.xml.asc
.clj-kondo .clj-kondo
.lsp .lsp
.calva .calva
*.svg
/logs

132
README.md
View File

@@ -1,53 +1,22 @@
# riot-clojure # riot-clojure
This is a simple tool for using Riot Games public API. Those APIs are HUGE and 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. privide a lot of information, but this tool is focused in the time data of matches.
The objetive of this tool is extract the playing time of one player. Taking the 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 starting and ending timestams we can create a chronogram with the time spent
playing. playing.
This simple program needs two API keys: one for LOL and another one for TFT. You This simple program needs a developer API. You can obtain a key from https://developer.riotgames.com/
can obtain a developer key from https://developer.riotgames.com/. Developer keys Developer keys are valid for 24 hours only, but you can refresh the key easily
are valid for 24 hours only, but you can refresh the key easily from the developer's from the developer's web site.
web site.
By default, the application searchs for the API key in the environment variables By default, the application searchs for the API key in the environment variable
`LOL_API` and `TFT_API`. If you prefer to provide the key via parameter, you must use `--lol-api-key` or `RIOT_API`. If you prefer to provide the key via parameter, you must use `-k KEY` or
`--tft-api-key` parameters. `--key=KEY`.
## Features
Those features are implemented in v1.1
* 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
## Goals ## Goals
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: Goals of the project, ordered from easiest to hardest:
* [x] Take API key from environment * [x] Take API key from environment
@@ -70,7 +39,7 @@ Goals of the project, ordered from easiest to hardest:
* [x] Win / Loss match * [x] Win / Loss match
* [x] Distribution as uberjar * [x] Distribution as uberjar
* [x] Distribution as native Linux executable * [x] Distribution as native Linux executable
* [x] Unit tests: move "comment" manual tests to real unit tests * [ ] Unit tests: move "comment" manual tests to real unit tests
* [ ] Output as a graphical chronogram or calendar. * [ ] Output as a graphical chronogram or calendar.
* [ ] Simple web server * [ ] Simple web server
* [ ] Universal player search * [ ] Universal player search
@@ -80,39 +49,88 @@ Goals of the project, ordered from easiest to hardest:
## Installation ## Installation
Download release from https://git.rcorral.es/ruben/riot-clojure/releases. Download from http://example.com/FIXME.
## Usage ## Usage
Using the Java uberjar FIXME: explanation
$ java -jar riot-clojure-0.1.0-standalone.jar [args] $ java -jar riot-clojure-0.1.0-standalone.jar [args]
Using the linux native image:
$ ./riot [args]
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: FIXME: listing of options this app accepts.
$ ./riot -?
## Examples ## Examples
Show all matches from a march the 1st (in ISO format): ...
$ ./riot t <username> <tag> -s "2025-03-01" ### Bugs
Get te same data in CSV and store it in a file: ...
$ ./riot t <username> <tag> -s "2025-03-01" -o csv > results.csv ### Graphs
Don't format durations, show them in seconds Posible ASCII art charts
$ ./riot t <username> <tag> -s "2025-03-01" --no-format-durations #### Simple day
A one dimension diagram. Draw a tick each day the player has played
```
####### ### ###### ###########
------------------------------------------------------------------------------------------
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
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
```
#### Day and hour
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.
```
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
```
## 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

View File

@@ -1,27 +1,34 @@
(defproject riot-clojure "1.1.0" (defproject riot-clojure "1.0.0"
:description "Utility for getting for Riot APIs in Clojure" :description "Utility for getting for Riot APIs in Clojure"
:url "https://git.rcorral.es/ruben/riot-clojure" :url "https://git.rcorral.es/ruben/riot-clojure"
:license {:name "MIT" :license {:name "MIT"
:url "https://mit-license.org/"} :url "https://mit-license.org/"}
:dependencies [[org.clojure/clojure "1.11.1"] :dependencies [[org.clojure/clojure "1.11.1"] ;; Clojure itself
[clj-http/clj-http "2.0.0"] [clj-http/clj-http "2.0.0"] ;; HTTP client
[cheshire/cheshire "6.0.0"] [cheshire/cheshire "6.0.0"] ;; Convert to/from JSON
[slingshot/slingshot "0.12.2"] [slingshot/slingshot "0.12.2"] ;; Better exceptions for clj-http
[org.clojure/tools.cli "1.1.230"] ;; [org.clojure/tools.cli "1.1.230"] ;; Parse params. NOT USED
[cli-matic/cli-matic "0.5.4"] ;; https://github.com/l3nz/cli-matic [cli-matic/cli-matic "0.5.4"] ;; Parse params and make a nice CLI app
[buddy/buddy-core "1.12.0-430"] [buddy/buddy-core "1.12.0-430"] ;; Cipher data
[org.clojure/tools.namespace "1.5.0"] [org.clojure/tools.namespace "1.5.0"] ;; Hot reload from REPL
[lt.tokenmill/timewords "0.5.0"] ;; https://github.com/tokenmill/timewords [lt.tokenmill/timewords "0.5.0"] ;; Parse dates and times in natural languaje
[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 ;;; LIBS FOR GRAPHS, SOME OF THEM WILL BE REMOVED IN THE FUTURE
[org.slf4j/slf4j-api "2.0.17"] ;; https://www.slf4j.org/
[ch.qos.logback/logback-classic "1.5.18"]] ;[com.hypirion/clj-xchart "0.2.0"] ;; Graphs and charts
:jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory"] ;[aerial.hanami "0.15.1"] ;; Parse vega-lite data and generate graphics
;[folcon/oz "1.6.0-alpha6.2"] ;; Parse vega and vega-lite
:plugins [[io.taylorwood/lein-native-image "0.3.1"]] [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 :main ^:skip-aot riot.app
:target-path "target/%s" :target-path "target/%s"
@@ -32,22 +39,4 @@
:profiles {:uberjar {:aot :all :profiles {:uberjar {:aot :all
:jvm-opts ["-Dclojure.compiler.direct-linking=true"]} :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}
:native-image {:jvm-opts ["-Dclojure.compiler.direct-linking=true"]} :native-image {:jvm-opts ["-Dclojure.compiler.direct-linking=true"]}
:dev {:dependencies [[org.clojure/test.check "1.1.1"]] :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}}
;; Test selectors
:test-selectors {:default (complement (some-fn :tft :timezone))
:tft :tft
:timezone :timezone})

View File

@@ -1,36 +0,0 @@
<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>

View File

@@ -6,9 +6,7 @@
[cli-matic.utils :as U] [cli-matic.utils :as U]
[clojure.pprint :as pp] [clojure.pprint :as pp]
[clojure.string :as str] [clojure.string :as str]
[timewords.core :refer [parse]] [timewords.core :refer [parse]])
[clojure.tools.logging :as log]
)
(:use [riot.core] (:use [riot.core]
[riot.data] [riot.data]
[slingshot.slingshot :only [try+]]) [slingshot.slingshot :only [try+]])
@@ -24,141 +22,179 @@
(some? puuid) puuid (some? puuid) puuid
(and (some? name) (some? tag)) (get-puuid-from-name name tag :api-key api-key))) (and (some? name) (some? tag)) (get-puuid-from-name name tag :api-key api-key)))
(comment
(get-puuid-from-params (get-lol-api-key) [] :puuid "annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ")
(get-puuid-from-params (get-lol-api-key) [] :name "Errepunto" :tag "4595")
(get-puuid-from-params (get-tft-api-key) [] :puuid "annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ")
(get-puuid-from-params (get-tft-api-key) [] :name "Errepunto" :tag "4595")
)
#_{:clj-kondo/ignore [:unresolved-symbol]}
(defn cmd-active (defn cmd-active
"Checks if a player is online at the moment" "Checks if a player is online at the moment"
[& {:keys [lol-api-key tft-api-key debug-http lol tft _arguments] [& {:keys [lol-api-key tft-api-key debug-http _arguments]
:as opts}] :as opts}]
(try+ (try+
;(println "Check online" opts) ;(println "Check online" opts)
(let [lol-key (when lol (get-lol-api-key lol-api-key)) (let [lol-key (get-lol-api-key lol-api-key)
tft-key (when tft (get-tft-api-key tft-api-key)) tft-key (get-tft-api-key tft-api-key)
lol-id (when lol (get-puuid-from-params lol-key _arguments opts)) lol-id (get-puuid-from-params lol-key _arguments opts)
tft-id (when tft (get-puuid-from-params tft-key _arguments opts))] tft-id (get-puuid-from-params tft-key _arguments opts)]
(if (and (nil? lol-id) (nil? tft-id)) ;(println "LOL API key" lol-key "TFT API key" tft-key)
(println "No games to check") (if (or (nil? lol-id) (nil? tft-id))
(U/exit! "Invalid params" 2)
(if (is-playing? lol-id tft-id :lol-api-key lol-key :tft-api-key tft-key :debug debug-http) (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) (println "Yes, it's playing")
(do (println "No, it's not playing right now") false)))) (println "No, it's not playing right now"))))
(catch [:status 401] _ (catch [:status 401] _
(U/exit! "Invalid API key" 3)) (U/exit! "Invalid API key" 3))
(catch [:status 404] _ (catch [:status 404] _
(U/exit! "Unknown user or tag" 5)))) (U/exit! "Unknown user or tag" 5))))
(comment
(cmd-active :_arguments ["annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ"])
(cmd-active :_arguments ["Errepunto" "4595"])
)
(defn format-result
(defn format-map
"Format result" "Format result"
[output x] [output x]
(case output (case output
"table" (as-ascii-table x)
"ptable" (as-pretty-table x)
"edn" (pp/pprint x) "edn" (pp/pprint x)
"json" (println (as-json x)) "json" (println (as-json x))
"table" (as-ascii-table x)
"csv" (println (as-csv x)) "csv" (println (as-csv x))
(pp/pprint x))) ;edn as default (pp/pprint x))) ;edn as default
(comment
(format-map "json" riot.test-examples/matches-example)
(format-map "csv" riot.test-examples/matches-example2))
(defn format-dates-cond (defn format-dates-cond
"Format dates and durations if specified" [format-dates format-durations pattern x]
[format-dates pattern x] (let [dur (if format-durations
(with-parsed-durations x)
x)]
(if format-dates (if format-dates
(if (some? pattern) (if (some? pattern)
(with-parsed-dates x :datetime-format pattern) (with-parsed-dates dur :datetime-format pattern)
(with-parsed-dates x)) (with-parsed-dates dur))
x)) dur)))
(comment
(format-dates-cond false false nil riot.test-examples/matches-example2)
(format-dates-cond false true nil riot.test-examples/matches-example2)
(format-dates-cond true false nil riot.test-examples/matches-example2)
(format-dates-cond true true nil riot.test-examples/matches-example2))
(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 (defn reverse-cond
"If true, data is ordered from nearest to fartest"
[rev x] (if rev x (reverse x))) [rev x] (if rev x (reverse x)))
(defn filter-today-cond
[only-today x] (if only-today (filter match-today? x) x))
(defn filter-columns (defn filter-columns
"Filter columns to show. With 'all' shows all columns"
[columns x] (if (= "all" columns) [columns x] (if (= "all" columns)
x x
(map #(select-keys % (map keyword (str/split columns #","))) x))) (map #(select-keys % (map keyword (str/split columns #","))) x)))
(comment
(filter-columns "all" riot.test-examples/matches-example2)
(filter-columns "start,end" riot.test-examples/matches-example2)
(map #(select-keys % (map keyword (str/split "start,end" #","))) riot.test-examples/matches-example2))
(defn get-date (defn get-date
"Converts an string into a java.util.Date"
[x] [x]
(if (nil? x) nil (parse x))) (if (nil? x) nil (parse x)))
#_{:clj-kondo/ignore [:unresolved-symbol]}
(defn cmd-timeline (defn cmd-timeline
"Get all matches" "Get match "
[& {: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] [& {:keys [lol-api-key tft-api-key output count max-total format-dates format-durations date-format reverse order-by show-columns show-active debug-http only-today since until _arguments]
:as opts}] :as opts}]
(try+ (try+
;(println "Get timeline" opts) ;(println "Get timeline" opts)
(let [lol-key (when lol (get-lol-api-key lol-api-key)) (let [lol-key (get-lol-api-key lol-api-key)
tft-key (when tft (get-tft-api-key tft-api-key)) tft-key (get-tft-api-key tft-api-key)
lol-id (when lol (get-puuid-from-params lol-key _arguments opts)) lol-id (get-puuid-from-params lol-key _arguments opts)
tft-id (when tft (get-puuid-from-params tft-key _arguments opts)) tft-id (get-puuid-from-params tft-key _arguments opts)]
since-date (get-date since) ;(println "LOL API key" lol-key "TFT API key" tft-key)
until-date (get-date until)] (if (or (nil? lol-id) (nil? tft-id))
(log/info (str "Fetching data between " since-date " and " (if (some? until-date) until-date (java.util.Date.)))) (U/exit! "Invalid params" 2)
(if (and (nil? lol-id) (nil? tft-id)) (->> (get-last-matches lol-id tft-id :lol-api-key lol-key :tft-api-key tft-key :count count :include-current show-active :debug debug-http :order-by (keyword order-by) :since (get-date since) :until (get-date until))
(U/exit! "No games selected" 2) (filter-today-cond only-today)
(->> (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) (reverse-cond reverse)
(format-dates-cond format-dates date-format) (take count)
(format-duration-cond format-durations) (format-dates-cond format-dates format-durations date-format)
(filter-columns show-columns) (filter-columns show-columns)
(format-result output)))) (map )
(format-map output))))
(catch [:status 401] _ (catch [:status 401] _
(U/exit! "Invalid API key" 3)) (U/exit! "Invalid API key" 3))
(catch [:status 404] _ (catch [:status 404] _
(U/exit! "Unknown user or tag" 5)) (U/exit! "Unknown user or tag" 5))
(catch [:status 429] _ (catch [:status 429] _
(U/exit! "Rate limit exceeded, please wait some minutes and try again" 4)) (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)) (comment
(catch [:status 503] _ (cmd-timeline :_arguments ["dMNR4Aj5OW9jrGj0RUVBRuzfu77p3iO5y1W16ASNp1PI7pxuJWLz14b2pJiUn16DCPlyeREoi0MJ7Q"])
(U/exit! "Server timeout, please wait some minutes and try again" 6)))) (cmd-timeline :_arguments ["Sarinailo" "EUW"]))
(defn cmd-bulk
"Get all matches"
[& {:keys [lol-api-key tft-api-key output format-dates format-durations date-format reverse order-by show-columns debug-http since until _arguments]
:as opts}]
(try+
;(println "Get timeline" opts)
(let [lol-key (get-lol-api-key lol-api-key)
tft-key (get-tft-api-key tft-api-key)
lol-id (get-puuid-from-params lol-key _arguments opts)
tft-id (get-puuid-from-params tft-key _arguments opts)]
;(println "LOL API key" lol-key "TFT API key" tft-key)
(if (or (nil? lol-id) (nil? tft-id))
(U/exit! "Invalid params" 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 (get-date since) :until (get-date until))
(reverse-cond reverse)
(format-dates-cond format-dates format-durations date-format)
(filter-columns show-columns)
(format-map 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 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 ;; More info: https://github.com/l3nz/cli-matic/blob/master/README.md
(def CONFIGURATION (def CONFIGURATION
{:app {:command "riot" {:app {:command "riot"
:version "1.1.0" :description ["Get how much time have you spent on LoL or FTF"
:description ["Get how much you play your favourite games"
"" ""
"EXAMPLE USAGES:" "Posible error codes:"
" Get all matches since March 1st 2025:" " 0: OK"
" riot timeline <your_player_name> <your_tag> -s 2025-03-01" " 1: player not playing"
"" " 2: Invalid params"
" Get all matches as CSV, don't format durations:" " 3: Invalid API key, please, get an updated one"
" riot timeline <your_player_name> <your_tag> -s 2025-03-01 -o csv --no-format-durations > my_matches.csv" " 4: Rate limit exceeded, please wait some minutes and try again"
"" " 5: Unknown user and tag"
"ERROR CODES:" " 6: Server error. Please, try again"]
" 0: OK, no error" :version "1.0.0"}
" 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" :global-opts [{:option "lol-api-key"
:as "API key for LOL data" :as "API key for LOL data"
:type :string :default nil :type :string :default nil
@@ -170,49 +206,114 @@
{:option "debug-http" {:option "debug-http"
:as "Show debug for HTTP connections. I'ts very verbose!" :as "Show debug for HTTP connections. I'ts very verbose!"
:type :flag :type :flag
:default false} :default false}]
;; {:option "log-level"
;; :as "Log level (debug, info, warn, etc)"
;; :type :keyword
;; :default :info}
]
:commands [{:command "active" :short "a" :commands [{:command "active" :short "a"
:description ["Shows if the player is currently playing" :description ["Shows if the player currently playing"
"By default only shows LOL matches"] "You can search by PUUID or name and tagline, but not both."
: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."] "You can set the PUUID passing only one argument or using -p param."
:opts [{:option "lol" "You can set the name and tag passing two arguments, or using -n and -t."]
:type :with-flag :default true :opts [{:option "puuid" :short "p"
:as "Include LOL matches"} :type :string
{:option "tft" :as "Summonner's unique PUUID."}
{:option "name" :short "n"
:type :string :default nil
:as "Summoner name (Riot ID)"}
{:option "tag" :short "t"
:type :string :default nil
:as "Summoner tagline"}]
:runs cmd-active}
{:command "last" :short "l"
:description ["Gets time info from last matches."
"You can search by PUUID or name and tagline, but not both."
""
"You can set the PUUID passing only one argument or using -p param."
"You can set the name and tag passing two arguments, or using -n and -t."]
:opts [{:option "puuid" :short "p"
:type :string
:as "Summonner's unique PUUID."}
{:option "name" :short "n"
:type :string :default nil
:as "Summoner name (Riot ID)"}
{:option "tag" :short "t"
:type :string :default nil
:as "Summoner tagline"}
{:option "output" :short "o"
:type #{"edn" "json" "table" "csv"} :default "table"
:as ["Output type. It can be one of:"
" - edn: Clojure's internal EDN format"
" - json: Classic JSON"
" - table: ASCII table (by default)"
" - csv: CSV data, using ',' as field separator"]}
{:option "count" :short "c"
:type :int :default 10
:as "Max number of matches retrieved for each type of game (max value: 100)"}
;{:option "max-total" :short "m"
; :type :int :default 10
; :as "Max number of matches of all types, including current match"}
{:option "order-by"
:type #{"start" "end" "duration"} :default "start"
:as "Order by field"}
{:option "reverse" :short "r"
:type :with-flag :default false :type :with-flag :default false
:as "Include TFT matches. Experimental"} :as "If true, newer matches are shown last in list"}
{:option "show-active" :short "a"
:type :with-flag :default true
:as "Include current playing match"}
{: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"
" - active: Match is in course"
" - game-type: lol or tft"
" - id: Game unique id"
" - winner: Winner of the match"]}
{:option "only-today"
:type :flag :default false
:as "If true, only shows today played matches"}
{:option "since" :short "s" {:option "since" :short "s"
:type :string :default "today" :type :string :default nil
:as ["Starting date (included) in 'yyyy-MM-dd' format." :as ["Starting date (included) in 'yyyy-MM-dd' format."
"Other valid values: 'today', 'yesterday', 'last monday', etc"]} "Other valid values: today, yesterday"]}
{:option "until" :short "u" {:option "until" :short "u"
:type :string :default nil :type :string :default nil
:as ["Ending date (not included) in 'yyyy-MM-dd' format." :as ["Ending date (not included) in 'yyyy-MM-dd' format."
"Other valid values: today, yesterday, tomorrow, etc"]} "Other valid values: today, yesterday, tomorrow"]}]
:runs cmd-timeline}
{:command "timeline" :short "t"
:description ["Get info from LOL and TFT matches in a time range."
"Warning: it can take several minutes to obtain all data, due to api restrictions."
"You can search by PUUID or name and tagline, but not both."
""
"You can set the PUUID passing only one argument or using -p param."
"You can set the name and tag passing two arguments, or using -n and -t."]
:opts [{:option "puuid" :short "p"
:type :string
:as "Summonner's unique PUUID."}
{:option "name" :short "n"
:type :string :default nil
:as "Summoner name (Riot ID)"}
{:option "tag" :short "t"
:type :string :default nil
:as "Summoner tagline"}
{:option "output" :short "o" {:option "output" :short "o"
:type #{"table" "ptable" "edn" "json" "csv"} :default "ptable" :type #{"edn" "json" "table" "csv"} :default "table"
:as ["Output data format. It can be one of:" :as ["Output type. It can be one of:"
" - table: ASCII table"
" - ptable: Pretty ASCII table (default)"
" - edn: Clojure's internal EDN format" " - edn: Clojure's internal EDN format"
" - json: Classic JSON" " - json: Classic JSON"
" - table: ASCII table (by default)"
" - csv: CSV data, using ',' as field separator"]} " - csv: CSV data, using ',' as field separator"]}
{:option "order-by" {:option "order-by"
:type #{"start" "end" "duration"} :default "start" :type #{"start" "end" "duration"} :default "start"
@@ -239,22 +340,51 @@
" - game-type: lol or tft" " - game-type: lol or tft"
" - id: Game unique id" " - id: Game unique id"
" - winner: Winner of the match"]} " - winner: Winner of the match"]}
{:option "statistics" {:option "only-today"
:type :with-flag :default true :type :flag :default false
:as "Show simple statistics"}] :as "If true, only shows today played matches"}
:runs cmd-timeline}]}) {:option "since" :short "s"
:type :string :default nil
:as ["Starting date (included) in 'yyyy-MM-dd' format."
"Other valid values: today, yesterday, 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"]}]
:runs cmd-bulk}]})
(defn -main (defn -main
"Main entry point" "Main entry point"
[& args] [& args]
(log/debug "")
(log/debug "** Starting application **")
(log/debug "")
(run-cmd args CONFIGURATION)) (run-cmd args CONFIGURATION))
(defn old-main
"Testing some basic things"
[& args]
(let [player "RdNVioNKYzvFuXI00zurL_8QvaU0E8P61NU0SzwIfbbHHk9HFvxLtSWiDHKuJ9iXb4UC0UUZ3ltLxw"]
;(println "API Key: " (get-lol-api-key))
(println "Last matches:")
(println "Player PUUID: " player)
;(as-json
;(as-ascii-table
; (with-parsed-dates
; (take 10
; (reverse
; (get-last-matches player :count 10 :print-not-active true)))))
(as-ascii-table
(with-parsed-dates-durations
; (filter match-today? ; Only today
(reverse (get-last-matches player :count 10 :print-not-active true))))
;)
))
(comment (comment
; Launch manually ; Launch manually
(-main) (-main)
)
(old-main)
)

View File

@@ -4,9 +4,7 @@
(ns riot.core (ns riot.core
(:require [clj-http.client :as client] (:require [clj-http.client :as client]
[buddy.core.crypto :as crypto] [buddy.core.crypto :as crypto]
[buddy.core.codecs :as codecs] [buddy.core.codecs :as codecs])
[clojure.tools.logging :as log]
)
(:use [slingshot.slingshot :only [try+]]) (:use [slingshot.slingshot :only [try+]])
(:gen-class)) (:gen-class))
@@ -35,7 +33,7 @@
;;;; API KEYS ;;;; API KEYS
(def DEV_KEY "RGAPI-ec3779d1-bc61-4a2e-a071-34addcc6bd56") (def DEV_KEY "RGAPI-e068aea3-5828-48e2-9500-259ef96c8c4f")
(def LOL_KEY "jXL+gA3LIeBPBvrOhLOYSZCiURC7eOtwMXahkxtwpdj6JDtT5NMu25zMz+UY2+9MuHBADjUJh46jSanrV5OBag==") (def LOL_KEY "jXL+gA3LIeBPBvrOhLOYSZCiURC7eOtwMXahkxtwpdj6JDtT5NMu25zMz+UY2+9MuHBADjUJh46jSanrV5OBag==")
(def TFT_KEY (encrypt-data DEV_KEY secret-key iv)) (def TFT_KEY (encrypt-data DEV_KEY secret-key iv))
@@ -104,54 +102,63 @@
[data response puuid] [data response puuid]
(assoc data :winner (winner? response puuid))) (assoc data :winner (winner? response puuid)))
(comment
(get-in riot.test-examples/response-lol-match [:metadata :participants])
(get-in riot.test-examples/response-lol-match [:info :participants 3])
(get-player-info riot.test-examples/response-lol-match riot.test-examples/example-lol-puuid)
(winner? riot.test-examples/response-lol-match riot.test-examples/example-lol-puuid)
)
(defn with-winner-status (defn with-winner-status
"Takes the original list of matches and adds winner info" "Takes the original list of matches and parses durations"
[matches puuid] [matches puuid]
(map #(assoc % :winner (winner? % puuid)) matches)) (map #(assoc % :winner (winner? % puuid)) matches))
(comment
(with-winner-status riot.test-examples/matches-example riot.test-examples/example-lol-puuid)
)
;; json adapters ;; json adapters
(def player-info-parser {:parser [(make-json-data [:puuid] :puuid)]}) (def player-info-parser {:parser [(make-json-data [:puuid] :puuid)]})
(def lol-match-parser {:parser [(make-json-data [:info :gameCreation] :start) (def lol-match-parser {:parser [(make-json-data [:info :gameCreation] :start)
(make-json-data [:info :gameEndTimestamp] :end) (make-json-data [:info :gameEndTimestamp] :end)
(make-json-data [:info :gameDuration] :duration) (make-json-data [:info :gameDuration] :duration)
;; (make-json-data [:none] :active (constantly false)) (make-json-data [:none] :active (constantly false))
(make-json-data [:none] :game-type (constantly "lol")) (make-json-data [:none] :game-type (constantly "lol"))
(make-json-data [:metadata :matchId] :id) (make-json-data [:metadata :matchId] :id)
(make-json-data [:none] :winner (constantly false)) (make-json-data [:none] :winner (constantly false))]
(make-json-data [:info :endOfGameResult] :result)]
:post post-calculate-win}) :post post-calculate-win})
(def tft-match-parser {:parser [(make-json-data [:info :gameCreation] :start) (def tft-match-parser {:parser [(make-json-data [:info :gameCreation] :start)
(make-json-data [:none] :end (constantly -1)) (make-json-data [:none] :end (constantly -1))
(make-json-data [:info :game_length] :duration #(int %)) ; rounds to integer (make-json-data [:info :game_length] :duration #(int %)) ; rounds to integer
;; (make-json-data [:none] :active (constantly false)) (make-json-data [:none] :active (constantly false))
(make-json-data [:none] :game-type (constantly "tft")) (make-json-data [:none] :game-type (constantly "ftf"))
(make-json-data [:metadata :match_id] :id) (make-json-data [:metadata :match_id] :id)
(make-json-data [:none] :winner (constantly false)) (make-json-data [:none] :winner (constantly false))]
(make-json-data [:info :endOfGameResult] :result)] :post #(post-calculate-win
:post #(post-calculate-win
(post-calculate-end %1 %2 %3) %2 %3)}) ; end = start + (duration * 1000) (post-calculate-end %1 %2 %3) %2 %3)}) ; end = start + (duration * 1000)
(def lol-current-parser {:parser [(make-json-data [:gameStartTime] :start) (def lol-current-parser {:parser [(make-json-data [:gameStartTime] :start)
(make-json-data [:none] :end (constantly nil)) (make-json-data [:none] :end (constantly nil))
(make-json-data [:gameLength] :duration) (make-json-data [:gameLength] :duration)
;; (make-json-data [:none] :active (constantly true)) (make-json-data [:none] :active (constantly true))
(make-json-data [:none] :game-type (constantly "lol")) (make-json-data [:none] :game-type (constantly "lol"))
(make-json-data [:gameId] :id) (make-json-data [:gameId] :id)
(make-json-data [:none] :winner (constantly nil)) (make-json-data [:none] :winner (constantly nil))]})
(make-json-data [:none] :result (constantly nil))]})
(def tft-current-parser {:parser [(make-json-data [:gameStartTime] :start) (def tft-current-parser {:parser [(make-json-data [:gameStartTime] :start)
(make-json-data [:none] :end (constantly nil)) (make-json-data [:none] :end (constantly nil))
(make-json-data [:gameLength] :duration) (make-json-data [:gameLength] :duration)
;; (make-json-data [:none] :active (constantly true)) (make-json-data [:none] :active (constantly true))
(make-json-data [:none] :game-type (constantly "tft")) (make-json-data [:none] :game-type (constantly "ftf"))
(make-json-data [:gameId] :id) (make-json-data [:gameId] :id)
(make-json-data [:none] :winner (constantly nil)) (make-json-data [:none] :winner (constantly nil))]})
(make-json-data [:none] :result (constantly nil))]})
(defn parse-response (defn parse-response
@@ -181,7 +188,6 @@
:or {params nil :or {params nil
debug false}}] debug false}}]
(when debug (println "** SENDING REQUEST **")) (when debug (println "** SENDING REQUEST **"))
(log/trace "Sending request. Params: " params)
;; (println "Params: " params) ;; (println "Params: " params)
(:body (client/get url (:body (client/get url
{:debug debug {:debug debug
@@ -215,7 +221,7 @@
(defn date-to-seconds (defn date-to-seconds
([] (date-to-seconds (java.util.Date.))) ([] (date-to-seconds (java.util.Date. )))
([d] ([d]
(when (some? d) (quot (.getTime d) 1000)))) (when (some? d) (quot (.getTime d) 1000))))
@@ -238,10 +244,8 @@
debug false debug false
server "europe" server "europe"
count 20 count 20
start 0} start 0}}]
:as params}]
;; (println "get lol start: " start) ;; (println "get lol start: " start)
(log/trace "Getting list of LOL matches. Params: " params)
(raw-get-query (raw-get-query
(create-endpoint server "/lol/match/v5/matches/by-puuid/" puuid "/ids") (create-endpoint server "/lol/match/v5/matches/by-puuid/" puuid "/ids")
:api-key api-key :api-key api-key
@@ -256,9 +260,7 @@
debug false debug false
server "europe" server "europe"
count 20 count 20
start 0} start 0}}]
:as params}]
(log/trace "Getting list of TFT matches. Params: " params)
(raw-get-query (raw-get-query
(create-endpoint server "/tft/match/v1/matches/by-puuid/" puuid "/ids") (create-endpoint server "/tft/match/v1/matches/by-puuid/" puuid "/ids")
:api-key api-key :api-key api-key
@@ -274,7 +276,6 @@
debug false debug false
server "europe"}}] server "europe"}}]
;; (println "get-lol-match-info match:" match-id) ;; (println "get-lol-match-info match:" match-id)
(log/debug "Getting data for LOL match " match-id)
(query (query
(create-endpoint server "/lol/match/v5/matches/" match-id) (create-endpoint server "/lol/match/v5/matches/" match-id)
lol-match-parser lol-match-parser
@@ -291,7 +292,6 @@
debug false debug false
server "europe"}}] server "europe"}}]
;; (println "get-tft-match-info match:" match-id) ;; (println "get-tft-match-info match:" match-id)
(log/debug "Getting data for TFT match " match-id)
(query (query
(create-endpoint server "/tft/match/v1/matches/" match-id) (create-endpoint server "/tft/match/v1/matches/" match-id)
tft-match-parser tft-match-parser
@@ -300,7 +300,7 @@
:puuid puuid)) :puuid puuid))
#_{:clj-kondo/ignore [:unresolved-symbol]}
(defn get-lol-current-info (defn get-lol-current-info
"Get current LoL match, if any" "Get current LoL match, if any"
[puuid & {:keys [api-key debug print-not-active server] [puuid & {:keys [api-key debug print-not-active server]
@@ -308,19 +308,16 @@
debug false debug false
print-not-active false print-not-active false
server "euw1"}}] server "euw1"}}]
(log/debug "Getting data for current LOL match") (try+
(when (some? api-key) (query
(try+ (create-endpoint server "/lol/spectator/v5/active-games/by-summoner/" puuid)
(query lol-current-parser
(create-endpoint server "/lol/spectator/v5/active-games/by-summoner/" puuid) :api-key api-key
lol-current-parser :debug debug)
:api-key api-key (catch [:status 404] _
:debug debug) (when print-not-active (println "No active LoL match")))))
(catch [:status 404] _
(when print-not-active (println "No active LoL match"))))))
#_{:clj-kondo/ignore [:unresolved-symbol]}
(defn get-tft-current-info (defn get-tft-current-info
"Get current TFT match, if any" "Get current TFT match, if any"
[puuid & {:keys [api-key debug print-not-active server] [puuid & {:keys [api-key debug print-not-active server]
@@ -328,16 +325,14 @@
debug false debug false
print-not-active false print-not-active false
server "euw1"}}] server "euw1"}}]
(log/debug "Getting data for current TFT match") (try+
(when (some? api-key) (query
(try+ (create-endpoint server "/lol/spectator/tft/v5/active-games/by-puuid/" puuid)
(query tft-current-parser
(create-endpoint server "/lol/spectator/tft/v5/active-games/by-puuid/" puuid) :api-key api-key
tft-current-parser :debug debug)
:api-key api-key (catch [:status 404] _
:debug debug) (when print-not-active (println "No active FTF match")))))
(catch [:status 404] _
(when print-not-active (println "No active tft match"))))))
@@ -348,13 +343,13 @@
tft-api-key (get-tft-api-key) tft-api-key (get-tft-api-key)
debug false debug false
print-not-active false print-not-active false
server "euw1"}}] 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)) (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)))) (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 (defn get-last-matches
"Get info for last LoL or tft matches" "Get info for last LoL or FTF 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] [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) :or {lol-api-key (get-lol-api-key)
tft-api-key (get-tft-api-key) tft-api-key (get-tft-api-key)
@@ -364,7 +359,7 @@
server1 "europe" server1 "europe"
server2 "euw1" server2 "euw1"
count 20 count 20
start 0 start 0
order-by :start}}] order-by :start}}]
(sort-by order-by (sort-by order-by
(filter some? (filter some?
@@ -381,14 +376,14 @@
;;;; Get data bulk ;;;; Get data bulk
#_{:clj-kondo/ignore [:unresolved-symbol]}
(defn get-with-wait (defn get-with-wait
"Calls a getter and waits for response. If rate limit has been exceeded, waits for some "Calls a getter and waits for response. If rate limit has been exceeded, waits for some
seconds and retries." seconds and retries."
[getter & {:keys [wait-limit-exceeded] [getter & {:keys [wait-limit-exceeded]
:or {wait-limit-exceeded 15}}] :or {wait-limit-exceeded 15}}]
(try+ (try+
(getter) ;; Executes getter function (getter)
(catch [:status 429] _ (catch [:status 429] _
(println "Rate limit exceeded, waiting for" wait-limit-exceeded "seconds") (println "Rate limit exceeded, waiting for" wait-limit-exceeded "seconds")
(Thread/sleep (* 1000 wait-limit-exceeded)) (Thread/sleep (* 1000 wait-limit-exceeded))
@@ -402,7 +397,6 @@
first-batch 0 first-batch 0
max-batches 10}}] max-batches 10}}]
;; (println "Obtaining bulk data - range") ;; (println "Obtaining bulk data - range")
(log/debug "Getting data in " max-batches " batches")
(doall (flatten (concat (doall (flatten (concat
(map (map
#(get-with-wait (partial getter %) :wait-limit-exceeded wait-limit-exceeded) #(get-with-wait (partial getter %) :wait-limit-exceeded wait-limit-exceeded)
@@ -431,9 +425,9 @@
debug false}}] debug false}}]
(when (every? some? [puuid api-key]) (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) (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 :wait-limit-exceeded wait-limit-exceeded
:first-batch first-batch :first-batch first-batch
:max-batches max-batches))) :max-batches max-batches)))
(defn get-matches-batch-tft (defn get-matches-batch-tft
@@ -452,12 +446,12 @@
:max-batches max-batches))) :max-batches max-batches)))
(defn get-matches-info-batch-lol (defn get-matches-info-batch-lol
"Get a lot of LOL matches info" "Get a lot of LOL matches info"
[matches-id puuid api-key & {:keys [server wait-limit-exceeded debug] [matches-id puuid api-key & {:keys [server wait-limit-exceeded debug]
:or {server "europe" :or {server "europe"
wait-limit-exceeded 15 wait-limit-exceeded 15
debug false}}] debug false}}]
;; (println "Matches: " matches-id) ;; (println "Matches: " matches-id)
(when (every? some? [matches-id puuid api-key]) (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) (get-batch-seq #(get-lol-match-info % :puuid puuid :api-key api-key :server server :wait-limit-exceeded wait-limit-exceeded :debug debug)
@@ -479,23 +473,18 @@
(defn get-matches-info-batch (defn get-matches-info-batch
"Get a lot of LOL and TFT matches info" "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] [lol-puuid tft-puuid & {:keys [lol-api-key tft-api-key wait-limit-exceeded debug server count order-by since until]
:or {lol-api-key (get-lol-api-key) :or {lol-api-key (get-lol-api-key)
tft-api-key (get-tft-api-key) tft-api-key (get-tft-api-key)
wait-limit-exceeded 15 wait-limit-exceeded 15
debug false debug false
server "europe" server "europe"
server2 "euw1"
count 100 count 100
order-by :start order-by :start}}]
include-current true}}]
(log/debug "Fetching data in batches")
(sort-by order-by (sort-by order-by
(filter some? (filter some?
(concat (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) (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) 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) (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) 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)])))))

View File

@@ -4,12 +4,10 @@
(ns riot.data (ns riot.data
(:import [java.time Instant LocalDate ZoneId Duration] (:import [java.time Instant LocalDate ZoneId Duration]
[java.time.format DateTimeFormatter]) [java.time.format DateTimeFormatter])
#_{:clj-kondo/ignore [:refer-all]}
(:require [clojure.pprint :as pp] (:require [clojure.pprint :as pp]
[clojure.string :as str] [clojure.string :as str]
[cheshire.core :refer :all] [cheshire.core :refer :all])
[riot.core :refer :all] (:use [riot.core :refer :all])
[clj-commons.format.table :refer [print-table] :as table])
(:gen-class)) (:gen-class))
@@ -19,37 +17,33 @@
;; Parse UNIX time ;; Parse UNIX time
(defn unix->ZonedDateTime (defn unix-to-datetime
"Convert from UNIX time in millis, to a ZonedDateTime" "Conver from UNIX time in millis, to a ZonedDateTime"
[millis] [millis]
(when (some? millis) (when (some? millis)
(.atZone (Instant/ofEpochMilli millis) (ZoneId/systemDefault)))) (.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 (defn localdate-to-date
"Converts a java.time.LocalDate to an ancient java.util.Date" "Converts a java.time.LocalDate to an ancient java.util.Date"
[localdate] [localdate]
(when (some? localdate) (when (some? localdate)
(java.util.Date/from (.toInstant (.atZone (.atStartOfDay localdate) (ZoneId/systemDefault)))))) (java.util.Date/from (.toInstant (.atZone (.atStartOfDay localdate) (ZoneId/systemDefault))))))
(defn str->LocalDate (defn parse-localdate
"Parse a yyyy-MM-dd date to java.time.LocalDate" "Parse a yyyy-MM-dd date to java.time.LocalDate"
[s] [s]
(java.time.LocalDate/parse s (DateTimeFormatter/ofPattern DEFAULT_DATE_FORMAT))) (java.time.LocalDate/parse s (DateTimeFormatter/ofPattern DEFAULT_DATE_FORMAT))
)
(defn str->Date (defn parse-date-str
[s] [s]
(LocalDate->Date (str->LocalDate s))) (localdate-to-date(parse-localdate s)))
(defn unix->Date (defn parse-date-epoch
[millis] [millis]
(LocalDate->Date (unix->ZonedDateTime millis))) (localdate-to-date (unix-to-datetime millis)))
(defn format-datetime (defn format-datetime
@@ -66,21 +60,18 @@
([millis] (format-datetime-millis millis DEFAULT_DATE_TIME_FORMAT)) ([millis] (format-datetime-millis millis DEFAULT_DATE_TIME_FORMAT))
([millis pattern] ([millis pattern]
(when (and (some? millis) (some? pattern)) (when (and (some? millis) (some? pattern))
(format-datetime (unix->ZonedDateTime millis) pattern)))) (format-datetime (unix-to-datetime millis) pattern))))
;; Parse durations ;; Parse durations
(defn format-duration-seconds (defn format-duration-seconds
"Converts a duration in seconds to a string with hours, minutes and seconds" "Converts a duration in seconds to a string with hours and seconds"
[seconds] [seconds]
(when (some? seconds) (when (some? seconds)
(if (< seconds 3600) (if (< seconds 3600)
(format "%02d:%02d" (quot seconds 60) (rem seconds 60)) (format "%02d:%02d" (quot seconds 60) (rem seconds 60))
(format "%02d:%02d:%02d" (format "%02d:%02d:%02d" (quot seconds 3600) ( - ( quot seconds 60) 60) (rem seconds 60)))))
(quot seconds 3600)
(- (quot seconds 60) (* 60 (quot seconds 3600)))
(rem seconds 60)))))
@@ -96,8 +87,8 @@
A match has been played today if its start date or end date is from today" A match has been played today if its start date or end date is from today"
[match] [match]
(or (or
(>= (or (:start match) 0) (today-millis)) (>= (or (:start match) 0) (today-millis))
(>= (or (:end match) 0) (today-millis)))) (>= (or (:end match) 0) (today-millis))))
@@ -111,6 +102,10 @@
(->> matches (->> matches
(map #(update % :start (fn [x] (format-datetime-millis x datetime-format)))) (map #(update % :start (fn [x] (format-datetime-millis x datetime-format))))
(map #(update % :end (fn [x] (format-datetime-millis x datetime-format)))))) (map #(update % :end (fn [x] (format-datetime-millis x datetime-format))))))
(comment
(with-parsed-dates riot.test-examples/matches-example)
)
(defn with-parsed-durations (defn with-parsed-durations
@@ -119,13 +114,15 @@
(map #(update % :duration format-duration-seconds) matches)) (map #(update % :duration format-duration-seconds) matches))
(defn with-parsed-dates-durations (defn with-parsed-dates-durations
"Takes the original list of matches and parses dates and durations" "Takes the original list of matches and parses dates and durations"
[matches & {:keys [datetime-format] [matches & {:keys [datetime-format]
:or {datetime-format DEFAULT_DATE_TIME_FORMAT}}] :or {datetime-format DEFAULT_DATE_TIME_FORMAT}}]
(-> matches (-> matches
(with-parsed-durations) (with-parsed-durations)
(with-parsed-dates :datetime-format datetime-format))) (with-parsed-dates :datetime-format datetime-format)
))
;;; Export to other formats ;;; Export to other formats
@@ -145,21 +142,23 @@
(generate-string matches {:pretty pretty})) (generate-string matches {:pretty pretty}))
(comment
(println (as-json (with-parsed-dates-durations riot.test-examples/matches-example
:pretty false)))
)
(defn as-ascii-table (defn as-ascii-table
"Export as ascii table" "Export as ascii table"
[matches] [matches]
(pp/print-table matches)) (pp/print-table matches))
(comment
(as-ascii-table riot.test-examples/matches-example)
(as-ascii-table (with-parsed-dates-durations riot.test-examples/matches-example))
)
(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 (defn as-csv
@@ -169,82 +168,12 @@
(str/join (System/lineSeparator) ; Local EOL characteres (str/join (System/lineSeparator) ; Local EOL characteres
(concat (concat
[(clojure.string/join separator (map name (keys (first matches))))] ; header [(clojure.string/join separator (map name (keys (first matches))))] ; header
(map #(clojure.string/join separator %) (map #(clojure.string/join separator %)
(map vals matches)))))) (map vals matches))))))
(comment
(defn calculate-num-days-played (as-csv riot.test-examples/matches-example)
[matches] (as-csv riot.test-examples/matches-example2)
(count (partition-by #(unix->LocalDate (:start %)) matches)) (as-csv (with-parsed-dates-durations riot.test-examples/matches-example))
) (as-csv (with-parsed-dates-durations riot.test-examples/matches-example2))
)
(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}))
)

View 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"))
)

View File

@@ -1,12 +1,10 @@
(ns riot.core-test (ns riot.core-test
{:clj-kondo/ignore [:unresolved-symbol]}
#_{:clj-kondo/ignore [:refer-all]}
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[riot.test-examples :refer :all] [riot.test-examples :refer :all]
[riot.core :refer :all] [riot.core :refer :all]
[timewords.core :refer [parse]] [timewords.core :refer [parse]]
[clojure.pprint :as pp] [clojure.pprint :as pp])
[slingshot.slingshot :refer [try+]])) (:use [slingshot.slingshot :only [try+]]))
@@ -32,8 +30,8 @@
(is (= (decrypt-data LOL_KEY) (get-lol-api-key nil))) (is (= (decrypt-data LOL_KEY) (get-lol-api-key nil)))
(is (= "abc" (get-lol-api-key "abc")))) (is (= "abc" (get-lol-api-key "abc"))))
(testing "Get TFT API key" (testing "Get TFT API key"
;; (is (= (decrypt-data TFT_KEY) (get-tft-api-key))) (is (= (decrypt-data TFT_KEY) (get-tft-api-key)))
;; (is (= (decrypt-data TFT_KEY) (get-tft-api-key nil))) (is (= (decrypt-data TFT_KEY) (get-tft-api-key nil)))
(is (= "abc" (get-tft-api-key "abc"))))) (is (= "abc" (get-tft-api-key "abc")))))
@@ -123,16 +121,17 @@
(comment (get-puuid-from-name example-name example-tag :api-key (get-tft-api-key))) (comment (get-puuid-from-name example-name example-tag :api-key (get-tft-api-key)))
;; From java.util.Date to seconds since 1970 ;; From java.util.Date to seconds since 1970
(deftest ^:timezone test-date-to-seconds (deftest test-date-to-seconds
(println "* Date to epoch seconds *") (println "* Date to epoch seconds *")
(testing "Some dates" (testing "Some dates"
(is (nil? (date-to-seconds nil))) (is (nil? (date-to-seconds nil)))
(is (some? (date-to-seconds))) (is (some? (date-to-seconds)))
(is (= 1738364400 (date-to-seconds (java.util.Date. 125 1 1)))))) (is (= 1738364400 (date-to-seconds (java.util.Date. 125 1 1)))))
)
;; Get parameter for querying data ;; Get parameter for querying data
(deftest ^:timezone test-query-params (deftest test-query-params
(println "* Generate query params *") (println "* Generate query params *")
(testing "None params" (testing "None params"
(is (= {"count" 100} (query-params)))) (is (= {"count" 100} (query-params))))
@@ -231,9 +230,10 @@
(is (not res) (str "Oh, well, the player is playing: " res)))) (is (not res) (str "Oh, well, the player is playing: " res))))
(testing "Include current" (testing "Include current"
(let [res (is-playing? example-lol-puuid example-tft-puuid)] (let [res (is-playing? example-lol-puuid example-tft-puuid)]
(is (not res) (str "Oh, well, the player is playing: " res)))))) (is (not res) (str "Oh, well, the player is playing: " res)))))
)
(deftest test-get-matches-batch-lol (deftest test-get-matches-batch-lol
(println "* Get lol matches batch *") (println "* Get lol matches batch *")
(testing "Invalid data" (testing "Invalid data"
@@ -241,9 +241,9 @@
(is (= nil (get-matches-batch-lol example-lol-puuid nil))) (is (= nil (get-matches-batch-lol example-lol-puuid nil)))
(is (= nil (get-matches-batch-lol nil (get-lol-api-key))))) (is (= nil (get-matches-batch-lol nil (get-lol-api-key)))))
(testing "Get LOL matches" (testing "Get LOL matches"
(is (= 10 (count (get-matches-batch-lol example-lol-puuid (get-lol-api-key) :count 5 :max-batches 2)))))) (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 (deftest test-get-matches-batch-tft
(println "* Get tft matches batch *") (println "* Get tft matches batch *")
(testing "Invalid data" (testing "Invalid data"
(is (= nil (get-matches-batch-tft nil nil))) (is (= nil (get-matches-batch-tft nil nil)))
@@ -252,7 +252,7 @@
(testing "Get LOL matches" (testing "Get LOL matches"
(is (= 10 (count (get-matches-batch-tft example-tft-puuid (get-tft-api-key) :count 5 :max-batches 2)))))) (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 (deftest test-get-matches-info-batch-lol
(println "* Get lol matches info batch *") (println "* Get lol matches info batch *")
(testing "Invalid data" (testing "Invalid data"
@@ -266,7 +266,7 @@
(get-lol-api-key))))))) (get-lol-api-key)))))))
(deftest ^:tft test-get-matches-info-batch-tft (deftest test-get-matches-info-batch-tft
(println "* Get tft matches info batch *") (println "* Get tft matches info batch *")
(testing "Invalid data" (testing "Invalid data"
(is (= nil (get-matches-info-batch-tft nil nil nil))) (is (= nil (get-matches-info-batch-tft nil nil nil)))
@@ -298,4 +298,5 @@
(get-matches-info-batch example-lol-puuid example-tft-puuid) (get-matches-info-batch example-lol-puuid example-tft-puuid)
(catch [:status 404] {:keys [request-time headers body] :as excp} (catch [:status 404] {:keys [request-time headers body] :as excp}
(println "NOT Found 404") (println "NOT Found 404")
(pp/pprint excp)))) (pp/pprint excp)))
)

View File

@@ -1,6 +1,5 @@
(ns riot.data-test (ns riot.data-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[clojure.pprint :as pp]
[riot.test-examples :refer :all] [riot.test-examples :refer :all]
[riot.data :refer :all] [riot.data :refer :all]
[riot.core :refer :all]) [riot.core :refer :all])
@@ -10,38 +9,30 @@
;; Convert from epoch in millis (1970-01-01 00:00:00) to ZonedDateTime ;; Convert from epoch in millis (1970-01-01 00:00:00) to ZonedDateTime
(deftest ^:timezone test-unix->ZonedDateTime (deftest test-unix-to-datetime
(println "* Epoch millis to java.util.Datetime *") (println "* Epoch millis to java.util.Datetime *")
(testing "Synthetic data" (testing "Get LOL API key"
(is (= (ZonedDateTime/of 2025 4 3 0 0 0 0 (ZoneId/systemDefault)) (is (= (ZonedDateTime/of 2025 4 3 0 0 0 0 (ZoneId/systemDefault))
(unix->ZonedDateTime 1743631200000))))) (unix-to-datetime 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 ;; Format a LocalDateTime o ZonedDateTime as a String
(deftest ^:timezone test-unix->ZonedDateTime (deftest test-format-datetime
(println "* DateTime to String *") (println "* DateTime to String *")
(testing "Null" (testing "Null"
(is (nil? (format-datetime-millis nil)))) (is (nil? (format-datetime-millis nil))))
(testing "Default format" (testing "Default format"
(is (= "2025-04-03 00:00:00" (is (= "2025-04-03 00:00:00"
(format-datetime (unix->ZonedDateTime 1743631200000))))) (format-datetime (unix-to-datetime 1743631200000)))))
(testing "Personalized time formats" (testing "Personalized time formats"
(is (= "2025-04-03" (is (= "2025-04-03"
(format-datetime (unix->ZonedDateTime 1743631200000) "yyyy-MM-dd"))) (format-datetime (unix-to-datetime 1743631200000) "yyyy-MM-dd")))
(is (= "00:00" (is (= "00:00"
(format-datetime (unix->ZonedDateTime 1743631200000) "HH:00"))))) (format-datetime (unix-to-datetime 1743631200000) "HH:00")))))
;; Format a epoch in millis as a String ;; Format a epoch in millis as a String
(deftest ^:timezone test-datetime-millis (deftest test-datetime-millis
(println "* DateTime to String *") (println "* DateTime to String *")
(testing "Null" (testing "Null"
(is (nil? (format-datetime-millis nil)))) (is (nil? (format-datetime-millis nil))))
@@ -67,14 +58,12 @@
(testing "Long durations" (testing "Long durations"
(is (= "01:00:00" (format-duration-seconds 3600))) (is (= "01:00:00" (format-duration-seconds 3600)))
(is (= "01:00:01" (format-duration-seconds 3601))) (is (= "01:00:01" (format-duration-seconds 3601)))
(is (= "01:01:01" (format-duration-seconds 3661))) (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 ;; Epoch in millis for today at 00:00:00 hours. Difficult to test
(deftest ^:timezone test-today-millis (deftest test-today-millis
(println "* Epoch for today *") (println "* Epoch for today *")
(testing "Epoch for today" (testing "Epoch for today"
(is (some? (today-millis))))) (is (some? (today-millis)))))
@@ -88,11 +77,12 @@
(is (match-today? {:start (+ 5000 (today-millis))}))) (is (match-today? {:start (+ 5000 (today-millis))})))
(testing "End date today" (testing "End date today"
(is (match-today? {:end (today-millis)})) (is (match-today? {:end (today-millis)}))
(is (match-today? {:end (+ 5000 (today-millis))})))) (is (match-today? {:end (+ 5000 (today-millis))})))
)
;; Parse dates inside a lis of matches ;; Parse dates inside a lis of matches
(deftest ^:timezone test-with-parsed-dates (deftest test-with-parsed-dates
(println "* Parse dates in a match list*") (println "* Parse dates in a match list*")
(testing "Empty or invalid lists" (testing "Empty or invalid lists"
(is (= '() (with-parsed-dates '()))) (is (= '() (with-parsed-dates '())))
@@ -100,11 +90,12 @@
(is (= '({:a "AAA" :b 2 :start nil, :end nil}) (with-parsed-dates '({:a "AAA" :b 2}))))) (is (= '({:a "AAA" :b 2 :start nil, :end nil}) (with-parsed-dates '({:a "AAA" :b 2})))))
(testing "Valid list" (testing "Valid list"
(is (= '({:start "2025-06-11 19:00:54", :end nil, :duration 1288, :active true, :game-type "lol", :id "EUW1_111111"}) (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)))))) (with-parsed-dates (take 1 matches-example))))
))
;; Parse durations ;; Parse durations
(deftest ^:timezone test-with-parsed-durations (deftest test-with-parsed-dates
(println "* Parse durations in a match list *") (println "* Parse durations in a match list *")
(testing "Empty or invalid lists" (testing "Empty or invalid lists"
(is (= '() (with-parsed-durations '()))) (is (= '() (with-parsed-durations '())))
@@ -112,68 +103,22 @@
(is (= '({:a "AAA" :b 2 :duration nil}) (with-parsed-durations '({:a "AAA" :b 2}))))) (is (= '({:a "AAA" :b 2 :duration nil}) (with-parsed-durations '({:a "AAA" :b 2})))))
(testing "Valid list" (testing "Valid list"
(is (= '({:start 1749661254854, :end nil, :duration "21:28", :active true, :game-type "lol", :id "EUW1_111111"}) (is (= '({:start 1749661254854, :end nil, :duration "21:28", :active true, :game-type "lol", :id "EUW1_111111"})
(with-parsed-durations (take 1 matches-example)))))) (with-parsed-durations (take 1 matches-example)))))
)
;; Parse dates and durations ;; Parse dates and durations
(deftest ^:timezone test-with-parsed-dates-durations (deftest test-with-parsed-dates
(println "* Parse dates and durations in a match list *") (println "* Parse dates and durations in a match list *")
(testing "Empty or invalid lists" (testing "Empty or invalid lists"
(is (= '() (with-parsed-dates-durations '()))) (is (= '() (with-parsed-dates-durations '())))
(is (= '({:duration nil, :start nil, :end nil}) (is (= '({:duration nil, :start nil, :end nil})
(with-parsed-dates-durations '({})))) (with-parsed-dates-durations '({}))))
(is (= '({:a "AAA" :b 2 :duration nil, :start nil, :end nil}) (is (= '({:a "AAA" :b 2 :duration nil, :start nil, :end nil})
(with-parsed-dates-durations '({:a "AAA" :b 2}))))) (with-parsed-dates-durations '({:a "AAA" :b 2})))))
(testing "Valid list" (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"}) (is (= '({:start "2025-06-11 19:00:54", :end nil, :duration "21:28", :active true, :game-type "lol", :id "EUW1_111111"})
(with-parsed-dates-durations (take 1 matches-example)))))) (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))
)

View File

@@ -8,7 +8,7 @@
(def example-tft-puuid "yJawqnx9nzvj9ZJdW_7ok0wmB2WqSqafKy1sDrA48Zefbx0Iuco9jfGyVdUhldjDt8IxLvMcs4r8MA") (def example-tft-puuid "yJawqnx9nzvj9ZJdW_7ok0wmB2WqSqafKy1sDrA48Zefbx0Iuco9jfGyVdUhldjDt8IxLvMcs4r8MA")
(def example-lol-match {:start 1750106168280, :end 1750108132077, :duration 1882, :game-type "lol", :id "EUW1_7434497430" :winner false :result "GameComplete"}) (def example-lol-match {:start 1750106168280, :end 1750108132077, :duration 1882, :active false, :game-type "lol", :id "EUW1_7434497430" :winner false})
(def example-lol-never-played "annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ") (def example-lol-never-played "annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ")
@@ -17,120 +17,83 @@
'({:start 1749661254854, '({:start 1749661254854,
:end nil, :end nil,
:duration 1288, :duration 1288,
:active true,
:game-type "lol" :game-type "lol"
:id "EUW1_111111" :id "EUW1_111111"}
:winner true,
:result "GameComplete"}
{:start 1749659482211, {:start 1749659482211,
:end 1749661133145, :end 1749661133145,
:duration 1622, :duration 1622,
:active false,
:game-type "lol" :game-type "lol"
:id "EUW1_222222" :id "EUW1_222222"}
:winner false,
:result "GameComplete"}
{:start 1749655924238, {:start 1749655924238,
:end 1749658146178, :end 1749658146178,
:duration nil, :duration nil,
:active false,
:game-type "lol" :game-type "lol"
:id "EUW1_3333333" :id "EUW1_3333333"}))
:winner true,
:result "GameComplete"}
{:start 1749755924238,
:end 1749758146178,
:duration nil,
:game-type "tft"
:id "EUW1_4444444"
:winner true
:result "GameComplete"}
{:start 1749855924238,
:end 1749858146178,
:duration nil,
:game-type "tft"
:id "EUW1_5555555"
:winner false
:result "GameComplete"}))
;; Real example from:
;; riot-clojur t "Walid Georgey" euw --tft -o edn -s "24 may" -u "30 may" --no-format-dates --no-format-durations
(def matches-example2 (def matches-example2
'({:start 1748470702886, '({:start 1749893615000,
:end 1748472666457, :end 1749896091000,
:duration 1947, :duration 2476,
:active false,
:game-type "ftf",
:id "EUW1_7431741880"}
{:start 1749886475803,
:end 1749887505751,
:duration 999,
:active false,
:game-type "lol", :game-type "lol",
:id "EUW1_7415410708", :id "EUW1_7431686898"}
:winner true {:start 1749830764708,
:result "GameComplete"} :end 1749831241808,
{:start 1748466492126, :duration 232,
:end 1748468105264, :active false,
:duration 1585,
:game-type "lol", :game-type "lol",
:id "EUW1_7415317950", :id "EUW1_7431025296"}
:winner false {:start 1749740947765,
:result "GameComplete"} :end 1749743270521,
{:start 1748180985396, :duration 2214,
:end 1748182761075, :active false,
:duration 1754,
:game-type "lol", :game-type "lol",
:id "EUW1_7411793848", :id "EUW1_7429951366"}
:winner true} {:start 1749739258737,
:result "GameComplete" :end 1749740607134,
{:start 1748178332000, :duration 1311,
:end 1748180377000, :active false,
:duration 2045,
:game-type "tft",
:id "EUW1_7411747119",
:winner true
:result "GameComplete"}
{:start 1748119701678,
:end 1748121421926,
:duration 1578,
:game-type "lol", :game-type "lol",
:id "EUW1_7411183430", :id "EUW1_7429932844"}
:winner true {:start 1749737844367,
:result "GameComplete"} :end 1749739185680,
{:start 1748117211000, :duration 1144,
:end 1748119477000, :active false,
:duration 2266,
:game-type "tft",
:id "EUW1_7411136542",
:winner true
:result "GameComplete"}
{:start 1748110984521,
:end 1748112698320,
:duration 1694,
:game-type "lol", :game-type "lol",
:id "EUW1_7410995061", :id "EUW1_7429905240"}
:winner true {:start 1749736215680,
:result "GameComplete"} :end 1749737698464,
{:start 1748103234000, :duration 1425,
:end 1748105725000, :active false,
:duration 2491,
:game-type "tft",
:id "EUW1_7410863506",
:winner true
:result "GameComplete"}
{:start 1748097496984,
:end 1748099470873,
:duration 1933,
:game-type "lol", :game-type "lol",
:id "EUW1_7410750999", :id "EUW1_7429884230"}
:winner false {:start 1749733140474,
:result "GameComplete"} :end 1749734774103,
{:start 1748094441000, :duration 1535,
:end 1748096636000, :active false,
:duration 2195,
:game-type "tft",
:id "EUW1_7410701935",
:winner true
:result "GameComplete"}
{:start 1748088640068,
:end 1748090386650,
:duration 1701,
:game-type "lol", :game-type "lol",
:id "EUW1_7410601082", :id "EUW1_7429836245"}
:winner true {:start 1749727474332,
:result "GameComplete"})) :end 1749729494360,
:duration 1899,
:active false,
:game-type "lol",
:id "EUW1_7429774673"}
{:start 1749717297978,
:end 1749719815603,
:duration 2281,
:active false,
:game-type "lol",
:id "EUW1_7429678096"}))
(def response-player-info {:puuid "annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ", (def response-player-info {:puuid "annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ",