Compare commits

...

44 Commits

Author SHA1 Message Date
be4ccf088f Resolving conflicts after rebase
Some checks failed
Compile and test using leiningen / Run tests (push) Failing after 6m2s
2025-08-13 15:24:42 +02:00
22bd6c766b Update README.md 2025-08-13 15:19:26 +02:00
f77815ad7f Reestrcture and new graph library 2025-08-13 15:19:23 +02:00
e75d9219e1 Update README.md
Added idea for charts
2025-08-13 15:18:06 +02:00
2c4f836829 graph tests 2025-08-13 15:16:35 +02:00
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
2fd314b972 Update README.md 2025-07-15 12:38:29 +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
3a2679040e Retrofit test changes from graph branch 2025-07-07 23:19:49 +02:00
a50cb8a3eb Take some changes from graph branch 2025-07-07 23:13:40 +02:00
11 changed files with 701 additions and 484 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
@@ -49,25 +80,38 @@ Goals of the project, ordered from easiest to hardest:
## Installation
Download from http://example.com/FIXME.
Download release from https://git.rcorral.es/ruben/riot-clojure/releases.
## Usage
FIXME: explanation
Using the Java uberjar
$ 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
FIXME: listing of options this app accepts.
Run the application without params or wich `-?` param to show all options:
$ ./riot -?
## Examples
...
Show all matches from a march the 1st (in ISO format):
### Bugs
$ ./riot t <username> <tag> -s "2025-03-01"
...
Get te same data in CSV and store it in a file:
$ ./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
### Graphs

View File

@@ -1,20 +1,24 @@
(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"
:license {:name "MIT"
:url "https://mit-license.org/"}
:dependencies [[org.clojure/clojure "1.11.1"] ;; Clojure itself
[clj-http/clj-http "2.0.0"] ;; HTTP client
[cheshire/cheshire "6.0.0"] ;; Convert to/from JSON
[slingshot/slingshot "0.12.2"] ;; Better exceptions for clj-http
;; [org.clojure/tools.cli "1.1.230"] ;; Parse params. NOT USED
[cli-matic/cli-matic "0.5.4"] ;; Parse params and make a nice CLI app
[buddy/buddy-core "1.12.0-430"] ;; Cipher data
[org.clojure/tools.namespace "1.5.0"] ;; Hot reload from REPL
[lt.tokenmill/timewords "0.5.0"] ;; Parse dates and times in natural languaje
:dependencies [[org.clojure/clojure "1.11.1"]
[clj-http/clj-http "2.0.0"]
[cheshire/cheshire "6.0.0"]
[slingshot/slingshot "0.12.2"]
[org.clojure/tools.cli "1.1.230"]
[cli-matic/cli-matic "0.5.4"] ;; https://github.com/l3nz/cli-matic
[buddy/buddy-core "1.12.0-430"]
[org.clojure/tools.namespace "1.5.0"]
[lt.tokenmill/timewords "0.5.0"] ;; https://github.com/tokenmill/timewords
[org.clj-commons/pretty "3.5.0"] ;; https://github.com/clj-commons/pretty
[org.clojure/tools.logging "1.3.0"] ;; https://github.com/clojure/tools.logging
[org.slf4j/slf4j-api "2.0.17"] ;; https://www.slf4j.org/
[ch.qos.logback/logback-classic "1.5.18"]
;;; LIBS FOR GRAPHS, SOME OF THEM WILL BE REMOVED IN THE FUTURE
;[com.hypirion/clj-xchart "0.2.0"] ;; Graphs and charts
@@ -39,4 +43,23 @@
: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,179 +24,141 @@
(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]
"Get all matches"
[& {:keys [lol-api-key tft-api-key lol tft output format-dates format-durations date-format reverse order-by show-columns debug-http since until statistics _arguments]
:as opts}]
(try+
;(println "Get timeline" opts)
(let [lol-key (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)
;(println "Get timeline" opts)
(let [lol-key (when lol (get-lol-api-key lol-api-key))
tft-key (when tft (get-tft-api-key tft-api-key))
lol-id (when lol (get-puuid-from-params lol-key _arguments opts))
tft-id (when tft (get-puuid-from-params tft-key _arguments opts))
since-date (get-date since)
until-date (get-date until)]
(log/info (str "Fetching data between " since-date " and " (if (some? until-date) until-date (java.util.Date.))))
(if (and (nil? lol-id) (nil? tft-id))
(U/exit! "No games selected" 2)
(->> (get-matches-info-batch lol-id tft-id :lol-api-key lol-key :tft-api-key tft-key :debug debug-http :order-by (keyword order-by) :since since-date :until until-date)
(calculate-statistics-cond statistics)
(reverse-cond reverse)
(take count)
(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)
(map )
(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))))
(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]
: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))
))
(U/exit! "Rate limit exceeded, please wait some minutes and try again" 4))
(catch [:status 502] _
(U/exit! "Server timeout, please wait some minutes and try again" 6))
(catch [:status 503] _
(U/exit! "Server timeout, please wait some minutes and try again" 6))))
;; More info: https://github.com/l3nz/cli-matic/blob/master/README.md
(def CONFIGURATION
{:app {:command "riot"
: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
@@ -206,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"
@@ -340,51 +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-e068aea3-5828-48e2-9500-259ef96c8c4f")
(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,63 +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))]
:post #(post-calculate-win
(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
@@ -188,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
@@ -221,7 +215,7 @@
(defn date-to-seconds
([] (date-to-seconds (java.util.Date. )))
([] (date-to-seconds (java.util.Date.)))
([d]
(when (some? d) (quot (.getTime d) 1000))))
@@ -244,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
@@ -260,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
@@ -276,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
@@ -292,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
@@ -300,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]
@@ -308,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]
@@ -325,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"))))))
@@ -343,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)
@@ -359,7 +364,7 @@
server1 "europe"
server2 "euw1"
count 20
start 0
start 0
order-by :start}}]
(sort-by order-by
(filter some?
@@ -376,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))
@@ -397,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)
@@ -425,9 +431,9 @@
debug false}}]
(when (every? some? [puuid api-key])
(get-batch-range #(get-lol-matches puuid :api-key api-key :start (* count %) :since since :until until :server server :wait-limit-exceeded wait-limit-exceeded :debug debug :count count)
:wait-limit-exceeded wait-limit-exceeded
:first-batch first-batch
:max-batches max-batches)))
:wait-limit-exceeded wait-limit-exceeded
:first-batch first-batch
:max-batches max-batches)))
(defn get-matches-batch-tft
@@ -446,12 +452,12 @@
:max-batches max-batches)))
(defn get-matches-info-batch-lol
(defn get-matches-info-batch-lol
"Get a lot of LOL matches info"
[matches-id puuid api-key & {:keys [server wait-limit-exceeded debug]
:or {server "europe"
wait-limit-exceeded 15
debug false}}]
:or {server "europe"
wait-limit-exceeded 15
debug false}}]
;; (println "Matches: " matches-id)
(when (every? some? [matches-id puuid api-key])
(get-batch-seq #(get-lol-match-info % :puuid puuid :api-key api-key :server server :wait-limit-exceeded wait-limit-exceeded :debug debug)
@@ -473,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,28 +19,42 @@
;; 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))
)
(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 unix->Date
[millis]
(LocalDate->Date (unix->ZonedDateTime millis)))
(defn parse-date-epoch
[millis]
(localdate-to-date (unix-to-datetime millis)))
(defn parse-date-epoch
@@ -60,18 +76,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)
(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)))))
@@ -87,8 +106,8 @@
A match has been played today if its start date or end date is from today"
[match]
(or
(>= (or (:start match) 0) (today-millis))
(or
(>= (or (:start match) 0) (today-millis))
(>= (or (:end match) 0) (today-millis))))
@@ -102,10 +121,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
@@ -114,15 +129,13 @@
(map #(update % :duration format-duration-seconds) matches))
(defn with-parsed-dates-durations
"Takes the original list of matches and parses dates and durations"
[matches & {:keys [datetime-format]
:or {datetime-format DEFAULT_DATE_TIME_FORMAT}}]
(-> matches
(with-parsed-durations)
(with-parsed-dates :datetime-format datetime-format)
))
(with-parsed-durations)
(with-parsed-dates :datetime-format datetime-format)))
;;; Export to other formats
@@ -142,23 +155,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
@@ -168,12 +179,82 @@
(str/join (System/lineSeparator) ; Local EOL characteres
(concat
[(clojure.string/join separator (map name (keys (first matches))))] ; header
(map #(clojure.string/join separator %)
(map vals matches))))))
(map #(clojure.string/join separator %)
(map vals matches))))))
(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))
)
(defn calculate-num-days-played
[matches]
(count (partition-by #(unix->LocalDate (:start %)) matches))
)
(defn calculate-days-played
[matches]
(map #(.toString %) (sort (keys (group-by #(unix->LocalDate (:start %)) matches)))))
(defn calculate-total-seconds-played
[matches]
(reduce + (filter some? (map :duration matches))))
(defn calculate-seconds-played-per-day
[matches]
)
(defn calculate-statistics
"Calculate several statistics about the matches"
[matches]
(when (and (some? matches) (seq matches))
(let [matches-lol (filter #(= (:game-type %) "lol") matches)
matches-tft (filter #(= (:game-type %) "tft") matches)
total (count matches)
total-lol (count matches-lol)
total-tft (count matches-tft)
win (count (filter :winner matches))
win-lol (count (filter :winner matches-lol))
win-tft (count (filter :winner matches-tft))
loss (- total win)
loss-lol (- total-lol win-lol)
loss-tft (- total-tft win-tft)
win-percent (if (zero? total) 0 (* (/ win total) 100.0))
win-percent-lol (if (zero? total-lol) 0 (* (/ win-lol total-lol) 100.0))
win-percent-tft (if (zero? total-tft) 0 (* (/ win-tft total-tft) 100.0))
num-days-played (calculate-num-days-played matches)
num-days-played-lol (calculate-num-days-played matches-lol)
num-days-played-tft (calculate-num-days-played matches-tft)
days-played (calculate-days-played matches)
days-played-lol (calculate-days-played matches-lol)
days-played-tft (calculate-days-played matches-tft)
seconds-played (calculate-total-seconds-played matches)
seconds-played-lol (calculate-total-seconds-played matches-lol)
seconds-played-tft (calculate-total-seconds-played matches-tft)
seconds-per-day (if (zero? num-days-played) 0 (/ seconds-played num-days-played))
seconds-per-day-lol (if (zero? num-days-played-lol) 0 (/ seconds-played-lol num-days-played-lol))
seconds-per-day-tft (if (zero? num-days-played-tft) 0 (/ seconds-played-tft num-days-played-tft))]
{:total {:all total
:lol total-lol
:tft total-tft}
:win {:all win
:lol win-lol
:tft win-tft}
:loss {:all loss
:lol loss-lol
:tft loss-tft}
:win-percent {:all win-percent
:lol win-percent-lol
:tft win-percent-tft}
:num-days-played {:all num-days-played
:lol num-days-played-lol
:tft num-days-played-tft}
:days-played {:all days-played
:lol days-played-lol
:tft days-played-tft}
:time-played {:all (format-duration-seconds seconds-played)
:lol (format-duration-seconds seconds-played-lol)
:tft (format-duration-seconds seconds-played-tft)}
:played-per-day {:all (format-duration-seconds (int seconds-per-day))
:lol (format-duration-seconds (int seconds-per-day-lol))
:tft (format-duration-seconds (int seconds-per-day-tft))}})))
(comment
(calculate-statistics '({:winner true} {:winner false} {:winner true}))
)

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,17 +123,16 @@
(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)))
(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
(deftest test-query-params
(deftest ^:timezone test-query-params
(println "* Generate query params *")
(testing "None params"
(is (= {"count" 100} (query-params))))
@@ -230,10 +231,9 @@
(is (not res) (str "Oh, well, the player is playing: " res))))
(testing "Include current"
(let [res (is-playing? example-lol-puuid example-tft-puuid)]
(is (not res) (str "Oh, well, the player is playing: " res)))))
)
(is (not res) (str "Oh, well, the player is playing: " res))))))
(deftest test-get-matches-batch-lol
(println "* Get lol matches batch *")
(testing "Invalid data"
@@ -241,9 +241,9 @@
(is (= nil (get-matches-batch-lol example-lol-puuid nil)))
(is (= nil (get-matches-batch-lol nil (get-lol-api-key)))))
(testing "Get LOL matches"
(is (= 10 (count (get-matches-batch-lol example-lol-puuid (get-lol-api-key):count 5 :max-batches 2))))))
(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)))
@@ -252,7 +252,7 @@
(testing "Get LOL matches"
(is (= 10 (count (get-matches-batch-tft example-tft-puuid (get-tft-api-key) :count 5 :max-batches 2))))))
(deftest test-get-matches-info-batch-lol
(println "* Get lol matches info batch *")
(testing "Invalid data"
@@ -266,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)))
@@ -298,5 +298,4 @@
(get-matches-info-batch example-lol-puuid example-tft-puuid)
(catch [:status 404] {:keys [request-time headers body] :as excp}
(println "NOT Found 404")
(pp/pprint excp)))
)
(pp/pprint excp))))

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,12 +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)))))
@@ -77,12 +88,11 @@
(is (match-today? {:start (+ 5000 (today-millis))})))
(testing "End date today"
(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
(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 '())))
@@ -90,12 +100,11 @@
(is (= '({:a "AAA" :b 2 :start nil, :end nil}) (with-parsed-dates '({:a "AAA" :b 2})))))
(testing "Valid list"
(is (= '({:start "2025-06-11 19:00:54", :end nil, :duration 1288, :active true, :game-type "lol", :id "EUW1_111111"})
(with-parsed-dates (take 1 matches-example))))
))
(with-parsed-dates (take 1 matches-example))))))
;; 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 '())))
@@ -103,22 +112,68 @@
(is (= '({:a "AAA" :b 2 :duration nil}) (with-parsed-durations '({:a "AAA" :b 2})))))
(testing "Valid list"
(is (= '({:start 1749661254854, :end nil, :duration "21:28", :active true, :game-type "lol", :id "EUW1_111111"})
(with-parsed-durations (take 1 matches-example)))))
)
(with-parsed-durations (take 1 matches-example))))))
;; 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 '())))
(is (= '({:duration nil, :start nil, :end nil})
(is (= '({:duration nil, :start nil, :end nil})
(with-parsed-dates-durations '({}))))
(is (= '({:a "AAA" :b 2 :duration nil, :start nil, :end nil})
(with-parsed-dates-durations '({:a "AAA" :b 2})))))
(testing "Valid list"
(is (= '({:start "2025-06-11 19:00:54", :end nil, :duration "21:28", :active true, :game-type "lol", :id "EUW1_111111"})
(with-parsed-dates-durations (take 1 matches-example)))))
)
(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",