lichess.org
Donate

Как получить доступ к данным LiChess через API и автоматически обрабатывать их

TournamentSoftware DevelopmentLichessOff topic
Главное - правильно составить API-запрос, содержащий параметр Авторизации AUTHORIZATIONS и параметр Запросов QUERY PARAMETERS. Приведем пример для JavaScript и curl.

Данный текст написан для непрофессионалов, которые не знают ничего про интерфейс API и программирование в целом, к которым автор относит и себя. Скорее всего, с точки зрения профессионала, всё, что здесь написано - элементарные вещи, которые есть в справке. Но так получилось, что мне не попалось профессионалов, у которых можно было проконсультироваться или поручить сделать эту задачу, и пришлось во всём разбираться самому. Тем более, что программисты тоже бывают разные, и не все из них представляют, как работать с API. Итак, определим, что конкретно мы хотим добиться (цель работы) и посредством чего мы это сделаем (задачи, которые нужно выполнить).

Цель: автоматический доступ к данным LiChess (результаты партий, данные игрока и клубов) для организации турниров, подведения статистики и т.п.

Задачи:

  • составление API-запроса;
  • получение информации из запроса;
  • полезные запросы;
  • обработка информации;

Что такое API-запрос

Что такое API? Это Application Programming Interface, программный интерфейс приложения. Этот интерфейс определяет, как данное приложение должно взаимодействовать с другими приложениями, используя запросы и ответы. Поскольку ЛиЧесс создавался программистами, его API содержит достаточно много команд и описано довольно подробно по адресу https://lichess.org/api, на него можно выйти, нажав на стартовой странице слева под текущей игрой на любую ссылку и в открывшемся окне нажать на ссылку "API =>". Если в API есть некоторая операция (endpoint, конечная точка), мы можем её выполнить, если нет, то не можем. По вопросам работы с API достаточно охотно могут подсказать разработчики, спрашивать лучше всего здесь: https://discord.gg/lichess#lichess-api-support . С нуля, наверное, вам разжёвывать не будут - этим занимаюсь я.

Но если вам кажется, что в API что-то работает не так, как надо, или вы можете предложить интересную/нужную фичу, весьма вероятно, что вам пойдут навстречу и исправят/добавят то, что вам нужно.

По крайней мере, мне так сделали.

Виды API-запросов

Только путь (PATH PARAMETERS)

Это самый простой тип запросов, например на чесскоме запросы были только такого типа. Выглядит, как некоторый адрес на сайте. Например, чтобы узнать список лидеров в каком либо варианте шахмат, нужно ввести команду

Образец
https://lichess.org/api/player/top/{nb}/{perfType}
вводим команду
https://lichess.org/api/player/top/10/blitz

, где вместо {nb} нужно ввести целое от 1 до 200, а вместо {perfType} - вариант шахмат из предлагаемого списка, например "blitz". Такую команду можно просто написать в адресной строке браузера и получить ответ в новой вкладке.

С параметрами запросов (QUERY PARAMETERS)

Кроме пути, появляется еще и параметр(ы). Пример: получить список турниров-швейцарок клуба :

Образец
https://lichess.org/api/team/{teamId}/swiss 
вводим команду
https://lichess.org/api/team/----team-siberian-federal-okrug/swiss?max=10

Вместо {teamId} пишется идентификатор клуба (например "----team-siberian-federal-okrug"), имя параметра ("max") указывается после знака "?", и далее - его значение, например "10" в данном примере. Если параметров несколько, ставится знак "&" и пишется второй параметр и его значение и т.д. Порядок параметров значения не имеет. Вывод необходимо сохранить в виде файла.
Ту же самую команду можно запустить и из командной строки, используя, например утилиту curl (не входит в состав Виндоуз7, необходимо установить дополнительно). Ключи curl можно посмотреть при помощи команды

curl --help (короткий список)
curl --help all (полный список)

Для метода GET нужен ключ "-G", для параметров нужен ключ "-d". Получается команда

или то же самое для curl
curl -G "https://lichess.org/api/team/----team-siberian-federal-okrug/swiss" -d "max=10"

, в которой вывод идет в командную строку.
Однако в таком виде информацию получать неудобно. Лучше использовать какой-нибудь язык программирования. Если вы проводите турнир или публикуете статистику, то скорее всего, они включают таблицы. Общедоступные таблицы - это Google Таблицы, ссылка для разработчиков. Если вы пользуетесь андроидом, то у вас должен быть гугл-аккаунт, а в нём и ссылка на ваши Таблицы. В Таблицах (и в сервисах гугл в целом) доступен скриптовый язык Apps Script, который является ничем иным, как почти 100% копией JavaScript (JS). Вот на нем я и пишу. Про другие языки программирования я не скажу ничего, наверняка там есть схожие функции и если вы поймете принцип, то сами сможете использовать API-запросы на любом языке. А по JS есть неплохой учебник.
Итак, мы используем следующую конструкцию:

var link = "https://lichess.org/api/team/----team-siberian-federal-okrug/swiss?max=10"
var response = UrlFetchApp.fetch(link);

ВАЖНО: В данном случае API использует метод "GET" (который в явном виде можно не указывать) и который не поддерживает параметры "payload", поэтому все параметры запроса приходится указывать в гиперссылке после знака "?", см. https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app?hl=en.
Если используется метод "POST", то можно использовать такой синтаксис:
Export games by IDs

var link = "https://lichess.org/api/games/export/_ids"
var options = {
    "method" : "POST",
    "payload" : "D5yIyGiY,otaekfc1"
}
var response = UrlFetchApp.fetch(link, options);

Про то, как обработать эти данные будет написано чуть ниже.

С авторизацией (AUTHORIZATIONS) и параметрами запросов (QUERY PARAMETERS)

Если вы внимательно изучили API ЛиЧесс, то наверняка заметили, что большинство команд требует авторизации. Как это сделать? Почти в самом начале мануала по API есть раздел Authentication, а в нем пункт Generate a personal access token. Там же приводится пример его использования для curl

curl https://lichess.org/api/account -H "Authorization: Bearer {token}"

Ключ "-H" - это заголовок. Но, самое главное, здесь нужен токен {token}, подтверждающий, что действие производится от вашего аккаунта
Переходим на страничку генерации токенов. На ней будут перечислены все токены, заведенные вами с описанием возможностей, которые они дают. Также на этой странице можно удалять токены. А по ссылке generate a personal access token можно их создавать. При создании токена задавайте ему только те разрешения, которые необходимы для данной операции, пусть лучше токенов будет несколько. Если не требуется ничего (например в Get user public data), то не задавайте ничего, такие токены тоже работают. Если требуется принимать вызовы, выбирайте "challenge:write", если требуется создавать много вызовов, то "challenge:bulk", если управлять клубом, то "team:lead" и т.д. Про токены у меня было написано, с картинками, в отдельном блоге.
Итак, вы выбрали нужные разрешения и получили строку вида "lip_****(двадцать_знаков)". Это и есть токен и его надо записать в надежное место, потому что больше его значение вам не покажут.
Теперь напишем какой-нибудь запрос на JS, например загрузим игры какого-нибудь игрока Export games of a user. Все его игры нам конечно не нужны, но мы хотим найти партии между Игроком1 и Игроком2 в турнире по переписке, и знаем, что турнир начался такого-то числа.

var link = "https://lichess.org/api/games/user/{username}?vs=Игрок2&since=1672531200000&perfType=correspondence"
//{username} поменять на ник Игрок1, вставить Игрок2
var options = {
    "method" : "GET",
    "headers" : {Authorization: 'Bearer lip_tgjTPltt2WmoS6VHncok' }
}
var response = UrlFetchApp.fetch(link, options); 

при этом вывод будет в виде pgn партии.
Более сложный пример: создаем массовый старт партий. Отмечаем, что он требует метод "POST", токен администратора вида "challenge:bulk" и указание множества параметров.

var link = "https://lichess.org/api/bulk-pairing";
var allBulks = "token1:token2,token3:token4,token5:token6"
// перечислить токены вида "challenge:write" для участвующих игроков
var options = {
    'method' : 'post',
    'headers' : {Authorization: 'Bearer lip***'},
    'payload' : "rated=true&variant=standard&days=3&players=" + allBulks
// партии рейтинговые, классика, 3 дня на ход, список токенов
};
var response = UrlFetchApp.fetch(link, options); 

Итак, мы научились писать запросы, теперь давайте разберёмся, что на эти запросы отвечает сайт.

Ответы на запросы

В мануале API расписано, ответы какого типа возвращает каждый запрос. Самый простой случай, это JSON.

JSON

JSON - это строка пар "ключ:значение" (или структур из таких пар), заключенная в {}, например:

{"ключ1":"значение1", "ключ2":"значение2", и т.д.}

Чтобы зрительно развернуть её, используются парсеры, например https://jsonformatter.org/json-parser. После этого вы видите каждый ключ, прописанный на отдельной строке. Какие именно ключи содержатся в ответе на запрос, можно посмотреть в мануале по API в примерах справа. Как правило, названия ключей интуитивно понятны.
Строка JSON переводится в объект функцией parse. Доступ к значению ключей в объекте достигается по их имени, например для ключа "id" или "perfs.blitz.rating". Вывести эти значения в лог можно командой Logger.log(параметр).
Рассмотрим в качестве примера Get user public data. Авторизация может быть проведена, но это не обязательно. Параметры запроса тоже не обязательны. Поэтому пишем просто:

Образец:
var link = "https://lichess.org/api/user/{username}";
Пишем:
var link = "https://lichess.org/api/user/NechIvVas";
var response = UrlFetchApp.fetch(link);
var objJSONData = JSON.parse(response);
var ID = objJSONData.id;
var ratingBlitz = objJSONData.perfs.blitz.rating;
Logger.log(ID);
Logger.log(ratingBlitz);

NDJSON

Это более сложный вид ответа, называется "JSON, разделенный новой строкой", т.е. он имеет такое строение:

{ {JSON1} \n
    {JSON2} \r (допускается присутствие этого символа) \n
   ...
    {JSONn} }

В этом случае его уже нельзя распарсить встроенной функцией и приходится превращать его в текст. Возьмем Get members of a team. Авторизация нужна только для клубов со скрытым списком игроков, т.е. делать её не будем.

Пример:
https://lichess.org/api/team/{teamId}/users
Пишем:
var link = "https://lichess.org/api/team/teams-russia-correspondence-swiss-tournaments/users";
var response = UrlFetchApp.fetch(link); 
var ndjson = response.getContentText();
Logger.log(ndjson);

Получаем строку NDJSON в текстовом виде. Теперь её нужно разобрать на отдельные JSON, каждый из которых потом можно парсить.

Текст в виде pgn

Рассмотрим ранее уже разобранный случай Export games by IDs. В нем по умолчанию мы получаем вывод в виде текста, в чем можно убедиться, дополнив код строками

var text = response.getContentText();
Logger.log(text );

Также можно получить ответ и в виде NDJSON, для этого запрос нужно дополнить заголовком

"headers" : {Accept: "application/x-ndjson" },

Лично мне кажется, что при выводе в виде текста png, в нем, как в строке, искать легче.

Полезные запросы, которые могут пригодиться при создании турнира

Теперь, когда мы умеем строить запрос и получать из него информацию, разберём, что можно сделать при помощи доступного нам API-инструментария. Видно, что доступные точки сгруппированы по темам в меню слева. Для создания турнира нам могут пригодиться:

Обработка информации

Приведу один из возможных алгоритмов работы турнира шахмат по переписке

  1. Считать список игроков, проверить, что игроки активны (не забанены) на сайте, в клубе. Обновить рейтинг.
  2. Провести жеребьевку тура и запустить партии. Для швейцарской системы - единовременный запуск, для круговой - либо все партии за раз, либо партии одного тура или какое-то определенное количество партий.
  3. Вписать ссылки на партии в таблицу. Можно записывать при генерации партий, а можно искать партию между игроками, учитывая дату и вариант шахмат.
  4. Проверить результаты партий, вписать их в таблицу.
  5. Посчитать очки игрока и коэффициенты.
  6. Повторять п.4,5 с какой-то периодичностью.
  7. Для швейцарской системы при начале нового тура перейти к п.2.
  8. Если тур последний и все начатые партии закончены - объявить об окончании турнира.

Ну и пара советов. Информацию из Таблицы Гугл нужно считывать всю за один раз и выводить также за раз, так как это очень времязатратная операция. Например, если данные начинаются в ячейке А4 и занимают в строке 7 ячеек на описание игрока и по 4 ячейки на партию (а их две в туре), получается такой код:

var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet(); 
// определяем диапазон с данными
var allRange = sheet.getRange(4, 1, maxPlayers, 7+tourNumber*8);
// считываем значения из диапазона
var arrAllValues = new Array();
arrAllValues = allRange.getValues();
... меняем значения в массиве arrAllValues (записываем результаты партий)
// выводим значения в диапазон
allRange.setValues(arrAllValues);

Другой совет. Не давайте пользователям доступа к основной таблице со всеми скриптами. Скопируйте результат в отдельный файл, установите доступ "Для всех, у кого есть ссылка".

На самом деле, турниры - это лишь один из вариантов, как можно использовать доступ к функциям сайта через API.

У меня реализованы: автоматически обновляемая клубная статистика (с сортировкой по посещаемости и по рейтингу), автоматическое создание онлайн-турниров в клубе. На чесскоме мы предсказывали счет в заочных матчах, подводили статистику игрока в сезоне, находили мульти-игроков в турнире.

На этом всё. Полный текст скриптов у меня не просите, там много чего нескладного, особенно как я писал в самом начале. Надеюсь, что программу для швейцарки я отладил, но это не точно. Ошибки в ваших скриптах искать я тоже не буду, и обучать языку я не способен. Часто ошибки бывают из-за плохой связи, нужно различать - когда ошибка присутствует в коде, а когда написано правильно, но не поступила информация по запросу. Самое "звёздное", это когда сайт меняет какие-то свои стандарты, как это было со ссылками на партии на ЧессКоме.
На заказ не пишу (пока). Если у вас есть интересные проекты и вы готовы сами писать, можно обсудить их.