module Network.Metaverse (
login,
sendAgentThrottle,
botThrottle,
sendAgentUpdate,
agentName2Key,
agentKey2Name,
groupKey2Name,
sendSimpleIM,
ChatMethod(..),
sendChat,
whisper,
say,
shout,
getGroupRoles,
getGroupMembers,
addGroupRole,
delGroupRole,
inviteToGroup,
acceptGroupInvite,
acceptFriendship,
giveMoney,
getBalance,
estateBan,
estateUnban,
setTerrainVariables,
handlePackets,
handlePackets_,
expectResponse,
bytesToString,
stringToBytes,
module Network.Metaverse.Circuit,
module Network.Metaverse.PacketTypes
) where
import Control.Concurrent
import Control.Monad
import Data.Binary
import Data.Binary.Put
import Data.Binary.IEEE754
import Data.Char
import Data.Maybe
import Data.UUID hiding (null)
import Data.UUID.V1
import Network.Metaverse.PacketTypes
import Network.Metaverse.Circuit
import Network.Metaverse.Login
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
login :: String
-> String
-> String
-> IO Circuit
login firstName lastName password = do
token <- loginXmlRpc firstName lastName password
circ <- circuitConnect token
forkIO $ regionHandshaker circ =<< dupChan (circuitIncoming circ)
circuitSend circ True $ CompleteAgentMovement $ CompleteAgentMovement_AgentData
(tokenAgentID token) (tokenSessionID token) (tokenCircuitCode token)
return circ
regionHandshaker :: Circuit -> Chan (Maybe PacketBody) -> IO ()
regionHandshaker circ source = do
p <- readChan source
case p of
Just (RegionHandshake _ _ _) -> do
circuitSend circ True $ RegionHandshakeReply
(RegionHandshakeReply_AgentData (circuitAgentID circ) (circuitSessionID circ))
(RegionHandshakeReply_RegionInfo 0)
Just _ -> regionHandshaker circ source
Nothing -> return ()
sendAgentThrottle :: Circuit
-> Float
-> Float
-> Float
-> Float
-> Float
-> Float
-> Float
-> IO ()
sendAgentThrottle circ resend land wind cloud task texture asset = do
let putter = do putFloat32le resend
putFloat32le land
putFloat32le wind
putFloat32le cloud
putFloat32le task
putFloat32le texture
putFloat32le asset
let bin = B.concat (L.toChunks (runPut putter))
circuitSend circ True $ AgentThrottle
(AgentThrottle_AgentData (circuitAgentID circ) (circuitSessionID circ) (circuitCode circ))
(AgentThrottle_Throttle 0 bin)
botThrottle :: Circuit
-> IO ()
botThrottle circ = sendAgentThrottle circ 50000 0 0 0 50000 0 220000
sendAgentUpdate :: Circuit
-> IO ()
sendAgentUpdate circ =
circuitSend circ True $ AgentUpdate $ AgentUpdate_AgentData
(circuitAgentID circ) (circuitSessionID circ)
(0,0,0,1) (0,0,0,1) 0 (128,128,20) (1,0,0) (0,1,0) (0,0,1) 128.0 0 0
agentName2Key :: Circuit
-> String
-> String
-> IO (Maybe UUID)
agentName2Key circ first last = do
Just qid <- nextUUID
let req = DirFindQuery
(DirFindQuery_AgentData (circuitAgentID circ) (circuitSessionID circ))
(DirFindQuery_QueryData qid (stringToBytes $ first ++ " " ++ last) 1 0)
fmap join $ expectResponse circ req $ \p -> case p of
DirPeopleReply _ (DirPeopleReply_QueryData qid') answers | qid == qid' -> do
let f (DirPeopleReply_QueryReplies key fn ln _ _ _)
| map toLower first == map toLower (bytesToString fn)
&& map toLower last == map toLower (bytesToString ln)
= Just key
f _ = Nothing
return $ Just $ listToMaybe $ catMaybes $ map f answers
_ -> return Nothing
agentKey2Name :: Circuit
-> UUID
-> IO (Maybe (String, String))
agentKey2Name circ key = do
let req = UUIDNameRequest [ UUIDNameRequest_UUIDNameBlock key ]
expectResponse circ req $ \p -> case p of
UUIDNameReply names -> do
let results = catMaybes $ map (\(UUIDNameReply_UUIDNameBlock key' first last) ->
if key == key' then Just (bytesToString first, bytesToString last) else Nothing) names
if null results then return Nothing else return (Just (head results))
_ -> return Nothing
groupKey2Name :: Circuit
-> UUID
-> IO (Maybe String)
groupKey2Name circ key = do
let req = UUIDGroupNameRequest [ UUIDGroupNameRequest_UUIDNameBlock key ]
expectResponse circ req $ \p -> case p of
UUIDGroupNameReply names -> do
let results = catMaybes $ map (\(UUIDGroupNameReply_UUIDNameBlock key' name) ->
if key == key' then Just (bytesToString name) else Nothing) names
if null results then return Nothing else return (Just (head results))
_ -> return Nothing
sendSimpleIM :: Circuit
-> UUID
-> String
-> IO Bool
sendSimpleIM circ toAgentID msg =
circuitSendSync circ $ ImprovedInstantMessage
(ImprovedInstantMessage_AgentData
(circuitAgentID circ) (circuitSessionID circ))
(ImprovedInstantMessage_MessageBlock
False toAgentID 0 nil (0,0,0) 0 0 nil 0 (B.pack [0])
(stringToBytes msg) B.empty)
data ChatMethod = Whisper
| Say
| Shout
deriving (Eq, Enum)
sendChat :: Circuit
-> Int
-> ChatMethod
-> String
-> IO Bool
sendChat circ channel method msg =
circuitSendSync circ $ ChatFromViewer
(ChatFromViewer_AgentData
(circuitAgentID circ) (circuitSessionID circ))
(ChatFromViewer_ChatData
(stringToBytes msg) (fromIntegral $ fromEnum method) (fromIntegral channel))
whisper :: Circuit
-> String
-> IO Bool
whisper circ msg = sendChat circ 0 Whisper msg
say :: Circuit
-> String
-> IO Bool
say circ msg = sendChat circ 0 Say msg
shout :: Circuit
-> String
-> IO Bool
shout circ msg = sendChat circ 0 Shout msg
getGroupRoles :: Circuit
-> UUID
-> IO (Maybe [(String, UUID)])
getGroupRoles circ groupid = do
Just rqid <- nextUUID
let req = GroupRoleDataRequest
(GroupRoleDataRequest_AgentData (circuitAgentID circ) (circuitSessionID circ))
(GroupRoleDataRequest_GroupData groupid rqid)
expectResponse circ req $ \ p -> case p of
GroupRoleDataReply _ (GroupRoleDataReply_GroupData _ rqid' _) roles | rqid == rqid' -> do
let f (GroupRoleDataReply_RoleData rid name _ _ _ _) =
(takeWhile (/= '\0') $ map (chr . fromIntegral) $ B.unpack name, rid)
return $ Just (map f roles)
_ -> return Nothing
getGroupMembers :: Circuit
-> UUID
-> IO (Maybe [UUID])
getGroupMembers circ groupid = do
Just rqid <- nextUUID
memlist <- newMVar []
let req = GroupMembersRequest
(GroupMembersRequest_AgentData (circuitAgentID circ) (circuitSessionID circ))
(GroupMembersRequest_GroupData groupid rqid)
expectResponse circ req $ \p -> case p of
GroupMembersReply _ (GroupMembersReply_GroupData _ rqid' n) recs | rqid == rqid' -> do
let theseMembers = map groupMembersReply_MemberData_AgentID recs
members <- takeMVar memlist
let members' = members ++ theseMembers
putMVar memlist members'
if length members' >= fromIntegral n then return (Just members') else return Nothing
_ -> return Nothing
addGroupRole :: Circuit
-> UUID
-> UUID
-> UUID
-> IO ()
addGroupRole circ groupid personid roleid = do
circuitSend circ True $ GroupRoleChanges
(GroupRoleChanges_AgentData (circuitAgentID circ) (circuitSessionID circ) groupid)
[ (GroupRoleChanges_RoleChange roleid personid 0) ]
delGroupRole :: Circuit
-> UUID
-> UUID
-> UUID
-> IO ()
delGroupRole circ groupid personid roleid = do
circuitSend circ True $ GroupRoleChanges
(GroupRoleChanges_AgentData (circuitAgentID circ) (circuitSessionID circ) groupid)
[ (GroupRoleChanges_RoleChange roleid personid 1) ]
inviteToGroup :: Circuit
-> UUID
-> UUID
-> UUID
-> IO ()
inviteToGroup circ groupid personid roleid = do
circuitSend circ True $ InviteGroupRequest
(InviteGroupRequest_AgentData (circuitAgentID circ) (circuitSessionID circ))
(InviteGroupRequest_GroupData groupid)
[ (InviteGroupRequest_InviteData personid roleid) ]
acceptGroupInvite :: Circuit
-> UUID
-> UUID
-> IO ()
acceptGroupInvite circ groupID inviteID =
circuitSend circ True $
ImprovedInstantMessage
(ImprovedInstantMessage_AgentData
(circuitAgentID circ) (circuitSessionID circ))
(ImprovedInstantMessage_MessageBlock
False groupID 0 nil (0, 0, 0) 0 35 inviteID 0
(stringToBytes "name") (stringToBytes "message") (B.pack [0]))
acceptFriendship :: Circuit
-> UUID
-> IO ()
acceptFriendship circ offerID =
circuitSend circ True $
AcceptFriendship
(AcceptFriendship_AgentData (circuitAgentID circ) (circuitSessionID circ))
(AcceptFriendship_TransactionBlock offerID)
[]
giveMoney :: Circuit
-> UUID
-> Int
-> IO ()
giveMoney circ toID amt = do
circuitSend circ True $ MoneyTransferRequest
(MoneyTransferRequest_AgentData (circuitAgentID circ) (circuitSessionID circ))
(MoneyTransferRequest_MoneyData (circuitAgentID circ) toID 0 (fromIntegral amt) 0 0 5001 (B.pack [ 0 ]))
getBalance :: Circuit
-> IO (Maybe Int)
getBalance circ = do
Just txid <- nextUUID
let req = MoneyBalanceRequest
(MoneyBalanceRequest_AgentData (circuitAgentID circ) (circuitSessionID circ))
(MoneyBalanceRequest_MoneyData txid)
fmap join $ expectResponse circ req $ \ p -> case p of
(MoneyBalanceReply (MoneyBalanceReply_MoneyData _ txid' True (fromIntegral -> bal) _ _ _)) | txid == txid' -> return (Just (Just bal))
(MoneyBalanceReply (MoneyBalanceReply_MoneyData _ txid' False _ _ _ _)) | txid == txid' -> return (Just Nothing)
_ -> return Nothing
estateBan :: Circuit
-> UUID
-> IO ()
estateBan circ uuid = do
let flag = 64
Just invoice <- nextUUID
circuitSend circ True $ EstateOwnerMessage
(EstateOwnerMessage_AgentData (circuitAgentID circ) (circuitSessionID circ) nil)
(EstateOwnerMessage_MethodData (stringToBytes "estateaccessdelta") invoice)
[ EstateOwnerMessage_ParamList (stringToBytes (show (circuitAgentID circ))),
EstateOwnerMessage_ParamList (stringToBytes (show flag)),
EstateOwnerMessage_ParamList (stringToBytes (show uuid)) ]
estateUnban :: Circuit
-> UUID
-> IO ()
estateUnban circ uuid = do
let flag = 128
Just invoice <- nextUUID
circuitSend circ True $ EstateOwnerMessage
(EstateOwnerMessage_AgentData (circuitAgentID circ) (circuitSessionID circ) nil)
(EstateOwnerMessage_MethodData (stringToBytes "estateaccessdelta") invoice)
[ EstateOwnerMessage_ParamList (stringToBytes (show (circuitAgentID circ))),
EstateOwnerMessage_ParamList (stringToBytes (show flag)),
EstateOwnerMessage_ParamList (stringToBytes (show uuid)) ]
setTerrainVariables :: Circuit
-> Float
-> Float
-> Float
-> Bool
-> Bool
-> Float
-> IO ()
setTerrainVariables circ water raise lower esun fsun sunpos = do
Just invoice <- nextUUID
circuitSend circ True $ EstateOwnerMessage
(EstateOwnerMessage_AgentData (circuitAgentID circ) (circuitSessionID circ) nil)
(EstateOwnerMessage_MethodData (stringToBytes "setregionterrain") invoice)
[ EstateOwnerMessage_ParamList (stringToBytes (show water)),
EstateOwnerMessage_ParamList (stringToBytes (show raise)),
EstateOwnerMessage_ParamList (stringToBytes (show lower)),
EstateOwnerMessage_ParamList (stringToBytes (if esun then "Y" else "N")),
EstateOwnerMessage_ParamList (stringToBytes (if fsun then "Y" else "N")),
EstateOwnerMessage_ParamList (stringToBytes (show sunpos)),
EstateOwnerMessage_ParamList (stringToBytes "Y"),
EstateOwnerMessage_ParamList (stringToBytes "N"),
EstateOwnerMessage_ParamList (stringToBytes "0.00") ]
handlePackets :: Chan (Maybe PacketBody)
-> (PacketBody -> IO (Maybe a))
-> IO (Maybe a)
handlePackets chan handler = readChan chan >>= maybe (return Nothing) process
where process p = handler p >>= maybe (handlePackets chan handler) (return . Just)
handlePackets_ :: Chan (Maybe PacketBody)
-> (PacketBody -> IO ())
-> IO ()
handlePackets_ chan handler = handlePackets chan handler' >> return ()
where handler' p = handler p >> return Nothing
expectResponse :: Circuit
-> PacketBody
-> (PacketBody -> IO (Maybe a))
-> IO (Maybe a)
expectResponse circ req rsp = do
chan <- dupChan (circuitIncoming circ)
result <- circuitSendSync circ req
if result then handlePackets chan rsp else return Nothing
bytesToString :: ByteString -> String
bytesToString bs = takeWhile (/= '\0') $ map (chr . fromIntegral) $ B.unpack bs
stringToBytes :: String -> ByteString
stringToBytes "" = B.empty
stringToBytes s = B.pack $ map (fromIntegral . ord) $ s ++ "\0"