Compare commits

...

36 Commits

Author SHA1 Message Date
d49d80af94 Merged branch 1.1
All checks were successful
Compile and test using leiningen / Run tests (push) Successful in 9m41s
2025-08-12 09:27:57 +02:00
68cf337f48 Update README.md
All checks were successful
Compile and test using leiningen / Run tests (push) Successful in 9m44s
2025-08-12 08:44:37 +02:00
4ecab127af Update test/riot/core_test.clj
All checks were successful
Compile and test using leiningen / Run tests (push) Successful in 9m39s
2025-08-12 08:29:38 +02:00
61f8639a67 Update test/riot/data_test.clj
Some checks failed
Compile and test using leiningen / Run tests (push) Has been cancelled
2025-08-12 08:28:14 +02:00
7f0866cbac Fix some tests
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m17s
2025-08-12 08:16:52 +02:00
f2fbeaaa4d exclude :tft and :timezone from default tests
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m16s
2025-08-12 01:08:19 +02:00
abf0b9dbde Exclude tests that relay in the timezone
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m11s
2025-08-12 00:36:45 +02:00
c881b8ae49 Deactivate TFT tests
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m12s
2025-08-12 00:17:33 +02:00
19eb496d35 More changes to actions
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m25s
2025-08-11 23:56:26 +02:00
156bbdb20f Trying to deploy on maven repository
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m12s
2025-08-11 23:42:35 +02:00
83ffa76f1f goodbye demo
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m9s
2025-08-11 23:19:24 +02:00
de6c41d9f8 Update .gitea/workflows/compile.yaml
Some checks failed
Compile and test using leiningen / Run tests (push) Has been cancelled
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-08-11 23:18:26 +02:00
af58b8602a Run lein deps
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
Compile and test using leiningen / Run tests (push) Has been cancelled
2025-08-11 23:18:02 +02:00
f2ca083dde Let's try chatgpt response...
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m47s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-08-11 23:09:08 +02:00
117275043c Triying to install Leiningen automatically
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 5m50s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
2025-08-11 22:58:22 +02:00
b0ae302c2f Triying with versións from example
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Compile and test using leiningen / Run tests (push) Failing after 4m43s
2025-08-11 20:08:59 +02:00
05963e9fec Update .gitea/workflows/compile.yaml
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
Compile and test using leiningen / Run tests (push) Failing after 12s
2025-08-11 20:04:11 +02:00
914fbb57e2 Ok, let's forget graalvm for now...
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 4m56s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-08-11 19:54:36 +02:00
3f8409f007 Trying another graal version
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 12s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-08-11 19:53:21 +02:00
8c51ef4da5 Trying to configure an usefull gitea actions
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 2m29s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
2025-08-11 19:47:25 +02:00
44b8e62b5e Show endOfGameResult data item
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
2025-08-11 01:01:52 +02:00
cceae6eadd Check zero divisions
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 11s
2025-08-11 00:22:25 +02:00
d20d7bbbcf Simple statistics
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 39s
2025-08-03 01:02:03 +02:00
c09f5681c2 Added log dependency
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 57s
2025-07-31 11:28:30 +02:00
25349fef02 Added log support
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
2025-07-18 00:29:19 +02:00
35eca77223 Adding logging dependency
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-07-17 11:14:58 +02:00
3317241f86 Logging
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s
2025-07-17 00:32:09 +02:00
c90ab941df Today by default and include current playing info
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-07-16 09:35:32 +02:00
eed4104ae3 Clean the CLI parameters
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-07-16 00:41:59 +02:00
3b54917551 Better help text
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-07-15 21:52:02 +02:00
43c2d31d4f Pretty table and simplified parameters
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 8s
2025-07-15 21:43:49 +02:00
df414c37d0 Ignore example svg files
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 18s
2025-07-15 19:22:33 +02:00
3f405d1723 Simple demo Gitea Action
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m15s
2025-07-15 13:00:50 +02:00
93ce4e7539 Update README.md 2025-07-15 12:54:33 +02:00
3d86c3b7be Update README.md 2025-07-15 12:47:07 +02:00
515c51a4bc Configuring development of v1.1
Configure project and adapt the README.md for the next development
branch (1.1)
2025-07-15 12:30:34 +02:00
11 changed files with 649 additions and 429 deletions

View File

@@ -0,0 +1,59 @@
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,3 +14,5 @@ pom.xml.asc
.clj-kondo
.lsp
.calva
*.svg
/logs

View File

@@ -1,22 +1,53 @@
# riot-clojure
This is a simple tool for using Riot Games public API. Those APIs are HUGE and
privide a lot of information, but this tool is focused in the time data of matches.
provide 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
starting and ending timestams we can create a chronogram with the time spent
starting and ending timestamps we can create a chronogram with the time spent
playing.
This simple program needs a developer API. You can obtain a 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.
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.
By default, the application searchs for the API key in the environment variable
`RIOT_API`. If you prefer to provide the key via parameter, you must use `-k KEY` or
`--key=KEY`.
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.
## 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 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
@@ -39,7 +70,7 @@ Goals of the project, ordered from easiest to hardest:
* [x] Win / Loss match
* [x] Distribution as uberjar
* [x] Distribution as native Linux executable
* [ ] Unit tests: move "comment" manual tests to real unit tests
* [x] Unit tests: move "comment" manual tests to real unit tests
* [ ] Output as a graphical chronogram or calendar.
* [ ] Simple web server
* [ ] Universal player search
@@ -53,29 +84,35 @@ Download release from https://git.rcorral.es/ruben/riot-clojure/releases.
## Usage
You can execute the app without params to get the help. Uberjar distribution needs
to be run with your java executable and the `-jar` parameter:
Using the Java uberjar
$ java -jar riot-clojure-1.0.0-standalone.jar [args]
Linux native imagen can be executed directly:
$ 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
There are a lot of paremeters. Execute the app without parameters to see all of them.
Run the application without params or wich `-?` param to show all options:
$ ./riot -?
## Examples
The most tipycal usage is to show matches from some date (in ISO format):
Show all matches from a march the 1st (in ISO format):
$ ./riot <username> <tag> -s "2025-01-01"
$ ./riot t <username> <tag> -s "2025-03-01"
If you wan't to export data as a CSV file
Get te same data in CSV and store it in a file:
$ ./riot <username> <tag> -s "2025-01-01" -o csv > matches.csv
$ ./riot t <username> <tag> -s "2025-03-01" -o csv > results.csv
Don't format durations, show them in seconds
$ ./riot t <username> <tag> -s "2025-03-01" --no-format-durations
## License

View File

@@ -1,4 +1,4 @@
(defproject riot-clojure "1.0.0"
(defproject riot-clojure "1.1.0"
:description "Utility for getting for Riot APIs in Clojure"
:url "https://git.rcorral.es/ruben/riot-clojure"
@@ -10,10 +10,15 @@
[cheshire/cheshire "6.0.0"]
[slingshot/slingshot "0.12.2"]
[org.clojure/tools.cli "1.1.230"]
[cli-matic/cli-matic "0.5.4"]
[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"]]
[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"]]
:jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory"]
:plugins [[io.taylorwood/lein-native-image "0.3.1"]]
@@ -23,8 +28,26 @@
: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"]} ;; 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"]}
: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})

36
resources/logback.xml Normal file
View 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>

View File

@@ -6,7 +6,9 @@
[cli-matic.utils :as U]
[clojure.pprint :as pp]
[clojure.string :as str]
[timewords.core :refer [parse]])
[timewords.core :refer [parse]]
[clojure.tools.logging :as log]
)
(:use [riot.core]
[riot.data]
[slingshot.slingshot :only [try+]])
@@ -22,158 +24,115 @@
(some? puuid) puuid
(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
"Checks if a player is online at the moment"
[& {:keys [lol-api-key tft-api-key debug-http _arguments]
[& {:keys [lol-api-key tft-api-key debug-http lol tft _arguments]
:as opts}]
(try+
;(println "Check online" 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)
(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)
(println "Yes, it's playing")
(println "No, it's not playing right now"))))
(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))))
(comment
(cmd-active :_arguments ["annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ"])
(cmd-active :_arguments ["Errepunto" "4595"]))
(defn format-map
(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))
"table" (as-ascii-table x)
"csv" (println (as-csv x))
(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
[format-dates format-durations pattern x]
(let [dur (if format-durations
(with-parsed-durations x)
x)]
"Format dates and durations if specified"
[format-dates pattern x]
(if format-dates
(if (some? pattern)
(with-parsed-dates dur :datetime-format pattern)
(with-parsed-dates dur))
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))
(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-today-cond
[only-today x] (if only-today (filter match-today? x) 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)))
(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
"Converts an string into a java.util.Date"
[x]
(if (nil? x) nil (parse x)))
#_{:clj-kondo/ignore [:unresolved-symbol]}
(defn cmd-timeline
"Get match "
[& {: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}]
(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-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))
(filter-today-cond only-today)
(reverse-cond reverse)
(take count)
(format-dates-cond format-dates format-durations date-format)
(filter-columns show-columns)
(map)
(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))))
(comment
(cmd-timeline :_arguments ["dMNR4Aj5OW9jrGj0RUVBRuzfu77p3iO5y1W16ASNp1PI7pxuJWLz14b2pJiUn16DCPlyeREoi0MJ7Q"])
(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]
[& {: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 (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))
(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 format-durations date-format)
(format-dates-cond format-dates date-format)
(format-duration-cond format-durations)
(filter-columns show-columns)
(format-map output))))
(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))))
@@ -181,17 +140,25 @@
;; More info: https://github.com/l3nz/cli-matic/blob/master/README.md
(def CONFIGURATION
{:app {:command "riot"
:description ["Get how much time have you spent on LoL or FTF"
:version "1.1.0"
:description ["Get how much you play your favourite games"
""
"Posible error codes:"
" 0: OK"
" 1: player not playing"
" 2: Invalid params"
" 3: Invalid API key, please, get 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"]
:version "1.0.0"}
"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
@@ -203,114 +170,49 @@
{:option "debug-http"
:as "Show debug for HTTP connections. I'ts very verbose!"
: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"
:description ["Shows if the player currently playing"
"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"}]
: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 "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
: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"
:type :string :default nil
:as ["Starting date (included) in 'yyyy-MM-dd' format."
"Other valid values: today, yesterday"]}
{:option "until" :short "u"
:type :string :default nil
:as ["Ending date (not included) in 'yyyy-MM-dd' format."
"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."
:description ["Get info from LOL and TFT (experimental, disabled by default) matches in a time range."
""
"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"
"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 "Summoner name (Riot ID)"}
{:option "tag" :short "t"
:type :string :default nil
:as "Summoner tagline"}
:as ["Ending date (not included) in 'yyyy-MM-dd' format."
"Other valid values: today, yesterday, tomorrow, etc"]}
{:option "output" :short "o"
:type #{"edn" "json" "table" "csv"} :default "table"
:as ["Output type. It can be one of:"
: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"
" - table: ASCII table (by default)"
" - csv: CSV data, using ',' as field separator"]}
{:option "order-by"
:type #{"start" "end" "duration"} :default "start"
@@ -337,50 +239,22 @@
" - 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"
: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}]})
{: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))
(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
; Launch manually
(-main)
(old-main))
)

View File

@@ -4,7 +4,9 @@
(ns riot.core
(:require [clj-http.client :as client]
[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+]])
(:gen-class))
@@ -33,7 +35,7 @@
;;;; API KEYS
(def DEV_KEY "RGAPI-fc8c6d88-2c13-4a43-b53f-3465bb564acc")
(def DEV_KEY "RGAPI-ec3779d1-bc61-4a2e-a071-34addcc6bd56")
(def LOL_KEY "jXL+gA3LIeBPBvrOhLOYSZCiURC7eOtwMXahkxtwpdj6JDtT5NMu25zMz+UY2+9MuHBADjUJh46jSanrV5OBag==")
(def TFT_KEY (encrypt-data DEV_KEY secret-key iv))
@@ -102,61 +104,54 @@
[data 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
"Takes the original list of matches and parses durations"
"Takes the original list of matches and adds winner info"
[matches puuid]
(map #(assoc % :winner (winner? % puuid)) matches))
(comment
(with-winner-status riot.test-examples/matches-example riot.test-examples/example-lol-puuid))
;; 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] :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 [: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 "ftf"))
;; (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 [: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] :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] :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 "ftf"))
;; (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] :winner (constantly nil))
(make-json-data [:none] :result (constantly nil))]})
(defn parse-response
@@ -186,6 +181,7 @@
: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
@@ -242,8 +238,10 @@
debug false
server "europe"
count 20
start 0}}]
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
@@ -258,7 +256,9 @@
debug false
server "europe"
count 20
start 0}}]
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
@@ -274,6 +274,7 @@
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
@@ -290,6 +291,7 @@
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
@@ -298,7 +300,7 @@
: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]
@@ -306,16 +308,19 @@
debug false
print-not-active false
server "euw1"}}]
(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")))))
(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"))))))
#_{: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]
@@ -323,14 +328,16 @@
debug false
print-not-active false
server "euw1"}}]
(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 FTF match")))))
(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"))))))
@@ -341,13 +348,13 @@
tft-api-key (get-tft-api-key)
debug 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))
(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 FTF 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)
@@ -374,14 +381,14 @@
;;;; 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)
(getter) ;; Executes getter function
(catch [:status 429] _
(println "Rate limit exceeded, waiting for" wait-limit-exceeded "seconds")
(Thread/sleep (* 1000 wait-limit-exceeded))
@@ -395,6 +402,7 @@
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)
@@ -471,18 +479,23 @@
(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 count order-by since until]
[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}}]
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)))))
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,10 +4,12 @@
(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])
(:use [riot.core :refer :all])
[cheshire.core :refer :all]
[riot.core :refer :all]
[clj-commons.format.table :refer [print-table] :as table])
(:gen-class))
@@ -17,32 +19,37 @@
;; Parse UNIX time
(defn unix-to-datetime
"Conver from UNIX time in millis, to a ZonedDateTime"
(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-to-date
(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 parse-localdate
(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 parse-date-str
(defn str->Date
[s]
(localdate-to-date (parse-localdate s)))
(LocalDate->Date (str->LocalDate s)))
(defn parse-date-epoch
(defn unix->Date
[millis]
(localdate-to-date (unix-to-datetime millis)))
(LocalDate->Date (unix->ZonedDateTime millis)))
(defn format-datetime
@@ -59,18 +66,21 @@
([millis] (format-datetime-millis millis DEFAULT_DATE_TIME_FORMAT))
([millis pattern]
(when (and (some? millis) (some? pattern))
(format-datetime (unix-to-datetime millis) pattern))))
(format-datetime (unix->ZonedDateTime millis) pattern))))
;; Parse durations
(defn format-duration-seconds
"Converts a duration in seconds to a string with hours and 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) (rem seconds 60)))))
(format "%02d:%02d:%02d"
(quot seconds 3600)
(- (quot seconds 60) (* 60 (quot seconds 3600)))
(rem seconds 60)))))
@@ -101,9 +111,6 @@
(->> matches
(map #(update % :start (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
@@ -112,7 +119,6 @@
(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]
@@ -139,21 +145,21 @@
(generate-string matches {:pretty pretty}))
(comment
(println (as-json (with-parsed-dates-durations riot.test-examples/matches-example
:pretty false))))
(defn as-ascii-table
"Export as ascii 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
@@ -166,8 +172,79 @@
(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
(as-csv riot.test-examples/matches-example)
(as-csv riot.test-examples/matches-example2)
(as-csv (with-parsed-dates-durations riot.test-examples/matches-example))
(as-csv (with-parsed-dates-durations riot.test-examples/matches-example2)))
(calculate-statistics '({:winner true} {:winner false} {:winner true}))
)

View File

@@ -1,10 +1,12 @@
(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])
(:use [slingshot.slingshot :only [try+]]))
[clojure.pprint :as pp]
[slingshot.slingshot :refer [try+]]))
@@ -30,8 +32,8 @@
(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 (= (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")))))
@@ -121,7 +123,7 @@
(comment (get-puuid-from-name example-name example-tag :api-key (get-tft-api-key)))
;; From java.util.Date to seconds since 1970
(deftest test-date-to-seconds
(deftest ^:timezone test-date-to-seconds
(println "* Date to epoch seconds *")
(testing "Some dates"
(is (nil? (date-to-seconds nil)))
@@ -130,7 +132,7 @@
;; Get parameter for querying data
(deftest test-query-params
(deftest ^:timezone test-query-params
(println "* Generate query params *")
(testing "None params"
(is (= {"count" 100} (query-params))))
@@ -241,7 +243,7 @@
(testing "Get LOL matches"
(is (= 10 (count (get-matches-batch-lol example-lol-puuid (get-lol-api-key) :count 5 :max-batches 2))))))
(deftest test-get-matches-batch-tft
(deftest ^:tft test-get-matches-batch-tft
(println "* Get tft matches batch *")
(testing "Invalid data"
(is (= nil (get-matches-batch-tft nil nil)))
@@ -264,7 +266,7 @@
(get-lol-api-key)))))))
(deftest test-get-matches-info-batch-tft
(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)))

View File

@@ -1,5 +1,6 @@
(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])
@@ -9,30 +10,38 @@
;; Convert from epoch in millis (1970-01-01 00:00:00) to ZonedDateTime
(deftest test-unix-to-datetime
(deftest ^:timezone test-unix->ZonedDateTime
(println "* Epoch millis to java.util.Datetime *")
(testing "Get LOL API key"
(testing "Synthetic data"
(is (= (ZonedDateTime/of 2025 4 3 0 0 0 0 (ZoneId/systemDefault))
(unix-to-datetime 1743631200000)))))
(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 test-format-datetime
(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-to-datetime 1743631200000)))))
(format-datetime (unix->ZonedDateTime 1743631200000)))))
(testing "Personalized time formats"
(is (= "2025-04-03"
(format-datetime (unix-to-datetime 1743631200000) "yyyy-MM-dd")))
(format-datetime (unix->ZonedDateTime 1743631200000) "yyyy-MM-dd")))
(is (= "00:00"
(format-datetime (unix-to-datetime 1743631200000) "HH:00")))))
(format-datetime (unix->ZonedDateTime 1743631200000) "HH:00")))))
;; Format a epoch in millis as a String
(deftest test-datetime-millis
(deftest ^:timezone test-datetime-millis
(println "* DateTime to String *")
(testing "Null"
(is (nil? (format-datetime-millis nil))))
@@ -58,11 +67,14 @@
(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 (= "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 test-today-millis
(deftest ^:timezone test-today-millis
(println "* Epoch for today *")
(testing "Epoch for today"
(is (some? (today-millis)))))
@@ -80,7 +92,7 @@
;; Parse dates inside a lis of matches
(deftest test-with-parsed-dates
(deftest ^:timezone test-with-parsed-dates
(println "* Parse dates in a match list*")
(testing "Empty or invalid lists"
(is (= '() (with-parsed-dates '())))
@@ -92,7 +104,7 @@
;; Parse durations
(deftest test-with-parsed-dates
(deftest ^:timezone test-with-parsed-durations
(println "* Parse durations in a match list *")
(testing "Empty or invalid lists"
(is (= '() (with-parsed-durations '())))
@@ -104,7 +116,7 @@
;; Parse dates and durations
(deftest test-with-parsed-dates
(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 '())))
@@ -113,7 +125,55 @@
(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", :active true, :game-type "lol", :id "EUW1_111111"})
(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))
)

View File

@@ -8,7 +8,7 @@
(def example-tft-puuid "yJawqnx9nzvj9ZJdW_7ok0wmB2WqSqafKy1sDrA48Zefbx0Iuco9jfGyVdUhldjDt8IxLvMcs4r8MA")
(def example-lol-match {:start 1750106168280, :end 1750108132077, :duration 1882, :active false, :game-type "lol", :id "EUW1_7434497430" :winner false})
(def example-lol-match {:start 1750106168280, :end 1750108132077, :duration 1882, :game-type "lol", :id "EUW1_7434497430" :winner false :result "GameComplete"})
(def example-lol-never-played "annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ")
@@ -17,83 +17,120 @@
'({:start 1749661254854,
:end nil,
:duration 1288,
:active true,
:game-type "lol"
:id "EUW1_111111"}
:id "EUW1_111111"
:winner true,
:result "GameComplete"}
{:start 1749659482211,
:end 1749661133145,
:duration 1622,
:active false,
:game-type "lol"
:id "EUW1_222222"}
:id "EUW1_222222"
:winner false,
:result "GameComplete"}
{:start 1749655924238,
:end 1749658146178,
:duration nil,
:active false,
: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
'({:start 1749893615000,
:end 1749896091000,
:duration 2476,
:active false,
:game-type "ftf",
:id "EUW1_7431741880"}
{:start 1749886475803,
:end 1749887505751,
:duration 999,
:active false,
'({:start 1748470702886,
:end 1748472666457,
:duration 1947,
:game-type "lol",
:id "EUW1_7431686898"}
{:start 1749830764708,
:end 1749831241808,
:duration 232,
:active false,
:id "EUW1_7415410708",
:winner true
:result "GameComplete"}
{:start 1748466492126,
:end 1748468105264,
:duration 1585,
:game-type "lol",
:id "EUW1_7431025296"}
{:start 1749740947765,
:end 1749743270521,
:duration 2214,
:active false,
:id "EUW1_7415317950",
:winner false
:result "GameComplete"}
{:start 1748180985396,
:end 1748182761075,
:duration 1754,
:game-type "lol",
:id "EUW1_7429951366"}
{:start 1749739258737,
:end 1749740607134,
:duration 1311,
:active false,
:id "EUW1_7411793848",
:winner true}
:result "GameComplete"
{:start 1748178332000,
:end 1748180377000,
:duration 2045,
:game-type "tft",
:id "EUW1_7411747119",
:winner true
:result "GameComplete"}
{:start 1748119701678,
:end 1748121421926,
:duration 1578,
:game-type "lol",
:id "EUW1_7429932844"}
{:start 1749737844367,
:end 1749739185680,
:duration 1144,
:active false,
:id "EUW1_7411183430",
:winner true
:result "GameComplete"}
{:start 1748117211000,
:end 1748119477000,
:duration 2266,
:game-type "tft",
:id "EUW1_7411136542",
:winner true
:result "GameComplete"}
{:start 1748110984521,
:end 1748112698320,
:duration 1694,
:game-type "lol",
:id "EUW1_7429905240"}
{:start 1749736215680,
:end 1749737698464,
:duration 1425,
:active false,
:id "EUW1_7410995061",
:winner true
:result "GameComplete"}
{:start 1748103234000,
:end 1748105725000,
:duration 2491,
:game-type "tft",
:id "EUW1_7410863506",
:winner true
:result "GameComplete"}
{:start 1748097496984,
:end 1748099470873,
:duration 1933,
:game-type "lol",
:id "EUW1_7429884230"}
{:start 1749733140474,
:end 1749734774103,
:duration 1535,
:active false,
:id "EUW1_7410750999",
:winner false
:result "GameComplete"}
{:start 1748094441000,
:end 1748096636000,
:duration 2195,
:game-type "tft",
:id "EUW1_7410701935",
:winner true
:result "GameComplete"}
{:start 1748088640068,
:end 1748090386650,
:duration 1701,
:game-type "lol",
:id "EUW1_7429836245"}
{:start 1749727474332,
: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"}))
:id "EUW1_7410601082",
:winner true
:result "GameComplete"}))
(def response-player-info {:puuid "annlAfxhJnTwlwRgQZYbGeQpD3jWb-ju7vVKEW_g-EIJf6xQT0eeb-0obARVekrksf8n9XCjcxyHHQ",