Compare commits

...

46 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
e9064f9b61 Update README.md 2025-07-15 19:41:28 +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
33410e68ed Reestrcture and new graph library 2025-07-15 19:17:51 +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
9cf756ffbd Update README.md
Added idea for charts
2025-07-08 15:46:23 +02:00
28720bfcd5 graph tests 2025-07-07 21:55:07 +02:00
12 changed files with 1058 additions and 427 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 .clj-kondo
.lsp .lsp
.calva .calva
*.svg
/logs

139
README.md
View File

@@ -1,22 +1,53 @@
# riot-clojure # riot-clojure
This is a simple tool for using Riot Games public API. Those APIs are HUGE and This is a simple tool for using Riot Games public API. Those APIs are HUGE and
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 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. playing.
This simple program needs a developer API. You can obtain a key from https://developer.riotgames.com/ This simple program needs two API keys: one for LOL and another one for TFT. You
Developer keys are valid for 24 hours only, but you can refresh the key easily can obtain a developer key from https://developer.riotgames.com/. Developer keys
from the developer's web site. 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 By default, the application searchs for the API key in the environment variables
`RIOT_API`. If you prefer to provide the key via parameter, you must use `-k KEY` or `LOL_API` and `TFT_API`. If you prefer to provide the key via parameter, you must use `--lol-api-key` or
`--key=KEY`. `--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
Goals for version 1.1:
* [x] Simplify CLI options and parameters
* [x] Calculate simple statistics: win/loss rate, hours played every day, etc.
* [x] Get more data and delete some unused fields
* [x] Clean and refactor some code
* [x] Logging
* [x] Get only LOL or TFT data
* [x] Use Gitea Actions
Goals of the project, ordered from easiest to hardest: Goals of the project, ordered from easiest to hardest:
* [x] Take API key from environment * [x] Take API key from environment
@@ -39,7 +70,7 @@ Goals of the project, ordered from easiest to hardest:
* [x] Win / Loss match * [x] Win / Loss match
* [x] Distribution as uberjar * [x] Distribution as uberjar
* [x] Distribution as native Linux executable * [x] Distribution as native Linux executable
* [ ] 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. * [ ] Output as a graphical chronogram or calendar.
* [ ] Simple web server * [ ] Simple web server
* [ ] Universal player search * [ ] Universal player search
@@ -49,29 +80,101 @@ Goals of the project, ordered from easiest to hardest:
## Installation ## Installation
Download from http://example.com/FIXME. Download release from https://git.rcorral.es/ruben/riot-clojure/releases.
## Usage ## Usage
FIXME: explanation Using the Java uberjar
$ java -jar riot-clojure-0.1.0-standalone.jar [args] $ java -jar riot-clojure-0.1.0-standalone.jar [args]
Using the linux native image:
$ ./riot [args]
Both are equivalent. In the examples we will use the native image, because is shorter.
## Options ## Options
FIXME: listing of options this app accepts. Run the application without params or wich `-?` param to show all options:
$ ./riot -?
## Examples ## 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:
### Any Other Sections $ ./riot t <username> <tag> -s "2025-03-01" -o csv > results.csv
### That You Think
### Might be Useful Don't format durations, show them in seconds
$ ./riot t <username> <tag> -s "2025-03-01" --no-format-durations
### Graphs
Posible ASCII art charts
#### Simple day
A one dimension diagram. Draw a tick each day the player has played
```
####### ### ###### ###########
------------------------------------------------------------------------------------------
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
june
```
#### Day and hour
The x axis represents days of month, while y axis represents hours of each day. A tick is drawed in the hour
when the player was playing.
```
23 |
22 |
21 |
20 |
19 | # #
18 | # # #
17 | # # # #
16 | #
15 | # # # #
14 | # # # #
13 | #
12 | # # # #
11 | # # # # #
10 | # # # #
09 | # #
08 |
07 |
06 |
05 |
04 |
03 |
02 |
01 |
00 |
------------------------------------------------------------------------------------------
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
june
```
## Interesting graphic and UI libraries
- Raster SVG: https://github.com/soulspace-org/cmp.batik
- Modify SVG graphics: https://github.com/stathissideris/dali
- Create Vega-lite SVG graphics: https://github.com/techascent/tech.viz
- Enhance CLI text: https://github.com/clj-commons/pretty?tab=readme-ov-file
- Universal UI: https://github.com/phronmophobic/membrane?tab=readme-ov-file
- Interesting libraries: https://www.clojure-toolbox.com/
## License ## License

View File

@@ -1,4 +1,4 @@
(defproject riot-clojure "1.0.0" (defproject riot-clojure "1.1.0"
:description "Utility for getting for Riot APIs in Clojure" :description "Utility for getting for Riot APIs in Clojure"
:url "https://git.rcorral.es/ruben/riot-clojure" :url "https://git.rcorral.es/ruben/riot-clojure"
@@ -10,21 +10,56 @@
[cheshire/cheshire "6.0.0"] [cheshire/cheshire "6.0.0"]
[slingshot/slingshot "0.12.2"] [slingshot/slingshot "0.12.2"]
[org.clojure/tools.cli "1.1.230"] [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"] [buddy/buddy-core "1.12.0-430"]
[org.clojure/tools.namespace "1.5.0"] [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"]
:plugins [[io.taylorwood/lein-native-image "0.3.1"]] ;;; LIBS FOR GRAPHS, SOME OF THEM WILL BE REMOVED IN THE FUTURE
;[com.hypirion/clj-xchart "0.2.0"] ;; Graphs and charts
;[aerial.hanami "0.15.1"] ;; Parse vega-lite data and generate graphics
;[folcon/oz "1.6.0-alpha6.2"] ;; Parse vega and vega-lite
[metasoarous/oz "1.6.0-alpha36"] ;; Other version of Oz
;[metasoarous/oz "2.0.0-alpha5"] ;; Newer version of Oz
[incanter/incanter-charts "1.9.3"] ;; graphics with Incanter
;[hswick/jutsu "0.1.1"] ;; Wrapper para plotly
[techascent/tech.viz "6.00-beta-16-4"] ;; Vega-lite parser (https://github.com/techascent/tech.viz)
]
:plugins [[io.taylorwood/lein-native-image "0.3.1"]] ;; Compile to native using graal
:main ^:skip-aot riot.app :main ^:skip-aot riot.app
:target-path "target/%s" :target-path "target/%s"
:native-image {:name "riot" ;; name of output image, optional :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 :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 :opts ["--verbose"]} ;; pass-thru args to GraalVM native-image, optional
:profiles {:uberjar {:aot :all :profiles {:uberjar {:aot :all
:jvm-opts ["-Dclojure.compiler.direct-linking=true"]} :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}
:native-image {:jvm-opts ["-Dclojure.compiler.direct-linking=true"]} :native-image {:jvm-opts ["-Dclojure.compiler.direct-linking=true"]}
:dev {:dependencies [[org.clojure/test.check "1.1.1"]]}}) :dev {:dependencies [[org.clojure/test.check "1.1.1"]]
:plugins [[io.taylorwood/lein-native-image "0.3.1"]
[lein-binplus "0.6.8"]]}}
;; lein bin configuration
:bin {:name "riot_clj"
:bin-path "~/bin"
:jvm-opts ["-server" "-Dfile.encoding=utf-8" "$JVM_OPTS"]}
;; Deploy to repository
:repositories {"gitea"
{:url "https://git.rcorral.es/api/packages/ruben/maven"
:username :env/DEPLOY_USER
:password :env/DEPLOY_TOKEN}}
;; Test selectors
:test-selectors {:default (complement (some-fn :tft :timezone))
:tft :tft
:timezone :timezone})

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

View File

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

View File

@@ -4,10 +4,12 @@
(ns riot.data (ns riot.data
(:import [java.time Instant LocalDate ZoneId Duration] (:import [java.time Instant LocalDate ZoneId Duration]
[java.time.format DateTimeFormatter]) [java.time.format DateTimeFormatter])
#_{:clj-kondo/ignore [:refer-all]}
(:require [clojure.pprint :as pp] (:require [clojure.pprint :as pp]
[clojure.string :as str] [clojure.string :as str]
[cheshire.core :refer :all]) [cheshire.core :refer :all]
(:use [riot.core :refer :all]) [riot.core :refer :all]
[clj-commons.format.table :refer [print-table] :as table])
(:gen-class)) (:gen-class))
@@ -17,27 +19,42 @@
;; Parse UNIX time ;; Parse UNIX time
(defn unix-to-datetime (defn unix->ZonedDateTime
"Conver from UNIX time in millis, to a ZonedDateTime" "Convert from UNIX time in millis, to a ZonedDateTime"
[millis] [millis]
(when (some? millis) (when (some? millis)
(.atZone (Instant/ofEpochMilli millis) (ZoneId/systemDefault)))) (.atZone (Instant/ofEpochMilli millis) (ZoneId/systemDefault))))
(defn unix->LocalDate
"Convert from UNIX time in millis to LocalDate"
[millis]
(when (some? millis)
(.toLocalDate (unix->ZonedDateTime millis))))
(defn localdate-to-date (defn LocalDate->Date
"Converts a java.time.LocalDate to an ancient java.util.Date" "Converts a java.time.LocalDate to an ancient java.util.Date"
[localdate] [localdate]
(when (some? localdate) (when (some? localdate)
(java.util.Date/from (.toInstant (.atZone (.atStartOfDay localdate) (ZoneId/systemDefault)))))) (java.util.Date/from (.toInstant (.atZone (.atStartOfDay localdate) (ZoneId/systemDefault))))))
(defn parse-localdate (defn str->LocalDate
"Parse a yyyy-MM-dd date to java.time.LocalDate" "Parse a yyyy-MM-dd date to java.time.LocalDate"
[s] [s]
(java.time.LocalDate/parse s (DateTimeFormatter/ofPattern DEFAULT_DATE_FORMAT))) (java.time.LocalDate/parse s (DateTimeFormatter/ofPattern DEFAULT_DATE_FORMAT)))
(defn parse-date-str (defn str->Date
[s] [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 (defn parse-date-epoch
@@ -59,18 +76,21 @@
([millis] (format-datetime-millis millis DEFAULT_DATE_TIME_FORMAT)) ([millis] (format-datetime-millis millis DEFAULT_DATE_TIME_FORMAT))
([millis pattern] ([millis pattern]
(when (and (some? millis) (some? pattern)) (when (and (some? millis) (some? pattern))
(format-datetime (unix-to-datetime millis) pattern)))) (format-datetime (unix->ZonedDateTime millis) pattern))))
;; Parse durations ;; Parse durations
(defn format-duration-seconds (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] [seconds]
(when (some? seconds) (when (some? seconds)
(if (< seconds 3600) (if (< seconds 3600)
(format "%02d:%02d" (quot seconds 60) (rem seconds 60)) (format "%02d:%02d" (quot seconds 60) (rem seconds 60))
(format "%02d:%02d:%02d" (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 +121,6 @@
(->> matches (->> matches
(map #(update % :start (fn [x] (format-datetime-millis x datetime-format)))) (map #(update % :start (fn [x] (format-datetime-millis x datetime-format))))
(map #(update % :end (fn [x] (format-datetime-millis x datetime-format)))))) (map #(update % :end (fn [x] (format-datetime-millis x datetime-format))))))
(comment
(with-parsed-dates riot.test-examples/matches-example))
(defn with-parsed-durations (defn with-parsed-durations
@@ -112,7 +129,6 @@
(map #(update % :duration format-duration-seconds) matches)) (map #(update % :duration format-duration-seconds) matches))
(defn with-parsed-dates-durations (defn with-parsed-dates-durations
"Takes the original list of matches and parses dates and durations" "Takes the original list of matches and parses dates and durations"
[matches & {:keys [datetime-format] [matches & {:keys [datetime-format]
@@ -139,21 +155,21 @@
(generate-string matches {:pretty pretty})) (generate-string matches {:pretty pretty}))
(comment
(println (as-json (with-parsed-dates-durations riot.test-examples/matches-example
:pretty false))))
(defn as-ascii-table (defn as-ascii-table
"Export as ascii table" "Export as ascii table"
[matches] [matches]
(pp/print-table matches)) (pp/print-table matches))
(comment
(as-ascii-table riot.test-examples/matches-example)
(as-ascii-table (with-parsed-dates-durations riot.test-examples/matches-example)))
(defn as-pretty-table
"Export as an ANSI coloured text table"
[matches]
(if (and (some? matches) (< 0 (count matches)))
(let [columns {:columns (-> (first matches) keys vec)
:style table/default-style}]
(print-table columns matches))
(println "No data available")))
(defn as-csv (defn as-csv
@@ -166,8 +182,79 @@
(map #(clojure.string/join separator %) (map #(clojure.string/join separator %)
(map vals matches)))))) (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 (comment
(as-csv riot.test-examples/matches-example) (calculate-statistics '({:winner true} {:winner false} {:winner true}))
(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)))

View File

@@ -0,0 +1,323 @@
(ns riot.graph_experiments
(:use riot.data)
(:require ;[com.hypirion.clj-xchart :as c]
[clojure.pprint :as pprint]
[clojure.math :as m]
[clojure.core.matrix :as mtx])
(:require [incanter.core :as incanter]
[incanter.stats :as stats]
[incanter.charts :as charts]
[incanter.datasets :as datasets])
(:require [oz.core :as oz])
(:require [tech.viz.vega :as vega]))
;; ;;;; XCHART
;; ;; XY chart
;; (def xchart_xy
;; (c/xy-chart {"Expected rate" [(range 10) (range 10)]
;; "Actual rate" [(range 10) (map #(+ % (rand-int 5) -2) (range 10))]}))
;; (comment
;; (c/view xchart_xy))
;; ;;;; HANAMI
;; (comment
;; (hc/xform ht/point-chart
;; :UDATA "data/cars.json"
;; :X "Horsepower" :Y "Miles_per_Gallon" :COLOR "Origin"))
;;;; OZ
(def test-plot
{:data {:values
[{:time "18:00" :volume 10}
{:time "18:02" :volume 41}
{:time "18:07" :volume 192}
{:time "18:30" :volume 257}
{:time "19:00" :volume 300}]
:format {:parse {:time "date:'%H:%M'"}}}
:encoding {:x {:field "time" :type "temporal" :timeUnit "hoursminutes"}
:y {:field "volume" :type "quantitative"}}
:mark "point"})
;;; to compile and view in Clojure - Oz:
(comment
(do
(println "calling (oz/start-server!)")
(oz/start-server!)
(println "calling (oz/view!)")
(oz/view! test-plot)
(println "calling (Thread/sleep)")
(Thread/sleep 5000))
)
;;;; INCANTER
;; Simple histogram
(comment
(incanter/view (charts/histogram (stats/sample-normal 1000))))
;; Boxplot demo
(defn create-box-plot [data]
(let [box-plot (charts/box-plot data
:title "Box Plot Example"
:y-label "Values"
:x-label "Dataset")]
(incanter/view box-plot)))
(def sample-data [5, 7, 8, 9, 10, 14, 15, 21, 23, 23, 24, 26, 28, 30, 37])
(comment
(create-box-plot sample-data))
;; Scatter
(comment
(incanter/view (charts/scatter-plot :Sepal.Length :Sepal.Width
:data (datasets/get-dataset :iris)))
(clojure.pprint/pprint (datasets/get-dataset :iris)))
;; Scatter grouped
(comment
(incanter/view (charts/scatter-plot :Sepal.Length :Sepal.Width
:group-by :Species
:data (datasets/get-dataset :iris)
:legend true)))
;; Scatter matrix (too complicated)
(comment
(incanter/view (charts/scatter-plot-matrix
(datasets/get-dataset :iris)
:nbins 20
:group-by :Species))
(incanter/with-data (datasets/get-dataset :iris) (incanter/view (charts/scatter-plot-matrix :nbins 20 :group-by :Species)))
(incanter/view (charts/scatter-plot-matrix
(datasets/get-dataset :chick-weight)
:group-by :Diet
:nbins 20)))
;; Heatmaps
(comment
(defn f [x y] (incanter/sin (incanter/sqrt (mtx/add (incanter/sq x) (incanter/sq y)))))
(incanter/view (charts/heat-map f -10 10 -15 15))
(incanter/view (charts/heat-map f -10 10 -10 10 :color? false))
(incanter/view (charts/heat-map f 5 10 5 10 :include-zero? false))
)
;; Personalize a scatter
(def my-data '({:start 1751537581404,
:end 1751539178158,
:duration 1583,
:active false,
:game-type "lol",
:id "EUW1_7450441192",
:winner true}
{:start 1751489335800,
:end 1751491099813,
:duration 1748,
:active false,
:game-type "lol",
:id "EUW1_7450117155",
:winner true}
{:start 1751486177961,
:end 1751487764068,
:duration 1568,
:active false,
:game-type "lol",
:id "EUW1_7450048068",
:winner true}
{:start 1751484450321,
:end 1751485680510,
:duration 1201,
:active false,
:game-type "lol",
:id "EUW1_7450013510",
:winner true}
{:start 1751400380588,
:end 1751401545922,
:duration 1133,
:active false,
:game-type "lol",
:id "EUW1_7449129548",
:winner true}
{:start 1751398221584,
:end 1751399322722,
:duration 1020,
:active false,
:game-type "lol",
:id "EUW1_7449082553",
:winner false}
{:start 1751394541765,
:end 1751396611534,
:duration 2040,
:active false,
:game-type "lol",
:id "EUW1_7449005657",
:winner false}
{:start 1751388187077,
:end 1751390143357,
:duration 1937,
:active false,
:game-type "lol",
:id "EUW1_7448887620",
:winner true}
{:start 1751384103153,
:end 1751385903519,
:duration 1767,
:active false,
:game-type "lol",
:id "EUW1_7448818616",
:winner false}
{:start 1751363056183,
:end 1751364555063,
:duration 1462,
:active false,
:game-type "lol",
:id "EUW1_7448564681",
:winner false}))
(defn extract-date-millis
"Extract date from a long epoch timestamp in milliseconds"
[x]
;(java.util.Date. x)
(let [date (new java.util.Date x)]
(. (java.util.Date. (. date getYear) (. date getMonth) (. date getDay)) getTime)))
(defn extract-date
"Extract date from a long epoch timestamp in milliseconds"
[x]
;(java.util.Date. x)
(let [date (new java.util.Date x)]
(java.util.Date. (. date getYear) (. date getMonth) (. date getDay))))
(comment
(extract-date-millis 1751363056183)
(. (extract-date-millis 1751363056183) getTime)
)
(defn extract-hour
"Extract hour from a long epoch timestamp in milliseconds"
[x]
;(java.util.Date. x)
(let [date (new java.util.Date x)]
(. date getHours)))
(comment
(java.util.Date. 1751537581404)
(java.util.Date. 1751539178158)
(extract-hour 1751537581404)
(extract-hour 1751539178158)
)
(defn extract-active-hours-millis
[match]
(when (every? some? [ (:start match) (:end match)])
(let [d-ini (extract-date-millis (:start match))
d-end (extract-date-millis (:end match))
h-ini (extract-hour (:start match))
h-end (extract-hour (:end match))
game-type (:game-type match)
winner (:winner match)]
(if (= d-ini d-end)
(for [day [d-ini]
hour (range h-ini (inc h-end))]
[day hour game-type winner])
(concat
(for [day [d-ini]
hour (range h-ini 24)]
[day hour game-type winner])
(for [day [d-ini]
hour (range 0 (inc h-end))]
[day hour game-type winner]))))))
(defn extract-active-hours
[match]
(when (every? some? [(:start match) (:end match)])
(let [d-ini (extract-date (:start match))
d-end (extract-date (:end match))
h-ini (extract-hour (:start match))
h-end (extract-hour (:end match))
game-type (:game-type match)
winner (:winner match)]
(if (= d-ini d-end)
(for [day [d-ini]
hour (range h-ini (inc h-end))]
[day hour game-type winner])
(concat
(for [day [d-ini]
hour (range h-ini 24)]
[day hour game-type winner])
(for [day [d-ini]
hour (range 0 (inc h-end))]
[day hour game-type winner]))))))
(comment
(extract-active-hours-millis (first my-data))
(partition 4 (flatten (map extract-active-hours-millis my-data)))
)
(defn extract-dataset-millis
[matches]
(incanter/dataset
[:days :hours :game-type :winner]
(partition 4 (flatten (map extract-active-hours-millis matches)))))
(defn extract-dataset
[matches]
(incanter/dataset
[:days :hours :game-type :winner]
(partition 4 (flatten (map extract-active-hours matches)))))
(comment
(extract-dataset-millis my-data)
(extract-dataset my-data)
(incanter/view (charts/scatter-plot :days :hours
:data (extract-dataset-millis my-data)
:group-by :game-type
:x-label "Day"
:y-label "Hour"
:legend false))
)
(comment
(defn process-my-data [x y] (+ x y (m/random)))
(incanter/view (charts/heat-map process-my-data
-10 10 -15 15
:x-label "Day"
:y-label "Hour"
:z-label "Count"
:color? true
:include-zero? false))
)
;;;; tech.viz
(comment
(let [ my-graph (vega/scatterplot [{:a 1 :b 2} {:a 2 :b 3}] :a :b)]
(vega/vega->svg-file my-graph "timeseries.svg"))
)

View File

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

View File

@@ -1,5 +1,6 @@
(ns riot.data-test (ns riot.data-test
(:require [clojure.test :refer :all] (:require [clojure.test :refer :all]
[clojure.pprint :as pp]
[riot.test-examples :refer :all] [riot.test-examples :refer :all]
[riot.data :refer :all] [riot.data :refer :all]
[riot.core :refer :all]) [riot.core :refer :all])
@@ -9,30 +10,38 @@
;; Convert from epoch in millis (1970-01-01 00:00:00) to ZonedDateTime ;; Convert from epoch in millis (1970-01-01 00:00:00) to ZonedDateTime
(deftest test-unix-to-datetime (deftest ^:timezone test-unix->ZonedDateTime
(println "* Epoch millis to java.util.Datetime *") (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)) (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 ;; Format a LocalDateTime o ZonedDateTime as a String
(deftest test-format-datetime (deftest ^:timezone test-unix->ZonedDateTime
(println "* DateTime to String *") (println "* DateTime to String *")
(testing "Null" (testing "Null"
(is (nil? (format-datetime-millis nil)))) (is (nil? (format-datetime-millis nil))))
(testing "Default format" (testing "Default format"
(is (= "2025-04-03 00:00:00" (is (= "2025-04-03 00:00:00"
(format-datetime (unix-to-datetime 1743631200000))))) (format-datetime (unix->ZonedDateTime 1743631200000)))))
(testing "Personalized time formats" (testing "Personalized time formats"
(is (= "2025-04-03" (is (= "2025-04-03"
(format-datetime (unix-to-datetime 1743631200000) "yyyy-MM-dd"))) (format-datetime (unix->ZonedDateTime 1743631200000) "yyyy-MM-dd")))
(is (= "00:00" (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 ;; Format a epoch in millis as a String
(deftest test-datetime-millis (deftest ^:timezone test-datetime-millis
(println "* DateTime to String *") (println "* DateTime to String *")
(testing "Null" (testing "Null"
(is (nil? (format-datetime-millis nil)))) (is (nil? (format-datetime-millis nil))))
@@ -58,11 +67,14 @@
(testing "Long durations" (testing "Long durations"
(is (= "01:00:00" (format-duration-seconds 3600))) (is (= "01:00:00" (format-duration-seconds 3600)))
(is (= "01:00:01" (format-duration-seconds 3601))) (is (= "01:00:01" (format-duration-seconds 3601)))
(is (= "01:01:01" (format-duration-seconds 3661))))) (is (= "01:01:01" (format-duration-seconds 3661)))
(is (= "05:53:09" (format-duration-seconds 21189)))
)
)
;; Epoch in millis for today at 00:00:00 hours. Difficult to test ;; Epoch in millis for today at 00:00:00 hours. Difficult to test
(deftest test-today-millis (deftest ^:timezone test-today-millis
(println "* Epoch for today *") (println "* Epoch for today *")
(testing "Epoch for today" (testing "Epoch for today"
(is (some? (today-millis))))) (is (some? (today-millis)))))
@@ -80,7 +92,7 @@
;; Parse dates inside a lis of matches ;; 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*") (println "* Parse dates in a match list*")
(testing "Empty or invalid lists" (testing "Empty or invalid lists"
(is (= '() (with-parsed-dates '()))) (is (= '() (with-parsed-dates '())))
@@ -92,7 +104,7 @@
;; Parse durations ;; Parse durations
(deftest test-with-parsed-dates (deftest ^:timezone test-with-parsed-durations
(println "* Parse durations in a match list *") (println "* Parse durations in a match list *")
(testing "Empty or invalid lists" (testing "Empty or invalid lists"
(is (= '() (with-parsed-durations '()))) (is (= '() (with-parsed-durations '())))
@@ -104,7 +116,7 @@
;; Parse dates and durations ;; 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 *") (println "* Parse dates and durations in a match list *")
(testing "Empty or invalid lists" (testing "Empty or invalid lists"
(is (= '() (with-parsed-dates-durations '()))) (is (= '() (with-parsed-dates-durations '())))
@@ -113,7 +125,55 @@
(is (= '({:a "AAA" :b 2 :duration nil, :start nil, :end nil}) (is (= '({:a "AAA" :b 2 :duration nil, :start nil, :end nil})
(with-parsed-dates-durations '({:a "AAA" :b 2}))))) (with-parsed-dates-durations '({:a "AAA" :b 2})))))
(testing "Valid list" (testing "Valid list"
(is (= '({:start "2025-06-11 19:00:54", :end nil, :duration "21:28", :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)))))) (with-parsed-dates-durations (take 1 matches-example))))))
;; Pretty table
(deftest test-as-pretty-table
(println "* Generates a pretty table *")
(testing "Some examples, printed to System.out"
(is (= nil (as-pretty-table [])))
(is (= nil (as-pretty-table nil)))
(is (= nil (as-pretty-table matches-example)))
(is (= nil (as-pretty-table matches-example2)))))
(deftest test-calculate-statistics
(println "* Calculate statistics *")
(testing "Simple check"
(is (nil? (calculate-statistics nil)))
(is (nil? (calculate-statistics '())))
(is (map? (calculate-statistics matches-example)))
(is (map? (calculate-statistics matches-example2))))
(let [data (calculate-statistics matches-example)]
(testing "With manual generated data"
(is (= 5 (get-in data [:total :all])))
(is (= 3 (get-in data [:total :lol])))
(is (= 2 (get-in data [:total :tft])))
(is (= 3 (get-in data [:win :all])))
(is (= 2 (get-in data [:loss :all])))
(is (= 60.0 (get-in data [:win-percent :all])))
(is (= 3 (get-in data [:num-days-played :all]))))))
(comment
matches-example
(.toLocalDate (unix->ZonedDateTime 1749661254854))
(partition-by #(unix->LocalDate (:start %)) matches-example)
(group-by #(unix->LocalDate (:start %)) matches-example)
(keys (group-by #(unix->LocalDate (:start %)) matches-example2))
(calculate-days-played matches-example2)
(reduce + (filter some? (map :duration matches-example)))
(calculate-statistics matches-example)
(calculate-statistics matches-example2)
(pp/pprint (calculate-statistics matches-example2))
)

View File

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