follows and inbox
This commit is contained in:
parent
9b3da936cf
commit
1fde45736d
6 changed files with 147 additions and 34 deletions
|
@ -11,6 +11,16 @@ import Html
|
||||||
import Lucid qualified as H
|
import Lucid qualified as H
|
||||||
import System.IO.Unsafe (unsafePerformIO)
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
import Web.Twain qualified as Twain
|
import Web.Twain qualified as Twain
|
||||||
|
import Network.HTTP.Req
|
||||||
|
( runReq
|
||||||
|
, defaultHttpConfig
|
||||||
|
, req
|
||||||
|
, POST(POST)
|
||||||
|
, ReqBodyJson(ReqBodyJson)
|
||||||
|
, jsonResponse
|
||||||
|
, responseBody
|
||||||
|
, https
|
||||||
|
)
|
||||||
|
|
||||||
routes :: DB -> FilePath -> [Twain.Middleware]
|
routes :: DB -> FilePath -> [Twain.Middleware]
|
||||||
routes db detailsFile =
|
routes db detailsFile =
|
||||||
|
@ -43,12 +53,8 @@ routes db detailsFile =
|
||||||
Fedi.handleCreateNote details notes
|
Fedi.handleCreateNote details notes
|
||||||
, -- Match inbox
|
, -- Match inbox
|
||||||
Twain.get (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do
|
Twain.get (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do
|
||||||
let
|
Fedi.handleInbox (handleInbox db detailsFile)
|
||||||
handle activity = do
|
|
||||||
liftIO (print activity)
|
|
||||||
pure $ Fedi.jsonLD $ A.encode activity
|
|
||||||
|
|
||||||
Fedi.handleInbox handle
|
|
||||||
, -- Match Create object
|
, -- Match Create object
|
||||||
Twain.get (Fedi.matchCreateNote $ unsafePerformIO $ fetchUserDetails detailsFile) do
|
Twain.get (Fedi.matchCreateNote $ unsafePerformIO $ fetchUserDetails detailsFile) do
|
||||||
details <- liftIO $ fetchUserDetails detailsFile
|
details <- liftIO $ fetchUserDetails detailsFile
|
||||||
|
@ -70,10 +76,20 @@ routes db detailsFile =
|
||||||
Nothing -> Twain.next
|
Nothing -> Twain.next
|
||||||
Just thenote ->
|
Just thenote ->
|
||||||
Twain.send $ Twain.html $ H.renderBS $ actorPage details [thenote]
|
Twain.send $ Twain.html $ H.renderBS $ actorPage details [thenote]
|
||||||
|
|
||||||
|
, -- Followers
|
||||||
|
Twain.get (Fedi.matchFollowers $ unsafePerformIO $ fetchUserDetails detailsFile) do
|
||||||
|
details <- liftIO $ fetchUserDetails detailsFile
|
||||||
|
Fedi.handleFollowers details
|
||||||
|
, -- Following
|
||||||
|
Twain.get (Fedi.matchFollowing $ unsafePerformIO $ fetchUserDetails detailsFile) do
|
||||||
|
details <- liftIO $ fetchUserDetails detailsFile
|
||||||
|
Fedi.handleFollowing details
|
||||||
, -- Match webfinger
|
, -- Match webfinger
|
||||||
Twain.get Fedi.matchWebfinger do
|
Twain.get Fedi.matchWebfinger do
|
||||||
details <- liftIO $ fetchUserDetails detailsFile
|
details <- liftIO $ fetchUserDetails detailsFile
|
||||||
Fedi.handleWebfinger details
|
Fedi.handleWebfinger details
|
||||||
|
--------------------------------------------------------------------------------------------
|
||||||
, -- Admin page
|
, -- Admin page
|
||||||
Twain.get (fromString $ "/" <> (unsafePerformIO $ fetchUserDetails detailsFile).username <> "/admin") do
|
Twain.get (fromString $ "/" <> (unsafePerformIO $ fetchUserDetails detailsFile).username <> "/admin") do
|
||||||
details <- liftIO $ fetchUserDetails detailsFile
|
details <- liftIO $ fetchUserDetails detailsFile
|
||||||
|
@ -109,3 +125,48 @@ fetchUserDetails detailsFile =
|
||||||
|
|
||||||
noteToCreate :: Fedi.Note -> Fedi.Create
|
noteToCreate :: Fedi.Note -> Fedi.Create
|
||||||
noteToCreate note = Fedi.makeCreateNote note
|
noteToCreate note = Fedi.makeCreateNote note
|
||||||
|
|
||||||
|
handleInbox :: DB -> FilePath -> Fedi.AnyActivity -> Twain.ResponderM Twain.Response
|
||||||
|
handleInbox db detailsFile activity = do
|
||||||
|
details <- liftIO $ fetchUserDetails detailsFile
|
||||||
|
case activity of
|
||||||
|
Fedi.ActivityFollow follow -> do
|
||||||
|
let
|
||||||
|
id' = follow.id
|
||||||
|
actor = follow.otype.actor
|
||||||
|
object = follow.otype.atype.object
|
||||||
|
case id' of
|
||||||
|
Just id'' -> do
|
||||||
|
if object == Fedi.LLink (Fedi.Link $ Fedi.actorUrl details)
|
||||||
|
then do
|
||||||
|
liftIO do
|
||||||
|
insertId <- db.insertFollower FollowerEntry
|
||||||
|
{ actorId = fromString actor.unwrap
|
||||||
|
, followId = fromString id''.unwrap
|
||||||
|
}
|
||||||
|
(result :: A.Value) <- sendRequest
|
||||||
|
(id''.unwrap <> "/inbox")
|
||||||
|
( Fedi.makeAccept
|
||||||
|
follow
|
||||||
|
(Fedi.actorUrl details <> "/accepts/follows/" <> show insertId)
|
||||||
|
)
|
||||||
|
print result
|
||||||
|
pure $ Fedi.jsonLD "{}"
|
||||||
|
else Twain.next
|
||||||
|
Nothing ->
|
||||||
|
Twain.next
|
||||||
|
_ -> do
|
||||||
|
liftIO (print activity)
|
||||||
|
Twain.next
|
||||||
|
|
||||||
|
sendRequest :: (A.ToJSON input, A.FromJSON output) => Fedi.Url -> input -> IO output
|
||||||
|
sendRequest url payload = do
|
||||||
|
runReq defaultHttpConfig do
|
||||||
|
r <-
|
||||||
|
req
|
||||||
|
POST
|
||||||
|
(https $ fromString url)
|
||||||
|
(ReqBodyJson payload)
|
||||||
|
jsonResponse
|
||||||
|
mempty
|
||||||
|
pure $ responseBody r
|
||||||
|
|
|
@ -77,6 +77,7 @@ executable fedi
|
||||||
, raw-strings-qq
|
, raw-strings-qq
|
||||||
, securemem
|
, securemem
|
||||||
, lucid2
|
, lucid2
|
||||||
|
, req
|
||||||
|
|
||||||
hs-source-dirs: app
|
hs-source-dirs: app
|
||||||
default-language: GHC2021
|
default-language: GHC2021
|
||||||
|
|
|
@ -151,3 +151,18 @@ emptyOrderedCollectionPage url =
|
||||||
, current = Nothing
|
, current = Nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
makeAccept :: Follow -> Url -> Accept
|
||||||
|
makeAccept theirFollow myfollowId =
|
||||||
|
emptyObject
|
||||||
|
{ id = Just $ ObjectId myfollowId
|
||||||
|
, otype =
|
||||||
|
TypeActivity
|
||||||
|
{ actor = theirFollow.otype.actor
|
||||||
|
, atype = TypeAccept
|
||||||
|
{ object = ActivityFollow theirFollow
|
||||||
|
}
|
||||||
|
, target = Nothing
|
||||||
|
, origin = Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -175,6 +175,40 @@ handleInbox handle = do
|
||||||
response <- handle activity
|
response <- handle activity
|
||||||
Twain.send response
|
Twain.send response
|
||||||
|
|
||||||
|
-- * Followers
|
||||||
|
|
||||||
|
matchFollowers :: UserDetails -> Twain.PathPattern
|
||||||
|
matchFollowers details =
|
||||||
|
fromString ("/" <> details.username <> "/followers")
|
||||||
|
|
||||||
|
handleFollowers :: UserDetails -> Twain.ResponderM b
|
||||||
|
handleFollowers details = do
|
||||||
|
let
|
||||||
|
collection :: Collection ()
|
||||||
|
collection =
|
||||||
|
emptyUnorderedCollection
|
||||||
|
{ id = Just $ ObjectId $ actorUrl details <> "/followers"
|
||||||
|
, summary = Just $ fromString $ details.username <> "'s followers"
|
||||||
|
}
|
||||||
|
Twain.send $ jsonLD (A.encode collection)
|
||||||
|
|
||||||
|
-- * Following
|
||||||
|
|
||||||
|
matchFollowing :: UserDetails -> Twain.PathPattern
|
||||||
|
matchFollowing details =
|
||||||
|
fromString ("/" <> details.username <> "/following")
|
||||||
|
|
||||||
|
handleFollowing :: UserDetails -> Twain.ResponderM b
|
||||||
|
handleFollowing details = do
|
||||||
|
let
|
||||||
|
collection :: Collection ()
|
||||||
|
collection =
|
||||||
|
emptyUnorderedCollection
|
||||||
|
{ id = Just $ ObjectId $ actorUrl details <> "/following"
|
||||||
|
, summary = Just $ fromString $ details.username <> " is following"
|
||||||
|
}
|
||||||
|
Twain.send $ jsonLD (A.encode collection)
|
||||||
|
|
||||||
-- * Other stuff
|
-- * Other stuff
|
||||||
|
|
||||||
checkContentTypeAccept :: Twain.Request -> Bool
|
checkContentTypeAccept :: Twain.Request -> Bool
|
||||||
|
|
|
@ -37,7 +37,7 @@ data Object typ
|
||||||
mediaType :: Maybe MediaType
|
mediaType :: Maybe MediaType
|
||||||
-- , duration :: Maybe String
|
-- , duration :: Maybe String
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
class ToObject a where
|
class ToObject a where
|
||||||
toObject :: a -> [A.Pair]
|
toObject :: a -> [A.Pair]
|
||||||
|
@ -101,13 +101,13 @@ newtype ObjectId = ObjectId {unwrap :: String}
|
||||||
deriving (Show, Eq, A.FromJSON, A.ToJSON) via String
|
deriving (Show, Eq, A.FromJSON, A.ToJSON) via String
|
||||||
|
|
||||||
newtype Link = Link {unwrap :: Url}
|
newtype Link = Link {unwrap :: Url}
|
||||||
deriving (Show, A.FromJSON, A.ToJSON) via Url
|
deriving (Show, Eq, A.FromJSON, A.ToJSON) via Url
|
||||||
|
|
||||||
data LinkOrObject a
|
data LinkOrObject a
|
||||||
= LLink Link
|
= LLink Link
|
||||||
| OObject (Object a)
|
| OObject (Object a)
|
||||||
| CCollection [LinkOrObject a]
|
| CCollection [LinkOrObject a]
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance (A.FromJSON a) => A.FromJSON (LinkOrObject a) where
|
instance (A.FromJSON a) => A.FromJSON (LinkOrObject a) where
|
||||||
parseJSON = \case
|
parseJSON = \case
|
||||||
|
@ -130,7 +130,7 @@ instance (ToObject o) => A.ToJSON (LinkOrObject o) where
|
||||||
|
|
||||||
data AnyMedia
|
data AnyMedia
|
||||||
= ImageMedia Image
|
= ImageMedia Image
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance A.ToJSON AnyMedia where
|
instance A.ToJSON AnyMedia where
|
||||||
toJSON = \case
|
toJSON = \case
|
||||||
|
@ -142,7 +142,7 @@ instance A.FromJSON AnyMedia where
|
||||||
|
|
||||||
type Image = Object TypeImage
|
type Image = Object TypeImage
|
||||||
|
|
||||||
data TypeImage = TypeImage deriving (Show)
|
data TypeImage = TypeImage deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeImage where
|
instance ToObject TypeImage where
|
||||||
toObject TypeImage =
|
toObject TypeImage =
|
||||||
|
@ -158,7 +158,7 @@ instance A.FromJSON TypeImage where
|
||||||
data Name
|
data Name
|
||||||
= StringName String
|
= StringName String
|
||||||
| ObjectName (LinkOrObject Actor)
|
| ObjectName (LinkOrObject Actor)
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance A.ToJSON Name where
|
instance A.ToJSON Name where
|
||||||
toJSON = \case
|
toJSON = \case
|
||||||
|
@ -184,7 +184,7 @@ data TypeNote
|
||||||
, replies :: Collection Note
|
, replies :: Collection Note
|
||||||
, sensitive :: Bool
|
, sensitive :: Bool
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeNote where
|
instance ToObject TypeNote where
|
||||||
toObject note =
|
toObject note =
|
||||||
|
@ -209,16 +209,16 @@ data TypeTag
|
||||||
= TypeTag
|
= TypeTag
|
||||||
{ href :: Url
|
{ href :: Url
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
type Preview = Object TypePreview
|
type Preview = Object TypePreview
|
||||||
|
|
||||||
data TypePreview = TypePreview
|
data TypePreview = TypePreview
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
type Share = Object TypeShare
|
type Share = Object TypeShare
|
||||||
|
|
||||||
data TypeShare = TypeShare deriving (Show)
|
data TypeShare = TypeShare deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeShare where
|
instance ToObject TypeShare where
|
||||||
toObject TypeShare =
|
toObject TypeShare =
|
||||||
|
@ -230,7 +230,7 @@ instance A.FromJSON TypeShare where
|
||||||
A.withObject "TypeShare" \value -> do
|
A.withObject "TypeShare" \value -> do
|
||||||
typ :: String <- value A..: "type"
|
typ :: String <- value A..: "type"
|
||||||
guard (typ == "Share")
|
guard (typ == "Share")
|
||||||
pure TypeShare {..}
|
pure TypeShare
|
||||||
|
|
||||||
-- * Activities
|
-- * Activities
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ data TypeActivity t
|
||||||
-- , result :: Maybe String
|
-- , result :: Maybe String
|
||||||
-- , instrument :: Maybe String
|
-- , instrument :: Maybe String
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance (ToObject t) => ToObject (TypeActivity t) where
|
instance (ToObject t) => ToObject (TypeActivity t) where
|
||||||
toObject activity =
|
toObject activity =
|
||||||
|
@ -284,7 +284,7 @@ data TypeCreate
|
||||||
= TypeCreate
|
= TypeCreate
|
||||||
{ object :: Note
|
{ object :: Note
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeCreate where
|
instance ToObject TypeCreate where
|
||||||
toObject create =
|
toObject create =
|
||||||
|
@ -304,9 +304,9 @@ type Follow = Object (TypeActivity TypeFollow)
|
||||||
|
|
||||||
data TypeFollow
|
data TypeFollow
|
||||||
= TypeFollow
|
= TypeFollow
|
||||||
{ object :: Actor
|
{ object :: LinkOrObject Actor
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeFollow where
|
instance ToObject TypeFollow where
|
||||||
toObject follow =
|
toObject follow =
|
||||||
|
@ -325,7 +325,7 @@ instance A.FromJSON TypeFollow where
|
||||||
--
|
--
|
||||||
type Like = Object (TypeActivity TypeLike)
|
type Like = Object (TypeActivity TypeLike)
|
||||||
|
|
||||||
data TypeLike = TypeLike deriving (Show)
|
data TypeLike = TypeLike deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeLike where
|
instance ToObject TypeLike where
|
||||||
toObject TypeLike =
|
toObject TypeLike =
|
||||||
|
@ -337,7 +337,7 @@ instance A.FromJSON TypeLike where
|
||||||
A.withObject "TypeLike" \value -> do
|
A.withObject "TypeLike" \value -> do
|
||||||
typ :: String <- value A..: "type"
|
typ :: String <- value A..: "type"
|
||||||
guard (typ == "Like")
|
guard (typ == "Like")
|
||||||
pure TypeLike {..}
|
pure TypeLike
|
||||||
|
|
||||||
data AnyActivity
|
data AnyActivity
|
||||||
= -- ActivityAnnounce Announce
|
= -- ActivityAnnounce Announce
|
||||||
|
@ -346,7 +346,7 @@ data AnyActivity
|
||||||
| -- | ActivityLike Like
|
| -- | ActivityLike Like
|
||||||
ActivityAccept Accept
|
ActivityAccept Accept
|
||||||
| ActivityReject Reject
|
| ActivityReject Reject
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance A.ToJSON AnyActivity where
|
instance A.ToJSON AnyActivity where
|
||||||
toJSON = \case
|
toJSON = \case
|
||||||
|
@ -376,7 +376,7 @@ data TypeAccept
|
||||||
= TypeAccept
|
= TypeAccept
|
||||||
{ object :: AnyActivity
|
{ object :: AnyActivity
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeAccept where
|
instance ToObject TypeAccept where
|
||||||
toObject obj =
|
toObject obj =
|
||||||
|
@ -398,7 +398,7 @@ data TypeReject
|
||||||
= TypeReject
|
= TypeReject
|
||||||
{ object :: AnyActivity
|
{ object :: AnyActivity
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeReject where
|
instance ToObject TypeReject where
|
||||||
toObject obj =
|
toObject obj =
|
||||||
|
@ -418,7 +418,9 @@ instance A.FromJSON TypeReject where
|
||||||
|
|
||||||
-- | An Actor is an object that has one of the following types.
|
-- | An Actor is an object that has one of the following types.
|
||||||
-- <https://www.w3.org/TR/activitystreams-vocabulary/#actor-types>
|
-- <https://www.w3.org/TR/activitystreams-vocabulary/#actor-types>
|
||||||
data Actor = ActorPerson Person deriving (Show)
|
data Actor
|
||||||
|
= ActorPerson Person
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance A.FromJSON Actor where
|
instance A.FromJSON Actor where
|
||||||
parseJSON =
|
parseJSON =
|
||||||
|
@ -457,7 +459,7 @@ data TypePerson
|
||||||
, following :: Link
|
, following :: Link
|
||||||
, followers :: Link
|
, followers :: Link
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypePerson where
|
instance ToObject TypePerson where
|
||||||
toObject person =
|
toObject person =
|
||||||
|
@ -476,7 +478,7 @@ data PublicKey
|
||||||
, owner :: Url
|
, owner :: Url
|
||||||
, publicKeyPem :: Pem
|
, publicKeyPem :: Pem
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance A.ToJSON PublicKey where
|
instance A.ToJSON PublicKey where
|
||||||
toJSON pk =
|
toJSON pk =
|
||||||
|
@ -513,7 +515,7 @@ data CollectionType t
|
||||||
, last :: Maybe Url
|
, last :: Maybe Url
|
||||||
, current :: Maybe Url
|
, current :: Maybe Url
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance (ToObject t) => ToObject (CollectionType t) where
|
instance (ToObject t) => ToObject (CollectionType t) where
|
||||||
toObject collection =
|
toObject collection =
|
||||||
|
@ -539,7 +541,7 @@ data Unordered e
|
||||||
= UnorderedCollectionType
|
= UnorderedCollectionType
|
||||||
{ items :: [e]
|
{ items :: [e]
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance (A.ToJSON e) => ToObject (Unordered e) where
|
instance (A.ToJSON e) => ToObject (Unordered e) where
|
||||||
toObject collection =
|
toObject collection =
|
||||||
|
@ -560,7 +562,7 @@ data Ordered e
|
||||||
= OrderedCollectionType
|
= OrderedCollectionType
|
||||||
{ orderedItems :: [e]
|
{ orderedItems :: [e]
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance (A.ToJSON e) => ToObject (Ordered e) where
|
instance (A.ToJSON e) => ToObject (Ordered e) where
|
||||||
toObject collection =
|
toObject collection =
|
||||||
|
@ -584,7 +586,7 @@ data OrderedPage e
|
||||||
, next :: Maybe Url
|
, next :: Maybe Url
|
||||||
, porderedItems :: [e]
|
, porderedItems :: [e]
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance (A.ToJSON e) => ToObject (OrderedPage e) where
|
instance (A.ToJSON e) => ToObject (OrderedPage e) where
|
||||||
toObject page =
|
toObject page =
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Domain = String
|
||||||
type Username = String
|
type Username = String
|
||||||
|
|
||||||
newtype Pem = Pem T.Text
|
newtype Pem = Pem T.Text
|
||||||
deriving (Show)
|
deriving (Show, Eq)
|
||||||
deriving (A.FromJSON) via T.Text
|
deriving (A.FromJSON) via T.Text
|
||||||
|
|
||||||
instance A.ToJSON Pem where
|
instance A.ToJSON Pem where
|
||||||
|
@ -37,7 +37,7 @@ data UserDetails
|
||||||
, publicPem :: Pem
|
, publicPem :: Pem
|
||||||
, privatePem :: FilePath
|
, privatePem :: FilePath
|
||||||
}
|
}
|
||||||
deriving (Show, Generic, A.FromJSON)
|
deriving (Show, Eq, Generic, A.FromJSON)
|
||||||
|
|
||||||
actorUrl :: UserDetails -> Url
|
actorUrl :: UserDetails -> Url
|
||||||
actorUrl details =
|
actorUrl details =
|
||||||
|
|
Loading…
Add table
Reference in a new issue