You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

155 lines
5.8 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. {-# LANGUAGE RecordWildCards #-}
  2. {-# LANGUAGE OverloadedStrings #-}
  3. {-# LANGUAGE DataKinds #-}
  4. {-# OPTIONS_GHC -Wno-deferred-out-of-scope-variables #-}
  5. module Commands.EDT
  6. ( getEdt
  7. ) where
  8. import Conf ( Config(..)
  9. , Group(..)
  10. )
  11. import Control.Monad.IO.Class ( MonadIO
  12. , liftIO
  13. )
  14. import qualified Data.Array as Ar
  15. import qualified Data.CaseInsensitive as CI
  16. import Data.List ( sortOn )
  17. import qualified Data.Map.Strict as Map
  18. import Data.Maybe ( fromMaybe )
  19. import qualified Data.Text as TS
  20. import qualified Data.Text.Lazy as T
  21. import qualified Data.Text.Lazy.Encoding as TE
  22. import qualified Data.Time as DT
  23. import qualified Data.Time.Calendar.Easter as DT
  24. import qualified Network.HTTP.Simple as HTTP
  25. import qualified Text.ICalendar.Parser as IP
  26. import qualified Text.ICalendar.Types as IT
  27. import Text.Printf ( printf )
  28. import qualified Text.Regex.PCRE as Re
  29. import qualified Text.URI as Uri
  30. getEdt :: Config -> Map.Map TS.Text TS.Text -> IO TS.Text
  31. getEdt conf@Config {..} opts = do
  32. cal_req <- HTTP.httpLBS req
  33. let cal = IP.parseICalendar
  34. (IP.DecodingFunctions TE.decodeUtf8 (CI.mk . TE.decodeUtf8))
  35. (TS.unpack group)
  36. (HTTP.getResponseBody cal_req)
  37. dates <- readDate date
  38. tz <- DT.getTimeZone $ DT.UTCTime (head dates) $ DT.secondsToDiffTime 43200
  39. let message = case cal of
  40. Left _ -> "I don't know this group, alternatively ADE doesn't work"
  41. Right (vcal@IT.VCalendar { IT.vcEvents = evm } : _, _) ->
  42. TS.unlines
  43. . map
  44. (\d -> if not (null (ev d))
  45. then
  46. "**"
  47. `TS.append` TS.pack (show d)
  48. `TS.append` "**\n"
  49. `TS.append` renderEvents tz (ev d)
  50. else "No classes for the selected dates"
  51. )
  52. $ dates
  53. where
  54. ev :: DT.Day -> [IT.VEvent]
  55. ev d =
  56. sortOn IT.veDTStart . filter (inDate d) . map snd . Map.toList $ evm
  57. _ -> "An unexpected error has occured"
  58. return message
  59. where
  60. group :: TS.Text
  61. group = fromMaybe "" $ Map.lookup "group" opts
  62. date :: TS.Text
  63. date = fromMaybe "week" $ Map.lookup "day" opts
  64. url :: String
  65. url =
  66. TS.unpack
  67. . groupAde
  68. . fromMaybe (Group 0 0 dummyAddress)
  69. . (`Map.lookup` configGroups)
  70. . fromMaybe ""
  71. . Map.lookup "group"
  72. $ opts
  73. req = HTTP.parseRequest_ url
  74. -- not a place where you'll find valid ICS so we know the group must be wrong
  75. dummyAddress :: TS.Text
  76. dummyAddress = "https://example.com"
  77. readDate :: TS.Text -> IO [DT.Day]
  78. readDate dtT = do
  79. DT.UTCTime today _ <- DT.getCurrentTime
  80. return $ case TS.unpack dtT of
  81. "today" -> [today]
  82. "tomorrow" -> [DT.addDays 1 today]
  83. "week" -> map (`DT.addDays` lastSunday) [1 .. 7] where
  84. lastweek :: DT.Day
  85. lastweek = DT.addDays (-6) today
  86. lastSunday = DT.sundayAfter lastweek
  87. [dd, du, '/', md, mu, '/', yk, yh, yd, yu] ->
  88. [DT.fromGregorian (read [yk, yh, yd, yu]) (read [md, mu]) (read [dd, du])]
  89. _ -> [today]
  90. inDate :: DT.Day -> IT.VEvent -> Bool
  91. inDate date ev@IT.VEvent { veDTStart = mdts } = evDay == date
  92. where
  93. evDay :: DT.Day
  94. evDay = case mdts of
  95. Just dts -> case dts of
  96. IT.DTStartDateTime dt _ -> case dt of
  97. IT.FloatingDateTime lt -> DT.localDay lt
  98. IT.UTCDateTime ut -> DT.utctDay ut
  99. IT.ZonedDateTime lt _ -> DT.localDay lt
  100. IT.DTStartDate da _ -> IT.dateValue da
  101. Nothing -> DT.fromGregorian 0 0 0
  102. renderEvents :: DT.TimeZone -> [IT.VEvent] -> TS.Text
  103. renderEvents tz = TS.unlines . map renderEvent
  104. where
  105. renderEvent :: IT.VEvent -> TS.Text
  106. renderEvent ev = TS.pack $ printf
  107. "*%02d:%02d → %02d:%02d* : **%s** with **%s** in **%s**"
  108. (DT.todHour $ startT ev)
  109. (DT.todMin $ startT ev)
  110. (DT.todHour $ endT ev)
  111. (DT.todMin $ endT ev)
  112. (summary ev)
  113. (teacher ev)
  114. (room ev)
  115. startT :: IT.VEvent -> DT.TimeOfDay
  116. startT IT.VEvent { veDTStart = Just (IT.DTStartDateTime dt _) } = case dt of
  117. IT.FloatingDateTime lt -> DT.localTimeOfDay lt
  118. IT.UTCDateTime ut ->
  119. snd . DT.utcToLocalTimeOfDay tz . DT.timeToTimeOfDay $ DT.utctDayTime ut
  120. IT.ZonedDateTime lt _ -> DT.localTimeOfDay lt
  121. startT _ = DT.TimeOfDay 0 0 0
  122. endT :: IT.VEvent -> DT.TimeOfDay
  123. endT IT.VEvent { veDTEndDuration = Just (Left (IT.DTEndDateTime dt _)) } =
  124. case dt of
  125. IT.FloatingDateTime lt -> DT.localTimeOfDay lt
  126. IT.UTCDateTime ut ->
  127. snd . DT.utcToLocalTimeOfDay tz . DT.timeToTimeOfDay $ DT.utctDayTime ut
  128. IT.ZonedDateTime lt txt -> DT.localTimeOfDay lt
  129. endT _ = DT.TimeOfDay 0 0 0
  130. summary IT.VEvent { veSummary = Just IT.Summary { summaryValue = x } } = x
  131. summary _ = "Unknown"
  132. room IT.VEvent { veLocation = Just IT.Location { locationValue = x } } = x
  133. room _ = "Unknown"
  134. teacher :: IT.VEvent -> String
  135. teacher IT.VEvent { veDescription = Just IT.Description { descriptionValue = val } }
  136. = if '\n' `elem` teacher' then "Unknown" else teacher'
  137. where
  138. teacher' = if uncurry (>) (Ar.bounds marray)
  139. then "Unknown"
  140. else fst . last . Ar.elems $ marray
  141. marray = if null matches then Ar.listArray (1, 0) [] else head matches
  142. matches = Re.matchAllText teacherRe $ T.unpack val
  143. teacher _ = "Unknown"
  144. teacherRe = Re.makeRegexOpts Re.compMultiline
  145. Re.execBlank
  146. ("^\\n\\n.+\\n(.+)\\n" :: String)