UNPKG

@juzi/wechaty

Version:

Wechaty is a RPA SDK for Chatbot Makers.

416 lines 14.6 kB
/** * Wechaty Chatbot SDK - https://github.com/wechaty/wechaty * * @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and * Wechaty Contributors <https://github.com/wechaty>. * * 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. * */ /** * Issue #2245 - New Wechaty User Module (WUM): * `Post` for supporting Moments, Channel, Tweet, Weibo, Facebook feeds, etc. * * @see https://github.com/wechaty/wechaty/issues/2245#issuecomment-914886835 */ import * as PUPPET from '@juzi/wechaty-puppet'; import { log } from '@juzi/wechaty-puppet'; import { validationMixin, wechatifyMixinBase, } from '../user-mixins/mod.js'; import { sayableToPayload, payloadToSayableWechaty, } from '../sayable/mod.js'; import { ContactImpl } from './contact.js'; import { concurrencyExecuter } from 'rx-queue'; class PostBuilder { Impl; payload; /** * Wechaty Sayable List */ sayableList = []; /** * Huan(202201): why use Impl as a parameter? */ static new(Impl) { return new this(Impl); } constructor(Impl) { this.Impl = Impl; this.payload = { sayableList: [], type: PUPPET.types.Post.Unspecified, }; } add(sayable) { this.sayableList.push(sayable); return this; } type(type) { this.payload.type = type; return this; } reply(post) { if (!post.id) { throw new Error('can not link to a post without id: ' + JSON.stringify(post)); } this.payload.parentId = post.payload.id; this.payload.rootId = post.payload.rootId || post.payload.id; return this; } location(location) { this.payload.location = { ...location.payload, }; } visible(contactList) { const contactIds = contactList.map(contact => { if (!ContactImpl.valid(contact)) { log.warn(`expect contact instance but ${contact} is not a contact`); return ''; } return contact.id; }); this.payload.visibleList = contactIds.filter(id => !!id); } async build() { const sayablePayloadListNested = await Promise.all(this.sayableList.map(sayableToPayload)); this.payload.sayableList = sayablePayloadListNested.filter(Boolean); return this.Impl.create(this.payload); } } class PostMixin extends wechatifyMixinBase() { static builder() { return PostBuilder.new(this); } /** * * Create * */ static create(payload) { log.verbose('Post', 'create()'); return new this(payload); } static load(id) { log.verbose('Post', 'static load(%s)', id); /** * Must NOT use `Post` at here * MUST use `this` at here * * because the class will be `cloneClass`-ed */ const post = new this(id); return post; } static async find(filter) { log.verbose('Post', 'find(%s)', JSON.stringify(filter)); if (filter.id) { const post = this.wechaty.Post.load(filter.id); await post.ready(); return post; } const [postList] = await this.findAll(filter, { pageSize: 1 }); if (postList.length > 0) { return postList[0]; } return undefined; } static async findAll(filter, pagination) { log.verbose('Post', 'findAll(%s%s)', JSON.stringify(filter), pagination ? ', ' + JSON.stringify(pagination) : ''); const { nextPageToken, response: postIdList, } = await this.wechaty.puppet.postSearch(filter, pagination); const idToPost = async (id) => this.wechaty.Post.find({ id }) .catch(e => this.wechaty.emitError(e)); /** * we need to use concurrencyExecuter to reduce the parallel number of the requests */ const CONCURRENCY = 17; const postIterator = concurrencyExecuter(CONCURRENCY)(idToPost)(postIdList); const postList = []; for await (const post of postIterator) { if (post) { postList.push(post); } } return [postList, nextPageToken]; } _payload; get payload() { if (!this._payload) { throw new Error('no payload, need to call `ready()` first.'); } return this._payload; } id; /* * @hideconstructor */ constructor(idOrPayload) { super(); log.verbose('Post', 'constructor(%s)', typeof idOrPayload === 'string' ? idOrPayload : JSON.stringify(idOrPayload.id)); if (typeof idOrPayload === 'string') { this.id = idOrPayload; } else { this._payload = idOrPayload; this.id = idOrPayload.id; } } counter() { return { children: 0, descendant: 0, taps: {}, ...(PUPPET.payloads.isPostServer(this.payload) && this.payload.counter), }; } async author() { log.silly('Post', 'author()'); if (PUPPET.payloads.isPostClient(this.payload)) { return this.wechaty.currentUser; } const author = await this.wechaty.Contact.find({ id: this.payload.contactId }); if (!author) { throw new Error('no author for id: ' + this.payload.contactId); } return author; } async root() { log.silly('Post', 'root()'); if (!this.payload.rootId) { return undefined; } const post = this.wechaty.Post.load(this.payload.rootId); await post.ready(); return post; } async parent() { log.silly('Post', 'parent()'); if (!this.payload.parentId) { return undefined; } const post = this.wechaty.Post.load(this.payload.parentId); await post.ready(); return post; } async sync() { log.silly('Post', 'sync()'); if (!this.id) { throw new Error('no post id found'); } this._payload = await this.wechaty.puppet.postPayload(this.id); } async ready() { log.silly('Post', 'ready()'); if (!this.id) { throw new Error('no post id found'); } if (this._payload) { return; } await this.sync(); } async *[Symbol.asyncIterator]() { log.verbose('Post', '[Symbol.asyncIterator]()'); const payloadToSayable = payloadToSayableWechaty(this.wechaty); if (PUPPET.payloads.isPostServer(this.payload)) { for (const sayableId of this.payload.sayableList) { const sayable = await this.getSayableWithId(sayableId); if (sayable) { yield sayable; } } } else { // client for (const sayablePayload of this.payload.sayableList) { const sayable = await payloadToSayable(sayablePayload); if (sayable) { yield sayable; } } } } async getSayableWithIndex(sayableIndex) { log.verbose('Post', 'getSayableWithIndex(%s)', sayableIndex); const payloadToSayable = payloadToSayableWechaty(this.wechaty); if (PUPPET.payloads.isPostServer(this.payload)) { const sayablePayload = await this.wechaty.puppet.postPayloadSayable(this.id, this.payload.sayableList[sayableIndex]); const sayable = await payloadToSayable(sayablePayload); return sayable; } else { const sayablePayload = this.payload.sayableList[sayableIndex]; if (sayablePayload) { const sayable = await payloadToSayable(sayablePayload); return sayable; } else { throw new Error(`post has no sayable with index ${sayableIndex}`); } } } async getSayableWithId(id) { log.verbose('Post', 'getSayableWithId(%s)', id); if (PUPPET.payloads.isPostServer(this.payload)) { const payloadToSayable = payloadToSayableWechaty(this.wechaty); const sayablePayload = await this.wechaty.puppet.postPayloadSayable(this.id, id); const sayable = await payloadToSayable(sayablePayload); return sayable; } else { throw new Error('client post sayable has no Id'); } } async *children(filter = {}) { log.verbose('Post', '*children(%s)', Object.keys(filter).length ? JSON.stringify(filter) : ''); const pagination = { pageSize: 100, }; const parentIdFilter = { ...filter, parentId: this.id, }; let [postList, nextPageToken] = await this.wechaty.Post.findAll(parentIdFilter, pagination); while (true) { yield* postList; postList.length = 0; if (!nextPageToken) { break; } [postList, nextPageToken] = await this.wechaty.Post.findAll(parentIdFilter, { ...pagination, pageToken: nextPageToken, }); } } async *descendants(filter = {}) { log.verbose('Post', '*descendants(%s)', Object.keys(filter).length ? JSON.stringify(filter) : ''); for await (const post of this.children(filter)) { yield post; yield* post.descendants(filter); } } async *likes(filter = {}) { log.verbose('Post', '*likes(%s)', Object.keys(filter).length ? JSON.stringify(filter) : ''); return this.taps({ ...filter, type: PUPPET.types.Tap.Like, }); } async *taps(filter = {}) { log.verbose('Post', '*taps(%s)', Object.keys(filter).length ? JSON.stringify(filter) : ''); const pagination = {}; let [tapList, nextPageToken] = await this.tapFind(filter, pagination); while (true) { yield* tapList; tapList.length = 0; if (!nextPageToken) { break; } [tapList, nextPageToken] = await this.tapFind(filter, { ...pagination, pageToken: nextPageToken }); } } async reply(sayable) { log.verbose('Post', 'reply(%s)', sayable); if (!this.id) { console.error('You can only call `reply()` on received posts, but it seems that you are trying to call reply on a post created from local.'); throw new Error('no post id found'); } const builder = this.wechaty.Post.builder(); if (Array.isArray(sayable)) { sayable.forEach(s => builder.add(s)); } else { builder.add(sayable); } const post = await builder .reply(this) .build(); const postId = await this.wechaty.puppet.postPublish(post.payload); if (postId) { const newPost = this.wechaty.Post.load(postId); await newPost.ready(); return newPost; } } async like(status) { log.verbose('Post', 'like(%s)', typeof status === 'undefined' ? '' : status); if (typeof status === 'undefined') { return this.tap(PUPPET.types.Tap.Like); } else { return this.tap(PUPPET.types.Tap.Like, status); } } async tap(type, status) { log.verbose('Post', 'tap(%s%s)', PUPPET.types.Tap[type], typeof status === 'undefined' ? '' : ', ' + status); if (!this.id) { throw new Error('can not tap for post without id'); } return this.wechaty.puppet.tap(this.id, type, status); } async tapFind(filter, pagination) { log.verbose('Post', 'tapFind()'); if (!this.id) { throw new Error('can not get tapFind for client created post'); } const { nextPageToken, response, } = await this.wechaty.puppet.tapSearch(this.id, filter, pagination); const tapList = []; for (const [type, data] of Object.entries(response)) { for (const [i, contactId] of data.contactId.entries()) { const contact = await this.wechaty.Contact.find({ id: contactId }); if (!contact) { log.warn('Post', 'tapFind() contact not found for id: %s', contactId); continue; } const timestamp = data.timestamp[i]; const date = timestamp ? new Date(timestamp) : new Date(); tapList.push({ contact, date, type: Number(type), }); } } return [tapList, nextPageToken]; } location() { log.verbose('Post', 'location()'); if (!this.payload.location) { log.warn('this post has no location info'); return; } return new this.wechaty.Location(this.payload.location); } async visibleList() { log.verbose('Post', 'visibleList()'); if (!this.payload.visibleList) { return []; } const contactIdList = this.payload.visibleList; const idToContact = async (id) => this.wechaty.Contact.find({ id }).catch(e => this.wechaty.emitError(e)); /** * we need to use concurrencyExecuter to reduce the parallel number of the requests */ const CONCURRENCY = 17; const contactIterator = concurrencyExecuter(CONCURRENCY)(idToContact)(contactIdList); const contactList = []; for await (const contact of contactIterator) { if (contact) { contactList.push(contact); } } return contactList; } } class PostImpl extends validationMixin(PostMixin)() { } export { PostBuilder, PostImpl, }; //# sourceMappingURL=post.js.map