diff --git a/lib/LoginHandler.ts b/lib/LoginHandler.ts index 959389e..1a32905 100644 --- a/lib/LoginHandler.ts +++ b/lib/LoginHandler.ts @@ -1,10 +1,10 @@ import * as xmlrpc from 'xmlrpc'; import * as crypto from 'crypto'; -import * as uuid from 'uuid'; import * as url from 'url'; import { LoginParameters } from './classes/LoginParameters'; import { LoginResponse } from './classes/LoginResponse'; import { ClientEvents } from './classes/ClientEvents'; +import { Utils } from './classes/Utils'; import { BotOptionFlags } from './enums/BotOptionFlags'; export class LoginHandler @@ -12,23 +12,6 @@ export class LoginHandler private clientEvents: ClientEvents; private options: BotOptionFlags; - static GenerateMAC(): string - { - const hexDigits = '0123456789ABCDEF'; - let macAddress = ''; - for (let i = 0; i < 6; i++) - { - macAddress += hexDigits.charAt(Math.round(Math.random() * 15)); - macAddress += hexDigits.charAt(Math.round(Math.random() * 15)); - if (i !== 5) - { - macAddress += ':'; - } - } - - return macAddress; - } - constructor(ce: ClientEvents, options: BotOptionFlags) { this.clientEvents = ce; @@ -61,7 +44,16 @@ export class LoginHandler rejectUnauthorized: false, timeout: 60000 }; + const viewerDigest = 'ce50e500-e6f0-15ab-4b9d-0591afb91ffe'; const client = (secure) ? xmlrpc.createSecureClient(secureClientOptions) : xmlrpc.createClient(secureClientOptions); + + const nameHash = Utils.SHA1String(params.firstName + params.lastName + viewerDigest); + const macAddress: string[] = []; + for (let i = 0; i < 12; i = i + 2) + { + macAddress.push(nameHash.substr(i, 2)); + } + client.methodCall('login_to_simulator', [ { @@ -74,8 +66,8 @@ export class LoginHandler 'patch': '1', 'build': '0', 'platform': 'win', - 'mac': LoginHandler.GenerateMAC(), - 'viewer_digest': uuid.v4(), + 'mac': macAddress.join(':'), + 'viewer_digest': viewerDigest, 'user_agent': 'node-metaverse', 'author': 'nmv@caspertech.co.uk', 'options': [ diff --git a/lib/classes/Comms.ts b/lib/classes/Comms.ts index 933719e..ef8c3c8 100644 --- a/lib/classes/Comms.ts +++ b/lib/classes/Comms.ts @@ -359,7 +359,7 @@ export class Comms private async groupChatExpired(groupID: UUID): Promise { // Reconnect to group chat since it's been idle for 15 minutes - await this.agent.currentRegion.clientCommands.comms.endGroupChatSession(groupID, false); + await this.agent.currentRegion.clientCommands.comms.endGroupChatSession(groupID); await this.agent.currentRegion.clientCommands.comms.startGroupChatSession(groupID, ''); } } diff --git a/lib/classes/Utils.ts b/lib/classes/Utils.ts index 7717510..3eeb7b7 100644 --- a/lib/classes/Utils.ts +++ b/lib/classes/Utils.ts @@ -12,6 +12,7 @@ import { GlobalPosition } from './public/interfaces/GlobalPosition'; import { Quaternion } from './Quaternion'; import { Vector3 } from './Vector3'; import Timeout = NodeJS.Timeout; +import * as crypto from 'crypto'; export class Utils { @@ -28,6 +29,11 @@ export class Utils return Buffer.from(str + '\0', 'utf8'); } + static SHA1String(str: string): string + { + return crypto.createHash('sha1').update(str).digest('hex'); + } + static BufferToStringSimple(buf: Buffer): string { if (buf.length === 0) diff --git a/lib/classes/commands/CommunicationsCommands.ts b/lib/classes/commands/CommunicationsCommands.ts index 05a4954..51f8140 100644 --- a/lib/classes/commands/CommunicationsCommands.ts +++ b/lib/classes/commands/CommunicationsCommands.ts @@ -1,25 +1,24 @@ -import { CommandsBase } from './CommandsBase'; -import { UUID } from '../UUID'; -import { Utils } from '../Utils'; -import { ImprovedInstantMessageMessage } from '../messages/ImprovedInstantMessage'; -import { Vector3 } from '../Vector3'; -import { ChatFromViewerMessage } from '../messages/ChatFromViewer'; +import * as LLSD from '@caspertech/llsd'; +import { AssetType } from '../../enums/AssetType'; import { ChatType } from '../../enums/ChatType'; +import { FilterResponse } from '../../enums/FilterResponse'; import { InstantMessageDialog } from '../../enums/InstantMessageDialog'; -import { ScriptDialogReplyMessage } from '../messages/ScriptDialogReply'; +import { InstantMessageOnline } from '../../enums/InstantMessageOnline'; import { PacketFlags } from '../../enums/PacketFlags'; import { GroupChatSessionJoinEvent } from '../../events/GroupChatSessionJoinEvent'; import { ScriptDialogEvent } from '../../events/ScriptDialogEvent'; -import { StartLureMessage } from '../messages/StartLure'; -import { InventoryItem } from '../InventoryItem'; import { InventoryFolder } from '../InventoryFolder'; -import { InstantMessageOnline } from '../../enums/InstantMessageOnline'; -import { AssetType } from '../../enums/AssetType'; - +import { InventoryItem } from '../InventoryItem'; +import { ChatFromViewerMessage } from '../messages/ChatFromViewer'; +import { ImprovedInstantMessageMessage } from '../messages/ImprovedInstantMessage'; +import { ScriptDialogReplyMessage } from '../messages/ScriptDialogReply'; +import { StartLureMessage } from '../messages/StartLure'; +import { Utils } from '../Utils'; +import { UUID } from '../UUID'; +import { Vector3 } from '../Vector3'; +import { CommandsBase } from './CommandsBase'; import Timer = NodeJS.Timer; -import * as LLSD from '@caspertech/llsd'; - export class CommunicationsCommands extends CommandsBase { async giveInventory(to: UUID | string, itemOrFolder: InventoryItem | InventoryFolder): Promise @@ -389,7 +388,7 @@ export class CommunicationsCommands extends CommandsBase }); } - public async endGroupChatSession(groupID: UUID | string, removeEntry = true): Promise + public async endGroupChatSession(groupID: UUID | string): Promise { if (typeof groupID === 'string') { @@ -423,63 +422,55 @@ export class CommunicationsCommands extends CommandsBase im.EstateBlock = { EstateID: 0 }; - if (removeEntry) - { - this.agent.deleteChatSession(groupID); - } + this.agent.deleteChatSession(groupID); const sequenceNo = this.circuit.sendMessage(im, PacketFlags.Reliable); return this.circuit.waitForAck(sequenceNo, 10000); } - startGroupChatSession(groupID: UUID | string, message: string): Promise + public async startGroupChatSession(groupID: UUID | string, message: string): Promise { - return new Promise((resolve, reject) => + if (typeof groupID === 'string') { - if (typeof groupID === 'string') - { - groupID = new UUID(groupID); - } + groupID = new UUID(groupID); + } - const circuit = this.circuit; - const agentName = this.agent.firstName + ' ' + this.agent.lastName; - const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); - im.AgentData = { - AgentID: this.agent.agentID, - SessionID: circuit.sessionID - }; - im.MessageBlock = { - FromGroup: false, - ToAgentID: groupID, - ParentEstateID: 0, - RegionID: UUID.zero(), - Position: Vector3.getZero(), - Offline: 0, - Dialog: InstantMessageDialog.SessionGroupStart, - ID: groupID, - Timestamp: Math.floor(new Date().getTime() / 1000), - FromAgentName: Utils.StringToBuffer(agentName), - Message: Utils.StringToBuffer(message), - BinaryBucket: Utils.StringToBuffer('') - }; - im.EstateBlock = { - EstateID: 0 - }; - const waitForJoin = this.currentRegion.clientEvents.onGroupChatSessionJoin.subscribe((event: GroupChatSessionJoinEvent) => + if (this.agent.hasChatSession(groupID)) + { + return; + } + + const circuit = this.circuit; + const agentName = this.agent.firstName + ' ' + this.agent.lastName; + const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); + im.AgentData = { + AgentID: this.agent.agentID, + SessionID: circuit.sessionID + }; + im.MessageBlock = { + FromGroup: false, + ToAgentID: groupID, + ParentEstateID: 0, + RegionID: UUID.zero(), + Position: Vector3.getZero(), + Offline: 0, + Dialog: InstantMessageDialog.SessionGroupStart, + ID: groupID, + Timestamp: Math.floor(new Date().getTime() / 1000), + FromAgentName: Utils.StringToBuffer(agentName), + Message: Utils.StringToBuffer(message), + BinaryBucket: Utils.StringToBuffer('') + }; + im.EstateBlock = { + EstateID: 0 + }; + circuit.sendMessage(im, PacketFlags.Reliable); + await Utils.waitOrTimeOut(this.currentRegion.clientEvents.onGroupChatSessionJoin, 10000, (event: GroupChatSessionJoinEvent) => + { + if (event.sessionID.toString() === groupID.toString()) { - if (event.sessionID.toString() === groupID.toString()) - { - if (event.success) - { - waitForJoin.unsubscribe(); - resolve(); - } - else - { - reject(); - } - } - }); - circuit.sendMessage(im, PacketFlags.Reliable); + return FilterResponse.Finish; + } + return FilterResponse.NoMatch; }); } @@ -510,12 +501,16 @@ export class CommunicationsCommands extends CommandsBase async sendGroupMessage(groupID: UUID | string, message: string): Promise { - await this.startGroupChatSession(groupID, message); - if (typeof groupID === 'string') { groupID = new UUID(groupID); } + + if (!this.agent.hasChatSession(groupID)) + { + await this.startGroupChatSession(groupID, message); + } + const circuit = this.circuit; const agentName = this.agent.firstName + ' ' + this.agent.lastName; const im: ImprovedInstantMessageMessage = new ImprovedInstantMessageMessage(); diff --git a/package.json b/package.json index 82d8f83..d40dcb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.5.32", + "version": "0.5.33", "description": "A node.js interface for Second Life.", "main": "dist/lib/index.js", "types": "dist/lib/index.d.ts",