-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#4 send image/video #337
#4 send image/video #337
Conversation
1 similar comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @mukaiu , Great job, the PR looks great!
I had made some comments for you, mainly is talking about where is the right place to put those functions.
Please follow my reviews, let's move on and make it ready today. :)
P.S. I'm just thinking about the API of Wechaty of sending/replying files(PDF/Video/Photo/Emotions), it seems that we use stream for doing this is not the best choice.
For example, when user want to send/reply a file, the simpliest way is just use the full file path, because that's straight forward, and no need to specify the MIME type and filename anymore(which is required when we using stream).
So, maybe we could consider to use file path name in the API, at lease we support both types(file path name & stream).
Then we can design the API like this:
contact.say('/tmp/file.jpg')
room.say('/tmp/document.pdf')
message.reply('/tmp/haha.gif'))
example/ding-img-bot.ts
Outdated
) | ||
|
||
if (/^(ding|ping|bing)$/i.test(m.content()) && !m.self()) { | ||
bot.uploadMedia(fs.createReadStream(__dirname '/../image/BotQrcode.png'), 'BotQrcode.png', m.from().id).then(mediaId => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think we can add this new feature(reply the QRCode image) to the classic ding-dong-bot example, instead of creating this new one, because send image should be a basic function for all users.
- In Wechaty, we prefer to use
await
instead ofthen
.
src/message.ts
Outdated
@@ -125,6 125,7 @@ export type MsgObj = { | |||
status: string, | |||
digest: string, | |||
date: string, | |||
mediaId: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the current design of Message, there's two Message Classes: Message
and MediaMessage
.
- the Message is the basic one, mainly for dealing the plain text;
- the MediaMessage is extended from Message and will focus on dealing with all the media type messages(attachment files).
So I think we should better to move all the media logic to the MediaMessage instead of putting them directly into Message, which is more clear for the code.
src/message.ts
Outdated
@@ -370,6 372,17 @@ export class Message implements Sayable { | |||
return this.obj.content | |||
} | |||
|
|||
public mediaId(): string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move mediaId()
to MediaMessage
class.
src/puppet-web/wechaty-bro.js
Outdated
var m = chatFactory.createMessage({ | ||
ToUserName: ToUserName | ||
, MediaId: MediaId | ||
, MsgType: confFactory.MSGTYPE_IMAGE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we support other MSGTYPE(Such as PDF, VIDEO, etc...) as well? Because the function name is sendMedia
. :)
src/puppet.ts
Outdated
@@ -30,6 30,8 @@ export abstract class Puppet extends EventEmitter implements Sayable { | |||
|
|||
public abstract self(): Contact | |||
|
|||
public abstract getBaseRequest(): Promise<any> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Abstract Puppet class is designed for abstract all the APIs at the high level for all puppets, such as PuppetWeb(which we are mainly developing on), PuppetAndroid(which we are hoping to develop), etc.
So we should only put the common methods into this abstract class.
The getBaseRequest()
is highly related to the PuppetWeb. The other puppet, let's say PuppetAndroid, is more likely not need this method.
So we should better move getBaseRequest()
to PuppetWeb
and capsulate it inside.
src/puppet.ts
Outdated
@@ -30,6 30,8 @@ export abstract class Puppet extends EventEmitter implements Sayable { | |||
|
|||
public abstract self(): Contact | |||
|
|||
public abstract getBaseRequest(): Promise<any> | |||
public abstract sendMedia(message: Message): Promise<void> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use send()
to both Message & Media Message?
I purpose this form:
public abstract send(message: Message | MediaMessage)
src/wechaty.ts
Outdated
@@ -417,6 420,106 @@ export class Wechaty extends EventEmitter implements Sayable { | |||
return this.puppet.self() | |||
} | |||
|
|||
public async uploadMedia(stream: NodeJS.ReadableStream, filename: string, toUserName: string): Promise<string> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Wechaty Class is the highest abstract class for the FrameWork, it should not include any implement details, such as how to upload a media file.
We should move uploadMedia()
from here to PuppetWeb instead, then we can call PuppetWeb.uploadMedia() to do the job.
src/wechaty.ts
Outdated
return mediaId as string | ||
} | ||
|
||
public async sendMedia(message: Message): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned before, I'd very like to use the same send()
method to send both Message & MediaMessage.
Update: I realized that Do you have any suggestion for using the file path name directly for this scanerio? |
How about design a new type like export type Content = {
Type?: 'file' | 'pic' | 'voice' | ...
Content: ''/tmp/file.jpg''
}
contact.say(para: Content | string) Then, when we want to send voice or any other type of the content, just add Even, we can add |
I had a problem with this feature and created a ticket here: |
@dcsan |
@lijiarui Yes, a new type is needed with this scenario. What I'm thinking about is to create a new Class for doing this instead of an Interface. I.e. @mukaiu @JasLin I want to hear from you too because this API will be needed after this PR is merged. Thanks! |
I think,
|
Yap, that's a better idea to solve this problem! :D
👍 @mukaiu |
merge chatie
@zixia but the logic of room.say is uncertain. |
Is there a docker image with this version of Wechaty? It would make testing much easier... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @mukaiu ,
The new PR with enhanced media type upload is fantastic!
I just made some minor options in the comment; I think after finishing this we could be able to prepare the merging.
One more thing: about the circle dependency of Message
and MediaMessage
, I think we should consider solving this problem by merge message-media.ts to message.ts.
src/contact.ts
Outdated
@@ -578,20 581,23 @@ export class Contact implements Sayable { | |||
* await contact.say('welcome to wechaty!') | |||
* ``` | |||
*/ | |||
public async say(content: string): Promise<void> { | |||
log.verbose('Contact', 'say(%s)', content) | |||
public async say(content: string|MediaMessage): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Typescript override syntax here:
public async say(text: string)
public async say(mediaMessage: MediaMessage)
public async say(textOrMedia: string | MessageMessage)
src/contact.ts
Outdated
if (typeof content === 'string') { | ||
m = new Message() | ||
m.content(content) | ||
} else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} elseif (textOrMedia instanceof MediaMessage) {
...
} else {
throw new Error('not support args')
}
```ts
src/message-media.ts
Outdated
|
||
constructor(rawObj) { | ||
super(rawObj) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Typescript override syntax here:
constructor(rawObj: object)
constructor(filePath: string)
constructor(rawObjOrFilePath: object | string) {
src/message-media.ts
Outdated
if (typeof rawObj === 'string') { | ||
super() | ||
this.filepath = rawObj | ||
} else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if (rawObj instanceof object) {
...
} else {
throw new Error('not supported construct param')
}
src/message-media.ts
Outdated
} from './message' | ||
import { UtilLib } from './util-lib' | ||
import { PuppetWeb } from './puppet-web/puppet-web' | ||
import { Bridge } from './puppet-web/bridge' | ||
|
||
export class MediaMessage extends Message { | ||
private bridge: Bridge | ||
private filepath: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we only keep the ReadableStream in MediaMessage?
I want to get rid of the absolute file path in the class because it's not necessary.
private fileStream: ReadableStream
private fileName: string // 'music'
private fileExt: string // 'mp3'
src/message.ts
Outdated
@@ -492,31 493,44 @@ export class Message implements Sayable { | |||
}) | |||
} | |||
|
|||
public say(content: string, replyTo?: Contact|Contact[]): Promise<any> { | |||
public say(content: string | MediaMessage, replyTo?: Contact|Contact[]): Promise<any> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use Override to declare say()
at here.
say(textOrMedia: string | MediaMessage)
src/message.ts
Outdated
} | ||
m.content(mentionList ' ' content) | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if (textOrMedia instnaceof MediaMessage) {
FileMd5: md5, | ||
FromUserName: this.self().id, | ||
ToUserName: toUserName, | ||
UploadType: 2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you change this magic number to a enum
value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UploadType optional value 1 or 2, use unknown
src/puppet-web/puppet-web.ts
Outdated
ToUserName: toUserName, | ||
UploadType: 2, | ||
ClientMediaId: new Date, | ||
MediaType: 4, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you change this magic number to a enum
value?
src/puppet-web/puppet-web.ts
Outdated
let ext = path.extname(filepath) | ||
|
||
let contentType = UtilLib.mime(ext) | ||
let mediatype |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you define a new type for MediaType like:
type MediaTpye = 'pic' | 'video' | 'doc'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here maybe 'file' is better than 'doc'
@zixia what do you think about it?
@dcsan docker pull mukaiu/wechaty |
# Conflicts: # package.json
# Conflicts: # package.json
Hi @mukaiu, Thank you so much for this Pull Request! Now I feel it's good enough for supporting attachment messages in Wechaty, and I believe it will help many developers who want to use this functionality! I have no more reviews for your PR, and I'll approve the changes. However, We all know it's not perfect. I'm afraid there's some part that we missed to review. Could you talk with other Reviewers, and let some of them to Approve Changes? Let's do merge the PR after at least other two contributors approved it. Cheers! |
apologies for delay, I have been trying to check this out but haven't got a good docker usage development workflow, it takes too long to make a code change and test out, so I haven't been able to test it yet. FWIW my flow is to build a container with mukai/wechaty fork as below is there a faster dev workflow you use? I guess I could just run off a local build of wechaty, but I had problems with googlechrome and other deps, so aiming to use docker. perhaps I can just any other suggestions for fast workflow appreciated.
|
src/puppet-web/puppet-web.ts
Outdated
let ext = path.extname(filepath) | ||
|
||
let contentType = UtilLib.mime(ext) | ||
let mediatype |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here maybe 'file' is better than 'doc'
@zixia what do you think about it?
@dcsan
After modifying the code, restart container
Use the following command to display the container log
|
@mukaiu thanks i didn't know about docker volumes. perhaps if i combine that with hot reloading it will be much less painful to develop this way. So i went to try and test this. in a wechaty checkout
but now it seems the Since the API has changed, looking at your code above
I modified
but gives TS compile error:
I also looked in the /test directory for some examples but can't see anything: grep -r Media test/*
test/README.md: # Subtest: Media download smoking test
test/README.md: ok 2 - Media download smoking test # time=207.54ms
➜ wechaty git:(master) ✗ It would be great if there was some example code of how to use /test this functionality.
which is since Message.say signature is:
what is the interface to send a MediaMessage? since if (/^(img)$/i.test(m.content()) ) {
let img = new MediaMessage('/image/wechaty-icon.png')
img.say("")
log.info('Bot', 'REPLY: dong')
} but gives error:
|
UPDATE I tried just doing a clone of @mukaiu 's fork, and that seems to work very well in sending an image message. So at this point I'm not sure about this PR but the forked repo is functional. https://github.com/mukaiu/wechaty/blob/master/example/ding-dong-bot.ts#L75 So, I guess my method for reviewing this PR isn't working. this message signature extensions did not come down for me public async say(textOrMedia: string | MediaMessage): Promise<void> { even though it shows up in the PR
update git pull origin pull/337/head now seems to get the latest from this PR
So anyway, mukais fork works fine. These problems are more about all the tooling/PRs/workflow on this project. I'll approve the PR as it doesn't introduce any new problems, and then do a clean checkout of the project where I can actually test this feature. The other suggestion could be to make an unstable / |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't get the checkout of the PR to work but the original fork is working very well. I'd like to get this into master (or an edge
branch) asap so we can actually use it.
if you did make an "edge" branch it would make it a lot easier for me to make a fork based off of that and start modifying / sending PRs. Making a PR based on someone else's PR on a repo that you don't own is just too much complication. |
Thanks @dcsan @lijiarui for approving this PR, it really helps. @dcsan I like your idea of creating a edge branch to automaticaly merge the Pull Requests(which passed CI) for testers. Do you know any web service or tools to do this kind of job(merge pull requests to a branch) automaticaly? That will be very efficiency. |
I'm not sure, but I dont think we have that many PRs now? |
Support Message Type of Image/Video for #4
Add
wechaty.uploadMedia(stream: NodeJS.ReadableStream, filename: string, toUserName: string)
wechaty.sendMedia(message: Message)