More refactoring stuff

- Add a waitForEventQueue promise, to get rid of the 5 second fudge from testBot.js
- Async'ify testBot.js
- Complete plumbing for Hint's ScriptDialog support (see changes to ScriptDialogEvent.ts and Comms.ts
- Fix: The EventQueue was pausing for 5 seconds every 30 seconds
This commit is contained in:
Casper Warden
2018-10-07 17:06:54 +01:00
parent 3e993a07e3
commit 960f024ea4
33 changed files with 475 additions and 207 deletions

3
dist/Bot.d.ts vendored
View File

@@ -13,11 +13,14 @@ export declare class Bot {
private lastSuccessfulPing;
private circuitSubscription;
private options;
private eventQueueRunning;
clientEvents: ClientEvents;
clientCommands: ClientCommands;
private eventQueueWaits;
constructor(login: LoginParameters, options: BotOptionFlags);
login(): Promise<LoginResponse>;
changeRegion(region: Region): Promise<void>;
waitForEventQueue(timeout?: number): Promise<void>;
private closeCircuit;
private kicked;
private disconnected;

32
dist/Bot.js vendored
View File

@@ -25,15 +25,29 @@ const ClientCommands_1 = require("./classes/ClientCommands");
const DisconnectEvent_1 = require("./events/DisconnectEvent");
const StartPingCheck_1 = require("./classes/messages/StartPingCheck");
const FilterResponse_1 = require("./enums/FilterResponse");
const UUID_1 = require("./classes/UUID");
class Bot {
constructor(login, options) {
this.ping = null;
this.pingNumber = 0;
this.lastSuccessfulPing = 0;
this.circuitSubscription = null;
this.eventQueueRunning = false;
this.eventQueueWaits = {};
this.clientEvents = new ClientEvents_1.ClientEvents();
this.loginParams = login;
this.options = options;
this.clientEvents.onEventQueueStateChange.subscribe((evt) => {
this.eventQueueRunning = evt.active;
for (const waitID of Object.keys(this.eventQueueWaits)) {
try {
clearTimeout(this.eventQueueWaits[waitID].timer);
this.eventQueueWaits[waitID].resolve();
delete this.eventQueueWaits[waitID];
}
catch (ignore) { }
}
});
}
login() {
return __awaiter(this, void 0, void 0, function* () {
@@ -56,6 +70,24 @@ class Bot {
yield this.connectToSim();
});
}
waitForEventQueue(timeout = 1000) {
return new Promise((resolve, reject) => {
if (this.eventQueueRunning) {
resolve();
}
else {
const waitID = UUID_1.UUID.random().toString();
const newWait = {
'resolve': resolve
};
newWait.timer = setTimeout(() => {
delete this.eventQueueWaits[waitID];
reject(new Error('Timeout'));
}, timeout);
this.eventQueueWaits[waitID] = newWait;
}
});
}
closeCircuit() {
this.agent.shutdown();
this.currentRegion.shutdown();

2
dist/Bot.js.map vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
import { Subject } from 'rxjs/Subject';
import { ChatEvent, DisconnectEvent, FriendRequestEvent, FriendResponseEvent, GroupChatEvent, GroupChatSessionAgentListEvent, GroupChatSessionJoinEvent, GroupInviteEvent, InstantMessageEvent, InventoryOfferedEvent, LureEvent, TeleportEvent, ScriptDialogEvent } from '..';
import { ChatEvent, DisconnectEvent, FriendRequestEvent, FriendResponseEvent, GroupChatEvent, GroupChatSessionAgentListEvent, GroupChatSessionJoinEvent, GroupInviteEvent, InstantMessageEvent, InventoryOfferedEvent, LureEvent, TeleportEvent, ScriptDialogEvent, EventQueueStateChangeEvent } from '..';
export declare class ClientEvents {
onNearbyChat: Subject<ChatEvent>;
onInstantMessage: Subject<InstantMessageEvent>;
@@ -15,4 +15,5 @@ export declare class ClientEvents {
onGroupChatAgentListUpdate: Subject<GroupChatSessionAgentListEvent>;
onFriendResponse: Subject<FriendResponseEvent>;
onScriptDialog: Subject<ScriptDialogEvent>;
onEventQueueStateChange: Subject<EventQueueStateChangeEvent>;
}

View File

@@ -17,6 +17,7 @@ class ClientEvents {
this.onGroupChatAgentListUpdate = new Subject_1.Subject();
this.onFriendResponse = new Subject_1.Subject();
this.onScriptDialog = new Subject_1.Subject();
this.onEventQueueStateChange = new Subject_1.Subject();
}
}
exports.ClientEvents = ClientEvents;

View File

@@ -1 +1 @@
{"version":3,"file":"ClientEvents.js","sourceRoot":"","sources":["../../lib/classes/ClientEvents.ts"],"names":[],"mappings":";;AAAA,0CAAqC;AAcrC;IAAA;QAEI,iBAAY,GAAuB,IAAI,iBAAO,EAAa,CAAC;QAC5D,qBAAgB,GAAiC,IAAI,iBAAO,EAAuB,CAAC;QACpF,kBAAa,GAA8B,IAAI,iBAAO,EAAoB,CAAC;QAC3E,oBAAe,GAAgC,IAAI,iBAAO,EAAsB,CAAC;QACjF,uBAAkB,GAAmC,IAAI,iBAAO,EAAyB,CAAC;QAC1F,WAAM,GAAuB,IAAI,iBAAO,EAAa,CAAC;QACtD,oBAAe,GAA2B,IAAI,iBAAO,EAAiB,CAAC;QACvE,mBAAc,GAA8B,IAAI,iBAAO,EAAmB,CAAC;QAC3E,qBAAgB,GAAoB,IAAI,iBAAO,EAAU,CAAC;QAC1D,gBAAW,GAA4B,IAAI,iBAAO,EAAkB,CAAC;QACrE,2BAAsB,GAAuC,IAAI,iBAAO,EAA6B,CAAC;QACtG,+BAA0B,GAA4C,IAAI,iBAAO,EAAkC,CAAC;QACpH,qBAAgB,GAAiC,IAAI,iBAAO,EAAuB,CAAC;QACpF,mBAAc,GAA+B,IAAI,iBAAO,EAAqB,CAAC;IAClF,CAAC;CAAA;AAhBD,oCAgBC"}
{"version":3,"file":"ClientEvents.js","sourceRoot":"","sources":["../../lib/classes/ClientEvents.ts"],"names":[],"mappings":";;AAAA,0CAAqC;AAmBrC;IAAA;QAEI,iBAAY,GAAuB,IAAI,iBAAO,EAAa,CAAC;QAC5D,qBAAgB,GAAiC,IAAI,iBAAO,EAAuB,CAAC;QACpF,kBAAa,GAA8B,IAAI,iBAAO,EAAoB,CAAC;QAC3E,oBAAe,GAAgC,IAAI,iBAAO,EAAsB,CAAC;QACjF,uBAAkB,GAAmC,IAAI,iBAAO,EAAyB,CAAC;QAC1F,WAAM,GAAuB,IAAI,iBAAO,EAAa,CAAC;QACtD,oBAAe,GAA2B,IAAI,iBAAO,EAAiB,CAAC;QACvE,mBAAc,GAA8B,IAAI,iBAAO,EAAmB,CAAC;QAC3E,qBAAgB,GAAoB,IAAI,iBAAO,EAAU,CAAC;QAC1D,gBAAW,GAA4B,IAAI,iBAAO,EAAkB,CAAC;QACrE,2BAAsB,GAAuC,IAAI,iBAAO,EAA6B,CAAC;QACtG,+BAA0B,GAA4C,IAAI,iBAAO,EAAkC,CAAC;QACpH,qBAAgB,GAAiC,IAAI,iBAAO,EAAuB,CAAC;QACpF,mBAAc,GAA+B,IAAI,iBAAO,EAAqB,CAAC;QAC9E,4BAAuB,GAAwC,IAAI,iBAAO,EAA8B,CAAC;IAC7G,CAAC;CAAA;AAjBD,oCAiBC"}

69
dist/classes/Comms.js vendored
View File

@@ -12,7 +12,8 @@ class Comms {
this.circuit.subscribeToMessages([
Message_1.Message.ImprovedInstantMessage,
Message_1.Message.ChatFromSimulator,
Message_1.Message.AlertMessage
Message_1.Message.AlertMessage,
Message_1.Message.ScriptDialog
], (packet) => {
switch (packet.message.id) {
case Message_1.Message.ImprovedInstantMessage:
@@ -211,27 +212,53 @@ class Comms {
}
break;
case Message_1.Message.ChatFromSimulator:
const chat = packet.message;
const event = new __1.ChatEvent();
event.fromName = Utils_1.Utils.BufferToStringSimple(chat.ChatData.FromName);
event.message = Utils_1.Utils.BufferToStringSimple(chat.ChatData.Message);
event.from = chat.ChatData.SourceID;
event.ownerID = chat.ChatData.OwnerID;
event.chatType = chat.ChatData.ChatType;
event.sourceType = chat.ChatData.SourceType;
event.audible = chat.ChatData.Audible;
event.position = chat.ChatData.Position;
this.clientEvents.onNearbyChat.next(event);
break;
{
const chat = packet.message;
const event = new __1.ChatEvent();
event.fromName = Utils_1.Utils.BufferToStringSimple(chat.ChatData.FromName);
event.message = Utils_1.Utils.BufferToStringSimple(chat.ChatData.Message);
event.from = chat.ChatData.SourceID;
event.ownerID = chat.ChatData.OwnerID;
event.chatType = chat.ChatData.ChatType;
event.sourceType = chat.ChatData.SourceType;
event.audible = chat.ChatData.Audible;
event.position = chat.ChatData.Position;
this.clientEvents.onNearbyChat.next(event);
break;
}
case Message_1.Message.AlertMessage:
const alertm = packet.message;
const alertMessage = Utils_1.Utils.BufferToStringSimple(alertm.AlertData.Message);
console.log('Alert message: ' + alertMessage);
alertm.AlertInfo.forEach((info) => {
const alertInfoMessage = Utils_1.Utils.BufferToStringSimple(info.Message);
console.log('Alert info message: ' + alertInfoMessage);
});
break;
{
const alertm = packet.message;
const alertMessage = Utils_1.Utils.BufferToStringSimple(alertm.AlertData.Message);
console.log('Alert message: ' + alertMessage);
alertm.AlertInfo.forEach((info) => {
const alertInfoMessage = Utils_1.Utils.BufferToStringSimple(info.Message);
console.log('Alert info message: ' + alertInfoMessage);
});
break;
}
case Message_1.Message.ScriptDialog:
{
const scriptd = packet.message;
const event = new __1.ScriptDialogEvent();
event.ObjectID = scriptd.Data.ObjectID;
event.FirstName = Utils_1.Utils.BufferToStringSimple(scriptd.Data.FirstName);
event.LastName = Utils_1.Utils.BufferToStringSimple(scriptd.Data.LastName);
event.ObjectName = Utils_1.Utils.BufferToStringSimple(scriptd.Data.ObjectName);
event.Message = Utils_1.Utils.BufferToStringSimple(scriptd.Data.Message);
event.ChatChannel = scriptd.Data.ChatChannel;
event.ImageID = scriptd.Data.ImageID;
event.Buttons = [];
event.Owners = [];
for (const button of scriptd.Buttons) {
event.Buttons.push(Utils_1.Utils.BufferToStringSimple(button.ButtonLabel));
}
for (const owner of scriptd.OwnerData) {
event.Owners.push(owner.OwnerID);
}
this.clientEvents.onScriptDialog.next(event);
break;
}
}
});
}

File diff suppressed because one or more lines are too long

View File

@@ -13,5 +13,5 @@ export declare class EventQueueClient {
shutdown(): void;
Get(): void;
request(url: string, data: string, contentType: string): Promise<string>;
capsRequestXML(capability: string, data: any): Promise<any>;
capsRequestXML(capability: string, data: any, attempt?: number): Promise<any>;
}

View File

@@ -4,12 +4,9 @@ const LLSD = require("@caspertech/llsd");
const request = require("request");
const Long = require("long");
const IPAddress_1 = require("./IPAddress");
const TeleportEvent_1 = require("../events/TeleportEvent");
const TeleportEventType_1 = require("../enums/TeleportEventType");
const GroupChatEvent_1 = require("../events/GroupChatEvent");
const UUID_1 = require("./UUID");
const GroupChatSessionJoinEvent_1 = require("../events/GroupChatSessionJoinEvent");
const GroupChatSessionAgentListEvent_1 = require("../events/GroupChatSessionAgentListEvent");
const __1 = require("..");
class EventQueueClient {
constructor(agent, caps, clientEvents) {
this.done = false;
@@ -18,8 +15,14 @@ class EventQueueClient {
this.clientEvents = clientEvents;
this.caps = caps;
this.Get();
const state = new __1.EventQueueStateChangeEvent();
state.active = true;
this.clientEvents.onEventQueueStateChange.next(state);
}
shutdown() {
const state = new __1.EventQueueStateChangeEvent();
state.active = false;
this.clientEvents.onEventQueueStateChange.next(state);
if (this.currentRequest !== null) {
this.currentRequest.abort();
}
@@ -30,6 +33,7 @@ class EventQueueClient {
'ack': this.ack,
'done': this.done
};
const startTime = new Date().getTime();
this.capsRequestXML('EventQueueGet', req).then((data) => {
if (data['events']) {
data['events'].forEach((event) => {
@@ -46,7 +50,7 @@ class EventQueueClient {
break;
case 'TeleportFailed':
{
const tpEvent = new TeleportEvent_1.TeleportEvent();
const tpEvent = new __1.TeleportEvent();
tpEvent.message = event['body']['Info'][0]['Reason'];
tpEvent.eventType = TeleportEventType_1.TeleportEventType.TeleportFailed;
tpEvent.simIP = '';
@@ -58,7 +62,7 @@ class EventQueueClient {
case "ChatterBoxSessionStartReply":
{
if (event['body']) {
const gcsje = new GroupChatSessionJoinEvent_1.GroupChatSessionJoinEvent();
const gcsje = new __1.GroupChatSessionJoinEvent();
gcsje.sessionID = new UUID_1.UUID(event['body']['session_id'].toString());
gcsje.success = event['body']['success'];
if (gcsje.success) {
@@ -73,7 +77,7 @@ class EventQueueClient {
if (event['body'] && event['body']['instantmessage'] && event['body']['instantmessage']['message_params'] && event['body']['instantmessage']['message_params']['id']) {
const messageParams = event['body']['instantmessage']['message_params'];
const imSessionID = messageParams['id'];
const groupChatEvent = new GroupChatEvent_1.GroupChatEvent();
const groupChatEvent = new __1.GroupChatEvent();
groupChatEvent.from = new UUID_1.UUID(messageParams['from_id'].toString());
groupChatEvent.fromName = messageParams['from_name'];
groupChatEvent.groupID = new UUID_1.UUID(messageParams['id'].toString());
@@ -82,9 +86,9 @@ class EventQueueClient {
'method': 'accept invitation',
'session-id': imSessionID
};
this.caps.capsRequestXML('ChatSessionRequest', requestedFolders).then((result) => {
this.caps.capsRequestXML('ChatSessionRequest', requestedFolders).then((ignore) => {
this.agent.addChatSession(groupChatEvent.groupID);
const gcsje = new GroupChatSessionJoinEvent_1.GroupChatSessionJoinEvent();
const gcsje = new __1.GroupChatSessionJoinEvent();
gcsje.sessionID = groupChatEvent.groupID;
gcsje.success = true;
this.clientEvents.onGroupChatSessionJoin.next(gcsje);
@@ -101,7 +105,7 @@ class EventQueueClient {
if (event['body']['agent_updates']) {
Object.keys(event['body']['agent_updates']).forEach((agentUpdate) => {
const updObj = event['body']['agent_updates'][agentUpdate];
const gcsale = new GroupChatSessionAgentListEvent_1.GroupChatSessionAgentListEvent();
const gcsale = new __1.GroupChatSessionAgentListEvent();
gcsale.agentID = new UUID_1.UUID(agentUpdate);
gcsale.groupID = new UUID_1.UUID(event['body']['session_id'].toString());
gcsale.canVoiceChat = false;
@@ -128,7 +132,7 @@ class EventQueueClient {
info['RegionHandle'] = new Long(regionHandleBuf.readUInt32LE(0), regionHandleBuf.readUInt32LE(4), true);
info['SimIP'] = new IPAddress_1.IPAddress(Buffer.from(info['SimIP'].toArray()), 0).toString();
info['TeleportFlags'] = Buffer.from(info['TeleportFlags'].toArray()).readUInt32LE(0);
const tpEvent = new TeleportEvent_1.TeleportEvent();
const tpEvent = new __1.TeleportEvent();
tpEvent.message = '';
tpEvent.eventType = TeleportEventType_1.TeleportEventType.TeleportCompleted;
tpEvent.simIP = info['SimIP'];
@@ -160,11 +164,20 @@ class EventQueueClient {
this.Get();
}
}).catch((err) => {
setTimeout(() => {
const time = (new Date().getTime()) - startTime;
if (time > 30000) {
if (!this.done) {
this.Get();
}
}, 5000);
}
else {
console.error('Event queue aborted after ' + time + 'ms. Reconnecting in 5 seconds');
setTimeout(() => {
if (!this.done) {
this.Get();
}
}, 5000);
}
});
}
request(url, data, contentType) {
@@ -190,7 +203,7 @@ class EventQueueClient {
});
});
}
capsRequestXML(capability, data) {
capsRequestXML(capability, data, attempt = 0) {
return new Promise((resolve, reject) => {
this.caps.getCapability(capability).then((url) => {
const serializedData = LLSD.LLSD.formatXML(data);
@@ -201,7 +214,12 @@ class EventQueueClient {
resolve(parsed);
}
else {
throw new Error('Not an LLSD response');
if (attempt < 3) {
return this.capsRequestXML(capability, data, ++attempt);
}
else {
reject(new Error('Not an LLSD response, capability: ' + capability));
}
}
}
catch (error) {

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@ import { Caps } from './Caps';
import { Comms } from './Comms';
import { ClientEvents } from './ClientEvents';
import { IObjectStore } from './interfaces/IObjectStore';
import { BotOptionFlags } from '../enums/BotOptionFlags';
import { BotOptionFlags } from '..';
export declare class Region {
xCoordinate: number;
yCoordinate: number;

View File

@@ -4,15 +4,15 @@ const Circuit_1 = require("./Circuit");
const Caps_1 = require("./Caps");
const Comms_1 = require("./Comms");
const ObjectStoreFull_1 = require("./ObjectStoreFull");
const BotOptionFlags_1 = require("../enums/BotOptionFlags");
const ObjectStoreLite_1 = require("./ObjectStoreLite");
const __1 = require("..");
class Region {
constructor(agent, clientEvents, options) {
this.agent = agent;
this.options = options;
this.clientEvents = clientEvents;
this.circuit = new Circuit_1.Circuit(clientEvents);
if (options & BotOptionFlags_1.BotOptionFlags.LiteObjectStore) {
if (options & __1.BotOptionFlags.LiteObjectStore) {
this.objects = new ObjectStoreLite_1.ObjectStoreLite(this.circuit, agent, clientEvents, options);
}
else {

View File

@@ -1 +1 @@
{"version":3,"file":"Region.js","sourceRoot":"","sources":["../../lib/classes/Region.ts"],"names":[],"mappings":";;AAAA,uCAAkC;AAElC,iCAA4B;AAC5B,mCAA8B;AAG9B,uDAAkD;AAClD,4DAAuD;AACvD,uDAAkD;AAElD;IAYI,YAAY,KAAY,EAAE,YAA0B,EAAE,OAAuB;QAEzE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,iBAAO,CAAC,YAAY,CAAC,CAAC;QACzC,IAAI,OAAO,GAAG,+BAAc,CAAC,eAAe,EAC5C;YACI,IAAI,CAAC,OAAO,GAAG,IAAI,iCAAe,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;SAClF;aAED;YACI,IAAI,CAAC,OAAO,GAAG,IAAI,iCAAe,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;SAClF;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,aAAK,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAC9D,CAAC;IACD,YAAY,CAAC,OAAe;QAExB,IAAI,CAAC,IAAI,GAAG,IAAI,WAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACvE,CAAC;IACD,QAAQ;QAEJ,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IAE5B,CAAC;CACJ;AAxCD,wBAwCC"}
{"version":3,"file":"Region.js","sourceRoot":"","sources":["../../lib/classes/Region.ts"],"names":[],"mappings":";;AAAA,uCAAkC;AAElC,iCAA4B;AAC5B,mCAA8B;AAG9B,uDAAkD;AAClD,uDAAkD;AAClD,0BAAkC;AAElC;IAYI,YAAY,KAAY,EAAE,YAA0B,EAAE,OAAuB;QAEzE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,iBAAO,CAAC,YAAY,CAAC,CAAC;QACzC,IAAI,OAAO,GAAG,kBAAc,CAAC,eAAe,EAC5C;YACI,IAAI,CAAC,OAAO,GAAG,IAAI,iCAAe,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;SAClF;aAED;YACI,IAAI,CAAC,OAAO,GAAG,IAAI,iCAAe,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;SAClF;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,aAAK,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAC9D,CAAC;IACD,YAAY,CAAC,OAAe;QAExB,IAAI,CAAC,IAAI,GAAG,IAAI,WAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACvE,CAAC;IACD,QAAQ;QAEJ,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IAE5B,CAAC;CACJ;AAxCD,wBAwCC"}

View File

@@ -0,0 +1,3 @@
export declare class EventQueueStateChangeEvent {
active: boolean;
}

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class EventQueueStateChangeEvent {
}
exports.EventQueueStateChangeEvent = EventQueueStateChangeEvent;
//# sourceMappingURL=EventQueueStateChangeEvent.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"EventQueueStateChangeEvent.js","sourceRoot":"","sources":["../../lib/events/EventQueueStateChangeEvent.ts"],"names":[],"mappings":";;AAAA;CAGC;AAHD,gEAGC"}

View File

@@ -1,12 +1,12 @@
import { UUID, Vector3 } from '..';
export declare class ScriptDialogEvent {
ObjectID: UUID;
FirstName: string;
LastName: string;
ObjectName: Vector3;
Message: string;
ChatChannel: number;
ImageID: UUID;
Buttons: string;
OwnerID: UUID;
}
import { UUID } from '..';
export declare class ScriptDialogEvent {
ObjectID: UUID;
FirstName: string;
LastName: string;
ObjectName: string;
Message: string;
ChatChannel: number;
ImageID: UUID;
Buttons: string[];
Owners: UUID[];
}

View File

@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class ScriptDialogEvent {
}
exports.ScriptDialogEvent = ScriptDialogEvent;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class ScriptDialogEvent {
}
exports.ScriptDialogEvent = ScriptDialogEvent;
//# sourceMappingURL=ScriptDialogEvent.js.map

3
dist/index.d.ts vendored
View File

@@ -38,4 +38,5 @@ import { DecodeFlags } from './enums/DecodeFlags';
import { ParcelInfoFlags } from './enums/ParcelInfoFlags';
import { ParcelInfoReplyEvent } from './events/ParcelInfoReplyEvent';
import { ScriptDialogEvent } from './events/ScriptDialogEvent';
export { Bot, LoginParameters, AssetType, HTTPAssets, ClientEvents, BVH, ChatSourceType, BotOptionFlags, UUID, Vector3, AgentFlags, CompressedFlags, ControlFlags, DecodeFlags, InstantMessageEventFlags, InventoryItemFlags, LoginFlags, MessageFlags, ParcelInfoFlags, PacketFlags, RegionProtocolFlags, SoundFlags, TeleportFlags, ChatEvent, DisconnectEvent, FriendRequestEvent, FriendResponseEvent, GroupChatEvent, GroupChatSessionAgentListEvent, GroupChatSessionJoinEvent, GroupInviteEvent, InstantMessageEvent, InventoryOfferedEvent, LureEvent, MapInfoRangeReplyEvent, MapInfoReplyEvent, ParcelInfoReplyEvent, RegionInfoReplyEvent, TeleportEvent, ScriptDialogEvent };
import { EventQueueStateChangeEvent } from './events/EventQueueStateChangeEvent';
export { Bot, LoginParameters, AssetType, HTTPAssets, ClientEvents, BVH, ChatSourceType, BotOptionFlags, UUID, Vector3, AgentFlags, CompressedFlags, ControlFlags, DecodeFlags, InstantMessageEventFlags, InventoryItemFlags, LoginFlags, MessageFlags, ParcelInfoFlags, PacketFlags, RegionProtocolFlags, SoundFlags, TeleportFlags, ChatEvent, DisconnectEvent, FriendRequestEvent, FriendResponseEvent, GroupChatEvent, GroupChatSessionAgentListEvent, GroupChatSessionJoinEvent, GroupInviteEvent, InstantMessageEvent, InventoryOfferedEvent, LureEvent, MapInfoRangeReplyEvent, MapInfoReplyEvent, ParcelInfoReplyEvent, RegionInfoReplyEvent, TeleportEvent, ScriptDialogEvent, EventQueueStateChangeEvent };

2
dist/index.js vendored
View File

@@ -80,4 +80,6 @@ const ParcelInfoReplyEvent_1 = require("./events/ParcelInfoReplyEvent");
exports.ParcelInfoReplyEvent = ParcelInfoReplyEvent_1.ParcelInfoReplyEvent;
const ScriptDialogEvent_1 = require("./events/ScriptDialogEvent");
exports.ScriptDialogEvent = ScriptDialogEvent_1.ScriptDialogEvent;
const EventQueueStateChangeEvent_1 = require("./events/EventQueueStateChangeEvent");
exports.EventQueueStateChangeEvent = EventQueueStateChangeEvent_1.EventQueueStateChangeEvent;
//# sourceMappingURL=index.js.map

2
dist/index.js.map vendored
View File

@@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;AAAA,+BAA0B;AA8CtB,cA9CI,SAAG,CA8CJ;AA7CP,+DAA0D;AA8CtD,0BA9CI,iCAAe,CA8CJ;AA7CnB,yDAAoD;AAgDhD,uBAhDI,2BAAY,CAgDJ;AA/ChB,uCAAkC;AAgD9B,cAhDI,SAAG,CAgDJ;AA3CP,iDAA4C;AAwCxC,oBAxCI,qBAAS,CAwCJ;AAvCb,mDAA8C;AAwC1C,qBAxCI,uBAAU,CAwCJ;AAvCd,+EAA0E;AAoDtE,mCApDI,mDAAwB,CAoDJ;AAnD5B,sEAAiE;AAsE7D,8BAtEI,yCAAmB,CAsEJ;AArEvB,2DAAsD;AAwClD,yBAxCI,+BAAc,CAwCJ;AAvClB,2DAAsD;AAwClD,yBAxCI,+BAAc,CAwCJ;AAvClB,yCAAoC;AAwChC,eAxCI,WAAI,CAwCJ;AAvCR,+CAA0C;AAwCtC,kBAxCI,iBAAO,CAwCJ;AAvCX,kDAA6C;AAyDzC,oBAzDI,qBAAS,CAyDJ;AAxDb,gEAA2D;AA+DvD,2BA/DI,mCAAgB,CA+DJ;AA9DpB,oEAA+D;AAyD3D,6BAzDI,uCAAkB,CAyDJ;AAxDtB,sEAAiE;AAyD7D,8BAzDI,yCAAmB,CAyDJ;AAxDvB,kDAA6C;AA+DzC,oBA/DI,qBAAS,CA+DJ;AA9Db,0DAAqD;AAmEjD,wBAnEI,6BAAa,CAmEJ;AAlEjB,8DAAyD;AAoDrD,0BApDI,iCAAe,CAoDJ;AAnDnB,4DAAuD;AAsDnD,yBAtDI,+BAAc,CAsDJ;AArDlB,kFAA6E;AAuDzE,oCAvDI,qDAAyB,CAuDJ;AAtD7B,4FAAuF;AAqDnF,yCArDI,+DAA8B,CAqDJ;AApDlC,wEAAmE;AA6D/D,+BA7DI,2CAAoB,CA6DJ;AA5DxB,kEAA6D;AA0DzD,4BA1DI,qCAAiB,CA0DJ;AAzDrB,4EAAuE;AAwDnE,iCAxDI,+CAAsB,CAwDJ;AAvD1B,0EAAqE;AAqDjE,gCArDI,6CAAqB,CAqDJ;AApDzB,mDAA8C;AA4B1C,qBA5BI,uBAAU,CA4BJ;AA3Bd,uDAAkD;AA6B9C,uBA7BI,2BAAY,CA6BJ;AA5BhB,mEAA8D;AA+B1D,6BA/BI,uCAAkB,CA+BJ;AA9BtB,mDAA8C;AA+B1C,qBA/BI,uBAAU,CA+BJ;AA9Bd,uDAAkD;AA+B9C,uBA/BI,2BAAY,CA+BJ;AA9BhB,qDAAgD;AAgC5C,sBAhCI,yBAAW,CAgCJ;AA/Bf,qEAAgE;AAgC5D,8BAhCI,yCAAmB,CAgCJ;AA/BvB,mDAA8C;AAgC1C,qBAhCI,uBAAU,CAgCJ;AA/Bd,yDAAoD;AAgChD,wBAhCI,6BAAa,CAgCJ;AA/BjB,6DAAwD;AAoBpD,0BApBI,iCAAe,CAoBJ;AAnBnB,qDAAgD;AAqB5C,sBArBI,yBAAW,CAqBJ;AApBf,6DAAwD;AAyBpD,0BAzBI,iCAAe,CAyBJ;AAxBnB,wEAAmE;AA4C/D,+BA5CI,2CAAoB,CA4CJ;AA3CxB,kEAA6D;AA8CzD,4BA9CI,qCAAiB,CA8CJ"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;AAAA,+BAA0B;AA4CtB,cA5CI,SAAG,CA4CJ;AA3CP,+DAA0D;AA4CtD,0BA5CI,iCAAe,CA4CJ;AA3CnB,yDAAoD;AA8ChD,uBA9CI,2BAAY,CA8CJ;AA7ChB,uCAAkC;AA8C9B,cA9CI,SAAG,CA8CJ;AA5CP,iDAA4C;AAyCxC,oBAzCI,qBAAS,CAyCJ;AAxCb,mDAA8C;AAyC1C,qBAzCI,uBAAU,CAyCJ;AAxCd,+EAA0E;AAqDtE,mCArDI,mDAAwB,CAqDJ;AApD5B,sEAAiE;AAuE7D,8BAvEI,yCAAmB,CAuEJ;AAtEvB,2DAAsD;AAyClD,yBAzCI,+BAAc,CAyCJ;AAxClB,2DAAsD;AAyClD,yBAzCI,+BAAc,CAyCJ;AAxClB,yCAAoC;AAyChC,eAzCI,WAAI,CAyCJ;AAxCR,+CAA0C;AAyCtC,kBAzCI,iBAAO,CAyCJ;AAxCX,kDAA6C;AA0DzC,oBA1DI,qBAAS,CA0DJ;AAzDb,gEAA2D;AAgEvD,2BAhEI,mCAAgB,CAgEJ;AA/DpB,oEAA+D;AA0D3D,6BA1DI,uCAAkB,CA0DJ;AAzDtB,sEAAiE;AA0D7D,8BA1DI,yCAAmB,CA0DJ;AAzDvB,kDAA6C;AAgEzC,oBAhEI,qBAAS,CAgEJ;AA/Db,0DAAqD;AAoEjD,wBApEI,6BAAa,CAoEJ;AAnEjB,8DAAyD;AAqDrD,0BArDI,iCAAe,CAqDJ;AApDnB,4DAAuD;AAuDnD,yBAvDI,+BAAc,CAuDJ;AAtDlB,kFAA6E;AAwDzE,oCAxDI,qDAAyB,CAwDJ;AAvD7B,4FAAuF;AAsDnF,yCAtDI,+DAA8B,CAsDJ;AArDlC,wEAAmE;AA8D/D,+BA9DI,2CAAoB,CA8DJ;AA7DxB,kEAA6D;AA2DzD,4BA3DI,qCAAiB,CA2DJ;AA1DrB,4EAAuE;AAyDnE,iCAzDI,+CAAsB,CAyDJ;AAxD1B,0EAAqE;AAsDjE,gCAtDI,6CAAqB,CAsDJ;AArDzB,mDAA8C;AA6B1C,qBA7BI,uBAAU,CA6BJ;AA5Bd,uDAAkD;AA8B9C,uBA9BI,2BAAY,CA8BJ;AA7BhB,mEAA8D;AAgC1D,6BAhCI,uCAAkB,CAgCJ;AA/BtB,mDAA8C;AAgC1C,qBAhCI,uBAAU,CAgCJ;AA/Bd,uDAAkD;AAgC9C,uBAhCI,2BAAY,CAgCJ;AA/BhB,qDAAgD;AAiC5C,sBAjCI,yBAAW,CAiCJ;AAhCf,qEAAgE;AAiC5D,8BAjCI,yCAAmB,CAiCJ;AAhCvB,mDAA8C;AAiC1C,qBAjCI,uBAAU,CAiCJ;AAhCd,yDAAoD;AAiChD,wBAjCI,6BAAa,CAiCJ;AAhCjB,6DAAwD;AAqBpD,0BArBI,iCAAe,CAqBJ;AApBnB,qDAAgD;AAsB5C,sBAtBI,yBAAW,CAsBJ;AArBf,6DAAwD;AA0BpD,0BA1BI,iCAAe,CA0BJ;AAzBnB,wEAAmE;AA6C/D,+BA7CI,2CAAoB,CA6CJ;AA5CxB,kEAA6D;AA+CzD,4BA/CI,qCAAiB,CA+CJ;AA9CrB,oFAA+E;AA+C3E,qCA/CI,uDAA0B,CA+CJ"}

View File

@@ -1,3 +1,8 @@
// Here's a more modern example of how to use node-metaverse with async/await.
// Modern node.js required
require('source-map-support').install();
const nmv = require('../dist/index');
const loginParameters = new nmv.LoginParameters();
@@ -12,7 +17,7 @@ loginParameters.start = "last";
//const options = nmv.BotOptionFlags.None;
// If you don't intend to use the object store (i.e you have no interest in inworld objects, textures, etc,
// using ObjectStoreLite will drastically reduce the footprint
// using None or LiteObjectStore will drastically reduce the footprint
//
const options = nmv.BotOptionFlags.LiteObjectStore | nmv.BotOptionFlags.StoreMyAttachmentsOnly;
@@ -24,20 +29,26 @@ const master = 'd1cd5b71-6209-4595-9bf0-771bf689ce00';
let loginResponse = null;
bot.clientEvents.onLure.subscribe((lureEvent) =>
bot.clientEvents.onLure.subscribe(async (lureEvent) =>
{
bot.clientCommands.grid.getRegionMapInfo(lureEvent.gridX / 256, lureEvent.gridY / 256).then((regionInfo) =>
try
{
const regionInfo = await bot.clientCommands.grid.getRegionMapInfo(lureEvent.gridX / 256, lureEvent.gridY / 256);
if (lureEvent.from.toString() === master)
{
console.log('Accepting teleport lure to ' + regionInfo.block.name + ' (' + regionInfo.avatars.length + ' avatar' + ((regionInfo.avatars.length === 1)?'':'s') + ' present) from ' + lureEvent.fromName + ' with message: ' + lureEvent.lureMessage);
bot.clientCommands.teleport.acceptTeleport(lureEvent);
bot.clientCommands.teleport.acceptTeleport(lureEvent).then(() => {});
}
else
{
console.log('Ignoring teleport lure to ' + regionInfo.block.name + ' (' + regionInfo.avatars.length + ' avatar' + ((regionInfo.avatars.length === 1)?'':'s') + ' present) from ' + lureEvent.fromName + ' with message: ' + lureEvent.lureMessage);
}
});
}
catch(error)
{
console.error("Failed to get region map info:");
console.error(error);
}
});
bot.clientEvents.onInstantMessage.subscribe((IMEvent) =>
@@ -46,7 +57,7 @@ bot.clientEvents.onInstantMessage.subscribe((IMEvent) =>
{
if (!(IMEvent.flags & nmv.InstantMessageEventFlags.startTyping || IMEvent.flags & nmv.InstantMessageEventFlags.finishTyping))
{
bot.clientCommands.comms.typeInstantMessage(IMEvent.from, 'Thanks for the message! This account is a scripted agent (bot), so cannot reply to your query. Sorry!');
bot.clientCommands.comms.typeInstantMessage(IMEvent.from, 'Thanks for the message! This account is a scripted agent (bot), so cannot reply to your query. Sorry!').then(() => {});
}
}
});
@@ -56,12 +67,12 @@ bot.clientEvents.onFriendRequest.subscribe((event) =>
if (event.from.toString() === master)
{
console.log("Accepting friend request from " + event.fromName);
bot.clientCommands.comms.acceptFriendRequest(event);
bot.clientCommands.comms.acceptFriendRequest(event).then(() => {});
}
else
{
console.log("Rejecting friend request from " + event.fromName);
bot.clientCommands.comms.rejectFriendRequest(event);
bot.clientCommands.comms.rejectFriendRequest(event).then(() => {});
}
});
@@ -70,12 +81,12 @@ bot.clientEvents.onInventoryOffered.subscribe((event) =>
if (event.from.toString() === master)
{
console.log("Accepting inventory offer from " + event.fromName);
bot.clientCommands.comms.acceptInventoryOffer(event);
bot.clientCommands.comms.acceptInventoryOffer(event).then(() => {});
}
else
{
console.log("Rejecting inventory offer from " + event.fromName);
bot.clientCommands.comms.rejectInventoryOffer(event);
bot.clientCommands.comms.rejectInventoryOffer(event).then(() => {});
}
});
@@ -88,70 +99,77 @@ bot.clientEvents.onDisconnected.subscribe((DisconnectEvent) =>
setTimeout(() =>
{
console.log("Reconnecting");
connect();
connect().then(() => {});
}, 5000)
}
});
let pings = {};
bot.clientEvents.onGroupChat.subscribe((GroupChatEvent) =>
bot.clientEvents.onGroupChat.subscribe(async (GroupChatEvent) =>
{
console.log("Group chat: " + GroupChatEvent.fromName + ': ' + GroupChatEvent.message);
if (GroupChatEvent.message === '!ping')
{
let ping = uuid.v4();
pings[ping] = Math.floor(new Date().getTime());
bot.clientCommands.comms.sendGroupMessage(GroupChatEvent.groupID, 'ping '+ping).then((memberCount) =>
{
console.log('Group message sent to ' + memberCount + ' members');
});
}
else if (GroupChatEvent.from.toString() === loginResponse.agent.agentID.toString())
{
if (GroupChatEvent.message.substr(0, 5) === 'ping ')
{
const pingID = GroupChatEvent.message.substr(5);
if (pings[pingID])
{
console.log("found ping");
const time = (new Date().getTime()) - pings[pingID];
delete pings[pingID];
bot.clientCommands.comms.sendGroupMessage(GroupChatEvent.groupID, 'Chat lag: ' + time + 'ms');
}
else
{
console.log("ping not found |"+pingID+"|");
}
}
console.log("Group chat: " + GroupChatEvent.fromName + ': ' + GroupChatEvent.message);
if (GroupChatEvent.message === '!ping')
{
let ping = uuid.v4();
pings[ping] = Math.floor(new Date().getTime());
try
{
const memberCount = await bot.clientCommands.comms.sendGroupMessage(GroupChatEvent.groupID, 'ping '+ping);
console.log('Group message sent to ' + memberCount + ' members');
}
catch(error)
{
console.error('Failed to send group message:');
console.error(error);
}
}
else if (GroupChatEvent.from.toString() === loginResponse.agent.agentID.toString())
{
if (GroupChatEvent.message.substr(0, 5) === 'ping ')
{
const pingID = GroupChatEvent.message.substr(5);
if (pings[pingID])
{
console.log("found ping");
const time = (new Date().getTime()) - pings[pingID];
delete pings[pingID];
bot.clientCommands.comms.sendGroupMessage(GroupChatEvent.groupID, 'Chat lag: ' + time + 'ms').then(() => {});
}
else
{
console.log("ping not found |"+pingID+"|");
}
}
}
}
});
bot.clientEvents.onGroupInvite.subscribe((GroupInviteEvent) =>
bot.clientEvents.onGroupInvite.subscribe(async (GroupInviteEvent) =>
{
console.log('Group invite from ' + GroupInviteEvent.fromName + ': '+GroupInviteEvent.message);
//Resolve avatar key
bot.clientCommands.grid.name2Key(GroupInviteEvent.fromName).then((key) =>
try
{
const key = await bot.clientCommands.grid.name2Key(GroupInviteEvent.fromName);
if (key.toString() === master)
{
console.log('Accepting');
bot.clientCommands.comms.acceptGroupInvite(GroupInviteEvent);
bot.clientCommands.group.acceptGroupInvite(GroupInviteEvent).then(() => {});
}
else
{
console.log('Unauthorised - rejecting');
bot.clientCommands.comms.rejectGroupInvite(GroupInviteEvent);
bot.clientCommands.group.rejectGroupInvite(GroupInviteEvent).then(() => {});
}
}).catch((err) =>
}
catch(error)
{
console.error(err);
console.log('Unknown avatar - rejecting');
bot.clientCommands.comms.rejectGroupInvite(GroupInviteEvent);
});
console.error('Failed to respond to group invite:');
console.error(error);
}
});
bot.clientEvents.onFriendResponse.subscribe((response) =>
@@ -166,18 +184,18 @@ bot.clientEvents.onFriendResponse.subscribe((response) =>
}
});
function connect()
async function connect()
{
console.log("Logging in..");
bot.login().then((response) =>
try
{
loginResponse = response;
console.log("Logging in..");
loginResponse = await bot.login();
console.log("Login complete");
//Establish circuit with region
return bot.connectToSim();
}).then(() =>
{
await bot.connectToSim();
console.log("Connected to simulator");
isConnected = true;
// Do some stuff
@@ -196,76 +214,85 @@ function connect()
const userToInvite = new nmv.UUID("d1cd5b71-6209-4595-9bf0-771bf689ce00");
const groupID = new nmv.UUID("c6424e05-6e2c-fb03-220b-ca7904d11e04");
bot.clientCommands.group.getGroupRoles(groupID).then((roles) =>
{
roles.forEach((role) =>
{
if (role.Name === 'Officers')
{
// IMPORTANT: IN PRODUCTION, IT IS HIGHLY RECOMMENDED TO CACHE THIS LIST.
//
bot.clientCommands.group.getMemberList(groupID).then((members) =>
{
let found = true;
members.forEach((member) =>
{
if (member.AgentID.toString() === userToInvite.toString())
{
found = true;
}
});
if (found)
{
console.log("User already in group, skipping invite");
}
else
{
bot.clientCommands.group.sendGroupInvite(groupID, userToInvite, role.RoleID);
}
});
}
});
});
setTimeout(() => // TODO: This 5 second delay is a fudge. We need to wait for the eventqueue to start properly
{
bot.clientCommands.teleport.teleportTo('Izanagi', new nmv.Vector3([128, 128, 20]), new nmv.Vector3([0,
1.0,
0])).then(() =>
{
console.log("Teleport completed");
}).catch((err) =>
{
console.error(err);
});
}, 5000);
bot.clientCommands.comms.sendFriendRequest(master, 'Be friends with me?');
// If you want to wait here for the request to be acknowledged, you can add "await"
bot.clientCommands.comms.sendFriendRequest(master, 'Be friends with me?').then(() => {});
const folders = bot.clientCommands.inventory.getInventoryRoot().getChildFolders();
folders.forEach((folder) =>
{
console.log('Top level folder: ' + folder.name);
folder.populate();
folder.populate().then(() => {});
});
// When it's time to go home, call bot.close();
}).catch((error) =>
const roles = await bot.clientCommands.group.getGroupRoles(groupID);
roles.forEach(async (role) =>
{
if (role.Name === 'Officers')
{
// IMPORTANT: IN PRODUCTION, IT IS HIGHLY RECOMMENDED TO CACHE THIS LIST.
//
try
{
const members = await bot.clientCommands.group.getMemberList(groupID);
let found = true;
members.forEach((member) =>
{
if (member.AgentID.toString() === userToInvite.toString())
{
found = true;
}
});
if (found)
{
console.log("User already in group, skipping invite");
}
else
{
bot.clientCommands.group.sendGroupInvite(groupID, userToInvite, role.RoleID).then(() => {});
}
}
catch(error)
{
console.error('Error retrieving member list for group invite');
}
}
});
await bot.waitForEventQueue();
try
{
await bot.clientCommands.teleport.teleportTo('Izanagi', new nmv.Vector3([128, 128, 20]), new nmv.Vector3([ 0, 1.0, 0]));
console.log("Teleport completed");
}
catch(error)
{
console.error(error);
}
}
catch (error)
{
isConnected = false;
console.log("Error:");
console.error(error);
setTimeout(() =>
{
connect();
connect().then(() => {});
}, 5000)
});
}
}
connect();
try
{
connect().then(() => {});
}
catch(error)
{
console.error('Connection failure: ');
console.log(error);
}
function exitHandler(options, err)
async function exitHandler(options, err)
{
if (err)
{
@@ -274,10 +301,16 @@ function exitHandler(options, err)
if (isConnected)
{
console.log("Disconnecting");
bot.close().then(() =>
try
{
process.exit()
});
await bot.close();
}
catch(error)
{
console.error('Error when closing client:');
console.error(error);
}
process.exit();
return;
}
if (options.exit)
@@ -292,7 +325,7 @@ process.on('exit', exitHandler.bind(null,{}));
//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(null, {exit:true}));
// catches "kill pid" (for example: nodemon restart)
// catches "kill pid"
process.on('SIGUSR1', exitHandler.bind(null, {exit:true}));
process.on('SIGUSR2', exitHandler.bind(null, {exit:true}));

View File

@@ -28,6 +28,8 @@ import {Subscription} from 'rxjs/Subscription';
import {BotOptionFlags} from './enums/BotOptionFlags';
import {FilterResponse} from './enums/FilterResponse';
import {LogoutReplyMessage} from './classes/messages/LogoutReply';
import {EventQueueStateChangeEvent} from './events/EventQueueStateChangeEvent';
import {UUID} from './classes/UUID';
export class Bot
{
@@ -39,8 +41,11 @@ export class Bot
private lastSuccessfulPing = 0;
private circuitSubscription: Subscription | null = null;
private options: BotOptionFlags;
private eventQueueRunning = false;
public clientEvents: ClientEvents;
public clientCommands: ClientCommands;
private eventQueueWaits: any = {};
constructor(login: LoginParameters, options: BotOptionFlags)
@@ -48,6 +53,21 @@ export class Bot
this.clientEvents = new ClientEvents();
this.loginParams = login;
this.options = options;
this.clientEvents.onEventQueueStateChange.subscribe((evt: EventQueueStateChangeEvent) =>
{
this.eventQueueRunning = evt.active;
for (const waitID of Object.keys(this.eventQueueWaits))
{
try
{
clearTimeout(this.eventQueueWaits[waitID].timer);
this.eventQueueWaits[waitID].resolve();
delete this.eventQueueWaits[waitID];
}
catch (ignore){}
}
});
}
async login()
@@ -73,6 +93,35 @@ export class Bot
await this.connectToSim();
}
waitForEventQueue(timeout: number = 1000): Promise<void>
{
return new Promise((resolve, reject) =>
{
if (this.eventQueueRunning)
{
resolve();
}
else
{
const waitID = UUID.random().toString();
const newWait: {
'resolve': any,
'timer'?: Timer
} = {
'resolve': resolve
};
newWait.timer = setTimeout(() =>
{
delete this.eventQueueWaits[waitID];
reject(new Error('Timeout'));
}, timeout);
this.eventQueueWaits[waitID] = newWait;
}
});
}
private closeCircuit()
{
this.agent.shutdown();

View File

@@ -2,13 +2,18 @@ import {Subject} from 'rxjs/Subject';
import {
ChatEvent,
DisconnectEvent,
FriendRequestEvent, FriendResponseEvent, GroupChatEvent, GroupChatSessionAgentListEvent, GroupChatSessionJoinEvent,
FriendRequestEvent,
FriendResponseEvent,
GroupChatEvent,
GroupChatSessionAgentListEvent,
GroupChatSessionJoinEvent,
GroupInviteEvent,
InstantMessageEvent,
InventoryOfferedEvent,
LureEvent,
TeleportEvent,
ScriptDialogEvent
ScriptDialogEvent,
EventQueueStateChangeEvent
} from '..';
@@ -28,4 +33,5 @@ export class ClientEvents
onGroupChatAgentListUpdate: Subject<GroupChatSessionAgentListEvent> = new Subject<GroupChatSessionAgentListEvent>();
onFriendResponse: Subject<FriendResponseEvent> = new Subject<FriendResponseEvent>();
onScriptDialog: Subject<ScriptDialogEvent> = new Subject<ScriptDialogEvent>();
onEventQueueStateChange: Subject<EventQueueStateChangeEvent> = new Subject<EventQueueStateChangeEvent>();
}

View File

@@ -17,8 +17,12 @@ import {
GroupInviteEvent,
InstantMessageEvent,
InstantMessageEventFlags,
InventoryOfferedEvent, LureEvent
InventoryOfferedEvent,
LureEvent,
ScriptDialogEvent,
UUID
} from '..';
import {ScriptDialogMessage} from './messages/ScriptDialog';
export class Comms
{
@@ -35,7 +39,8 @@ export class Comms
this.circuit.subscribeToMessages([
Message.ImprovedInstantMessage,
Message.ChatFromSimulator,
Message.AlertMessage
Message.AlertMessage,
Message.ScriptDialog
], (packet: Packet) =>
{
switch (packet.message.id)
@@ -243,7 +248,7 @@ export class Comms
break;
case Message.ChatFromSimulator:
{
const chat = packet.message as ChatFromSimulatorMessage;
const event = new ChatEvent();
event.fromName = Utils.BufferToStringSimple(chat.ChatData.FromName);
@@ -256,8 +261,10 @@ export class Comms
event.position = chat.ChatData.Position;
this.clientEvents.onNearbyChat.next(event);
break;
}
case Message.AlertMessage:
{
// TODO: this isn't finished
const alertm = packet.message as AlertMessageMessage;
const alertMessage = Utils.BufferToStringSimple(alertm.AlertData.Message);
@@ -269,6 +276,31 @@ export class Comms
console.log('Alert info message: ' + alertInfoMessage);
});
break;
}
case Message.ScriptDialog:
{
const scriptd = packet.message as ScriptDialogMessage;
const event = new ScriptDialogEvent();
event.ObjectID = scriptd.Data.ObjectID;
event.FirstName = Utils.BufferToStringSimple(scriptd.Data.FirstName);
event.LastName = Utils.BufferToStringSimple(scriptd.Data.LastName);
event.ObjectName = Utils.BufferToStringSimple(scriptd.Data.ObjectName);
event.Message = Utils.BufferToStringSimple(scriptd.Data.Message);
event.ChatChannel = scriptd.Data.ChatChannel;
event.ImageID = scriptd.Data.ImageID;
event.Buttons = [];
event.Owners = [];
for (const button of scriptd.Buttons)
{
event.Buttons.push(Utils.BufferToStringSimple(button.ButtonLabel));
}
for (const owner of scriptd.OwnerData)
{
event.Owners.push(owner.OwnerID);
}
this.clientEvents.onScriptDialog.next(event);
break;
}
}
});
}

View File

@@ -3,15 +3,17 @@ import * as LLSD from '@caspertech/llsd';
import * as request from 'request';
import * as Long from 'long';
import {IPAddress} from './IPAddress';
import {TeleportEvent} from '../events/TeleportEvent';
import {ClientEvents} from './ClientEvents';
import {TeleportEventType} from '../enums/TeleportEventType';
import {GroupChatEvent} from '../events/GroupChatEvent';
import {Utils} from './Utils';
import {UUID} from './UUID';
import {Agent} from './Agent';
import {GroupChatSessionJoinEvent} from '../events/GroupChatSessionJoinEvent';
import {GroupChatSessionAgentListEvent} from '../events/GroupChatSessionAgentListEvent';
import {
EventQueueStateChangeEvent,
GroupChatEvent,
GroupChatSessionAgentListEvent,
GroupChatSessionJoinEvent,
TeleportEvent
} from '..';
export class EventQueueClient
{
@@ -28,9 +30,15 @@ export class EventQueueClient
this.clientEvents = clientEvents;
this.caps = caps;
this.Get();
const state = new EventQueueStateChangeEvent();
state.active = true;
this.clientEvents.onEventQueueStateChange.next(state);
}
shutdown()
{
const state = new EventQueueStateChangeEvent();
state.active = false;
this.clientEvents.onEventQueueStateChange.next(state);
if (this.currentRequest !== null)
{
this.currentRequest.abort();
@@ -43,6 +51,7 @@ export class EventQueueClient
'ack': this.ack,
'done': this.done
};
const startTime = new Date().getTime();
this.capsRequestXML('EventQueueGet', req).then((data) =>
{
if (data['events'])
@@ -293,7 +302,7 @@ export class EventQueueClient
'method': 'accept invitation',
'session-id': imSessionID
};
this.caps.capsRequestXML('ChatSessionRequest', requestedFolders).then((result: any) =>
this.caps.capsRequestXML('ChatSessionRequest', requestedFolders).then((ignore: any) =>
{
this.agent.addChatSession(groupChatEvent.groupID);
@@ -393,14 +402,28 @@ export class EventQueueClient
}
}).catch((err) =>
{
// Wait 5 seconds before retrying
setTimeout(() =>
const time = (new Date().getTime()) - startTime;
if (time > 30000)
{
// This is the normal request timeout, so reconnect immediately
if (!this.done)
{
this.Get();
}
}, 5000);
}
else
{
console.error('Event queue aborted after ' + time + 'ms. Reconnecting in 5 seconds');
// Wait 5 seconds before retrying
setTimeout(() =>
{
if (!this.done)
{
this.Get();
}
}, 5000);
}
});
}
request(url: string, data: string, contentType: string): Promise<string>
@@ -432,7 +455,7 @@ export class EventQueueClient
});
}
capsRequestXML(capability: string, data: any): Promise<any>
capsRequestXML(capability: string, data: any, attempt: number = 0): Promise<any>
{
return new Promise<any>((resolve, reject) =>
{
@@ -450,7 +473,15 @@ export class EventQueueClient
}
else
{
throw new Error('Not an LLSD response');
// Retry caps request three times before giving up
if (attempt < 3)
{
return this.capsRequestXML(capability, data, ++attempt);
}
else
{
reject(new Error('Not an LLSD response, capability: ' + capability));
}
}
}
catch (error)
@@ -467,4 +498,4 @@ export class EventQueueClient
});
});
}
}
}

View File

@@ -5,8 +5,8 @@ import {Comms} from './Comms';
import {ClientEvents} from './ClientEvents';
import {IObjectStore} from './interfaces/IObjectStore';
import {ObjectStoreFull} from './ObjectStoreFull';
import {BotOptionFlags} from '../enums/BotOptionFlags';
import {ObjectStoreLite} from './ObjectStoreLite';
import {BotOptionFlags} from '..';
export class Region
{

View File

@@ -0,0 +1,4 @@
export class EventQueueStateChangeEvent
{
active: boolean;
}

View File

@@ -5,10 +5,10 @@ export class ScriptDialogEvent
ObjectID: UUID;
FirstName: string;
LastName: string;
ObjectName: Vector3;
ObjectName: string;
Message: string;
ChatChannel: number;
ImageID: UUID;
Buttons: string;
OwnerID: UUID;
Buttons: string[];
Owners: UUID[];
}

View File

@@ -3,9 +3,6 @@ import {LoginParameters} from './classes/LoginParameters';
import {ClientEvents} from './classes/ClientEvents';
import {BVH} from './classes/BVH';
// Enums
import {AssetType} from './enums/AssetType';
import {HTTPAssets} from './enums/HTTPAssets';
import {InstantMessageEventFlags} from './enums/InstantMessageEventFlags';
@@ -42,6 +39,7 @@ import {DecodeFlags} from './enums/DecodeFlags';
import {ParcelInfoFlags} from './enums/ParcelInfoFlags';
import {ParcelInfoReplyEvent} from './events/ParcelInfoReplyEvent';
import {ScriptDialogEvent} from './events/ScriptDialogEvent';
import {EventQueueStateChangeEvent} from './events/EventQueueStateChangeEvent';
export {
Bot,
@@ -87,5 +85,6 @@ export {
ParcelInfoReplyEvent,
RegionInfoReplyEvent,
TeleportEvent,
ScriptDialogEvent
ScriptDialogEvent,
EventQueueStateChangeEvent
};

25
package-lock.json generated
View File

@@ -4,6 +4,15 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@caspertech/llsd": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@caspertech/llsd/-/llsd-1.0.0.tgz",
"integrity": "sha1-Dyr7xO+JzsXonSAqBAmt0smaml0=",
"requires": {
"abab": "^1.0.4",
"xmldom": "^0.1.27"
}
},
"@types/caseless": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz",
@@ -17,7 +26,7 @@
"@types/form-data": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz",
"integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==",
"integrity": "sha1-7is7jqoRwJOCiZU2BrdFtzjFSx4=",
"requires": {
"@types/node": "*"
}
@@ -84,6 +93,11 @@
"@types/node": "*"
}
},
"abab": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4="
},
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
@@ -592,7 +606,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
@@ -851,7 +865,7 @@
"source-map-support": {
"version": "0.4.18",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
"integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
"integrity": "sha1-Aoam3ovkJkEzhZTpfM6nXwosWF8=",
"dev": true,
"requires": {
"source-map": "^0.5.6"
@@ -996,6 +1010,11 @@
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
"integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M="
},
"xmldom": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
"integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk="
},
"xmlrpc": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz",

View File

@@ -22,7 +22,7 @@
"label-position": true,
"max-line-length": [
true,
140
250
],
"member-access": false,
"member-ordering": [