rating-chgk-info 0.3.6.3 → 0.3.6.4
raw patch · 6 files changed
+478/−15 lines, 6 filesdep +directorydep +http-client-tlsdep +tagsoupdep ~aesondep ~base-nopreludedep ~reludenew-component:exe:calendar-ratingPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: directory, http-client-tls, tagsoup
Dependency ranges changed: aeson, base-noprelude, relude, servant, servant-client, servant-server, text, time
API changes (from Hackage documentation)
+ RatingChgkInfo.Api: tournamentRecaps :: TournamentId -> RatingClient [RecapTeam]
+ RatingChgkInfo.Api: tournamentSearch :: Maybe TournamentType -> Maybe Int -> Maybe Int -> RatingClient (Items TournamentShort)
+ RatingChgkInfo.NoApi: synchTown :: Int -> IO (Either ByteString [SynchTown])
+ RatingChgkInfo.NoApi: towns :: Maybe Int -> IO (Either ByteString [Town])
+ RatingChgkInfo.Types: RecapTeam :: TeamId -> [RecapPlayer] -> RecapTeam
+ RatingChgkInfo.Types: SynchTown :: TournamentId -> Text -> ClaimStatus -> PlayerId -> Text -> LocalTime -> SynchTown
+ RatingChgkInfo.Types: Town :: TownId -> Text -> Maybe Text -> Maybe Text -> Maybe Text -> Town
+ RatingChgkInfo.Types: [rt_idteam] :: RecapTeam -> TeamId
+ RatingChgkInfo.Types: [rt_recaps] :: RecapTeam -> [RecapPlayer]
+ RatingChgkInfo.Types: [stRepresentativeId] :: SynchTown -> PlayerId
+ RatingChgkInfo.Types: [stRepresentative] :: SynchTown -> Text
+ RatingChgkInfo.Types: [stStatus] :: SynchTown -> ClaimStatus
+ RatingChgkInfo.Types: [stTime] :: SynchTown -> LocalTime
+ RatingChgkInfo.Types: [stTournamentId] :: SynchTown -> TournamentId
+ RatingChgkInfo.Types: [stTournament] :: SynchTown -> Text
+ RatingChgkInfo.Types: [townCountry] :: Town -> Maybe Text
+ RatingChgkInfo.Types: [townId] :: Town -> TownId
+ RatingChgkInfo.Types: [townName] :: Town -> Text
+ RatingChgkInfo.Types: [townOtherName] :: Town -> Maybe Text
+ RatingChgkInfo.Types: [townRegion] :: Town -> Maybe Text
+ RatingChgkInfo.Types: [trn_archive] :: Tournament -> Text
+ RatingChgkInfo.Types: [trn_dateArchivedAt] :: Tournament -> Day
+ RatingChgkInfo.Types: [trs_archive] :: TournamentShort -> Text
+ RatingChgkInfo.Types: [trs_dateArchivedAt] :: TournamentShort -> Day
+ RatingChgkInfo.Types: data RecapTeam
+ RatingChgkInfo.Types: data SynchTown
+ RatingChgkInfo.Types: data Town
+ RatingChgkInfo.Types: instance Data.Aeson.Types.FromJSON.FromJSON RatingChgkInfo.Types.RecapTeam
+ RatingChgkInfo.Types: instance Data.Aeson.Types.ToJSON.ToJSON RatingChgkInfo.Types.RecapTeam
+ RatingChgkInfo.Types: instance Data.Aeson.Types.ToJSON.ToJSON RatingChgkInfo.Types.SynchTown
+ RatingChgkInfo.Types: instance Data.Aeson.Types.ToJSON.ToJSON RatingChgkInfo.Types.Town
+ RatingChgkInfo.Types: instance GHC.Classes.Eq RatingChgkInfo.Types.RecapTeam
+ RatingChgkInfo.Types: instance GHC.Classes.Eq RatingChgkInfo.Types.SynchTown
+ RatingChgkInfo.Types: instance GHC.Classes.Eq RatingChgkInfo.Types.Town
+ RatingChgkInfo.Types: instance GHC.Generics.Generic RatingChgkInfo.Types.RecapTeam
+ RatingChgkInfo.Types: instance GHC.Generics.Generic RatingChgkInfo.Types.SynchTown
+ RatingChgkInfo.Types: instance GHC.Generics.Generic RatingChgkInfo.Types.Town
+ RatingChgkInfo.Types: instance GHC.Read.Read RatingChgkInfo.Types.RecapTeam
+ RatingChgkInfo.Types: instance GHC.Read.Read RatingChgkInfo.Types.SynchTown
+ RatingChgkInfo.Types: instance GHC.Read.Read RatingChgkInfo.Types.Town
+ RatingChgkInfo.Types: instance GHC.Show.Show RatingChgkInfo.Types.RecapTeam
+ RatingChgkInfo.Types: instance GHC.Show.Show RatingChgkInfo.Types.SynchTown
+ RatingChgkInfo.Types: instance GHC.Show.Show RatingChgkInfo.Types.Town
+ RatingChgkInfo.Types: instance Web.Internal.HttpApiData.ToHttpApiData RatingChgkInfo.Types.TournamentType
- RatingChgkInfo.Api: runRatingApi :: RatingClient a -> IO (Either ServantError a)
+ RatingChgkInfo.Api: runRatingApi :: RatingClient a -> IO (Either ClientError a)
- RatingChgkInfo.Types: Tournament :: TournamentId -> Text -> Text -> Text -> LocalTime -> LocalTime -> Text -> Text -> Text -> Maybe Text -> Text -> TournamentType -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Tournament
+ RatingChgkInfo.Types: Tournament :: TournamentId -> Text -> Text -> Text -> LocalTime -> LocalTime -> Text -> Text -> Text -> Maybe Text -> Text -> TournamentType -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Day -> Tournament
- RatingChgkInfo.Types: TournamentShort :: TournamentId -> Text -> LocalTime -> LocalTime -> TournamentType -> TournamentShort
+ RatingChgkInfo.Types: TournamentShort :: TournamentId -> Text -> LocalTime -> LocalTime -> TournamentType -> Text -> Day -> TournamentShort
- RatingChgkInfo.Types: type RatingApi = "players" :> QueryParam "page" Int :> Get '[JSON] (Items Player) :<|> "players" :> Capture "idplayer" PlayerId :> Get '[JSON] [Player] :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> Get '[JSON] [PlayerTeam] :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> "last" :> Get '[JSON] [PlayerTeam] :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> Capture "idseason" Int :> Get '[JSON] [PlayerTeam] :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> Get '[JSON] (SeasonMap PlayerSeason) :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> "last" :> Get '[JSON] PlayerSeason :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> Capture "idseason" Int :> Get '[JSON] PlayerSeason :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> Get '[JSON] [PlayerRating] :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> "last" :> Get '[JSON] PlayerRating :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> Capture "idrelease" Int :> Get '[JSON] PlayerRating :<|> "teams" :> QueryParam "page" Int :> Get '[JSON] (Items Team) :<|> "teams" :> Capture "idteam" TeamId :> Get '[JSON] [Team] :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> Get '[JSON] (SeasonMap TeamBaseRecap) :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> "last" :> Get '[JSON] TeamBaseRecap :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> Capture "idseason" Int :> Get '[JSON] TeamBaseRecap :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> Get '[JSON] (SeasonMap TeamTournament) :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> "last" :> Get '[JSON] TeamTournament :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> Capture "idseason" Int :> Get '[JSON] TeamTournament :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> Get '[JSON] [TeamRating] :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> "a" :> Get '[JSON] TeamRating :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> "b" :> Get '[JSON] TeamRating :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> Capture "idrelease" Int :> Get '[JSON] TeamRating :<|> "tournaments" :> QueryParam "page" Int :> Get '[JSON] (Items TournamentShort) :<|> "tournaments" :> Capture "idtournament" TournamentId :> Get '[JSON] [Tournament] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "town" :> Capture "idtown" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "region" :> Capture "idregion" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "country" :> Capture "idcountry" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "recaps" :> Capture "idteam" TeamId :> Get '[JSON] [RecapPlayer] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "results" :> Capture "idteam" TeamId :> Get '[JSON] [TourResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "controversials" :> Get '[JSON] [Controversial] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "appeals" :> Get '[JSON] [Appeal] :<|> "teams" :> "search" :> QueryParam "name" Text :> QueryParam "town" Text :> QueryParam "region_name" Text :> QueryParam "country_name" Text :> QueryFlag "active_this_season" :> QueryParam "page" Int :> Get '[JSON] (Items Team) :<|> "players" :> "search" :> QueryParam "surname" Text :> QueryParam "name" Text :> QueryParam "patronymic" Text :> QueryParam "page" Int :> Get '[JSON] (Items Player)
+ RatingChgkInfo.Types: type RatingApi = "players" :> QueryParam "page" Int :> Get '[JSON] (Items Player) :<|> "players" :> Capture "idplayer" PlayerId :> Get '[JSON] [Player] :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> Get '[JSON] [PlayerTeam] :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> "last" :> Get '[JSON] [PlayerTeam] :<|> "players" :> Capture "idplayer" PlayerId :> "teams" :> Capture "idseason" Int :> Get '[JSON] [PlayerTeam] :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> Get '[JSON] (SeasonMap PlayerSeason) :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> "last" :> Get '[JSON] PlayerSeason :<|> "players" :> Capture "idplayer" PlayerId :> "tournaments" :> Capture "idseason" Int :> Get '[JSON] PlayerSeason :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> Get '[JSON] [PlayerRating] :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> "last" :> Get '[JSON] PlayerRating :<|> "players" :> Capture "idplayer" PlayerId :> "rating" :> Capture "idrelease" Int :> Get '[JSON] PlayerRating :<|> "teams" :> QueryParam "page" Int :> Get '[JSON] (Items Team) :<|> "teams" :> Capture "idteam" TeamId :> Get '[JSON] [Team] :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> Get '[JSON] (SeasonMap TeamBaseRecap) :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> "last" :> Get '[JSON] TeamBaseRecap :<|> "teams" :> Capture "idteam" TeamId :> "recaps" :> Capture "idseason" Int :> Get '[JSON] TeamBaseRecap :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> Get '[JSON] (SeasonMap TeamTournament) :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> "last" :> Get '[JSON] TeamTournament :<|> "teams" :> Capture "idteam" TeamId :> "tournaments" :> Capture "idseason" Int :> Get '[JSON] TeamTournament :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> Get '[JSON] [TeamRating] :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> "a" :> Get '[JSON] TeamRating :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> "b" :> Get '[JSON] TeamRating :<|> "teams" :> Capture "idteam" TeamId :> "rating" :> Capture "idrelease" Int :> Get '[JSON] TeamRating :<|> "tournaments" :> QueryParam "page" Int :> Get '[JSON] (Items TournamentShort) :<|> "tournaments" :> Capture "idtournament" TournamentId :> Get '[JSON] [Tournament] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "town" :> Capture "idtown" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "region" :> Capture "idregion" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "country" :> Capture "idcountry" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "recaps" :> Get '[JSON] [RecapTeam] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "recaps" :> Capture "idteam" TeamId :> Get '[JSON] [RecapPlayer] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "results" :> Capture "idteam" TeamId :> Get '[JSON] [TourResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "controversials" :> Get '[JSON] [Controversial] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "appeals" :> Get '[JSON] [Appeal] :<|> "teams" :> "search" :> QueryParam "name" Text :> QueryParam "town" Text :> QueryParam "region_name" Text :> QueryParam "country_name" Text :> QueryFlag "active_this_season" :> QueryParam "page" Int :> Get '[JSON] (Items Team) :<|> "players" :> "search" :> QueryParam "surname" Text :> QueryParam "name" Text :> QueryParam "patronymic" Text :> QueryParam "page" Int :> Get '[JSON] (Items Player) :<|> "tournaments" :> "search" :> QueryParam "type_name" TournamentType :> QueryParam "archive" Int :> QueryParam "page" Int :> Get '[JSON] (Items TournamentShort)
Files
- CHANGELOG.md +18/−1
- app/calendar-rating/Main.hs +104/−0
- rating-chgk-info.cabal +31/−5
- src/RatingChgkInfo/Api.hs +99/−6
- src/RatingChgkInfo/NoApi.hs +146/−1
- src/RatingChgkInfo/Types.hs +80/−2
CHANGELOG.md view
@@ -11,7 +11,24 @@ ## Fixed ## Security -# 0.3.6.3+# 0.3.6.4 - 2019-04-09++## Added++* Получение всех составов на турнире (API 3.6.4)+* Поиск по турнирам (API 3.6.4)+* Получение списка городов+* Получение ближайших синхронов в городе+* Приложение для генерации календарей синхронов в городе (app/calendar-rating)++## Changed++* Поля `archive` и `date_archived_at` в турнирах (API 3.6.4)+* Исправлены версии зависимостей, теперь они совместимы с GHC 8.6+* Работает с servant 0.16+* Данные запрашиваются через https++# 0.3.6.3 - 2019-02-09 ## Added
+ app/calendar-rating/Main.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE OverloadedStrings #-}++import RatingChgkInfo+import RatingChgkInfo.Types.Unsafe (TournamentId (..), PlayerId (..))++import Data.Aeson+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as T+import Data.Time+import System.Directory (withCurrentDirectory)+import System.Environment (getArgs)++main :: IO ()+main = do+ args <- getArgs+ case args of+ [rootDir, "town", townStr, town] -> withCurrentDirectory rootDir $ + case readMaybe townStr of+ Nothing -> usage+ Just townId -> workTown townId (T.pack town) Nothing >>= mapM_ print+ [rootDir, townPagesStr] -> withCurrentDirectory rootDir $ + case readMaybe townPagesStr of+ Nothing -> usage+ Just townPages -> work townPages+ _ -> usage++usage :: IO ()+usage = do+ die "Usage: calendar-rating <rootFolder> <townPages>"++work :: Int -> IO ()+work townPages = do+ ets <- fmap (fmap concat . sequence) $ forM [1..townPages] $ towns . Just+ case ets of+ Left err -> T.putStrLn $ decodeUtf8 err+ Right ts -> do+ ss <- forM ts $ \Town{ townId = ident, townName = town, townOtherName = other } -> do+ T.putStrLn town+ workTown ident town other+ encodeFile "names.json" $+ map fst $+ filter (not . null . snd) $+ zip ts ss++workTown :: Int -> Text -> Maybe Text -> IO [SynchTown]+workTown ident town other = do+ now <- getCurrentTime+ ests <- synchTown ident+ case ests of+ Left err -> T.putStrLn (decodeUtf8 err) >> pure []+ Right sts -> do+ let fname = "t" ++ show ident ++ ".ics"+ let events = filter ((==ClaimAccepted) . stStatus) sts+ unless (null events) $+ T.writeFile fname $ T.intercalate "\r\n"+ [ calStart+ , T.intercalate "\r\n" $+ map (mkEvent now town other) events+ , calEnd+ ]+ pure events++calStart :: Text+calStart = T.intercalate "\r\n"+ [ "BEGIN:VCALENDAR"+ , "VERSION:2.0"+ , "PRODID:-//calendar.chgk.me/EN"+ , "METHOD:PUBLISH"+ ]++calEnd :: Text+calEnd = "END:VCALENDAR"++mkEvent :: UTCTime -> Text -> Maybe Text -> SynchTown -> Text+mkEvent now town other SynchTown{ stTournamentId = TournamentId tid+ , stTournament = tourn+ , stRepresentativeId = PlayerId rid+ , stRepresentative = rep+ , stTime = time }+ = T.intercalate "\r\n"+ [ "BEGIN:VEVENT"+ , "UID:" `T.append` uid+ , "DTSTAMP:" `T.append` ts+ , "DTSTART;TZID=Europe/Moscow:" `T.append` start+ , "SUMMARY:" `T.append` summary+ , "ORGANIZER:" `T.append` org+ , "LOCATION:" `T.append` loc+ , "URL:" `T.append` url+ , "DESCRIPTION:" `T.append` desc+ , "END:VEVENT"+ ]+ where+ uid = T.concat [ "t", tid, "-r", rid, "@calendar.chgk.me" ]+ format :: FormatTime t => t -> Text+ format = T.pack . formatTime defaultTimeLocale "%Y%m%dT%H%M%S"+ ts = format now `T.append` "Z"+ start = format time+ summary = tourn+ org = rep+ loc = T.concat $ town : maybe [] pure other+ url = T.concat [ "https://rating.chgk.info/tournament/", tid ]+ desc = T.concat [ "Представитель: ", rep, "\\n", "Турнир: ", url]+
rating-chgk-info.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.24 name: rating-chgk-info-version: 0.3.6.3+version: 0.3.6.4 synopsis: Client for rating.chgk.info API and CSV tables (documentation in Russian) description: Клиент для REST API сайта рейтинга (rating.chgk.info) и функциональности, которой нет в REST API, но которая доступна через экспорт CSV.@@ -80,23 +80,25 @@ -fhide-source-paths -Wmissing-export-lists -Wpartial-fields- build-depends: base-noprelude == 4.11.*+ build-depends: base-noprelude >= 4.11 , relude >= 0.4.0 , aeson >=1.4 , bytestring >=0.10 , cassava >=0.5 , containers >=0.5 , http-client >= 0.5+ , http-client-tls >= 0.3 , iconv >=0.4 , lens >=4.17 , network >=2.8 , optparse-generic >=1.3- , servant >=0.15- , servant-client >=0.15+ , servant >=0.16+ , servant-client >=0.16 , servant-js >=0.9- , servant-server >=0.15+ , servant-server >=0.16 , servant-swagger >=1.1 , swagger2 >=2.2+ , tagsoup >=0.14 , text >=1.2 , time >=1.8 , vector >=0.12@@ -166,6 +168,30 @@ build-depends: base-noprelude , rating-chgk-info , relude+ default-language: Haskell2010+ +executable calendar-rating+ hs-source-dirs: app/calendar-rating+ main-is: Main.hs+ ghc-options: -Wall+ -threaded+ -rtsopts+ -with-rtsopts=-N+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ -Wcompat+ -Widentities+ -Wredundant-constraints+ -fhide-source-paths+ -Wmissing-export-lists+ -Wpartial-fields+ build-depends: base-noprelude+ , rating-chgk-info+ , relude+ , aeson+ , directory+ , text+ , time default-language: Haskell2010
src/RatingChgkInfo/Api.hs view
@@ -57,6 +57,7 @@ , tournamentResultsCountry , tournamentTeamResult -- ** Составы на турнире+ , tournamentRecaps , tournamentRecap -- ** Спорные и апелляции , tournamentControversials@@ -66,13 +67,15 @@ -- $maybeParams , teamSearch , playerSearch+ , tournamentSearch -- * Вспомогательные функции , getAllItems ) where import RatingChgkInfo.Types -import Network.HTTP.Client (newManager,defaultManagerSettings)+import Network.HTTP.Client (newManager)+import Network.HTTP.Client.TLS (tlsManagerSettings) import Servant.API import Servant.Client @@ -80,157 +83,235 @@ api = Proxy -- | Список всех игроков+--+-- Запрос @\/players@ players :: Maybe Int -- ^ Номер страницы в результате -> RatingClient (Items Player) -- ^ Список игроков, по 1000 элементов -- | Информация об игроке -- -- __API NOTE__. Результат должен быть 'Player', а не список игроков из одного элемента+--+-- Запрос @\/players\/:id@ player :: PlayerId -- ^ Идентификатор игрока -> RatingClient [Player] -- ^ Информация об игроке, список из единственного элемента -- | Команды, в базовых составах которых играл игрок+--+-- Запрос @\/players\/:id\/teams@ playerTeams :: PlayerId -- ^ Идентификатор игрока -> RatingClient [PlayerTeam] -- ^ Список команд игрока -- | Команды, в базовый состав которых игрок входит в текущем сезоне+--+-- Запрос @\/players\/:id\/teams\/last@ playerLastTeam :: PlayerId -- ^ Идентификатор игрока -> RatingClient [PlayerTeam] -- ^ Список команд игрока -- | Команды, в базовый состав которых игрок входил в указанном сезоне+--+-- Запрос @\/players\/:id\/teams\/:season@ playerTeam :: PlayerId -- ^ Идентификатор игрока -> Int -- ^ Идентификатор сезона -> RatingClient [PlayerTeam] -- ^ Список команд игрока -- | Турниры, которые отыграл игрок, по сезонам+--+-- Запрос @\/players\/:id\/tournaments@ playerTournaments :: PlayerId -- ^ Идентификатор игрока -> RatingClient (SeasonMap PlayerSeason) -- ^ Турниры игрока по сезонам -- | Турниры, которые игрок отыграл в текущем сезоне+--+-- Запрос @\/players\/:id\/tournaments\/last@ playerLastTournament :: PlayerId -- ^ Идентификатор игрока -> RatingClient PlayerSeason -- | Турниры, которые игрок отыграл в указанном сезоне+--+-- Запрос @\/players\/:id\/tournaments\/:season@ playerTournament :: PlayerId -- ^ Идентификатор игрока -> Int -- ^ Идентификатор сезона -> RatingClient PlayerSeason -- | Рейтинги игрока+--+-- Запрос @\/players\/:id\/rating@ playerRatings :: PlayerId -- ^ Идентификатор игрока -> RatingClient [PlayerRating] -- ^ Список рейтингов игрока, порядок не определён -- | Рейтинг игрока в последнем релизе -- -- __API NOTE__. Работает не всегда: например, для 54345 в декабре 2018 ничего не возвращало (off-by-one error?)+--+-- Запрос @\/players\/:id\/rating\/last@ playerLastRating :: PlayerId -- ^ Идентификатор игрока -> RatingClient PlayerRating -- | Рейтинг игрока в указанном релизе+--+-- Запрос @\/players\/:id\/rating\/:release@ playerRating :: PlayerId -- ^ Идентификатор игрока -> Int -- ^ Идентификатор релиза -> RatingClient PlayerRating -- | Список всех команд+--+-- Запрос @\/teams@ teams :: Maybe Int -- ^ Номер страницы в результате -> RatingClient (Items Team) -- ^ Команды по 1000 элементов -- | Информация о команде -- -- __API NOTE__: должна быть команда, а не список из команд+--+-- Запрос @\/teams\/:id@ team :: TeamId -- ^ Идентификатор команды -> RatingClient [Team] -- ^ Команда, список из единственного элемента -- | Базовые составы команды+--+-- Запрос @\/teams\/:id\/recaps@ teamBaseRecaps :: TeamId -- ^ Идентификатор команды -> RatingClient (SeasonMap TeamBaseRecap) -- ^ Базовые составы по сезонам -- | Базовый состав команды в последнем сезоне+--+-- Запрос @\/teams\/:id\/recaps\/last@ teamLastBaseRecap :: TeamId -- ^ Идентификатор команды -> RatingClient TeamBaseRecap -- | Базовый состав команды в указанном сезоне+--+-- Запрос @\/teams\/:id\/recaps\/:season@ teamBaseRecap :: TeamId -- ^ Идентификатор команды -> Int -- ^ Идентификатор сезона -> RatingClient TeamBaseRecap -- | Турниры, отыгранные командой+--+-- Запрос @\/teams\/:id\/tournaments@ teamTournaments :: TeamId -- ^ Идентификатор команды -> RatingClient (SeasonMap TeamTournament) -- ^ Турниры команды по сезонам -- | Турниры, отыгранные командой в последнем сезоне+--+-- Запрос @\/teams\/:id\/tournaments\/last@ teamLastTournament :: TeamId -- ^ Идентификатор команды -> RatingClient TeamTournament -- | Турниры, отыгранные командой в указанном сезоне+--+-- Запрос @\/teams\/:id\/tournaments\/:season@ teamTournament :: TeamId -- ^ Идентификатор команды -> Int -- ^ Идентификатор сезона -> RatingClient TeamTournament -- | Рейтинги команды+--+-- Запрос @\/teams\/:id\/rating@ teamRatings :: TeamId -- ^ Идентификатор команды -> RatingClient [TeamRating] -- ^ Список рейтингов команды, порядок не определён -- | Последний рейтинг команды по формуле A -- -- __API NOTE__. Работает не всегда: для 1 ничего не возвращает (off-by-one error?)+--+-- Запрос @\/teams\/:id\/rating\/a@ teamRatingA :: TeamId -- ^ Идентификатор команды -> RatingClient TeamRating -- | Последний рейтинг команды по формуле B -- -- __API NOTE__. Работает не всегда: для 1 ничего не возвращает (off-by-one error?)+--+-- Запрос @\/teams\/:id\/rating\/b@ teamRatingB :: TeamId -- ^ Идентификатор команды -> RatingClient TeamRating -- | Рейтинг команды в указанном релизе+--+-- Запрос @\/teams\/:id\/rating\/:release@ teamRating :: TeamId -- ^ Идентификатор команды -> Int -- ^ Идентификатор релиза -> RatingClient TeamRating -- | Список всех турниров-tournaments :: Maybe Int -- ^ Номер страницы в результате+--+-- Запрос @\/tournaments@+tournaments :: Maybe Int -- ^ Номер страницы (@page@) в результате -> RatingClient (Items TournamentShort) -- ^ Информация о турнирах по 1000 элементов -- | Информация о турнире -- -- __API NOTE__: должен быть турнир, а не список турниров+--+-- Запрос @\/tournaments\/:id@ tournament :: TournamentId -- ^ Идентификатор турнира -> RatingClient [Tournament] -- ^ Единственный элемент списка - турнир -- | Результаты турнира+--+-- Запрос @\/tournaments\/:id\/list@ tournamentResults :: TournamentId -- ^ Идентификатор турнира -> RatingClient [TournamentResult] -- ^ Результаты по командам, порядок не определён -- | Результаты турнира для команд города+--+-- Запрос @\/tournaments\/:id\/list\/town\/:town@ tournamentResultsTown :: TournamentId -- ^ Идентификатор турнира -> Int -- ^ Идентификатор города -> RatingClient [TournamentResult] -- ^ Результаты по командам, порядок не определён -- | Результаты турнира для команд региона+--+-- Запрос @\/tournaments\/:id\/list\/region\/:region@ tournamentResultsRegion :: TournamentId -- ^ Идентификатор турнира -> Int -- ^ Идентификатор региона -> RatingClient [TournamentResult] -- ^ Результаты по командам, порядок не определён -- | Результаты турнира для команд страны+--+-- Запрос @\/tournaments\/:id\/list\/country\/:country@ tournamentResultsCountry :: TournamentId -- ^ Идентификатор турнира -> Int -- ^ Идентификатор страны -> RatingClient [TournamentResult] -- ^ Результаты по командам, порядок не определён +-- | Составы команд на турнире+--+-- Запрос @\/tournaments\/:id\/recaps@+--+-- @since 0.3.6.4+tournamentRecaps :: TournamentId -- ^ Идентификаторр турнира+ -> RatingClient [RecapTeam]+ -- | Составы указанной команды на турнире+--+-- Запрос @\/tournaments\/:id\/recaps\/:team@ tournamentRecap :: TournamentId -- ^ Идентификатор турнира -> TeamId -- ^ Идентификатор команды -> RatingClient [RecapPlayer] -- ^ Список игроков с флагами К\/Б\/Л -- | Результат указанной команды на турнире+--+-- Запрос @\/tournaments\/:id\/results\/:team@ tournamentTeamResult :: TournamentId -- ^ Идентификатор турнира -> TeamId -- ^ Идентификатор команды -> RatingClient [TourResult] -- ^ Список результатов по турам -- | Спорные на турнире+--+-- Запрос @\/tournaments\/:id\/controversials@+--+-- @since 0.3.6.3 tournamentControversials :: TournamentId -- ^ Идентификатор турнира -> RatingClient [Controversial] -- ^ Спорные -- | Апелляции на турнире+--+-- Запрос @\/tournaments\/:id\/appeals@+--+-- @since 0.3.6.3 tournamentAppeals :: TournamentId -- ^ Идентификатор турнира -> RatingClient [Appeal] -- ^ Апелляции @@ -242,6 +323,8 @@ -- связки И. -- | Поиск по командам+--+-- Запрос @\/teams\/search@ teamSearch :: Maybe Text -- ^ Название (name) -> Maybe Text -- ^ Город (town) -> Maybe Text -- ^ Регион (region_name)@@ -251,14 +334,24 @@ -> RatingClient (Items Team) -- ^ Список команд по 1000 элементов -- | Поиск по игрокам+--+-- Запрос @\/players\/search@ playerSearch :: Maybe Text -- ^ Фамилия -> Maybe Text -- ^ Имя -> Maybe Text -- ^ Отчество -> Maybe Int -- ^ Номер страницы в результате -> RatingClient (Items Player) -- ^ Список игроков по 1000 элементов -players :<|> player :<|> playerTeams :<|> playerLastTeam :<|> playerTeam :<|> playerTournaments :<|> playerLastTournament :<|> playerTournament :<|> playerRatings :<|> playerLastRating :<|> playerRating :<|> teams :<|> team :<|> teamBaseRecaps :<|> teamLastBaseRecap :<|> teamBaseRecap :<|> teamTournaments :<|> teamLastTournament :<|> teamTournament :<|> teamRatings :<|> teamRatingA :<|> teamRatingB :<|> teamRating :<|> tournaments :<|> tournament :<|> tournamentResults :<|> tournamentResultsTown :<|> tournamentResultsRegion :<|> tournamentResultsCountry :<|> tournamentRecap :<|> tournamentTeamResult :<|> tournamentControversials :<|> tournamentAppeals :<|> teamSearch :<|> playerSearch = client api+-- | Поиск по турнирам+--+-- Запрос @\/tournaments\/search@+tournamentSearch :: Maybe TournamentType -- ^ Тип турнира+ -> Maybe Int -- ^ Находится ли турнир в архиве (0 - не находится, 1 - находится)+ -> Maybe Int -- ^ Номер страницы в результате+ -> RatingClient (Items TournamentShort) +players :<|> player :<|> playerTeams :<|> playerLastTeam :<|> playerTeam :<|> playerTournaments :<|> playerLastTournament :<|> playerTournament :<|> playerRatings :<|> playerLastRating :<|> playerRating :<|> teams :<|> team :<|> teamBaseRecaps :<|> teamLastBaseRecap :<|> teamBaseRecap :<|> teamTournaments :<|> teamLastTournament :<|> teamTournament :<|> teamRatings :<|> teamRatingA :<|> teamRatingB :<|> teamRating :<|> tournaments :<|> tournament :<|> tournamentResults :<|> tournamentResultsTown :<|> tournamentResultsRegion :<|> tournamentResultsCountry :<|> tournamentRecaps :<|> tournamentRecap :<|> tournamentTeamResult :<|> tournamentControversials :<|> tournamentAppeals :<|> teamSearch :<|> playerSearch :<|> tournamentSearch = client api+ -- | Получение всех элементов из запроса с разбиением по страницам -- -- В функции предполагается, что сайт рейтинга, как и указано в документации,@@ -275,8 +368,8 @@ -- -- Все запросы внутри 'RatingClient' используют один и тот же менеджер соединений runRatingApi :: RatingClient a -- ^ Набор команд, работающих с API сайта рейтинга- -> IO (Either ServantError a) -- ^ Результат работы, либо ошибка+ -> IO (Either ClientError a) -- ^ Результат работы, либо ошибка runRatingApi act = do- mgr <- newManager defaultManagerSettings- runClientM act $ mkClientEnv mgr $ BaseUrl Http "rating.chgk.info" 80 "api"+ mgr <- newManager tlsManagerSettings+ runClientM act $ mkClientEnv mgr $ BaseUrl Https "rating.chgk.info" 443 "api"
src/RatingChgkInfo/NoApi.hs view
@@ -17,25 +17,32 @@ module RatingChgkInfo.NoApi ( requests+ , synchTown+ , towns ) where import Prelude hiding (ByteString, get) import RatingChgkInfo.Types-import RatingChgkInfo.Types.Unsafe (TournamentId (..))+import RatingChgkInfo.Types.Unsafe (TournamentId (..), PlayerId (..)) import Codec.Text.IConv import Control.Lens import qualified Data.ByteString.Char8 as B import Data.ByteString.Lazy.Char8 (ByteString)+import qualified Data.ByteString.Lazy.Char8 as BL+import Data.Char import Data.Csv+import Data.Fixed import Data.List import qualified Data.Map as M import qualified Data.Map.Merge.Lazy as M import Data.Text (Text) import qualified Data.Text as T import Data.Text.Read+import Data.Time import Network.Wreq+import Text.HTML.TagSoup -- Команда в CSV data CsvTeam = CsvTeam@@ -191,4 +198,142 @@ csvOpts = defaultDecodeOptions { decDelimiter = fromIntegral $ ord ';' }++-- | Получает список предстоящих синхронов в городе+--+-- @since 0.3.6.4+synchTown :: Int -- ^ Идентификатор города+ -> IO (Either B.ByteString [SynchTown]) -- ^ Ошибка или список синхронов в городе+synchTown townId = do+ let url = "https://rating.chgk.info/jq_backend/synch.php?upcoming_synch=true&town_id=" ++ show townId+ -- TODO: better use: https://rating.chgk.info/synch_town/<townId>+ r <- get url+ pure $ case r^.responseStatus.statusCode of+ 200 -> parseSynchTown $ r^.responseBody+ _ -> Left $ r^.responseStatus.statusMessage++parseSynchTown :: ByteString -> Either B.ByteString [SynchTown]+parseSynchTown body = let+ tags = parseTags $+ convert "CP1251" "UTF-8" body :: [Tag ByteString]+ tbodyTagName = "tbody" :: ByteString+ tbody = takeWhile (\t -> t ~/= TagClose tbodyTagName) $+ dropWhile (\t -> t ~/= TagOpen tbodyTagName []) $+ mapMaybe trimTags tags+ trName = "tr" :: ByteString+ tdName = "td" :: ByteString+ cols = map (partitions (\t -> t ~== TagOpen tdName [])) $+ partitions (\t -> t ~== TagOpen trName []) tbody+ in mapM parseSynchTownRow cols++trimTags :: Tag ByteString -> Maybe (Tag ByteString)+trimTags (TagText t) = case BL.dropWhile isSpace t of+ "" -> Nothing+ u -> Just $ TagText $ BL.reverse $ BL.dropWhile isSpace $ BL.reverse u+trimTags t = Just t++parseSynchTownRow :: [[Tag ByteString]] -> Either B.ByteString SynchTown+parseSynchTownRow [syn, stat, rep, time] = do+ let listToEither s [] = Left s+ listToEither _ (x:_) = Right x+ toClaimStatus "Заявка не рассмотрена" = Right ClaimNew+ toClaimStatus "Заявка принята" = Right ClaimAccepted+ toClaimStatus "Заявка отклонена" = Right ClaimRejected+ toClaimStatus s = Left $ B.pack $ "Wrong claim status " ++ T.unpack s+ synHref <- fmap (TournamentId . decodeUtf8 . BL.drop 12 . fromAttrib "href") $+ listToEither "Can't find href for synch id in synchTown" $+ filter (isTagOpenName "a") syn+ synText <- fmap (decodeUtf8 . fromTagText) $+ listToEither "Can't find synch name in synchTown" $+ filter isTagText syn+ statI <- fmap (decodeUtf8 . fromAttrib "title") $+ listToEither "Can't find i for status in synchTown" $+ filter (isTagOpenName "i") stat+ status <- toClaimStatus statI+ repHref <- fmap (PlayerId . decodeUtf8 . BL.drop 8 . fromAttrib "href") $+ listToEither "Can't find href for rep id in synchTown" $+ filter (isTagOpenName "a") rep+ repName <- fmap (decodeUtf8 . fromTagText) $+ listToEither "Can't find rep name in synchTown" $+ filter isTagText rep+ timeText <- fmap (decodeUtf8 . fromTagText) $+ listToEither "Can't find time in synchTown" $+ filter isTagText time+ tim <- parseTimeText timeText+ pure $ SynchTown synHref synText status repHref repName tim+parseSynchTownRow _ = Left "Html changed in synchTown, please report to chgk@pm.me"++parseTimeText :: Text -> Either B.ByteString LocalTime+parseTimeText t = case T.words t of+ [dStr,monStr,yStr,timeStr] -> case T.split (==':') timeStr of+ [hStr,mStr,sStr] -> do+ d <- eparse "Can't parse day" dStr+ mon<-emonth monStr+ y <- eparse "Can't parse year" yStr+ h <- eparse "Can't parse hour" hStr+ m <- eparse "Can't parse min" mStr+ s <- eparse "Can't parse sec" sStr+ pure $ LocalTime (fromGregorian y mon d) (TimeOfDay h m $ MkFixed $ s * (resolution (MkFixed 0 :: Pico)))+ _ -> Left "Can't parse time"+ _ -> Left "Can't parse date"+ where eparse e t = case decimal t of+ Left _ -> Left e+ Right (n,_) -> Right n+ emonth m = case elemIndex m months of+ Nothing -> Left "Can't parse month"+ Just v -> Right $ v+1+ months = ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"]++-- | Получает список городов+--+-- @since 0.3.6.4+towns :: Maybe Int -- ^ Номер страницы (если не задан - первая)+ -> IO (Either B.ByteString [Town]) -- ^ Ошибка или список городов+towns mpage = do+ let url = "https://rating.chgk.info/geo.php?layout=town_list" ++ maybe "" (("&page=" ++) . show) mpage+ r <- get url+ pure $ case r^.responseStatus.statusCode of+ 200 -> parseTown $ r^.responseBody+ _ -> Left $ r^.responseStatus.statusMessage++parseTown :: ByteString -> Either B.ByteString [Town]+parseTown body = let+ tags = parseTags $+ convert "CP1251" "UTF-8" body :: [Tag ByteString]+ tbodyTagName = "tbody" :: ByteString+ tbody = takeWhile (\t -> t ~/= TagClose tbodyTagName) $+ dropWhile (\t -> t ~/= TagOpen tbodyTagName []) $+ mapMaybe trimTags tags+ trName = "tr" :: ByteString+ tdName = "td" :: ByteString+ cols = map (partitions (\t -> t ~== TagOpen tdName [])) $+ partitions (\t -> t ~== TagOpen trName []) tbody+ in mapM parseTownRow cols++parseTownRow :: [[Tag ByteString]] -> Either B.ByteString Town+parseTownRow [identT, nameT, regionT, countryT, _countT] = do+ let listToEither s [] = Left s+ listToEither _ (x:_) = Right x+ toId c t = case decimal t of+ Left e -> Left $ B.pack e+ Right (n,_) -> Right $ c n+ identText <- fmap (decodeUtf8 . fromTagText) $+ listToEither "Can't find ident in towns" $+ filter isTagText identT+ ident <- toId id identText+ let (townT, otherT) = span (\t -> t ~/= TagOpen ("span" :: ByteString) []) nameT+ town <- fmap (decodeUtf8 . fromTagText) $+ listToEither "Can't find town in towns" $+ filter isTagText townT+ other <- case filter (isTagOpenName "a") otherT of+ [] -> Right Nothing+ (t:_) -> Right $ Just $ decodeUtf8 $ fromAttrib "title" t+ region <- case filter isTagText regionT of+ [] -> Right Nothing+ (t:_) -> Right $ Just $ decodeUtf8 $ fromTagText t+ country <- case filter isTagText countryT of+ [] -> Right Nothing+ (t:_) -> Right $ Just $ decodeUtf8 $ fromTagText t+ pure $ Town ident town other region country+parseTownRow _ = Left "Html changed in synchTown, please report to chgk@pm.me"
src/RatingChgkInfo/Types.hs view
@@ -47,12 +47,17 @@ , TeamTournament(..) , TeamRating(..) -- ** Турнир+ -- *** Общая информация о турнире , TournamentShort(..) , Tournament(..) , tournamentToShort- , TournamentResult(..)+ -- *** Составы+ , RecapTeam(..) , RecapPlayer(..)+ -- *** Результаты+ , TournamentResult(..) , TourResult(..)+ -- *** Спорные и апелляции , Controversial (..) , Appeal (..) -- ** Типы-перечисления@@ -81,8 +86,13 @@ -- -- Функции для работы с этими типами находятся в модуле "RatingChgkInfo.NoApi" --+ -- ** Заявки на турниры , Request(..) , TeamName(..)+ -- ** География+ , Town (..)+ -- ** Синхроны в городе+ , SynchTown(..) ) where import RatingChgkInfo.Types.Unsafe@@ -313,6 +323,10 @@ toJSON tt = toJSON $ fromMaybe (error "Not all tournamentTypes have names") $ lookup tt tournamentTypenames toEncoding tt = toEncoding $ fromMaybe (error "Not all tournamentTypes have names") $ lookup tt tournamentTypenames +instance ToHttpApiData TournamentType where+ toUrlPiece tt = toUrlPiece $ fromMaybe (error "Not all tournamentTypes have names") $ lookup tt tournamentTypenames+ toQueryParam tt = toQueryParam $ fromMaybe (error "Not all tournamentTypes have names") $ lookup tt tournamentTypenames+ -- | Короткая информация о турнире (в списке турниров) data TournamentShort = TournamentShort { trs_idtournament :: TournamentId -- ^ Идентификатор турнира. __API NOTE__: должен быть Int@@ -320,6 +334,14 @@ , trs_dateStart :: LocalTime -- ^ Дата начала турнира (в часовом поясе МСК) , trs_dateEnd :: LocalTime -- ^ Дата окончания турнира (в часовом поясе МСК) , trs_typeName :: TournamentType -- ^ Тип турнира+ , trs_archive :: Text+ -- ^ Архивирован ли турнир (0 - нет, 1 - да, пустая строка - турнир слишком давний). __API NOTE__: должен быть Bool+ --+ -- @since 0.3.6.4+ , trs_dateArchivedAt :: Day+ -- ^ Дата занесения в архив (может быть пустая строка). __API NOTE__: должен быть Maybe UTCTime+ --+ -- @since 0.3.6.4 } deriving (Eq,Show,Read,Generic) instance FromJSON TournamentShort where@@ -362,6 +384,14 @@ , trn_dateRequestsAllowedTo :: Text -- ^ Дата, до которой разрешена подача заявок. __API NOTE__: должен быть Day , trn_comment :: Text -- ^ Комментарий (это __не__ текст внизу на странице турнира; например, в турнире 5003 комментарий пуст, хотя текст внизу гласит «Сроки турнира привязаны к Новому году и финалу года телеЧГК») , trn_siteUrl :: Text -- ^ Адрес официального сайта+ , trn_archive :: Text+ -- ^ Архивирован ли турнир (0 - нет, 1 - да, пустая строка - турнир слишком давний). __API NOTE__: должен быть Bool+ --+ -- @since 0.3.6.4+ , trn_dateArchivedAt :: Day+ -- ^ Дата занесения в архив (может быть пустая строка). __API NOTE__: должен быть Maybe UTCTime+ --+ -- @since 0.3.6.4 } deriving (Eq,Show,Read,Generic) instance FromJSON Tournament where@@ -377,12 +407,16 @@ , trn_dateStart = dateStart , trn_dateEnd = dateEnd , trn_typeName = typeName+ , trn_archive = archive+ , trn_dateArchivedAt = dateArchived } = TournamentShort { trs_idtournament = idtournament , trs_name = name , trs_dateStart = dateStart , trs_dateEnd = dateEnd , trs_typeName = typeName+ , trs_archive = archive+ , trs_dateArchivedAt = dateArchived } -- | Результаты турнира для команды@@ -409,7 +443,7 @@ -- | Информация об игроке в составе команды на турнире ----- __API NOTE__. Так как игрок не может быть одновременно в базовом составе и легионером, нужно заменить эти два поля одним.+-- __API NOTE__. Так как игрок не может быть одновременно в базовом составе и легионером, нужно заменить эти два поля одним, либо описать, когда игрок может не быть ни базовым, ни легионером data RecapPlayer = RecapPlayer { rp_idplayer :: PlayerId -- ^ Идентификатор игрока. __API NOTE__: должен быть Int , rp_is_captain :: Text -- ^ Является ли игрок капитаном (К). __API NOTE__: должен быть Bool@@ -423,6 +457,18 @@ toJSON = genericToJSON $ jsonOpts '_' 3 toEncoding = genericToEncoding $ jsonOpts '_' 3 +-- | Состав команды на турнире+data RecapTeam = RecapTeam+ { rt_idteam :: TeamId -- ^ Идентификатор команды. __API NOTE__: должен быть Int+ , rt_recaps :: [RecapPlayer] -- ^ Состав команды+ } deriving (Eq,Show,Read,Generic)++instance FromJSON RecapTeam where+ parseJSON = genericParseJSON $ jsonOpts '_' 3+instance ToJSON RecapTeam where+ toJSON = genericToJSON $ jsonOpts '_' 3+ toEncoding = genericToEncoding $ jsonOpts '_' 3+ -- | Результаты команды по турам data TourResult = TourResult { tor_tour :: Text -- ^ Номер тура. __API NOTE__: должен быть Int@@ -564,12 +610,14 @@ :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "town" :> Capture "idtown" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "region" :> Capture "idregion" Int :> Get '[JSON] [TournamentResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "list" :> "country" :> Capture "idcountry" Int :> Get '[JSON] [TournamentResult]+ :<|> "tournaments" :> Capture "idtournament" TournamentId :> "recaps" :> Get '[JSON] [RecapTeam] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "recaps" :> Capture "idteam" TeamId :> Get '[JSON] [RecapPlayer] -- TODO: Set? :<|> "tournaments" :> Capture "idtournament" TournamentId :> "results" :> Capture "idteam" TeamId :> Get '[JSON] [TourResult] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "controversials" :> Get '[JSON] [Controversial] :<|> "tournaments" :> Capture "idtournament" TournamentId :> "appeals" :> Get '[JSON] [Appeal] :<|> "teams" :> "search" :> QueryParam "name" Text :> QueryParam "town" Text :> QueryParam "region_name" Text :> QueryParam "country_name" Text :> QueryFlag "active_this_season" :> QueryParam "page" Int :> Get '[JSON] (Items Team) :<|> "players" :> "search" :> QueryParam "surname" Text :> QueryParam "name" Text :> QueryParam "patronymic" Text :> QueryParam "page" Int :> Get '[JSON] (Items Player)+ :<|> "tournaments" :> "search" :> QueryParam "type_name" TournamentType :> QueryParam "archive" Int :> QueryParam "page" Int :> Get '[JSON] (Items TournamentShort) -------------------------------------------------------------------------------- -- Non-API Types@@ -612,6 +660,36 @@ declareNamedSchema p = genericDeclareNamedSchema (schemaOpts 3) p & mapped.schema.title ?~ "Request" & mapped.schema.description ?~ "Заявка. В объекте содержатся поля accepted - статус заявки (null - не рассмотрена, false/true - отклонена или принята); town - город; representative-id - id представителя; representative-fullname - ФИО представителя, narrator-id - id ведущего (сейчас установлена в 0, сайт рейтинга не экспортирует id); narrator-fullname - ФИО ведущего; teams-count - примерное количество команд (заявлено); teams - список введённых команд"++-- | Синхрон, проводимый в городе+data SynchTown = SynchTown+ { stTournamentId :: TournamentId -- ^ Идентификатор турнира+ , stTournament :: Text -- ^ Название турнира+ , stStatus :: ClaimStatus -- ^ Статус заявки+ , stRepresentativeId :: PlayerId -- ^ Идентификатор представителя+ , stRepresentative :: Text -- ^ ФИО представителя+ , stTime :: LocalTime -- ^ Время проведения+ } deriving (Eq,Show,Read,Generic)++instance ToJSON SynchTown where+ toJSON = genericToJSON $ jsonOpts '-' 2+ toEncoding = genericToEncoding $ jsonOpts '-' 2++-- | Идентификатор города+type TownId = Int++-- | Город+data Town = Town+ { townId :: TownId -- ^ Идентификатор города+ , townName :: Text -- ^ Название города+ , townOtherName :: Maybe Text -- ^ Альтернативное название города (например, Бахмут - Артёмовск)+ , townRegion :: Maybe Text -- ^ Регион+ , townCountry :: Maybe Text -- ^ Страна+ } deriving (Eq,Show,Read,Generic)++instance ToJSON Town where+ toJSON = genericToJSON $ jsonOpts '-' 4+ toEncoding = genericToEncoding $ jsonOpts '-' 4 jsonOpts :: Char -> Int -> Options jsonOpts c k = defaultOptions { fieldLabelModifier = camelTo2 c . drop k }