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.

171 lines
6.1 KiB

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 (getEdt) where
  6. import qualified Data.Map.Strict as Map
  7. import qualified Text.ICalendar.Parser as IP
  8. import qualified Text.ICalendar.Types as IT
  9. import qualified Data.Text as TS
  10. import qualified Data.Text.Lazy.Encoding as TE
  11. import qualified Data.CaseInsensitive as CI
  12. import qualified Data.Text.Lazy as T
  13. import qualified Text.URI as Uri
  14. import qualified Network.HTTP.Simple as HTTP
  15. import Conf ( Group(..), Config(..) )
  16. import Data.Maybe ( fromMaybe )
  17. import Control.Monad.IO.Class ( MonadIO, liftIO )
  18. import qualified Data.Time as DT
  19. import qualified Data.Time.Calendar.Easter as DT
  20. import Data.List ( sortOn )
  21. import Text.Printf ( printf )
  22. import qualified Text.Regex.PCRE as Re
  23. import qualified Data.Array as Ar
  24. getEdt ::Config
  25. -> Map.Map TS.Text TS.Text
  26. -> IO TS.Text
  27. getEdt conf@Config{..} opts = do
  28. cal_req <- HTTP.httpLBS req
  29. let cal = IP.parseICalendar (IP.DecodingFunctions
  30. TE.decodeUtf8
  31. (CI.mk . TE.decodeUtf8)
  32. ) (TS.unpack group) (HTTP.getResponseBody cal_req)
  33. dates <-readDate date
  34. tz <- DT.getTimeZone
  35. $ DT.UTCTime (head dates)
  36. $ DT.secondsToDiffTime 43200
  37. let message = case cal of
  38. Left _ -> "I don't know this group, alternatively ADE doesn't work"
  39. Right (vcal@IT.VCalendar{IT.vcEvents = evm}:_, _) ->
  40. TS.unlines . map (\d ->
  41. if not (null (ev d)) then
  42. "**"
  43. `TS.append` TS.pack (show d)
  44. `TS.append` "**\n"
  45. `TS.append` renderEvents tz (ev d)
  46. else ""
  47. )
  48. $ dates
  49. where
  50. ev :: DT.Day -> [IT.VEvent]
  51. ev d =
  52. sortOn IT.veDTStart
  53. . filter (inDate d)
  54. . map snd
  55. . Map.toList
  56. $ 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
  89. (read [yk,yh,yd,yu])
  90. (read [md,mu])
  91. (read [dd,du])
  92. ]
  93. _ -> [today]
  94. inDate :: DT.Day -> IT.VEvent -> Bool
  95. inDate date ev@IT.VEvent{veDTStart = mdts} = evDay == date
  96. where
  97. evDay :: DT.Day
  98. evDay = case mdts of
  99. Just dts ->
  100. case dts of
  101. IT.DTStartDateTime dt _ ->
  102. case dt of
  103. IT.FloatingDateTime lt -> DT.localDay lt
  104. IT.UTCDateTime ut -> DT.utctDay ut
  105. IT.ZonedDateTime lt _ -> DT.localDay lt
  106. IT.DTStartDate da _ -> IT.dateValue da
  107. Nothing -> DT.fromGregorian 0 0 0
  108. renderEvents :: DT.TimeZone -> [IT.VEvent] -> TS.Text
  109. renderEvents tz = TS.unlines . map renderEvent
  110. where
  111. renderEvent :: IT.VEvent -> TS.Text
  112. renderEvent ev =
  113. TS.pack
  114. $ printf
  115. "*%02d:%02d → %02d:%02d* : **%s** with **%s** in **%s**"
  116. (DT.todHour $ startT ev) (DT.todMin $ startT ev)
  117. (DT.todHour $ endT ev) (DT.todMin $ endT ev)
  118. (summary ev) (teacher ev) (room ev)
  119. startT :: IT.VEvent -> DT.TimeOfDay
  120. startT IT.VEvent{veDTStart = Just (IT.DTStartDateTime dt _)} = case dt of
  121. IT.FloatingDateTime lt -> DT.localTimeOfDay lt
  122. IT.UTCDateTime ut ->
  123. snd
  124. . DT.utcToLocalTimeOfDay tz
  125. . DT.timeToTimeOfDay
  126. $ DT.utctDayTime ut
  127. IT.ZonedDateTime lt _ -> DT.localTimeOfDay lt
  128. startT _ = DT.TimeOfDay 0 0 0
  129. endT :: IT.VEvent -> DT.TimeOfDay
  130. endT IT.VEvent{veDTEndDuration = Just (Left (IT.DTEndDateTime dt _))} =
  131. case dt of
  132. IT.FloatingDateTime lt -> DT.localTimeOfDay lt
  133. IT.UTCDateTime ut ->
  134. snd
  135. . DT.utcToLocalTimeOfDay tz
  136. . DT.timeToTimeOfDay
  137. $ DT.utctDayTime ut
  138. IT.ZonedDateTime lt txt -> DT.localTimeOfDay lt
  139. endT _ = DT.TimeOfDay 0 0 0
  140. summary IT.VEvent{veSummary = Just IT.Summary{summaryValue = x}} = x
  141. summary _ = "Unknown"
  142. room IT.VEvent{veLocation = Just IT.Location{locationValue = x}} = x
  143. room _ = "Unknown"
  144. teacher :: IT.VEvent -> String
  145. teacher IT.VEvent{veDescription = Just IT.Description{descriptionValue = val}} =
  146. if '\n' `elem` teacher' then
  147. "Unknown"
  148. else
  149. teacher'
  150. where
  151. teacher' =
  152. if uncurry (>) (Ar.bounds marray) then
  153. "Unknown"
  154. else
  155. fst . last . Ar.elems $ marray
  156. marray = if null matches then Ar.listArray (1,0) [] else head matches
  157. matches = Re.matchAllText teacherRe $ T.unpack val
  158. teacher _ = "Unknown"
  159. teacherRe = Re.makeRegexOpts
  160. Re.compMultiline
  161. Re.execBlank
  162. ("^\\n\\n.+\\n(.+)\\n" :: String)