Files
Hippolyzer/pyogp/lib/base/agent.py

565 lines
21 KiB
Python

"""
@file agent.py
@date 2008-09-16
Contributors can be viewed at:
http://svn.secondlife.com/svn/linden/projects/2008/pyogp/CONTRIBUTORS.txt
$LicenseInfo:firstyear=2008&license=apachev2$
Copyright 2008, Linden Research, Inc.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
or in
http://svn.secondlife.com/svn/linden/projects/2008/pyogp/LICENSE.txt
$/LicenseInfo$
"""
# standard python libs
from logging import getLogger, CRITICAL, ERROR, WARNING, INFO, DEBUG
import re
import sys
import signal
import uuid
#related
from eventlet import api
# pyogp
from pyogp.lib.base.login import Login, LegacyLoginParams, OGPLoginParams
from pyogp.lib.base.datatypes import *
from pyogp.lib.base.exc import LoginError
from pyogp.lib.base.region import Region
from pyogp.lib.base.inventory import Inventory
from pyogp.lib.base.groups import GroupManager, Group
# from pyogp.lib.base.appearance import Appearance
# pyogp messaging
from pyogp.lib.base.message.packethandler import PacketHandler
from pyogp.lib.base.event_queue import EventQueueHandler
from pyogp.lib.base.message.packets import *
# pyogp utilities
from pyogp.lib.base.utilities.helpers import Helpers
# initialize logging
logger = getLogger('pyogp.lib.base.agent')
log = logger.log
class Agent(object):
""" an agent container
The Agent class is a container for agent specific data.
It is also a nice place for convenience code.
Example, of login via the agent class:
Initialize the login class
>>> client = Agent()
>>> client.login('https://login.agni.lindenlab.com/cgi-bin/login.cgi', 'firstname', 'lastname', 'secret', start_location = 'last')
Sample implementations: examples/sample_agent_login.py
Tests: tests/login.txt, tests/test_agent.py
"""
def __init__(self, settings = None):
""" initialize this agent """
# allow the settings to be passed in
# otherwise, grab the defaults
if settings != None:
self.settings = settings
else:
from pyogp.lib.base.settings import Settings
self.settings = Settings()
# signal handler to capture erm signals
self.signal_handler = signal.signal(signal.SIGINT, self.sigint_handler)
# storage containers for agent attributes
# we store what the grid tells us, rather than what
# is passed in and stored in Login()
self.firstname = None
self.lastname = None
self.agent_id = None
self.session_id = None
self.secure_session_id = None
# other storage containers
self.inventory_host = None
self.agent_access = None
self.udp_blacklist = None
self.home = None
self.inventory = None
self.group_manager = GroupManager(self, self.settings)
# additional attributes
self.login_response = None
self.connected = False
self.grid_type = None
self.running = True
self.packet_handler = PacketHandler(self.settings)
self.event_queue_handler = EventQueueHandler(self.settings)
self.helpers = Helpers()
# data we store as it comes in from the grid
self.Position = Vector3() # this will get updated later, but seed it with 000
self.ActiveGroupID = uuid.UUID('00000000-0000-0000-0000-000000000000')
# should we include these here?
self.agentdomain = None # the agent domain the agent is connected to if an OGP context
self.regions = [] # all known regions
self.region = None # the host simulation for the agent
# init Appearance()
# self.appearance = Appearance(self.settings, self)
# set up callbacks (is this a decent place to do this? it's perhaps premature)
if self.settings.HANDLE_PACKETS:
onAlertMessage_received = self.packet_handler._register('AlertMessage')
onAlertMessage_received.subscribe(onAlertMessage, self)
onAgentDataUpdate_received = self.packet_handler._register('AgentDataUpdate')
onAgentDataUpdate_received.subscribe(onAgentDataUpdate, self)
onAgentMovementComplete_received = self.packet_handler._register('AgentMovementComplete')
onAgentMovementComplete_received.subscribe(onAgentMovementComplete, self)
onHealthMessage_received = self.packet_handler._register('HealthMessage')
onHealthMessage_received.subscribe(onHealthMessage, self)
onAgentGroupDataUpdate_received = self.packet_handler._register('AgentGroupDataUpdate')
onAgentGroupDataUpdate_received.subscribe(onAgentGroupDataUpdate, self)
if self.settings.ENABLE_COMMUNICATIONS_TRACKING:
onChatFromSimulator_received = self.packet_handler._register('ChatFromSimulator')
onChatFromSimulator_received.subscribe(self.helpers.log_packet, self)
onImprovedInstantMessage_received = self.packet_handler._register('ImprovedInstantMessage')
onImprovedInstantMessage_received.subscribe(self.helpers.log_packet, self)
onChatterBoxInvitation_received = self.event_queue_handler._register('ChatterBoxInvitation')
onChatterBoxInvitation_received.subscribe(self.helpers.log_event_queue_data, self)
if self.settings.LOG_VERBOSE: log(DEBUG, 'Initializing agent: %s' % (self))
def login(self, loginuri, firstname=None, lastname=None, password=None, login_params = None, start_location=None, handler=None, connect_region = False):
""" login to a login endpoint. this should move to a login class in time """
if (re.search('auth.cgi$', loginuri)):
self.grid_type = 'OGP'
elif (re.search('login.cgi$', loginuri)):
self.grid_type = 'Legacy'
else:
log(WARNING, 'Unable to identify the loginuri schema. Stopping')
sys.exit(-1)
# handle either login params passed in, or, account info
if login_params == None:
if (firstname == None) or (lastname == None) or (password == None):
raise LoginError('Unable to login an unknown agent.')
else:
self._login_params = self._get_login_params(loginuri, firstname, lastname, password)
else:
self._login_params = login_params
# login and parse the response
login = Login(settings = self.settings)
try:
self.login_response = login.login(loginuri, self._login_params, start_location, handler = handler)
self._parse_login_response()
except LoginError, error:
log(WARNING, 'Failed to login user. Stopping')
sys.exit(-1)
# ToDo: what to do with self.login_response['look_at']?
if connect_region:
self._enable_current_region()
def logout(self):
""" logs an agent out of the current region """
if self.region.logout():
self.connected = False
def _get_login_params(self, loginuri, firstname, lastname, password):
""" get the proper login parameters """
if self.grid_type == 'OGP':
login_params = OGPLoginParams(firstname, lastname, password)
elif self.grid_type == 'Legacy':
login_params = LegacyLoginParams(firstname, lastname, password)
return login_params
def _parse_login_response(self):
""" evaluates the login response """
if self.grid_type == 'Legacy':
self.firstname = re.sub(r'\"', '', self.login_response['first_name'])
self.lastname = self.login_response['last_name']
self.agent_id = self.login_response['agent_id']
self.session_id = self.login_response['session_id']
self.secure_session_id = self.login_response['secure_session_id']
self.connected = bool(self.login_response['login'])
self.inventory_host = self.login_response['inventory_host']
self.agent_access = self.login_response['agent_access']
self.udp_blacklist = self.login_response['udp_blacklist']
if self.login_response.has_key('home'): self.home = Home(self.login_response['home'])
if self.settings.ENABLE_INVENTORY_MANAGEMENT:
self.inventory = Inventory(self)
self.inventory._parse_folders_from_login_response()
elif self.grid_type == 'OGP':
pass
def _enable_current_region(self, region_x = None, region_y = None, seed_capability = None, udp_blacklist = None, sim_ip = None, sim_port = None, circuit_code = None):
""" enables an agents current region """
# enable the current region, setting connect = True
self.region = Region(self.login_response['region_x'], self.login_response['region_y'], self.login_response['seed_capability'], self.login_response['udp_blacklist'], self.login_response['sim_ip'], self.login_response['sim_port'], self.login_response['circuit_code'], self, settings = self.settings, packet_handler = self.packet_handler, event_queue_handler = self.event_queue_handler)
# start the simulator udp and event queue connections
api.spawn(self.region.connect)
#self.region.connect()
while self.running:
api.sleep(0)
def send_AgentDataUpdateRequest(self):
""" request an agent data update """
packet = AgentDataUpdateRequestPacket()
packet.AgentData['AgentID'] = uuid.UUID(str(self.agent_id))
packet.AgentData['SessionID'] = uuid.UUID(str(self.session_id))
self.region.enqueue_message(packet())
# ~~~~~~~~~~~~~~
# Communications
# ~~~~~~~~~~~~~~
# Chat
def say(self, Message, Type = 1, Channel = 0):
""" Sends ChatFromViewer
Channel: 0 is open chat
Type: 0 = Whisper
1 = Say
2 = Shout
"""
packet = ChatFromViewerPacket()
packet.AgentData['AgentID'] = uuid.UUID(str(self.agent_id))
packet.AgentData['SessionID'] = uuid.UUID(str(self.session_id))
packet.ChatData['Message'] = Message + '\x00' # Message needs a terminator. Arnold was busy as gov...
packet.ChatData['Type'] = Type
packet.ChatData['Channel'] = Channel
self.region.enqueue_message(packet())
# Instant Message (im, group chat)
def instant_message(self, ToAgentID = None, Message = None, _ID = None):
""" sends an instant message to another avatar
wraps send_ImprovedInstantMessage with some handy defaults """
#ToDo: this opens a new im window every time a message is sent to a viewer. why is this? fix it.
if ToAgentID != None and Message != None:
if _ID == None: _ID = uuid.UUID(str(self.agent_id))
_AgentID = uuid.UUID(str(self.agent_id))
_SessionID = uuid.UUID(str(self.session_id))
_FromGroup = False
_ToAgentID = uuid.UUID(str(ToAgentID))
_ParentEstateID = 0
_RegionID = uuid.UUID('00000000-0000-0000-0000-000000000000')
_Position = self.Position
_Offline = 0
_Dialog = 0 # Dialog type 1 = instant message
_ID = _ID
_Timestamp = 0
_FromAgentName = self.firstname + ' ' + self.lastname
_Message = Message
_BinaryBucket = ''
self.send_ImprovedInstantMessage(_AgentID, _SessionID, _FromGroup, _ToAgentID, _ParentEstateID, _RegionID, _Position, _Offline, _Dialog, _ID, _Timestamp, _FromAgentName, _Message, _BinaryBucket)
else:
log(INFO, "Please specify an agentid and message to send in agent.instant_message")
def send_ImprovedInstantMessage(self, AgentID = None, SessionID = None, FromGroup = None, ToAgentID = None, ParentEstateID = None, RegionID = None, Position = None, Offline = None, Dialog = None, _ID = None, Timestamp = None, FromAgentName = None, Message = None, BinaryBucket = None, AgentDataBlock = {}, MessageBlockBlock = {}):
""" sends an instant message to ToAgentID
// ImprovedInstantMessage
// This message can potentially route all over the place
// ParentEstateID: parent estate id of the source estate
// RegionID: region id of the source of the IM.
// Position: position of the sender in region local coordinates
// Dialog see llinstantmessage.h for values
// ID May be used by dialog. Interpretation depends on context.
// BinaryBucket May be used by some dialog types
// reliable
"""
packet = ImprovedInstantMessagePacket()
if AgentDataBlock == {}:
packet.AgentData['AgentID'] = uuid.UUID(str(AgentID))
packet.AgentData['SessionID'] = uuid.UUID(str(SessionID))
else:
packet.AgentData = AgentDataBlock
if FromAgentName == None:
FromAgentName = self.firstname + ' ' + self.lastname
# ha! when scripting out packets.py, never considered a block named *block
if MessageBlockBlock == {}:
packet.MessageBlock['FromGroup'] = FromGroup # Bool
packet.MessageBlock['ToAgentID'] = uuid.UUID(str(ToAgentID)) # LLUUID
packet.MessageBlock['ParentEstateID'] = ParentEstateID # U32
packet.MessageBlock['RegionID'] = uuid.UUID(str(RegionID)) # LLUUID
packet.MessageBlock['Position'] = Position # LLVector3
packet.MessageBlock['Offline'] = Offline # U8
packet.MessageBlock['Dialog'] = Dialog # U8 IM Type
packet.MessageBlock['ID'] = uuid.UUID(str(_ID)) # LLUUID
packet.MessageBlock['Timestamp'] = Timestamp # U32
packet.MessageBlock['FromAgentName'] = FromAgentName # Variable 1
packet.MessageBlock['Message'] = Message # Variable 2
packet.MessageBlock['BinaryBucket'] = BinaryBucket # Variable 2
self.region.enqueue_message(packet(), True)
def send_RetrieveInstantMessages(self):
""" asks simulator for instant messages stored while agent was offline """
packet = RetrieveInstantMessagesPackets()
packet.AgentDataBlock['AgentID'] = uuid.UUID(str(self.agent_id))
packet.AgentDataBlock['SessionID'] = uuid.UUID(str(self.session_id))
self.region.enqueue_message(packet())
def sigint_handler(self, signal, frame):
log(INFO, "Caught signal... %d. Stopping" % signal)
self.running = False
self.logout()
#sys.exit(0)
def __repr__(self):
""" returns a representation of the agent """
if self.firstname == None:
return 'A new agent instance'
else:
return '%s %s' % (self.firstname, self.lastname)
def onAgentDataUpdate(packet, agent):
if agent.agent_id == None:
agent.agent_id = packet.message_data.blocks['AgentData'][0].get_variable('AgentID').data
if agent.firstname == None:
agent.firstname = packet.message_data.blocks['AgentData'][0].get_variable('FirstName').data
if agent.lastname == None:
agent.firstname = packet.message_data.blocks['AgentData'][0].get_variable('LastName').data
agent.GroupTitle = packet.message_data.blocks['AgentData'][0].get_variable('GroupTitle').data
agent.ActiveGroupID = packet.message_data.blocks['AgentData'][0].get_variable('ActiveGroupID').data
agent.GroupPowers = packet.message_data.blocks['AgentData'][0].get_variable('GroupPowers').data
agent.GroupName = packet.message_data.blocks['AgentData'][0].get_variable('GroupName').data
def onAgentMovementComplete(packet, agent):
agent.Position = packet.message_data.blocks['Data'][0].get_variable('Position').data
agent.LookAt = packet.message_data.blocks['Data'][0].get_variable('LookAt').data
agent.region.RegionHandle = packet.message_data.blocks['Data'][0].get_variable('RegionHandle').data
#agent.Timestamp = packet.message_data.blocks['Data'][0].get_variable('Timestamp')
agent.region.ChannelVersion = packet.message_data.blocks['SimData'][0].get_variable('ChannelVersion').data
def onHealthMessage(packet, agent):
agent.health = packet.message_data.blocks['HealthData'][0].get_variable('Health').data
def onAgentGroupDataUpdate(packet, agent):
# AgentData block
AgentID = packet.message_data.blocks['AgentData'][0].get_variable('AgentID').data
# GroupData block
for GroupData_block in packet.message_data.blocks['GroupData']:
AcceptNotices = GroupData_block.get_variable('AcceptNotices').data
GroupPowers = GroupData_block.get_variable('GroupPowers').data
GroupID = uuid.UUID(str(GroupData_block.get_variable('GroupID').data))
GroupName = GroupData_block.get_variable('GroupName').data
ListInProfile = GroupData_block.get_variable('ListInProfile').data
Contribution = GroupData_block.get_variable('Contribution').data
GroupInsigniaID = uuid.UUID(str(GroupData_block.get_variable('GroupInsigniaID').data))
# make sense of group powers
GroupPowers = [ord(x) for x in GroupPowers]
GroupPowers = ''.join([str(x) for x in GroupPowers])
group = Group(AcceptNotices, GroupPowers, GroupID, GroupName, ListInProfile, Contribution,GroupInsigniaID )
agent.group_manager.store_group(group)
'''
Name: AgentGroupDataUpdate
Block Name: GroupData
AcceptNotices: True
GroupPowers: ?
GroupID: 69fd708c-3f20-a01b-f9b5-b5c4b310e5ca
GroupName: EnusBot Army
ListInProfile: False
Contribution: 0
GroupInsigniaID: 00000000-0000-0000-0000-000000000000
AcceptNotices: True
GroupPowers: ?
GroupID: 69fd708c-3f20-a01b-f9b5-b5c4b310e5ca
GroupName: EnusBot Army
ListInProfile: False
Contribution: 0
GroupInsigniaID: 00000000-0000-0000-0000-000000000000
Block Name: AgentData
AgentID: a517168d-1af5-4854-ba6d-672c8a59e439
'''
def onChatFromSimulator(packet, agent):
pass
'''
{
ChatFromSimulator Low 139 Trusted Unencoded
{
ChatData Single
{ FromName Variable 1 }
{ SourceID LLUUID } // agent id or object id
{ OwnerID LLUUID } // object's owner
{ SourceType U8 }
{ ChatType U8 }
{ Audible U8 }
{ Position LLVector3 }
{ Message Variable 2 } // UTF-8 text
}
}
'''
def onImprovedInstantMessage(packet, agent):
pass
'''
// ImprovedInstantMessage
// This message can potentially route all over the place
// ParentEstateID: parent estate id of the source estate
// RegionID: region id of the source of the IM.
// Position: position of the sender in region local coordinates
// Dialog see llinstantmessage.h for values
// ID May be used by dialog. Interpretation depends on context.
// BinaryBucket May be used by some dialog types
// reliable
{
ImprovedInstantMessage Low 254 NotTrusted Zerocoded
{
AgentData Single
{ AgentID LLUUID }
{ SessionID LLUUID }
}
{
MessageBlock Single
{ FromGroup BOOL }
{ ToAgentID LLUUID }
{ ParentEstateID U32 }
{ RegionID LLUUID }
{ Position LLVector3 }
{ Offline U8 }
{ Dialog U8 } // U8 - IM type
{ ID LLUUID }
{ Timestamp U32 }
{ FromAgentName Variable 1 }
{ Message Variable 2 }
{ BinaryBucket Variable 2 }
}
}
'''
def onAlertMessage(packet, agent):
AlertMessage = packet.message_data.blocks['AlertData'][0].get_variable('Message').data
log(WARNING, "AlertMessage from simulator: %s" % (AlertMessage))
class Home(object):
""" contains the parameters descibing an agent's home location """
def __init__(self, params):
# eval(params) would be nice, but fails to parse the string the way one thinks it might
items = params.split(', \'')
# this creates:
# self.region_handle
# self.look_at
# self.position
for i in items:
i = re.sub(r'[\"\{}\'"]', '', i)
i = i.split(':')
setattr(self, i[0], eval(re.sub('r', '', i[1])))
self.global_x = self.region_handle[0]
self.global_y = self.region_handle[1]
self.local_x = self.position[0]
self.local_y = self.position[1]
self.local_z = self.position[2]