字幕列表 影片播放 列印英文字幕 (ding) - Hello. In this video, I am going to make a bot and I am going to make an image bot and I'm very excited about this project because it pulls together a whole lot of different things. I am going to use processing to generate an image. I am going to use node to call processing to generate the image and I'm going to use node to talk to an API and the API is Mastodon. So one thing you might want to do is I'll refer you to all of my, a tutorial series about the basics of making a Mastodon bot, I will start largely from scratch here, but I am going, I've already gotten my API keys and I already have imported and installed this node package called mastodon.api and this .env package that loads my API keys for me. So that is stuff that I've done in another video. If you want to see that, I'll link to that in the video description but I'm just going to get started. So what is the first thing that I want to do? Okay, actually, let's go to, so this is my inspiration. Tree bot, made by Alix Novosi for National Bot Making Month and I am going to try to recreate this. My trees won't be nearly as nice. So the first thing I want to do is I want to use processing. I don't have to, I could find some node package that does drawing or generates an image, svg, whatever, but I love processing, it's my happy place and I'm pretty sure I could get processing to generate a tree, in fact, I don't even have to write the code for this because I have before. I'm just going to go to examples, right, isn't it in here somewhere? Topics, simulate, no, no, no? Okay, I'll be back in a second. Oh yeah, I'm back, I found it under fractals and L-systems tree. Well look, recursive tree by Daniel Shiffman. This is my, this is a recursive tree and as I move the mouse, it changes the angle. So let's alter this code a little bit. Let's say that, we don't care about a frame rate. And let's not, let's have the angle be random between 0 and two pi. Oh and it's converting it to radians, so okay, between 0 and 360. Whoa and it's doing it over and over again in draw, that's exciting, I'm going to say no loop so it's done each time I run this. I get a random tree. There's so much more that I could do to the design of the tree and I'm so tempted to recode the tree but I'm going to just leave it as is, ya know maybe I want to not have so many large angles and there we go, beautiful tree, okay. So now, now that I've done that, what do I want? I want my processing sketch to also save and I'm just going to call it tree.png. So when it's done drawing, it's going to save an image. I've got my code here. This is the code that will talk to the Mastodon service and what I want is for this code to actually run a processing sketch but this is node, this is JavaScript. Processing is java and it's a desktop environment where I hit the play button, how can I possibly do this? Well the first thing I'm going to do is actually go and save this processing sketch in where my bot is and I'm actually working in this folder, Mastodon bot 3, you can see node modules, bot.js, so I'm going to save it there and I'm going to call it treegen. So I'm going to save it treegen, I'm going to make sure it still runs, it still runs. And now, what I'm going to do, I'm going to show you a little trick. Now wouldn't it be nice if I could just execute a command like processing run. Of course, in terminal, in the shell, it's not going to know what that is but a little known fact about processing, which I have done this in other videos before but just to start anew again, processing command line. There is actually a way to run a processing sketch command line by saying processing-java, the path to the sketch and then --run but the only way you can do that is by installing first processing.java to your system so this is installing a command line command to run a processing sketch. So I'm going to do this, install processing java, I'm going to say yes for all users. It's going to want to use my password. So I'm going to enter my password 'cause it needs to be able to put that where it needs to go and now I can actually say processing-java and you can see, I get all sorts of stuff. It doesn't know what I want to do but watch this, I can say, let me just look in here, there is the treegen processing sketch. So I can say processing-java, sketch=, is that what it was? Let's go look at the, sketch=, now I need the full path so the full path and I'm going to show you a way around this in a second, is this, treegen, right, this is the full path to that processing sketch. Then I can say --run, here we go. Magic. It ran it, look, there's the processing sketch. This is really cool, now watch. What's exciting about this and it's bothering me that the background isn't 51 which is very important. I can actually quit processing completely. I don't need to have processing open and still do this. Look at that, there it is. There's my tree, ah, but I actually do want to have processing open 'cause I've got a little bit of a problem here and we can see here, look, that image is there, it's now saved. What I want to do is go back and open this again, open, open, open, open, open and I want to add one thing. I want it to go away so I actually want to say after I save it, exit, and I'm also, this is going to be helpful for me later, I want to say println("Tree generated"); We'll see where this comes up later. Okay, now, quit processing. And now I'm going to run this. See it, there it is, oh, finished, tree generated, finished. One more time. There it is, oh, finished. Okay, this is really exciting 'cause there's so much stuff you could do. Now, here's the thing. I am executing this command via the shell but I want node to execute it. How can I execute it in node and it turns out there is a way to execute any generic shell command from node and it is with the child process package. So if I go to child process, you'll see here that there is a method called exec. Child_process.exec spawns a shell and runs a command within that shell. Truth of the matter is it might useful to use this execSync because I'm going to want to, sync means synchronous, meaning wait until it's done to go on to the next thing but I've got a crazy plan here. I want to do asynchronous stuff with es8, this await and async function, you may or may not have heard about. So I'm going to use this one, exec. So what I need to do is in my node code, I need to say const exec = require('') and where was this, it is in child process.exec, child_process, no there's no, yeah, that's right, huh, oh .exec, yes I'm confused. So this is me requiring the child process package and I don't have to mpm install this. You'll notice that I'm on the node.js documentation page. I'm not in some separate third party mpm package, this is built into node, but, just like file system is. But I've got to say that I want to use it. So require child process and all I want is that exec function. So now, there's no reason why I can't just say exec and then pass in what? Exactly this. Oh let me make, this is so unwieldy. So a nice little trick that I can do is I can actually always get the current path. If I want to get the current, I just type pwd, print working directory. Well guess what? I can actually have pwd executed within this command by using these back ticks. I'm pretty sure this is how I do it. So now I'm saying run and I think I don't actually even need this first slash, right, run sketch = print working directory, then treegen run. Let's see if this works. It is done, okay. So now, so great so I can grab this, this is my command. We're going to get to the Mastodon stuff in a second. Uh (clicking tongue). Constant command = let's just put this in a variable like this. Then I could say exec command and then that's going to have a callback, oh, I don't want the, let me just run this, let's just run this, let's see what happens. Node bot.js. Hey, look what happened, it made it happen. Now here's the thing, the next thing I was going to do was add a callback but I'm a new, I'm turning over a new leaf, I'm turning a new page in the book of JavaScript and I'm a kind of person who uses promises and I'm not only a kind of person uses promises, I even use the async and await keyword to really make my life full of just (sighs) ease. Eh, it's not full of ease but I'm doing my best. So what does that mean? The thing is, this particular node package, child process.exec, and I have a feeling if I bother to look at the chat (laughs) which I'm going to open up for a second, someone's going to tell me I could just use something else now that natively supports promises. Oh I'm being told that node.js has underscore directory names so there are other ways, people are telling me other ways I could get the directory name but what I'm going to do is I'm going to promisify, which is a word apparently, promisify, I'm going to, oh look at this, I must have googled this another time, unless it's just very common, by using java, not java, sorry, the node package util. So node package util, if I go look at util and I actually just want to be here, there is a promisify, util.promisify. So what I can actually do is I can say const util = require('util') and then I can say util.promisify, what a weird word, what happens if I promisify myself, uh, this. So now, this require child process exec function no longer uses a callback, now it uses a promise and what does that mean? That means I can say .then and whatever the result, the response is. I can console log that response. And then I can also catch any error and I console.error that error. Now, this might be and there's no semicolon there, there's a semicolon there. This might look completely insane to you if you haven't seen promises used before in JavaScript. It's very similar to a callback but instead of saying a callback, I basically have this callback that happens in .then. I'm also using the arrow syntax. The arrow syntax is a nice way of sort of shorthanding this. I'm getting the response as the argument to the callback and I'm console logging it. So if you want to know about those things, I have a whole playlist about promises and a video about the arrow function that you could go and look at but this is the basic idea. This is a little bit of an advanced video here. Not advanced but I'm using kind of modern JavaScript stuff if you consider three years ago modern. Okay, so now I've got this exec function so let's actually run this one more time. And see, it should do exactly the same thing but look at this, that response has standard out standard error so in other words, standard out it what? That's the thing that, I closed my processing sketch, I guess I should have left it open. The processing sketch has a print line in it so I can read whatever that print line is so I could actually get more information from processing if I want. For example, I could get the angle. Actually this is great, let's add a little feature to this, this will be fun 'cause why not make this video longer than it already is? Where am I, desktop, desktop, Mastodon, Mastodon bot 3, treegen, so what I'm going to do here, look at this, this is great. Let's leave this open because maybe I'm going to want to do more stuff with it. I am going to, let's make the angle between 0 and 90, that's what it says in the comments and then I'm also going to say print line and I'm just going to say angle or was it angle? Theta, whatever, A, I'll just keep the A and I'm going to say floor(a) so just get the, or int(a), I'm just going to convert it to an integer, so watch, so now, when I do this in node, I'm going to say console.log response.standard out. So I don't need to see that whole object, I just want to see the stuff that came out of processing. Let's run it one more time. And you can see it, that was the angle 42. Ooh, spooky, spooky 42, meaning of life. Okay, so. Great, so here's the thing, I want to once I have that image, I want to post that image to Mastodon. Isn't this all about Mastodon? I've loaded up and connected to Mastodon through this bot. If you don't know what Mastodon is, did I say this already? I've got a whole set of videos describing that and then you can go back and look but I want to somehow post it to this particular bot, the coding train bot, okay. So now, in order to do that, I need to look up the functions in the Mastodon API. So I'm using this Mastodon API node package and if I go here, I can look and see that it has Mastodon, get Mastodon post. So this is what I want, Mastodon post. Here's the thing, I didn't realize this when I made my other videos about Mastodon 'cause it says path, parameters and callback but guess what, this supports promises. So I actually am going to do this without a callback, with promises, I'm going to break this out and didn't I say I was going to use async and await? I was going to write it with just that but I think I have to start a little bit, I have to go a little bit further with the full promises syntax, then I'm going to clean it up with async and await in a second. So now, there's no semicolon there. So now what I want to do is I want to basically say this. I want to post the image and I want to return this 'cause if this returns a promise, guess what I get to do? I get to say .then response and have another function, right, so this is the idea of chaining promises. And this is what in theory, I mean, basically the whole theory of this is to avoid callback hell and really we're just in promises hell, it's all hell but eventually we will float into the clouds and feel like we're like butterflies on wings or something, I don't know, okay, so if I return the post that I want to do when that's done, so I'm executing this command when that's done, then do this, and then when the next promise is done, then do this. All right so, what do I need here? The path, so the first thing I need, what's weird about and actually it might be worth just taking a minute to write these steps out 'cause it'll make it more clear. I want to exec processing, that's one. Then two, I want to upload image and then three, I want to toot. All right so what's, this is the same thing for the Twitter API, it doesn't work that you just, if you want to tweet an image, you don't just simply send your tweet along with the image, you have to first upload the media, get that path to the media and then you can tweet with that media reference. So this will actually return an ID for the media and then as long as I attach the idea to this. So this creates output.png. This creates an ID and then I use that ID here and then I'm done, then we're just done. This is the three step process. Each one of these returns a promise. So when you do this, then this, than that. All right, that's the process I'm working with here. All right so (vocalizing) now, so, okay so the path. So let's go to the Mastodon API docs and I'm actually looking for media. So we can click here and this is what I, this is what I would do, media upload. I think media upload is just this. I post to here and then these are the things I need to send. Okay so file, description, focus, let's look at that. Okay so I need to, I'm going to create some parameters, oh no, I'm up here. I'm just going to create some parameters and I need the file which is presumably output.png and this is not exactly right yet. I can't just put the filename there but I'll get to that in a second. Then, I want the description. The description is really important, this is not the text that is going along with the actual post. What this is is alt text, alternative text. This is for accessibility. So somebody who's blind or with low vision who's using a screen reader instead of seeing this image would actually hear this description. So I would say a randomly generated fractal tree. Oh and I want to get, this is, I'm going to say const angle = response.stdout with and then, oh and I need to use my, the new thing that I always use now which is template literals for strings with angle. So this would be the description. And then there's one other. So the file is required, the description it says is optional but it really shouldn't be optional, you should be alt text for accessibility and then focus, this really is optional. I am assuming, actually I have not tried this yet but I'm assuming this has to do with where the crop is if it's showing a preview image, something like that. Okay so then, I can say so this is done. And then path and then those parameters. 'Cause this could be an array. Oh path, no, but okay. Path, oh yeah the path is media, sorry, I'm posting to this path, the media path of the API so almost there but this actually is incorrect. It doesn't work. The API is not going to just accept a string of the filename and figure out how to read that file and post all the data of that file to the server. What I actually need to do is give it a readable stream and so the code for doing that, it's part of the file system package so I need to actually also require that. We could look up the documentation for it but I happen to know it I think. So I need to say, I'm going to say const stream = file system create, there it is, read stream auto complete, thank you. Output.png, oh, and this actually isn't even right because guess what, remember, output.png was saved in the processing sketch which is the folder treegen/output.png. So now this is the stream and this is the description that goes with it and then I want to make sure this worked so I want to actually console log the response. So now in theory I have the code all the way for executing processing, uploading the image, I need to, when I get the response, I need to get the ID and then I go and actually just post the status. Okay here we go. Let's try this. Ugh, unhandled stream error in pipe, no such foul. No such foul, no such file. Oh it's not called output.png, I called it tree.png. Okay. Let's try that again. Finished and great. So look, this is all the stuff that I got back. Now it's kind of too much stuff for me to look through. Ugh, what a pain. Did this actually work? My goodness, craziness. But this is all I care about. There's more important stuff, there's more stuff, lots of tons of metadata about the image that you just uploaded but all I really need is data.id. So let me just go here and say console log response.data.id. And I apologize, apologies to the bots and dots space for overloading your server. You can see, there's the ID. Now, the next step would be to use this ID and then actually in this response, right, I exec, I execute the command, I upload the image and then I should be saying return m.post again and I'm going to, the path I'm going to is statuses. So this is what it should be doing but I can't resist. Do you see how this is, I mean it's great, it's kind of nice. Start the promise chain, then do this, return a new promise, then do this, return a new promise and if any error happens anywhere in here it catch, so why not but this is the thing, now is there a way, there is a new way for me to write all of this in what feels more sequential, more synchronous in fact with less kind of indentation and brackets and stuff and that is using this async and await syntax. So what I'm actually going to do is I'm going to write a function. I'm going to call it tooter or just toot and I'm going to modify this function with a keyword async. This means this is an asynchronous function. This is indicating to JavaScript, this is a feature of es8, a new feature of JavaScript that's indicating that this function will be handled asynchronously and always, always, always return a promise and it will automagically, it will basically automatically return that promise and we'll look at that later, how it does that. So in other words, but this, I want to do the same thing. What I want to do here is I want to say exec, execute the command, right, these are the steps. And then I want to say post the media. Post the media. The nice thing about using the syntax is I don't have to separate it out with these, chaining these dot thens. I can actually just use the await keyword. What the await keyword means is don't go to the next line of code, await the end of this function. So if I have a bunch of asynchronous things, if I package them altogether in an asynchronous function, I can write them sequentially as line after line after line and essentially, and I'm going to have some other params here, so this is basically I get to write it like this. Now I need to do stuff in between, that's the thing. For example, this returns a response and then what I want to do is get the angle and create the stream so this goes here. So this is my step one, right, execute processing. Look at it, it's just exactly like this and now I have my step two where I need to create these parameters that are getting passed and by the way, I don't have to make a separate variable, it could be embedded right in here but it's just sort of a little bit, for legibility, I'm making it something separate and so I'm not going to say basically step two is upload, upload, upload, ha, media and that's going to have a response and then what do I need? I need to get the ID out of that and then I am now going to say step three and by the way, I suppose if I'm being accurate about this, this is all, I suppose this is part of step two, I don't know, it doesn't really, this is a little bit silly what I'm getting myself worked up about but which line of code is part of which step, I don't know, but step three and maybe I'll name this params one just to be, just for, I don't know why, why not, have them have different variable names. Then step three which is params two, I want the status to be, and this is, I'd have to, I think I know what it's supposed to be but behold my beautiful tree with angle and then I should use the template literals again. Degrees. And then I need ah, so now, so this, okay, let me look this up in the Mastodon API docs. Okay I found it. This is the API, this is the path statuses for posting a status and this is the text of the status. There are other things you could do here that I've talked about in previous videos but this is what I want, media IDs. Notice this is an array. It's an array of media IDs because a particular status could have and it says here maximum four, more than one image associated with it so you can upload more than one image. So that might be an exercise that you try to do after this video but basically now I can say comma media_ids and I just have that one ID so I just put, I can make an array and put it there and then this has to be statuses params and then, what I can do, is I can, and this is the response, and I can just now say return response. So if we could look at this all together, I think I have to make this a tiny bit smaller for you to see it. Can I fit it all in one nice place? Look at this, look how beautiful this is, it's almost like I've written this code in a language like Java or C that's perfectly synchronous and linear and procedural because what I'm saying is await executing this command. Then, do some stuff, then await uploading the media. Then do some other stuff, then await posting the actual status and when you're done, return, and guess what, if I now say, let me make this bigger again. Whoops. If I say toot, guess what, this toot function that I've written async, remember when I said it natively returns a promise, well by the way, I said return right down here so this thing right there is the promise it's returning and I actually might, I could configure my own object which could be kind of interesting, like what if I said, return, I could just say something like success true and status, status behold my beautiful tree, whatever, and I could just say angle, whatever, I could put angle and then that angle there. I could actually configure an object and I could pull stuff out of the response there. I could return that and then I would say then, then response, I can't spell today, I mean I can never spell, console.log that response, catch any error, consol.error error. And this needs a period here and I need to hit save and something's wrong, oh there's too many dots. Too many dots, Mozart, too many dots. Still too many dots? (ding) Okay. It's not too many, it was too many dots but I fixed that. The issue is the dot goes on the next line, that's the convention, that's what it's looking for. There, now it's formatted the way I want it to be. So I still have to engage, oh boy, with this idea of a promise but I can basically wrap that all into this one asynchronous function and people on the chat are telling me I can actually, I don't need to do the then and catch in this way 'cause I could actually put a try and catch inside of here but aah, one step at a time. I think I might be done. All right let's see, let's see what happens. Okay, no, response, await, ooh. So I have also reused so I should call this response one, response two, maybe there's a more thoughtful way of doing this, response three. I didn't actually use the response but whatever. There we go, start the bot, make the image, the angle's 72, finished, params is not defined, oh I was so close, I was so close. Params one, params two, params two here. And I think params one there. All right. (light drum roll) I'm feeling pretty confident. Aah, success, angle is 56 slash finished. (buzzer buzzing) That's a little weird but let's see here. Let's go to the bot. Look, behold my beautiful tree with angle 56 finished degrees. Okay, so there's a little bit of awkwardness in there in that the standard out is just always giving me the word finished so because I would like this to work, work without that, I think, guess what I'm going to have to do is, is that just something that happens with processing java? Because I didn't write finished anywhere in here, right? That's not a print line I put in here. So I think what I could do is when that comes, the standard out comes, I could say response, I mean I could just do a substring, I could do a regular expression, there's so many things, so many things I could do. Why don't I split it? Out = response, response standard out split and I could just split it by the line break, right, 'cause finish comes after the line break and then, whoops, and then I could just take the first one. So this split is a function that takes a string and splits it up into chunks based on a limiter and you could get really fancy with that but I think and then this should just be out without the caps lock. I mean I should really test this but I'm going to have to just rely on the fact that I think I wrote that code correctly. That's my way of testing it. Let's run this one more time. (light drum roll) Undefined true angle 55. What's that undefined there? I liked seeing that. Oh there we go. (celebratory horn) Behold my beautiful tree with angle 55 degrees. What was undefined? What did I console log in an undefined way? Console log response standard out. Is that it? No, no, that's 10 finished. Wait, if it was 10, why did this become 55? I'm so confused. (laughing) Let's be a little more methodical here. Oh because, huh? Oh what am I looking at? Oh I did, aah, it does it twice, I have it happening twice. Let's take that out. Apologies, everybody. This thing is happening twice 'cause I had the old code in there. I mean, I'm just going to delete that. Throw caution to the wind. What a little mess here. Okay, that's weird that it did that twice. So confused. Okay. (light drum roll) Angle 74, just did it once. (celebratory horn) There we go. Okay one more thing that I need to do just so this becomes a true bot is what I want to do is I want to say set interval and have it do this thing that it just did there every, well, let's have it do it every five sec, every 10 seconds right now. I don't want it to do it every 10 seconds but just to, what's wrong here? This is opening the function, that's closing it, no, there's no parentheses there. That's the end of the function then this, then this. We're good, we're good, this is it. Oh but look out weird, I hate, I don't like this at all. This is making me crazy. I'm going to do, I'm going to do this. I mean it's so silly. Function tooter and then I'm going to put, this is, I'm a ridiculous person. I'm going to put this in ooh, no, aah, oh, help me, help, help, I'm going to get this and put this and then I'm just going to name my function. Forget about this anonymous stuff. This is what I want, right? I have a separate function which calls my asynchronous function, does the then the catch and then I'm going to have that happen every 10 seconds. Okay. By the way someone in the chat, k1nhjulian is asking would it be possible to ask the botch to create a tree with a specified angle, yes and that is what I'm going to do in a followup tutorial. You'll see that in the next one. Okay here we go. (light drum roll) And we have a tree, oh no, I forgot, it's going to wait, so one thing about set interval, womp womp. (sad horn) Which is that, I mean it works, okay, I'll just keep going now. Set interval will not execute that function immediately, it will wait the amount of time before doing it the first time but now we did it twice every 10 seconds, first one was angle 44 and the second was angle 28. We can wait 10 seconds, you can watch this video for 10 more seconds, we could also speed this up now, let it happen four or five times but I think that's good. So let's just go and check, here, and we can see 83 degrees, 28, 44, there we go and 44, 28 83. 44, 28, 83. Wonderful, okay, so this is working. You know, now of course, I don't want to leave it like this, right, having a bot post every 10 seconds, no one wants to follow a bot that posts every 10 seconds. Maybe I want it to just once a day it's going to do, maybe once an hour might be the maximum, the most I would do but let's just, if I want to do it once a day it would be 24 hours times 60 minutes, there's 60 seconds in a minute and 1000 milliseconds in a second. So this would now be but, I wouldn't want it to wait a whole day so I probably want to call it once. So I'll call it once, then set the interval and I'm sure there's a more elegant way to do that that all of you will someday will write in the comments and here we go and ah, ooh, ah, ooh, node bot, aah, bot.js, here we go. It does the first one and then now, we're going to wait, 24, we're going to wait 24 hours, I'll wait, yes we'll wait and then in 24 hours, I'll still be standing here. I could technically go home, have dinner, go to sleep, come back, this laptop would still be here and it would do the next one. We're just going to have to believe that that's going to happen. Again, there is a question of well where would I actually want to deploy this? I've got to talk about that in a separate but the quick answer is you're going to want to find server, maybe you have one through a hosting company, maybe you happen to have a computer that's always on in your home or raspberry pie that can act as a server but you need somewhere where you can just let it run over and over again. Forever. Okay, so thanks for watching this video. (ding) Making a Mastodon image bot. And what I'm going to do, I want to do one more tutorial because how would you do it so that if someone at mentions the bot, let's say with an angle, then the bot replies back with a tree with that angle. Let's see if we can make that work, that's going to be fun. Okay, see ya in the next video. (upbeat music)
B1 中級 編碼挑戰#118.1。猛獁象分形樹機器人第一部分 (Coding Challenge #118.1: Mastodon Fractal Tree Bot Part 1) 2 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字