diff --git a/src/puppet-mock/index.ts b/src/puppet-mock/index.ts new file mode 100644 index 000000000..069531209 --- /dev/null +++ b/src/puppet-mock/index.ts @@ -0,0 +1,25 @@ +/** + * Wechaty - https://github.com/chatie/wechaty + * + * @copyright 2016-2018 Huan LI + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import { PuppetMock } from './puppet-mock' + +export { + PuppetMock, +} + +export default PuppetMock diff --git a/src/puppet-mock/mock-contact.ts b/src/puppet-mock/mock-contact.ts new file mode 100644 index 000000000..6d2704eea --- /dev/null +++ b/src/puppet-mock/mock-contact.ts @@ -0,0 +1,124 @@ +/** + * Wechaty - https://github.com/chatie/wechaty + * + * @copyright 2016-2018 Huan LI + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @ignore + */ +import { + // config, + Sayable, + log, +} from '../config' + +import { + Contact, + Gender, +} from '../puppet/contact' + +// import PuppetMock from './puppet-mock' +import MockMessage from './mock-message' + +export class MockContact extends Contact implements Sayable { + + constructor( + public readonly id: string, + ) { + super(id) + log.silly('MockContact', `constructor(${id})`) + } + + public toString(): string { + return `MockContact<${this.id}>` + } + + public async say(text: string): Promise + public async say(message: MockMessage): Promise + public async say(textOrMessage: string | MockMessage): Promise { + log.verbose('MockContact', 'say(%s)', textOrMessage) + } + + public name() { + return 'MockName' + } + + public alias() : string | null + public alias(newAlias: string): Promise + public alias(empty: null) : Promise + + public alias(newAlias?: string|null): Promise | string | null { + if (newAlias === undefined) { + return 'MockAlias' + } + // pretend modified... + return Promise.resolve() + } + + public stranger(): boolean | null { + return null + } + + public official(): boolean { + return false + } + public personal(): boolean { + return !this.official() + } + + public star(): boolean | null { + return null + } + + public gender(): Gender { + return Gender.Unknown + } + + public province() { + return 'Guangdong' + } + + public city() { + return 'Shenzhen' + } + + public async avatar(): Promise { + log.verbose('MockContact', 'avatar()') + + throw new Error('To Be Mocked...') + } + + public isReady(): boolean { + return true + } + + public async refresh(): Promise { + return this + } + + public async ready(): Promise { + return this + } + + public self(): boolean { + return false + } + + public weixin(): string | null { + return null + } + +} + +export default MockContact diff --git a/src/puppet-mock/mock-friend-request.ts b/src/puppet-mock/mock-friend-request.ts new file mode 100644 index 000000000..4c49a477b --- /dev/null +++ b/src/puppet-mock/mock-friend-request.ts @@ -0,0 +1,59 @@ +/** + * Wechaty - https://github.com/chatie/wechaty + * + * @copyright 2016-2018 Huan LI + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * @ignore + */ + +import { + // config, + log, +} from '../config' +import FriendRequest from '../puppet/friend-request' + +import MockContact from './mock-contact' + +export class MockFriendRequest extends FriendRequest { + + public info: any + + private ticket: string + + constructor() { + log.verbose('MockFriendRequest', 'constructor()') + super() + } + + public receive(info: any): void { + log.verbose('MockFriendRequest', 'receive(%s)', info) + } + + public confirm(contact: MockContact): void { + log.verbose('MockFriendRequest', 'confirm(%s)', contact) + } + + public async send(contact: MockContact, hello = 'Hi'): Promise { + log.verbose('MockFriendRequest', 'send(%s)', contact) + await this.puppet.friendRequestSend(contact, hello) + } + + public async accept(): Promise { + log.verbose('FriendRequest', 'accept() %s', this.contact) + await this.puppet.friendRequestAccept(this.contact, this.ticket) + } + +} + +export default MockFriendRequest diff --git a/src/puppet-mock/mock-message.ts b/src/puppet-mock/mock-message.ts new file mode 100644 index 000000000..bb52b241d --- /dev/null +++ b/src/puppet-mock/mock-message.ts @@ -0,0 +1,197 @@ +/** + * Wechaty - https://github.com/chatie/wechaty + * + * @copyright 2016-2018 Huan LI + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * @ignore + */ +import * as path from 'path' +import { + Readable, +} from 'stream' + +import { + log, +} from '../config' +import Message from '../puppet/message' + +import MockContact from './mock-contact' +import WebRoom from './mock-room' + +import { + MsgRawObj, + MsgType, + AppMsgType, +} from '../puppet-web/schema' + +export type ParsedPath = Partial + +export class MockMessage extends Message { + public readonly id: string + + constructor( + fileOrObj?: string | MsgRawObj, + ) { + super() + log.silly('MockMessage', 'constructor()') + } + + public toString() { + return `MockMessage` + } + + public from(contact: MockContact): void + public from(id: string): void + public from(): MockContact + public from(contact?: MockContact|string): MockContact|void { + if (contact) { + return + } + + const loadedContact = MockContact.load('mockid') as MockContact + loadedContact.puppet = this.puppet + + return loadedContact + } + + public room(room: WebRoom): void + public room(id: string): void + public room(): WebRoom|null + + public room(room?: WebRoom|string): WebRoom|null|void { + if (room) { + return + } + return null + } + + public text(): string + public text(content: string): void + public text(text?: string): string | void { + if (text) { + return + } + return 'mock text' + } + + public async say(textOrMessage: string | MockMessage, replyTo?: MockContact|MockContact[]): Promise { + log.verbose('MockMessage', 'say(%s, %s)', textOrMessage, replyTo) + const m = new MockMessage() + await this.puppet.send(m) + } + + public type(): MsgType { + return MsgType.TEXT + } + + public self(): boolean { + const userId = this.puppet.user!.id + const fromId = this.from().id + + if (!userId || !fromId) { + throw new Error('no user or no from') + } + + return fromId === userId + } + + public mentioned(): MockContact[] { + return [] + } + + public async ready(): Promise { + log.silly('MockMessage', 'ready()') + await this.readyMedia() + return this + } + + public async readyMedia(): Promise { + log.silly('MockMessage', 'readyMedia()') + return this + } + + public static async find(query) { + return Promise.resolve(new MockMessage({MsgId: '-1'})) + } + + public static async findAll(query) { + return Promise.resolve([ + new MockMessage ({MsgId: '-2'}), + new MockMessage ({MsgId: '-3'}), + ]) + } + + public to(contact: MockContact): void + public to(id: string): void + public to(): MockContact | null // if to is not set, then room must had set + + public to(contact?: MockContact | string): MockContact | WebRoom | null | void { + if (contact) { + return + } + + const to = MockContact.load('mockid') as MockContact + to.puppet = this.puppet + + return to + } + + public async readyStream(): Promise { + log.verbose('MockMessage', 'readyStream()') + throw new Error('to be mocked') + } + + public filename(): string { + return 'mocked_filename.txt' + } + + public ext(): string { + return '.mocked_ext' + } + + public mimeType(): string | null { + return 'text/plain' + } + + public async forward(to: WebRoom|MockContact): Promise { + /** + * 1. Text message + */ + if (this.type() === MsgType.TEXT) { + await to.say(this.text()) + return + } + + /** + * 2. Media message + */ + try { + await this.puppet.forward(this, to) + } catch (e) { + log.error('MockMessage', 'forward(%s) exception: %s', to, e) + throw e + } + } + + public typeSub(): MsgType { + return MsgType.TEXT + } + + public typeApp(): AppMsgType { + return AppMsgType.TEXT + } + +} + +export default MockMessage diff --git a/src/puppet-mock/mock-room.ts b/src/puppet-mock/mock-room.ts new file mode 100644 index 000000000..ebf2a3618 --- /dev/null +++ b/src/puppet-mock/mock-room.ts @@ -0,0 +1,152 @@ +/** + * Wechaty - https://github.com/chatie/wechaty + * + * @copyright 2016-2018 Huan LI + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @ignore + */ +import { + log, +} from '../config' +import { + Room, + RoomMemberQueryFilter, +} from '../puppet/' + +import MockMessage from './mock-message' +import MockContact from './mock-contact' + +export class MockRoom extends Room { + + constructor( + public id: string, + ) { + super(id) + log.silly('MockRoom', `constructor(${id})`) + } + + public isReady(): boolean { + return true + } + public async ready(): Promise { + return this + } + + public say(message: MockMessage) : Promise + public say(text: string) : Promise + public say(text: string, replyTo: MockContact) : Promise + public say(text: string, replyTo: MockContact[]) : Promise + public say(text: never, ...args: never[]) : Promise + + public async say(textOrMessage: string | MockMessage, replyTo?: MockContact|MockContact[]): Promise { + log.verbose('MockRoom', 'say(%s, %s)', + textOrMessage, + Array.isArray(replyTo) + ? replyTo.map(c => c.name()).join(', ') + : replyTo ? replyTo.name() : '', + ) + + let m + m = new MockMessage() + m.puppet = this.puppet + m.room(this) + + await this.puppet.send(m) + } + + public async add(contact: MockContact): Promise { + log.verbose('MockRoom', 'add(%s)', contact) + await this.puppet.roomAdd(this, contact) + } + + public async del(contact: MockContact): Promise { + log.verbose('MockRoom', 'del(%s)', contact.name()) + await this.puppet.roomDel(this, contact) + } + + public async quit(): Promise { + return + } + + public topic() : string + public async topic(newTopic: string): Promise + + public topic(newTopic?: string): string | Promise { + log.verbose('MockRoom', 'topic(%s)', newTopic ? newTopic : '') + + if (typeof newTopic === 'undefined') { + return 'mock topic' + } + + this.puppet.roomTopic(this, newTopic) + .catch(e => { + log.warn('MockRoom', 'topic(newTopic=%s) exception: %s', + newTopic, e && e.message || e, + ) + }) + + return Promise.resolve() + } + + public alias(contact: MockContact): string | null { + return this.roomAlias(contact) + } + + public roomAlias(contact: MockContact): string | null { + return 'mock room alias' + } + + public has(contact: MockContact): boolean { + return false + } + + public memberAll(filter: RoomMemberQueryFilter) : MockContact[] + public memberAll(name: string) : MockContact[] + + public memberAll(queryArg: RoomMemberQueryFilter | string): MockContact[] { + return [] + } + + public member(name: string) : MockContact | null + public member(filter: RoomMemberQueryFilter): MockContact | null + + public member(queryArg: RoomMemberQueryFilter | string): MockContact | null { + log.verbose('MockRoom', 'member(%s)', JSON.stringify(queryArg)) + return null + } + + public memberList(): MockContact[] { + log.verbose('MockRoom', 'memberList') + return [] + } + + public static async create(contactList: MockContact[], topic?: string): Promise { + log.verbose('MockRoom', 'create(%s, %s)', contactList.join(','), topic) + const room = await this.puppet.roomCreate(contactList, topic) + return room + } + + public async refresh(): Promise { + return + } + + public owner(): MockContact | null { + log.info('MockRoom', 'owner()') + return null + } + +} + +export default MockRoom diff --git a/src/puppet-mock/puppet-mock.ts b/src/puppet-mock/puppet-mock.ts new file mode 100644 index 000000000..ab7519c58 --- /dev/null +++ b/src/puppet-mock/puppet-mock.ts @@ -0,0 +1,187 @@ +/** + * Wechaty - https://github.com/chatie/wechaty + * + * @copyright 2016-2018 Huan LI + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + ContactQueryFilter, + + Puppet, + PuppetOptions, + + RoomQueryFilter, +} from '../puppet/' + +import { + log, +} from '../config' +import { +} from '../message' + +import MockContact from './mock-contact' +import MockMessage from './mock-message' +import MockRoom from './mock-room' + +export type PuppetFoodType = 'scan' | 'ding' +export type ScanFoodType = 'scan' | 'login' | 'logout' + +export class PuppetMock extends Puppet { + + constructor( + public options: PuppetOptions, + ) { + super(options) + } + + public toString() { + return `PuppetMock<${this.options.profile.name}>` + } + + public ding(data?: any): Promise { + return data + } + + public async start(): Promise { + log.verbose('PuppetMock', `start() with ${this.options.profile}`) + this.state.on('pending') + // await some tasks... + this.state.on(true) + } + + public async stop(): Promise { + log.verbose('PuppetMock', 'quit()') + + if (this.state.off()) { + log.warn('PuppetMock', 'quit() is called on a OFF puppet. await ready(off) and return.') + await this.state.ready('off') + return + } + this.state.off('pending') + // await some tasks... + this.state.off(true) + } + + public logonoff(): boolean { + return !!(this.user) + } + + public self(): MockContact { + log.verbose('PuppetMock', 'self()') + + if (this.user) { + return this.user as MockContact + } + throw new Error('PuppetMock.self() no this.user') + } + + public async forward(message: MockMessage, sendTo: MockContact | MockRoom): Promise { + log.silly('PuppetMock', 'forward() to: %s, message: %s)', + sendTo, message.filename(), + // patchData.ToUserName, + // patchData.MMActualContent, + ) + } + + public async send(message: MockMessage): Promise { + // + } + + public async say(text: string): Promise { + if (!this.logonoff()) { + throw new Error('can not say before login') + } + + if (!text) { + log.warn('PuppetMock', 'say(%s) can not say nothing', text) + return + } + + if (!this.user) { + log.warn('PuppetMock', 'say(%s) can not say because no user', text) + this.emit('error', new Error('no this.user for PuppetMock')) + return + } + + return await this.user.say(text) + } + + public async logout(): Promise { + log.verbose('PuppetMock', 'logout()') + this.user = undefined + } + + public contactAlias(contact: MockContact) : Promise + public contactAlias(contact: MockContact, alias: string | null): Promise + + public async contactAlias(contact: MockContact, alias?: string|null): Promise { + if (typeof alias === 'undefined') { + return 'mock alias' + } + return + } + + public async contactFindAll(query: ContactQueryFilter): Promise { + return [] + } + + public async roomFindAll( + query: RoomQueryFilter = { topic: /.*/ }, + ): Promise { + return [] + } + + public async roomDel( + room: MockRoom, + contact: MockContact, + ): Promise { + // + } + + public async roomAdd( + room: MockRoom, + contact: MockContact, + ): Promise { + // + } + + public async roomTopic(room: MockRoom, topic?: string): Promise { + if (typeof topic === 'undefined') { + return 'mock room topic' + } + return + } + + public async roomCreate(contactList: MockContact[], topic: string): Promise { + if (!contactList || ! contactList.map) { + throw new Error('contactList not found') + } + const r = MockRoom.load('mock room id') as MockRoom + r.puppet = this + return r + } + + public async friendRequestSend(contact: MockContact, hello: string): Promise { + // + } + + public async friendRequestAccept(contact: MockContact, ticket: string): Promise { + // + } + +} + +export default PuppetMock