diff --git a/examples/Money/Money.ts b/examples/Money/Money.ts new file mode 100644 index 0000000..4b27c29 --- /dev/null +++ b/examples/Money/Money.ts @@ -0,0 +1,57 @@ +import { ExampleBot } from '../ExampleBot'; +import { BalanceUpdatedEvent } from '../../lib/events/BalanceUpdatedEvent'; +import { AvatarQueryResult } from '../../lib/classes/public/AvatarQueryResult'; +import { MoneyTransactionType } from '../../lib/enums/MoneyTransactionType'; + +class Money extends ExampleBot +{ + private balance = 0; + + async onConnected() + { + this.bot.clientEvents.onBalanceUpdated.subscribe(this.onBalanceUpdated.bind(this)); + try + { + this.balance = await this.bot.clientCommands.grid.getBalance(); + console.log('Balance is L$' + this.balance); + await this.bot.clientCommands.grid.payAvatar('d1cd5b71-6209-4595-9bf0-771bf689ce00', 1, 'This is a gift for being so awesome!'); + console.log('Payment success'); + } + catch (error) + { + console.log('Payment failed'); + } + } + + async onBalanceUpdated(evt: BalanceUpdatedEvent) + { + this.balance = evt.balance; + if (evt.transaction.from.equals(this.bot.agentID())) + { + if (evt.transaction.toGroup) + { + console.log('You paid a group L$' + evt.transaction.amount); + } + else + { + const result = await this.bot.clientCommands.grid.avatarKey2Name(evt.transaction.to) as AvatarQueryResult; + console.log('You paid L$' + evt.transaction.amount + ' to ' + result.getName()+ ' "' + evt.transaction.description + '" (' + MoneyTransactionType[evt.transaction.type] + ')'); + } + } + else + { + if (evt.transaction.fromGroup) + { + console.log('A group paid you L$' + evt.transaction.amount); + } + else + { + const result = await this.bot.clientCommands.grid.avatarKey2Name(evt.transaction.from) as AvatarQueryResult; + console.log(result.getName() + ' paid you L$' + evt.transaction.amount + ' "' + evt.transaction.description + '" (' + MoneyTransactionType[evt.transaction.type] + ')'); + } + } + console.log('Balance updated (New balance L$' + evt.balance + ')'); + } +} + +new Money().run().then(() => {}).catch((err) => { console.error(err) }); diff --git a/lib/classes/ClientEvents.ts b/lib/classes/ClientEvents.ts index 63fba8e..2a6b49d 100644 --- a/lib/classes/ClientEvents.ts +++ b/lib/classes/ClientEvents.ts @@ -30,6 +30,7 @@ import { BulkUpdateInventoryEvent } from '../events/BulkUpdateInventoryEvent'; import { InventoryResponseEvent } from '../events/InventoryResponseEvent'; import { LandStatsEvent } from '../events/LandStatsEvent'; import { SimStatsEvent } from '../events/SimStatsEvent'; +import { BalanceUpdatedEvent } from '../events/BalanceUpdatedEvent'; export class ClientEvents { @@ -66,4 +67,5 @@ export class ClientEvents onBulkUpdateInventoryEvent: Subject = new Subject(); onLandStatReplyEvent: Subject = new Subject(); onSimStats: Subject = new Subject(); + onBalanceUpdated: Subject = new Subject(); } diff --git a/lib/classes/Region.ts b/lib/classes/Region.ts index bf9a2e2..c15ed66 100644 --- a/lib/classes/Region.ts +++ b/lib/classes/Region.ts @@ -52,6 +52,8 @@ import { SimStatsEvent } from '../events/SimStatsEvent'; import { StatID } from '../enums/StatID'; import { CoarseLocationUpdateMessage } from './messages/CoarseLocationUpdate'; import { Avatar } from './public/Avatar'; +import { MoneyBalanceReplyMessage } from './messages/MoneyBalanceReply'; +import { BalanceUpdatedEvent } from '../events/BalanceUpdatedEvent'; export class Region { @@ -349,10 +351,34 @@ export class Region Message.SimulatorViewerTimeMessage, Message.SimStats, Message.CoarseLocationUpdate, + Message.MoneyBalanceReply ], async (packet: Packet) => { switch (packet.message.id) { + case Message.MoneyBalanceReply: + { + const msg = packet.message as MoneyBalanceReplyMessage; + const evt = new BalanceUpdatedEvent(); + if (msg.TransactionInfo.Amount === -1) + { + // This is a requested balance update, so don't sent an event + return; + } + evt.balance = msg.MoneyData.MoneyBalance; + evt.transaction = { + type: msg.TransactionInfo.TransactionType, + amount: msg.TransactionInfo.Amount, + from: msg.TransactionInfo.SourceID, + to: msg.TransactionInfo.DestID, + success: msg.MoneyData.TransactionSuccess, + fromGroup: msg.TransactionInfo.IsSourceGroup, + toGroup: msg.TransactionInfo.IsDestGroup, + description: Utils.BufferToStringSimple(msg.TransactionInfo.ItemDescription) + } + this.clientEvents.onBalanceUpdated.next(evt); + break; + } case Message.CoarseLocationUpdate: { const locations: CoarseLocationUpdateMessage = packet.message as CoarseLocationUpdateMessage; diff --git a/lib/classes/commands/GridCommands.ts b/lib/classes/commands/GridCommands.ts index df10ed0..fd4b61a 100644 --- a/lib/classes/commands/GridCommands.ts +++ b/lib/classes/commands/GridCommands.ts @@ -23,6 +23,12 @@ import { PacketFlags } from '../../enums/PacketFlags'; import { Vector2 } from '../Vector2'; import { MapInfoRangeReplyEvent } from '../../events/MapInfoRangeReplyEvent'; import { AvatarQueryResult } from '../public/AvatarQueryResult'; +import { MoneyTransferRequestMessage } from '../messages/MoneyTransferRequest'; +import { MoneyTransactionType } from '../../enums/MoneyTransactionType'; +import { TransactionFlags } from '../../enums/TransactionFlags'; +import { MoneyBalanceReplyMessage } from '../messages/MoneyBalanceReply'; +import { MoneyBalanceRequestMessage } from '../messages/MoneyBalanceRequest'; +import { GameObject } from '../public/GameObject'; export class GridCommands extends CommandsBase { @@ -389,6 +395,86 @@ export class GridCommands extends CommandsBase }); } + async getBalance(): Promise + { + const msg = new MoneyBalanceRequestMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + msg.MoneyData = { + TransactionID: UUID.zero() + } + this.circuit.sendMessage(msg, PacketFlags.Reliable); + const result = await this.circuit.waitForMessage(Message.MoneyBalanceReply, 10000); + return result.MoneyData.MoneyBalance; + } + + async payObject(target: GameObject, amount: number): Promise + { + const description = target.name || 'Object'; + const targetUUID = target.FullID; + + return this.pay(targetUUID, amount, description, MoneyTransactionType.PayObject); + } + + async payGroup(target: UUID | string, amount: number, description: string): Promise + { + if (typeof target === 'string') + { + target = new UUID(target); + } + + return this.pay(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.DestGroup); + } + + async payAvatar(target: UUID | string, amount: number, description: string): Promise + { + if (typeof target === 'string') + { + target = new UUID(target); + } + + return this.pay(target, amount, description, MoneyTransactionType.Gift, TransactionFlags.None); + } + + private async pay(target: UUID, amount: number, description: string, type: MoneyTransactionType, flags: TransactionFlags = TransactionFlags.None) + { + if (amount % 1 !== 0) + { + throw new Error('Amount to pay must be a whole number'); + } + + const msg = new MoneyTransferRequestMessage(); + msg.AgentData = { + AgentID: this.agent.agentID, + SessionID: this.circuit.sessionID + }; + msg.MoneyData = { + Description: Utils.StringToBuffer(description), + DestID: target, + SourceID: this.agent.agentID, + TransactionType: type, + AggregatePermInventory: 0, + AggregatePermNextOwner: 0, + Flags: flags, + Amount: amount + } + this.circuit.sendMessage(msg, PacketFlags.Reliable); + const result = await this.circuit.waitForMessage(Message.MoneyBalanceReply, 10000, (mes: MoneyBalanceReplyMessage): FilterResponse => + { + if (mes.TransactionInfo.DestID.equals(target) && mes.TransactionInfo.Amount === amount) + { + return FilterResponse.Finish; + } + return FilterResponse.NoMatch; + }); + if (!result.MoneyData.TransactionSuccess) + { + throw new Error('Payment failed'); + } + } + avatarKey2Name(uuid: UUID | UUID[]): Promise { return new Promise(async (resolve, reject) => diff --git a/lib/enums/MoneyTransactionType.ts b/lib/enums/MoneyTransactionType.ts new file mode 100644 index 0000000..1b1c2c1 --- /dev/null +++ b/lib/enums/MoneyTransactionType.ts @@ -0,0 +1,53 @@ +export enum MoneyTransactionType +{ + None = 0, + FailSimulatorTimeout = 1, + FailDataserverTimeout = 2, + ObjectClaim = 1000, + LandClaim = 1001, + GroupCreate = 1002, + ObjectPublicClaim = 1003, + GroupJoin = 1004, + TeleportCharge = 1100, + UploadCharge = 1101, + LandAuction = 1102, + ClassifiedCharge = 1103, + ObjectTax = 2000, + LandTax = 2001, + LightTax = 2002, + ParcelDirFee = 2003, + GroupTax = 2004, + ClassifiedRenew = 2005, + GiveInventory = 3000, + ObjectSale = 5000, + Gift = 5001, + LandSale = 5002, + ReferBonus = 5003, + InventorySale = 5004, + RefundPurchase = 5005, + LandPassSale = 5006, + DwellBonus = 5007, + PayObject = 5008, + ObjectPays = 5009, + GroupLandDeed = 6001, + GroupObjectDeed = 6002, + GroupLiability = 6003, + GroupDividend = 6004, + GroupMembershipDues = 6005, + ObjectRelease = 8000, + LandRelease = 8001, + ObjectDelete = 8002, + ObjectPublicDecay = 8003, + ObjectPublicDelete = 8004, + LindenAdjustment = 9000, + LindenGrant = 9001, + LindenPenalty = 9002, + EventFee = 9003, + EventPrize = 9004, + StipendBasic = 10000, + StipendDeveloper = 10001, + StipendAlways = 10002, + StipendDaily = 10003, + StipendRating = 10004, + StipendDelta = 10005 +} diff --git a/lib/enums/TransactionFlags.ts b/lib/enums/TransactionFlags.ts new file mode 100644 index 0000000..9a19407 --- /dev/null +++ b/lib/enums/TransactionFlags.ts @@ -0,0 +1,9 @@ +export enum TransactionFlags +{ + None = 0, + SourceGroup = 1, + DestGroup = 2, + OwnerGroup = 4, + SimultaneousContribution = 8, + ContributionRemoval = 16 +} diff --git a/lib/events/BalanceUpdatedEvent.ts b/lib/events/BalanceUpdatedEvent.ts new file mode 100644 index 0000000..b62a8f0 --- /dev/null +++ b/lib/events/BalanceUpdatedEvent.ts @@ -0,0 +1,17 @@ +import { UUID } from '../classes/UUID'; +import { MoneyTransactionType } from '../enums/MoneyTransactionType'; + +export class BalanceUpdatedEvent +{ + balance: number; + transaction: { + type: MoneyTransactionType, + success: boolean, + from: UUID, + to: UUID, + fromGroup: boolean, + toGroup: boolean + amount: number, + description: string + } +}