JobsUnifiedTopCard.purs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. module LinkedIn.JobsUnifiedTopCard where
  2. import Control.Alt
  3. import Prelude
  4. import Data.Either (Either(..), hush)
  5. import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault)
  6. import Data.Generic.Rep (class Generic)
  7. import Data.List.Types (NonEmptyList)
  8. import Data.Maybe (Maybe(..))
  9. import Data.Show.Generic (genericShow)
  10. import Data.Traversable (class Traversable, sequence, traverseDefault)
  11. import LinkedIn (DetachedNode(..))
  12. import LinkedIn.Types (ParseError(..), Parser)
  13. import LinkedIn.Utils (detachNonEmptyTextChild, parseDetachedNode, queryAndDetachMany, queryAndDetachOne, queryManyAndParse, queryOneAndParse)
  14. data JobsUnifiedTopCardElement a = JobsUnifiedTopCardElement {
  15. header :: a,
  16. primaryDescription :: TopCardPrimaryDescription a,
  17. insights :: Maybe (NonEmptyList (TopCardInsight a)),
  18. actions :: Maybe (NonEmptyList (TopCardAction a))
  19. }
  20. data TopCardPrimaryDescription a = TopCardPrimaryDescription {
  21. link :: a,
  22. text :: a,
  23. tvmText :: Maybe (NonEmptyList a)
  24. }
  25. data TopCardInsight a = TopCardInsight {
  26. icon :: a,
  27. content :: TopCardInsightContent a
  28. }
  29. data TopCardInsightContent a =
  30. TopCardInsightContentSingle a
  31. | TopCardInsightContentSecondary {primary :: a, secondary :: NonEmptyList (TopCardSecondaryInsight a)}
  32. | TopCardInsightContentButton a
  33. data TopCardSecondaryInsight a =
  34. TopCardSecondaryInsightNested a
  35. | TopCardSecondaryInsightPlain a
  36. -- External application : <button id="ember74" class="jobs-apply-button artdeco-button artdeco-button--3 artdeco-button--primary ember-view artdeco-button--icon-right" role="link">
  37. -- LinkedIn Applcation : <button id="ember115" class="jobs-apply-button artdeco-button artdeco-button--3 artdeco-button--primary ember-view" data-job-id="3786945580">
  38. data TopCardAction a = TopCardActionApplyButton a
  39. derive instance Generic (JobsUnifiedTopCardElement a) _
  40. derive instance Eq a => Eq (JobsUnifiedTopCardElement a)
  41. instance Show a => Show (JobsUnifiedTopCardElement a) where
  42. show = genericShow
  43. derive instance Functor JobsUnifiedTopCardElement
  44. instance Foldable JobsUnifiedTopCardElement where
  45. foldMap f (JobsUnifiedTopCardElement {header, primaryDescription, insights, actions}) =
  46. f header
  47. <> foldMap f primaryDescription
  48. <> foldMap (foldMap (foldMap f)) insights
  49. <> foldMap (foldMap (foldMap f)) actions
  50. foldl = \x -> foldlDefault x
  51. foldr = \x -> foldrDefault x
  52. instance Traversable JobsUnifiedTopCardElement where
  53. sequence (JobsUnifiedTopCardElement {header, primaryDescription, insights, actions}) = ado
  54. h <- header
  55. pd <- sequence primaryDescription
  56. i <- traverseMayNel insights
  57. a <- traverseMayNel actions
  58. in JobsUnifiedTopCardElement {header: h, primaryDescription: pd, insights: i, actions: a}
  59. traverse = \x -> traverseDefault x
  60. traverseMayNel :: forall m t a. Traversable t => Applicative m => Maybe(NonEmptyList (t (m a))) -> m (Maybe (NonEmptyList (t a)))
  61. traverseMayNel (Just o) = map pure (sequence (map sequence o))
  62. traverseMayNel Nothing = pure Nothing
  63. derive instance Generic (TopCardPrimaryDescription a) _
  64. derive instance Eq a => Eq (TopCardPrimaryDescription a)
  65. instance Show a => Show (TopCardPrimaryDescription a) where
  66. show = genericShow
  67. derive instance Functor TopCardPrimaryDescription
  68. instance Foldable TopCardPrimaryDescription where
  69. foldMap f (TopCardPrimaryDescription {link, text, tvmText}) = f link <> f text <> foldMap (foldMap f) tvmText
  70. foldl = \x -> foldlDefault x
  71. foldr = \x -> foldrDefault x
  72. instance Traversable TopCardPrimaryDescription where
  73. sequence (TopCardPrimaryDescription {link, text, tvmText}) = ado
  74. l <- link
  75. t <- text
  76. tvm <- sequence (map sequence tvmText)
  77. in TopCardPrimaryDescription {link: l, text: t, tvmText: tvm}
  78. traverse = \x -> traverseDefault x
  79. derive instance Generic (TopCardInsight a) _
  80. derive instance Eq a => Eq (TopCardInsight a)
  81. instance Show a => Show (TopCardInsight a) where
  82. show = genericShow
  83. derive instance Functor TopCardInsight
  84. instance Foldable TopCardInsight where
  85. foldMap f (TopCardInsight {icon, content}) = f icon <> foldMap f content
  86. foldl = \x -> foldlDefault x
  87. foldr = \x -> foldrDefault x
  88. instance Traversable TopCardInsight where
  89. sequence (TopCardInsight {icon, content}) = ado
  90. i <- icon
  91. c <- sequence content
  92. in TopCardInsight {icon: i, content: c}
  93. traverse = \x -> traverseDefault x
  94. derive instance Generic (TopCardInsightContent a) _
  95. derive instance Eq a => Eq (TopCardInsightContent a)
  96. instance Show a => Show (TopCardInsightContent a) where
  97. show = genericShow
  98. derive instance Functor TopCardInsightContent
  99. instance Foldable TopCardInsightContent where
  100. foldMap f (TopCardInsightContentSingle a) = f a
  101. foldMap f (TopCardInsightContentButton a) = f a
  102. foldMap f (TopCardInsightContentSecondary {primary, secondary}) = f primary <> foldMap (foldMap f) secondary
  103. foldl = \x -> foldlDefault x
  104. foldr = \x -> foldrDefault x
  105. instance Traversable TopCardInsightContent where
  106. sequence (TopCardInsightContentSingle ins) = TopCardInsightContentSingle <$> ins
  107. sequence (TopCardInsightContentButton ins) = TopCardInsightContentButton <$> ins
  108. sequence (TopCardInsightContentSecondary {primary, secondary}) = ado
  109. p <- primary
  110. s <- sequence (map sequence secondary)
  111. in TopCardInsightContentSecondary {primary: p, secondary: s}
  112. traverse = \x -> traverseDefault x
  113. derive instance Generic (TopCardSecondaryInsight a) _
  114. derive instance Eq a => Eq (TopCardSecondaryInsight a)
  115. instance Show a => Show (TopCardSecondaryInsight a) where
  116. show = genericShow
  117. derive instance Functor TopCardSecondaryInsight
  118. instance Foldable TopCardSecondaryInsight where
  119. foldMap f (TopCardSecondaryInsightNested a) = f a
  120. foldMap f (TopCardSecondaryInsightPlain a) = f a
  121. foldl = \x -> foldlDefault x
  122. foldr = \x -> foldrDefault x
  123. instance Traversable TopCardSecondaryInsight where
  124. sequence (TopCardSecondaryInsightNested ins) = TopCardSecondaryInsightNested <$> ins
  125. sequence (TopCardSecondaryInsightPlain ins) = TopCardSecondaryInsightPlain <$> ins
  126. traverse = \x -> traverseDefault x
  127. derive instance Generic (TopCardAction a) _
  128. derive instance Eq a => Eq (TopCardAction a)
  129. instance Show a => Show (TopCardAction a) where
  130. show = genericShow
  131. derive instance Functor TopCardAction
  132. instance Foldable TopCardAction where
  133. foldMap f (TopCardActionApplyButton a) = f a
  134. foldl = \x -> foldlDefault x
  135. foldr = \x -> foldrDefault x
  136. instance Traversable TopCardAction where
  137. sequence (TopCardActionApplyButton app) = ado
  138. a <- app
  139. in TopCardActionApplyButton a
  140. traverse = \x -> traverseDefault x
  141. parseTopCardAction :: Parser (TopCardAction DetachedNode)
  142. parseTopCardAction n = do
  143. self <- parseDetachedNode n
  144. pure $ ado
  145. s <- self
  146. in TopCardActionApplyButton s
  147. parseTopCardSecondaryInsight :: Parser (TopCardSecondaryInsight DetachedNode)
  148. parseTopCardSecondaryInsight n = do
  149. nested <- queryAndDetachOne ":scope span[aria-hidden=true]" n
  150. plain <- parseDetachedNode n
  151. pure $ case nested, plain of
  152. Right p@(DetachedElement _), _ -> Right $ TopCardSecondaryInsightNested p
  153. _, Right p@(DetachedElement _) -> Right $ TopCardSecondaryInsightPlain p
  154. _, _ -> Left TextNotFoundError
  155. parseTopCardInsightContent :: Parser (TopCardInsightContent DetachedNode)
  156. parseTopCardInsightContent n = do
  157. primary <- queryAndDetachOne ":scope > span:first-child span[aria-hidden=true]" n
  158. secondary <- queryManyAndParse
  159. ":scope > span.job-details-jobs-unified-top-card__job-insight-view-model-secondary"
  160. parseTopCardSecondaryInsight
  161. n
  162. self <- parseDetachedNode n
  163. pure $ case primary, secondary, self of
  164. _, _, Right b@(DetachedElement {tag: "BUTTON"}) -> Right $ TopCardInsightContentButton b
  165. Right p@(DetachedElement _), Right s, _ -> Right $ TopCardInsightContentSecondary {primary: p, secondary: s}
  166. _, _, Right el@(DetachedElement _) -> Right $ TopCardInsightContentSingle el
  167. _, _, _ -> Left TextNotFoundError
  168. parseTopCardInsight :: Parser (TopCardInsight DetachedNode)
  169. parseTopCardInsight n = do
  170. icon <- queryAndDetachOne ":scope li-icon" n
  171. svg <- queryAndDetachOne ":scope svg" n
  172. content <- queryOneAndParse ":scope > span" parseTopCardInsightContent n
  173. actionButton <- queryOneAndParse ":scope > button" parseTopCardInsightContent n
  174. pure $ ado
  175. i <- icon <|> svg
  176. c <- content <|> actionButton
  177. in TopCardInsight {icon: i, content: c}
  178. parseTopCardPrimaryDescription :: Parser (TopCardPrimaryDescription DetachedNode)
  179. parseTopCardPrimaryDescription n = do
  180. link <- queryAndDetachOne ":scope > a" n
  181. text <- detachNonEmptyTextChild n
  182. tvmText <- queryAndDetachMany "span.tvm__text" n
  183. pure $ ado
  184. l <- link
  185. t <- text
  186. in TopCardPrimaryDescription {link: l, text: t, tvmText: hush tvmText}
  187. parseJobsUnifiedTopCardElement :: Parser (JobsUnifiedTopCardElement DetachedNode)
  188. parseJobsUnifiedTopCardElement n = do
  189. h1 <- queryAndDetachOne "h1.job-details-jobs-unified-top-card__job-title" n
  190. primary <- queryOneAndParse
  191. "div.job-details-jobs-unified-top-card__primary-description-container > div"
  192. parseTopCardPrimaryDescription
  193. n
  194. insights <- queryManyAndParse
  195. "li.job-details-jobs-unified-top-card__job-insight"
  196. parseTopCardInsight
  197. n
  198. actions <- queryManyAndParse
  199. ".mt5 button"
  200. parseTopCardAction
  201. n
  202. pure $ ado
  203. h <- h1
  204. p <- primary
  205. in JobsUnifiedTopCardElement {
  206. header: h,
  207. primaryDescription: p,
  208. insights: hush insights,
  209. actions: hush actions
  210. }
  211. toHeader ∷ forall a. JobsUnifiedTopCardElement a → a
  212. toHeader (JobsUnifiedTopCardElement {header}) = header
  213. toPrimaryDescriptionLink ∷ forall a. JobsUnifiedTopCardElement a → a
  214. toPrimaryDescriptionLink (JobsUnifiedTopCardElement {
  215. primaryDescription: TopCardPrimaryDescription {link}
  216. }) = link
  217. toPrimaryDescriptionText ∷ forall a. JobsUnifiedTopCardElement a → a
  218. toPrimaryDescriptionText (JobsUnifiedTopCardElement {
  219. primaryDescription: TopCardPrimaryDescription {text}
  220. }) = text