-
Notifications
You must be signed in to change notification settings - Fork 7
Plugins/lichess #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Plugins/lichess #133
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "steps": [ | ||
| { | ||
| "displayName": "Authenticate", | ||
| "dataStream": { "name": "currentUser" }, | ||
| "required": true, | ||
| "error": "Could not authenticate. Check your API token is valid and hasn't expired.", | ||
| "success": "Connected successfully." | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| [ | ||
| { | ||
| "name": "Lichess Player", | ||
| "sourceType": "Lichess Player", | ||
| "icon": "chess-knight", | ||
| "singular": "Player", | ||
| "plural": "Players" | ||
| }, | ||
| { | ||
| "name": "Lichess Team", | ||
| "sourceType": "Lichess Team", | ||
| "icon": "people-group", | ||
| "singular": "Team", | ||
| "plural": "Teams" | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "name": "currentUser", | ||
| "displayName": "Current User", | ||
| "description": "The authenticated Lichess account's own profile", | ||
| "tags": ["Users"], | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/account" | ||
| }, | ||
| "matches": "none", | ||
| "visibility": { "type": "hidden" }, | ||
| "metadata": [ | ||
| { "name": "id", "displayName": "ID", "shape": "string" }, | ||
| { "name": "username", "displayName": "Username", "shape": "string", "role": "label" }, | ||
| { "pattern": ".*" } | ||
| ], | ||
| "timeframes": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "name": "playerActivity", | ||
| "displayName": "Recent Player Activity", | ||
| "description": "Daily game and puzzle activity for a player, one row per active day", | ||
| "tags": ["Users", "Activity"], | ||
| "baseDataSourceName": "httpRequestScopedSingle", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/user/{{object.rawId}}/activity", | ||
| "postRequestScript": "playerActivity.js" | ||
| }, | ||
| "matches": { "sourceType": { "type": "oneOf", "values": ["Lichess Player"] } }, | ||
| "metadata": [ | ||
| { "name": "date", "displayName": "Date", "shape": "date", "role": "timestamp" }, | ||
| { "name": "gamesWin", "displayName": "Games won", "shape": "number" }, | ||
| { "name": "gamesLoss", "displayName": "Games lost", "shape": "number" }, | ||
| { "name": "gamesDraw", "displayName": "Games drawn", "shape": "number" }, | ||
| { "name": "puzzlesWin", "displayName": "Puzzles won", "shape": "number" }, | ||
| { "name": "puzzlesLoss", "displayName": "Puzzles lost", "shape": "number" }, | ||
| { "name": "puzzlesDraw", "displayName": "Puzzles drawn", "shape": "number" } | ||
| ], | ||
| "timeframes": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "name": "playerProfile", | ||
| "displayName": "Player Profile", | ||
| "description": "Current profile and ratings for a player, one row per player", | ||
| "tags": ["Users", "Rating"], | ||
| "baseDataSourceName": "httpRequestScopedSingle", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/user/{{object.rawId}}", | ||
| "postRequestScript": "playerProfile.js" | ||
| }, | ||
| "matches": { "sourceType": { "type": "oneOf", "values": ["Lichess Player"] } }, | ||
| "metadata": [ | ||
| { "name": "id", "displayName": "ID", "shape": "string", "visible": false }, | ||
| { "name": "username", "displayName": "Username", "shape": "string", "role": "label" }, | ||
| { "name": "title", "displayName": "Title", "shape": "string" }, | ||
| { "name": "url", "displayName": "Profile", "shape": ["url", { "label": "View on Lichess" }] }, | ||
| { "name": "createdAt", "displayName": "Created", "shape": "date" }, | ||
| { "name": "seenAt", "displayName": "Last seen", "shape": "date" }, | ||
| { "name": "gamesAll", "displayName": "Games played", "shape": ["number", { "thousandsSeparator": true }] }, | ||
| { "name": "gamesWin", "displayName": "Wins", "shape": ["number", { "thousandsSeparator": true }] }, | ||
| { "name": "gamesLoss", "displayName": "Losses", "shape": ["number", { "thousandsSeparator": true }] }, | ||
| { "name": "gamesDraw", "displayName": "Draws", "shape": ["number", { "thousandsSeparator": true }] }, | ||
| { "name": "bulletRating", "displayName": "Bullet rating", "shape": "number" }, | ||
| { "name": "blitzRating", "displayName": "Blitz rating", "shape": "number" }, | ||
| { "name": "rapidRating", "displayName": "Rapid rating", "shape": "number" }, | ||
| { "name": "classicalRating", "displayName": "Classical rating", "shape": "number" }, | ||
| { "sourceId": "id", "sourceType": "Lichess Player", "name": "username" } | ||
| ], | ||
| "timeframes": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "name": "playerRatingHistory", | ||
| "displayName": "Player Rating History", | ||
| "description": "Rating history for a player, one row per variant per rating change", | ||
| "tags": ["Rating", "Users"], | ||
| "baseDataSourceName": "httpRequestScopedSingle", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/user/{{object.rawId}}/rating-history", | ||
| "postRequestScript": "playerRatingHistory.js" | ||
| }, | ||
| "matches": { "sourceType": { "type": "oneOf", "values": ["Lichess Player"] } }, | ||
| "metadata": [ | ||
| { "name": "variant", "displayName": "Variant", "shape": "string", "role": "label" }, | ||
| { "name": "date", "displayName": "Date", "shape": "date", "role": "timestamp" }, | ||
| { "name": "rating", "displayName": "Rating", "shape": "number" } | ||
| ], | ||
| "timeframes": true | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| { | ||
| "name": "playerRecentGames", | ||
| "displayName": "Player Recent Games", | ||
| "description": "Games played by a player within the timeframe, one row per game", | ||
| "tags": ["Games"], | ||
| "baseDataSourceName": "httpRequestScopedSingle", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/games/user/{{object.rawId}}", | ||
| "errorHandling": { "type": "path", "realm": "payload", "path": "error" }, | ||
| "headers": [{ "key": "Accept", "value": "application/x-ndjson" }], | ||
| "getArgs": [ | ||
| { "key": "since", "value": "{{timeframe.unixStart * 1000}}" }, | ||
| { "key": "until", "value": "{{timeframe.unixEnd * 1000}}" }, | ||
| { "key": "opening", "value": "true" }, | ||
| { "key": "max", "value": "200" } | ||
| ], | ||
| "postRequestScript": "playerRecentGames.js" | ||
| }, | ||
| "matches": { "sourceType": { "type": "oneOf", "values": ["Lichess Player"] } }, | ||
| "metadata": [ | ||
| { "name": "id", "displayName": "Game ID", "shape": "string" }, | ||
| { "name": "rated", "displayName": "Rated", "shape": "boolean" }, | ||
| { "name": "variant", "displayName": "Variant", "shape": "string" }, | ||
| { "name": "speed", "displayName": "Speed", "shape": "string" }, | ||
| { "name": "createdAt", "displayName": "Started", "shape": "date", "role": "timestamp" }, | ||
| { "name": "whiteUsername", "displayName": "White", "shape": "string", "role": "label" }, | ||
| { "name": "whiteRating", "displayName": "White rating", "shape": "number" }, | ||
| { "name": "blackUsername", "displayName": "Black", "shape": "string" }, | ||
| { "name": "blackRating", "displayName": "Black rating", "shape": "number" }, | ||
| { "name": "winner", "displayName": "Winner", "shape": "string" }, | ||
| { "name": "openingName", "displayName": "Opening", "shape": "string" } | ||
| ], | ||
| "timeframes": true | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "name": "players", | ||
| "displayName": "Players", | ||
| "description": "Usernames configured to be tracked as players, one row per username", | ||
| "tags": ["Users"], | ||
| "baseDataSourceName": "httpRequestUnscoped", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/account", | ||
| "postRequestScript": "players.js" | ||
| }, | ||
| "matches": "none", | ||
| "visibility": { "type": "hidden" }, | ||
| "metadata": [ | ||
| { "name": "id", "displayName": "ID", "shape": "string" }, | ||
| { "name": "username", "displayName": "Username", "shape": "string", "role": "label" } | ||
| ], | ||
| "timeframes": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // dataStreams/scripts/playerActivity.js | ||
| // | ||
| // `games` is keyed by perf/variant (blitz, bullet, rapid, ...) with win/loss/draw | ||
| // directly on each entry. Correspondence games that finished that day are NOT | ||
| // reported under `games` - they appear under a separate `correspondenceEnds` key, | ||
| // keyed by variant, with the win/loss/draw nested one level deeper under `.score` | ||
| // (`correspondenceEnds.correspondence.score.win`). `correspondenceMoves` is moves | ||
| // made in ongoing correspondence games (no result yet) and is intentionally excluded. | ||
| const scoreOf = (perfEntry) => (perfEntry && perfEntry.score) || perfEntry || {}; | ||
| const sumField = (map, field) => | ||
| Object.values(map || {}).reduce((sum, perfEntry) => sum + (scoreOf(perfEntry)[field] || 0), 0); | ||
|
|
||
| result = (data || []).map((entry) => { | ||
| const puzzles = (entry.puzzles && entry.puzzles.score) || {}; | ||
|
|
||
| return { | ||
| date: entry.interval && entry.interval.start ? new Date(entry.interval.start).toISOString() : null, | ||
| gamesWin: sumField(entry.games, "win") + sumField(entry.correspondenceEnds, "win"), | ||
| gamesLoss: sumField(entry.games, "loss") + sumField(entry.correspondenceEnds, "loss"), | ||
| gamesDraw: sumField(entry.games, "draw") + sumField(entry.correspondenceEnds, "draw"), | ||
| puzzlesWin: puzzles.win || 0, | ||
| puzzlesLoss: puzzles.loss || 0, | ||
| puzzlesDraw: puzzles.draw || 0 | ||
| }; | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // dataStreams/scripts/playerProfile.js | ||
| const perfs = data.perfs || {}; | ||
| const rating = (key) => (perfs[key] ? perfs[key].rating : null); | ||
| const count = data.count || {}; | ||
|
|
||
| result = [ | ||
| { | ||
| id: data.id, | ||
| username: data.username, | ||
| title: data.title || "", | ||
| url: `https://lichess.org/@/${data.username}`, | ||
| createdAt: data.createdAt, | ||
| seenAt: data.seenAt, | ||
| gamesAll: count.all || 0, | ||
| gamesWin: count.win || 0, | ||
| gamesLoss: count.loss || 0, | ||
| gamesDraw: count.draw || 0, | ||
| bulletRating: rating("bullet"), | ||
| blitzRating: rating("blitz"), | ||
| rapidRating: rating("rapid"), | ||
| classicalRating: rating("classical"), | ||
| }, | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // dataStreams/scripts/playerRatingHistory.js | ||
| // The rating-history endpoint takes no query params and always returns the | ||
| // player's full history, so the timeframe filter is applied client-side here. | ||
| const rows = (data || []).flatMap((v) => | ||
| (v.points || []).map((p) => ({ | ||
| variant: v.name, | ||
| date: new Date(Date.UTC(p[0], p[1], p[2])).toISOString(), | ||
| rating: p[3], | ||
| })), | ||
| ); | ||
|
|
||
| const timeframe = context && context.timeframe; | ||
|
|
||
| result = | ||
| timeframe && timeframe.start && timeframe.end | ||
| ? rows.filter((r) => r.date >= timeframe.start && r.date <= timeframe.end) | ||
| : rows; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| // dataStreams/scripts/playerRecentGames.js | ||
| // The games/user endpoint returns NDJSON, one game object per line. `data` is | ||
| // undefined since NDJSON isn't valid JSON, so we parse `response.body` ourselves. | ||
| // Edge case: if exactly one game matches (common with narrow timeframes), that | ||
| // single line IS valid JSON, and the platform auto-parses it into an object | ||
| // instead of leaving it as text - so response.body can be a string, an | ||
| // already-parsed single game object, or (defensively) an array of them. | ||
| const UNFINISHED_STATUSES = ["created", "started"]; | ||
|
|
||
| const body = response.body; | ||
| const games = | ||
| typeof body === "string" | ||
| ? body | ||
| .split("\n") | ||
| .map((line) => line.trim()) | ||
| .filter(Boolean) | ||
| .map((line) => JSON.parse(line)) | ||
| : Array.isArray(body) | ||
| ? body | ||
| : body | ||
| ? [body] | ||
| : []; | ||
|
|
||
| result = games.map((game) => { | ||
| const white = (game.players && game.players.white) || {}; | ||
| const black = (game.players && game.players.black) || {}; | ||
|
|
||
| const winner = | ||
| game.winner || | ||
| (UNFINISHED_STATUSES.includes(game.status) ? null : "draw"); | ||
|
|
||
| return { | ||
| id: game.id, | ||
| rated: !!game.rated, | ||
| variant: game.variant, | ||
| speed: game.speed, | ||
| createdAt: game.createdAt ? new Date(game.createdAt).toISOString() : null, | ||
| whiteUsername: (white.user && white.user.name) || "Anonymous", | ||
| whiteRating: typeof white.rating === "number" ? white.rating : null, | ||
| blackUsername: (black.user && black.user.name) || "Anonymous", | ||
| blackRating: typeof black.rating === "number" ? black.rating : null, | ||
| winner: winner, | ||
| openingName: (game.opening && game.opening.name) || "", | ||
| }; | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||||||||||||||||||
| // dataStreams/scripts/players.js | ||||||||||||||||||||||
| // Calls GET /api/account purely as a cheap, already-authenticated endpoint to | ||||||||||||||||||||||
| // hang the request on. Lichess only offers a bulk "get users by name" | ||||||||||||||||||||||
| // endpoint as a raw text/plain POST body, which this plugin framework | ||||||||||||||||||||||
| // cannot send (a templated postBody is validated as JSON before send, and a | ||||||||||||||||||||||
| // preRequestScript body override isn't applied either - confirmed by testing). | ||||||||||||||||||||||
| // So the rows here come entirely from the plugin's configured usernames, not | ||||||||||||||||||||||
| // from this endpoint's response; per-player profile data is fetched by the | ||||||||||||||||||||||
| // dependent playerProfile step/stream instead. | ||||||||||||||||||||||
| const usernames = ( | ||||||||||||||||||||||
| (context.dataSources[0] && context.dataSources[0].usernames) || "" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| .split(",") | ||||||||||||||||||||||
| .map((s) => s.trim()) | ||||||||||||||||||||||
| .filter(Boolean); | ||||||||||||||||||||||
|
Comment on lines
+13
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: sed -n '1,120p' plugins/Lichess/v1/ui.json
sed -n '1,100p' plugins/Lichess/v1/configValidation.json
sed -n '1,100p' plugins/Lichess/v1/dataStreams/scripts/players.js
rg -n '"type": "textarea"|validation.*required|trim\(|delimiter|at least one' plugins | head -120Repository: squaredup/plugins Length of output: 10101 π Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Lichess references ---'
rg -n -S 'Lichess|validation.*required|required.*validation|textarea' README.md docs plugins package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null | rg -i 'validation|required|textarea|Lichess' | head -200
printf '%s\n' '--- validation/config handling references ---'
rg -n -S 'required.*(trim|whitespace)|trim.*required|validate.*required|validation.*required|\.required\(|required:\s*true' . --glob '!plugins/**/dataStreams/scripts/**' --glob '!node_modules' --glob '!dist' --glob '!build' | head -250
printf '%s\n' '--- relevant files ---'
git ls-files '*Lichess*' '*validation*' '*config*' | head -200Repository: squaredup/plugins Length of output: 20134 π€ get_repo_knowledge executed:
Length of output: 47842 π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
Reject an empty normalized username list. The UI Reject the empty list before Proposed fix .map((s) => s.trim())
.filter(Boolean);
+if (usernames.length === 0) {
+ throw new Error("Configure at least one Lichess username.");
+}
+
result = usernames.map((username) => ({π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| result = usernames.map((username) => ({ | ||||||||||||||||||||||
| id: username.toLowerCase(), | ||||||||||||||||||||||
| username, | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // dataStreams/scripts/teamMembers.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Response is NDJSON (one member object per line), not JSON, so `data` is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // undefined - parse `response.body` ourselves. Edge case: if the team has | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // exactly one member, that single line IS valid JSON, and the platform | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // auto-parses it into an object instead of leaving it as text - so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // response.body can be a string, an already-parsed single member object, or | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // (defensively) an array of them. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const body = response.body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| typeof body === "string" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? body | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .split("\n") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map((line) => line.trim()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter((line) => line.length > 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map((line) => JSON.parse(line)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : Array.isArray(body) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? body | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : body | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? [body] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π‘ Minor | β‘ Quick win π Supported by static analysisπ Script executed: sed -n '1,120p' plugins/Lichess/v1/dataStreams/scripts/teamMembers.js
sed -n '1,140p' plugins/Lichess/v1/dataStreams/teamMembers.json
sed -n '45,90p' plugins/Lichess/v1/defaultContent/team.dash.json
find plugins -name AGENTS.md -o -name README.md | head
rg -n 'ISO 8601|timestamp columns|joinedTeamAt|lichess.org/@/' plugins | head -120Repository: squaredup/plugins Length of output: 5141 π€ get_repo_knowledge executed:
Length of output: 47746 π Script executed: #!/bin/bash
printf '%s\n' '--- relevant Lichess files ---'
cat -n plugins/Lichess/v1/dataStreams/scripts/teamMembers.js
cat -n plugins/Lichess/v1/dataStreams/teamMembers.json
cat -n plugins/Lichess/v1/defaultContent/team.dash.json | sed -n '55,85p'
printf '%s\n' '--- related Lichess transformations ---'
cat -n plugins/Lichess/v1/dataStreams/scripts/playerProfile.js
cat -n plugins/Lichess/v1/dataStreams/scripts/playerActivity.js
printf '%s\n' '--- timestamp/link guidance and response references ---'
rg -n -C 2 'ISO 8601|timestamp columns|joinedTeamAt|team.*users|teamMembers|profile URL|shape.*date|shape.*url' plugins/Lichess plugins | head -240Repository: squaredup/plugins Length of output: 31226 π Web query:
π‘ Result: <search_synthesis> <source_evidence> Citations:
Add profile URLs for team members. The Lichess team-members response includes Proposed fix-result =
+const members =
typeof body === "string"
? body
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0)
.map((line) => JSON.parse(line))
: Array.isArray(body)
? body
: body
? [body]
: [];
+
+result = members.map((member) => ({
+ ...member,
+ url: `https://lichess.org/@/${encodeURIComponent(member.id)}`
+}));π Committable suggestion
Suggested change
π€ Prompt for AI AgentsποΈ Data Integrity & Integration | π Major | β‘ Quick win Convert
Proposed fix-result =
+const members =
typeof body === "string"
? body
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0)
.map((line) => JSON.parse(line))
: Array.isArray(body)
? body
: body
? [body]
: [];
+
+result = members.map((member) => ({
+ ...member,
+ joinedTeamAt:
+ member.joinedTeamAt == null
+ ? null
+ : new Date(member.joinedTeamAt).toISOString(),
+}));π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "name": "teamMembers", | ||
| "displayName": "Team Members", | ||
| "description": "Members of a team, one row per member", | ||
| "tags": ["Teams"], | ||
| "baseDataSourceName": "httpRequestScopedSingle", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/team/{{object.rawId}}/users", | ||
| "errorHandling": { "type": "path", "realm": "payload", "path": "error" }, | ||
| "postRequestScript": "teamMembers.js" | ||
| }, | ||
| "matches": { "sourceType": { "type": "oneOf", "values": ["Lichess Team"] } }, | ||
| "metadata": [ | ||
| { "name": "id", "displayName": "ID", "shape": "string", "visible": false }, | ||
| { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, | ||
| { "name": "url", "displayName": "Profile", "shape": ["url", { "label": "View on Lichess" }] }, | ||
| { "name": "joinedTeamAt", "displayName": "Joined team", "shape": "date" }, | ||
| { "name": "title", "displayName": "Title", "shape": "string" } | ||
| ], | ||
| "timeframes": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "name": "teams", | ||
| "displayName": "Teams", | ||
| "description": "Teams that tracked players belong to, one row per team", | ||
| "tags": ["Teams"], | ||
| "baseDataSourceName": "httpRequestScopedSingle", | ||
| "config": { | ||
| "httpMethod": "get", | ||
| "endpointPath": "api/team/of/{{object.rawId}}" | ||
| }, | ||
| "matches": { "sourceType": { "type": "oneOf", "values": ["Lichess Player"] } }, | ||
| "visibility": { "type": "hidden" }, | ||
| "metadata": [ | ||
| { "name": "id", "displayName": "ID", "shape": "string" }, | ||
| { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, | ||
| { "name": "description", "displayName": "Description", "shape": "string" }, | ||
| { "name": "open", "displayName": "Open", "shape": "boolean" }, | ||
| { "name": "nbMembers", "displayName": "Members", "shape": "number" } | ||
| ], | ||
| "timeframes": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "items": [ | ||
| { "name": "overview", "type": "dashboard" }, | ||
| { "name": "player", "type": "dashboard" }, | ||
| { "name": "team", "type": "dashboard" } | ||
| ] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ποΈ Data Integrity & Integration | π Major | β‘ Quick win
Convert profile timestamps to ISO 8601 strings.
createdAtandseenAtpass through as epoch integers. The paired stream declares both fields asdate, so the output violates the data-stream timestamp contract. The Lichess user API defines both fields asint64. (lichess-org.github.io)Proposed fix
As per coding guidelines: βTimestamps - SquaredUp expects ISO 8601 strings for timestamp columns.β
π Committable suggestion
π€ Prompt for AI Agents
Source: Coding guidelines