diff --git a/lib/classes/Caps.ts b/lib/classes/Caps.ts index 5c28465..49a6c70 100644 --- a/lib/classes/Caps.ts +++ b/lib/classes/Caps.ts @@ -1,5 +1,6 @@ import * as LLSD from '@caspertech/llsd'; import * as request from 'request'; +import * as url from 'url'; import {Region} from './Region'; import {Subscription} from 'rxjs/internal/Subscription'; import {EventQueueClient} from './EventQueueClient'; @@ -8,6 +9,8 @@ import {ClientEvents} from './ClientEvents'; import {Agent} from './Agent'; import {Subject} from 'rxjs/internal/Subject'; import {HTTPAssets} from '..'; +import { ParsedUrlQuery } from 'querystring'; +import { ICapResponse } from './interfaces/ICapResponse'; export class Caps { @@ -127,9 +130,9 @@ export class Caps req.push('ViewerStartAuction'); req.push('ViewerStats'); this.active = true; - this.request(seedURL, LLSD.LLSD.formatXML(req), 'application/llsd+xml').then((body: string) => + this.request(seedURL, LLSD.LLSD.formatXML(req), 'application/llsd+xml').then((resp: ICapResponse) => { - this.capabilities = LLSD.LLSD.parseXML(body); + this.capabilities = LLSD.LLSD.parseXML(resp.body); this.gotSeedCap = true; this.onGotSeedCap.next(); if (this.capabilities['EventQueueGet']) @@ -151,9 +154,9 @@ export class Caps { return new Promise((resolve, reject) => { - this.getCapability('ViewerAsset').then((url) => + this.getCapability('ViewerAsset').then((capURL) => { - const assetURL = url + '/?' + type + '_id=' + uuid.toString(); + const assetURL = capURL + '/?' + type + '_id=' + uuid.toString(); request({ 'uri': assetURL, 'rejectUnauthorized': false, @@ -174,16 +177,16 @@ export class Caps }); } - request(url: string, data: string | Buffer, contentType: string): Promise + request(capURL: string, data: string | Buffer, contentType: string): Promise { - return new Promise((resolve, reject) => + return new Promise((resolve, reject) => { request({ 'headers': { 'Content-Length': data.length, 'Content-Type': contentType }, - 'uri': url, + 'uri': capURL, 'body': data, 'rejectUnauthorized': false, 'method': 'POST' @@ -195,18 +198,18 @@ export class Caps } else { - resolve(body); + resolve({status: res.statusCode, body: body}); } }); }); } - requestGet(url: string): Promise + requestGet(requestURL: string): Promise { - return new Promise((resolve, reject) => + return new Promise((resolve, reject) => { request({ - 'uri': url, + 'uri': requestURL, 'rejectUnauthorized': false, 'method': 'GET' }, (err, res, body) => @@ -217,7 +220,7 @@ export class Caps } else { - resolve(body); + resolve({status: res.statusCode, body: body}); } }); }); @@ -265,13 +268,31 @@ export class Caps }); } - capsRequestUpload(url: string, data: Buffer): Promise + capsRequestUpload(capURL: string, data: Buffer): Promise { return new Promise((resolve, reject) => { - this.request(url, data, 'application/octet-stream').then((body: string) => + this.request(capURL, data, 'application/octet-stream').then((resp: ICapResponse) => { - resolve(LLSD.LLSD.parseXML(body)); + try + { + resolve(LLSD.LLSD.parseXML(resp.body)); + } + catch (err) + { + if (resp.status === 201) + { + resolve({}); + } + else if (resp.status === 403) + { + reject(new Error('Access Denied')); + } + else + { + reject(err); + } + } }).catch((err) => { console.error(err); @@ -284,20 +305,29 @@ export class Caps { return new Promise((resolve, reject) => { - this.getCapability(capability).then((url) => + this.getCapability(capability).then((capURL) => { - this.requestGet(url).then((body: string) => + this.requestGet(capURL).then((resp: ICapResponse) => { let result: any = null; try { - result = LLSD.LLSD.parseXML(body); + result = LLSD.LLSD.parseXML(resp.body); } catch (err) { - console.error('Error parsing LLSD'); - console.error(body); - reject(err); + if (resp.status === 201) + { + resolve({}); + } + else if (resp.status === 403) + { + reject(new Error('Access Denied')); + } + else + { + reject(err); + } } resolve(result); }).catch((err) => @@ -346,22 +376,33 @@ export class Caps }); } - capsPerformXMLRequest(url: string, data: any): Promise + capsPerformXMLRequest(capURL: string, data: any): Promise { return new Promise(async (resolve, reject) => { const xml = LLSD.LLSD.formatXML(data); - this.request(url, xml, 'application/llsd+xml').then((body: string) => + this.request(capURL, xml, 'application/llsd+xml').then((resp: ICapResponse) => { let result: any = null; try { - result = LLSD.LLSD.parseXML(body); + result = LLSD.LLSD.parseXML(resp.body); resolve(result); } catch (err) { - reject(err); + if (resp.status === 201) + { + resolve({}); + } + else if (resp.status === 403) + { + reject(new Error('Access Denied')); + } + else + { + reject(err); + } } }).catch((err) => { @@ -371,23 +412,44 @@ export class Caps }); } - async capsRequestXML(capability: string, data: any, debug = false): Promise + async capsRequestXML(capability: string | [string, {[key: string]: string}], data: any, debug = false): Promise { if (debug) { console.log(data); } - await this.waitForCapTimeout(capability); + let capName = ''; + let queryParams: {[key: string]: string} = {}; + if (typeof capability === 'string') + { + capName = capability; + } + else + { + capName = capability[0]; + queryParams = capability[1]; + } - const url = await this.getCapability(capability); + await this.waitForCapTimeout(capName); + + let capURL = await this.getCapability(capName); + if (Object.keys(queryParams).length > 0) + { + const parsedURL = url.parse(capURL, true); + for (const key of Object.keys(queryParams)) + { + parsedURL.query[key] = queryParams[key]; + } + capURL = url.format(parsedURL); + } try { - return await this.capsPerformXMLRequest(url, data); + return await this.capsPerformXMLRequest(capURL, data); } catch (error) { - console.log('Error with cap ' + capability); + console.log('Error with cap ' + capName); console.log(error); throw error; } diff --git a/lib/classes/commands/GroupCommands.ts b/lib/classes/commands/GroupCommands.ts index 10d2bf6..a21032f 100644 --- a/lib/classes/commands/GroupCommands.ts +++ b/lib/classes/commands/GroupCommands.ts @@ -1,24 +1,23 @@ -import {CommandsBase} from './CommandsBase'; -import {UUID} from '../UUID'; -import {InstantMessageDialog} from '../../enums/InstantMessageDialog'; -import {Utils} from '../Utils'; -import {PacketFlags} from '../../enums/PacketFlags'; -import {ImprovedInstantMessageMessage} from '../messages/ImprovedInstantMessage'; -import {Vector3} from '../Vector3'; -import {InviteGroupRequestMessage} from '../messages/InviteGroupRequest'; -import {GroupRole} from '../GroupRole'; -import {GroupRoleDataRequestMessage} from '../messages/GroupRoleDataRequest'; -import {Message} from '../../enums/Message'; -import {Packet} from '../Packet'; -import {GroupRoleDataReplyMessage} from '../messages/GroupRoleDataReply'; -import {GroupMember} from '../GroupMember'; -import {FilterResponse} from '../../enums/FilterResponse'; +import { CommandsBase } from './CommandsBase'; +import { UUID } from '../UUID'; +import { InstantMessageDialog } from '../../enums/InstantMessageDialog'; +import { Utils } from '../Utils'; +import { PacketFlags } from '../../enums/PacketFlags'; +import { ImprovedInstantMessageMessage } from '../messages/ImprovedInstantMessage'; +import { Vector3 } from '../Vector3'; +import { InviteGroupRequestMessage } from '../messages/InviteGroupRequest'; +import { GroupRole } from '../GroupRole'; +import { GroupRoleDataRequestMessage } from '../messages/GroupRoleDataRequest'; +import { Message } from '../../enums/Message'; +import { GroupRoleDataReplyMessage } from '../messages/GroupRoleDataReply'; +import { GroupMember } from '../GroupMember'; +import { FilterResponse } from '../../enums/FilterResponse'; import * as LLSD from '@caspertech/llsd'; -import {GroupInviteEvent} from '../..'; -import {EjectGroupMemberRequestMessage} from '../messages/EjectGroupMemberRequest'; -import {GroupProfileRequestMessage} from '../messages/GroupProfileRequest'; -import {GroupProfileReplyMessage} from '../messages/GroupProfileReply'; -import {GroupProfileReplyEvent} from '../..'; +import { GroupInviteEvent, GroupProfileReplyEvent } from '../..'; +import { EjectGroupMemberRequestMessage } from '../messages/EjectGroupMemberRequest'; +import { GroupProfileRequestMessage } from '../messages/GroupProfileRequest'; +import { GroupProfileReplyMessage } from '../messages/GroupProfileReply'; +import { GroupBanAction } from '../../enums/GroupBanAction'; export class GroupCommands extends CommandsBase { @@ -176,6 +175,50 @@ export class GroupCommands extends CommandsBase return await circuit.waitForAck(sequenceNo, 10000); } + async unbanMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[]) + { + this.banMembers(groupID, avatars, GroupBanAction.Unban); + } + + async banMembers(groupID: UUID | string, avatars: UUID | string | string[] | UUID[], groupAction: GroupBanAction = GroupBanAction.Ban) + { + const listOfIDs: string[] = []; + + if (Array.isArray(avatars)) + { + for (const av of avatars) + { + if (typeof av === 'string') + { + listOfIDs.push(av); + } + else + { + listOfIDs.push(av.toString()); + } + } + } + else if (typeof avatars === 'string') + { + listOfIDs.push(avatars); + } + else + { + listOfIDs.push(avatars.toString()); + } + + const requestData: any = { + 'ban_action': groupAction, + 'ban_ids': [] + }; + for (const id of listOfIDs) + { + requestData.ban_ids.push(new LLSD.UUID(id)); + } + + await this.currentRegion.caps.capsRequestXML(['GroupAPIv1', {'group_id': groupID.toString()}], requestData); + } + getMemberList(groupID: UUID | string): Promise { return new Promise((resolve, reject) => diff --git a/lib/classes/interfaces/ICapResponse.ts b/lib/classes/interfaces/ICapResponse.ts new file mode 100644 index 0000000..c31775e --- /dev/null +++ b/lib/classes/interfaces/ICapResponse.ts @@ -0,0 +1,5 @@ +export interface ICapResponse +{ + status: number; + body: string; +} diff --git a/lib/enums/GroupBanAction.ts b/lib/enums/GroupBanAction.ts new file mode 100644 index 0000000..ab9b31e --- /dev/null +++ b/lib/enums/GroupBanAction.ts @@ -0,0 +1,5 @@ +export enum GroupBanAction +{ + Ban = 1, + Unban = 2 +} diff --git a/package-lock.json b/package-lock.json index 60ba810..2d729a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.5.8", + "version": "0.5.10", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2696ed7..720ce7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@caspertech/node-metaverse", - "version": "0.5.9", + "version": "0.5.10", "description": "A node.js interface for Second Life.", "main": "dist/index.js", "types": "dist/index.d.ts",