First off, thanks everyone for joining us. What my goal is today is to show how to contribute to an open source project, even if you can't work directly with the developer of that project, basically just how to get in with that community. And you guys know, if you've seen anything on my channel before, that we talk a lot about Surugi Linux, and it is surugilinux.org over here. So, Surugi Linux is a Linux distribution, and it has a lot of different tools inside of it, and some of those tools are developed by the Surugi Linux developers themselves, and some of them are developed by the community. Almost all of them are open source and freely available in terms of the tools. And so if you wanted to contribute to the community, an easy way to do that is just find a bug or find something that's not quite working right in your tools, and yeah, just try to make it better is essentially what we're going for here. So let's switch on over to Surugi Linux running. And I've already mounted up. I don't know if you guys have seen the last video I did, but I was using EWF mount to mount an EWF image, and then once we had that EWF image mounted, then we can mount the partition inside of it and start working with the partition directly. So I've basically done exactly the same thing. And in this slash mnt slash c drive, if I do ls, we can see all of the files in the suspect partition. So this is the suspect's c partition. This is mounted read only. So we know it's the c drive or the root directory because we have things like the swap file sys, and page file, everything like that. Okay. So what I wanted to do with this, imagine that you want to go through and search all of the data, all of the files inside this c drive. What we could do with Linux tools is just something like find and then grep, and let's say we want to find something with a JPEG extension. So let me try a JPG because I know they're in there. Okay. So JPG extension, we can just do a quick find and we'll look for the file names inside this c drive. Okay. So we found this generic English one dot JPG. So maybe we can process something with that. Okay. Well, in the Sorugi Linux, we have the applications and then under Sorugi, it has a bunch of different utilities already set up for us. And one of the utilities that I was interested in using is this picture analysis. And then if you scroll down, there's search screenshots, search screenshots. So if we open that up, we get a command line tool. I don't mind that one. We get a command line tool in the background. And basically the way that we use search screenshots, it's a Python script as a help menu. This is the help menu. We give it an input directory. We give it an output directory. Width, height, and pixels. And then it kind of explains what that is. But basically what we're interested in is the input directory, output directory, and then search screenshots will tell us if an image that it finds looks to be a screenshot. And it does it by the width and height of the image itself. Okay. And then of course, the image pixels, how many pixels it has could also be an indicator. So you can actually set the width and height yourself if you have some sort of custom device, but search screenshots already has a few sizes already built in. All right. So if we do, let's go back to our other, our C drive. If we do search screenshots.py, let's do input directory. And I'm just going to give it the current directory and the output directory I'll say I'll just put it on the desktop because I know I have right access to it. If I press enter, we get an error. Okay. One error is that it's a read-only file system. And this tends to happen a lot. So I'm inside the mounted read-only file system right now. I gave an output directory of the desktop, but the program still wants to write a folder into the current directory. Okay. And I'll just tell you, search screenshots has a lot of other problems.  All the images dumped into one folder with no subdirectories. Okay. So if you do an extraction and all of your, you know, images from your suspect system is in that single folder, then you can use search screenshots to try to filter them out very quickly and do all that. But that's kind of a pain, right? So instead we have a suspect device, mounted read-only.  But we already have the basis for a program that does that here. It just doesn't have the ability to do, for example, recursion and go into subdirectories. And some other things. So what I want to do today is show how we can start to modify or work on these programs that don't really have the features that we want, how we can add them actually pretty easily. Okay. So I'm going to go to... The first thing we can do in Linux is do which search screenshots. And then that will tell us the location of the screenshots, search screenshots pie program. Yep. So then now we know the location. Let's go ahead and copy that. And then if I just do, I'm just going to use nano for now, paste in that file. And then we can see it's just a normal Python program and it has, for example, the date that it was last updated. So it's getting kind of old. It doesn't mean it doesn't work. It's fine. It just is lacking some features. It's the MIT license, which is good because that means we can edit it. If you run into a program that has a... For every program you're working with, just check the license and make sure that you actually have edit and redistribution rights. If you don't, that'll be a problem. Don't edit it unless you actually have permission to do it. Notice you can talk to the developer of that program and try to get permission if you don't already have it or it's not an open license. So the license is provided as is. Liable for any claim, damages, connection software. So we do need to check if we can actually redistribute this, but I think under MIT we can. So let's go ahead and check that real quick. Does anyone in chat know? I'm pretty sure we can redistribute with the MIT license. So to deal with the software without restriction, including without limitation and rights usage, modify, merge, publish, distribute. So that's what we're wanting here, is the ability to be able to modify it and potentially redistribute it. And today we're going to try to distribute. All right. So okay, we know our license is okay. First, if you're familiar with Python, we have a bunch of imports. And then we have destination file directory is essentially creating a destination directory with the date and time. We're getting a string for that. And then it looks like we're just immediately making the directory. And this is a bit of a problem where as soon as we start the program, whether we're doing a help menu or whether we have a file path to actually work with, we still are creating a folder. So we're already going in and making a directory. And that's probably why we're having an error directly at the beginning whenever I try to run the program. So that could be something that we're interested in changing. Next, we have arg parse. There's a question in the chat. How do you create a list to sort files by type? Are you creating a list in Python or are you in an Excel spreadsheet? Like this program is a Python program, but it will create an Excel spreadsheet as an output. And then potentially you could sort by extension. Or are you just using Linux command line tools? Okay, answer in the chat and I will come back to it. So right now we have arg parse. And arg parse is just taking arguments from the command line and then using them. And basically we're just putting the input output directories. Next, we're creating our output directory. Notice we didn't get to that point before because we had that path exist make directory destination file directory was created immediately. So this is definitely something that we should take care of. And then the output directory seems to be a different variable or idea or directory from where we're saving all the files. Okay, we have some global variables created. Okay, and then we're creating... Sorry, it's not an Excel spreadsheet. It's a CSV file. So file name CSV, write, and then we have the file name width height, MD5 hash, SHA1 hash. So we know that they're creating two different hashes what looks like probably all of the files. We'll come back to that. But maybe it's all the screenshots or all the files. I'm not sure yet. And then we have images, OS, Lister. So this is the input directory. So it's listing everything from the input directory. And this is where we first start to get our file list that we're going to work with. Okay, so then once we get our image list, we basically have a for loop over every image in that list. Now remember that is in a single directory, not subdirectories or anything like that. So we might be able to just change the Lister's argument to be recursive. And then that would go into every subdirectory. And then that could solve some of our problem here. Right, and then we open the image, image file. We get the width using the image library from Python. And then we just print the width and height. And then we have an if check about the width and height. And that looks like maybe the custom image width and height that we give it if we do an argument. And then we have another if statement where the width and height is hard coded. So we have the width image 720 height image 1440. And they say moto g6 play PXT 1922. So for specifically a moto phone, I think we have a width and a height for screenshots that are normally created. So that's hard coded in there. And then we're doing image show. So the image, I guess probably pops up. And then we save the image to the output directory, print the width and height, hash the file, and then write all of that data to the CSV file.  Okay, so there's kind of a lot going on here. But I can already see that we have a lot of redundant code. Okay, so for the first if statement, we have the hash functions. For the second file size, we have the hash functions again. And then we have an L if, which is another just essentially case statement with a different size for an ASUS Z017D. And then we basically have all of the same code again. So whenever you see the same code being used over and over again with only a couple variables being changed, that's a really good candidate for a loop. Okay, so I don't really see any functions, like custom functions written in here, but I am seeing a lot of LF statements and then a lot of the same code over and over again. Okay, so there was a post, there was a post by the Seguri Linux team, and they basically said that their team is full and for security reasons and a lot of other reasons, they're not taking anyone else onto the team in terms of development, but it is an open source project. So we still can contribute to it. It just means that we can't be on the main team who rolls the distribution. But we can definitely update this program and it looks like there's a couple places where we can make it more efficient if we want to. I thought this would be an interesting case to look at. So the first thing I'm going to do is go into GitHub because if we're sharing data, especially if we're going to make it open source ourselves, the easiest way to share is GitHub.  Okay, and then this is the Regi Linux search screenshots utility. And the reason that I'm creating a new repository versus an old versus just using one that already exists is because I didn't find any Regi Linux repository. I don't think they actually have a repository. I think they keep it all within themselves. From the chat, the intention is to report the media files found in a forensic image, all image files, all video files, all audio files, regardless of the length. Okay, and then you want to sort them by extension. What I would do from Antonio from the chat, what I would likely do if I wanted to get a list of all of the files from a directory. Actually, we can do that here. It's bigger for you guys. So in Mount C drive, in Mount C drive, I have all of my, all of the suspects files in Windows, they're C drive. If you do find, for example, you can find by type or you can find by extension. So for example, he's looking for, yeah. Okay, again, it's still like JPEG. If you do grep I, you don't care about the case. And then find dot looks from the current directory and then all of the subdirectories. We are piping that into JPEG. So then that'll find everything with JPEGs. And basically what I would do is use grep with regular expressions to state all of the file extensions that I was interested in. Now what you'll find though is that it might not be just a file extension. So I would list out JPEG or what's another one you're looking at? BMP. I don't know if there'll be any BMPs here, but I think I have to use eGrep for that. I haven't done a chain of names for a long time. There we go. So you can see at the beginning here eGrep basically uses regular expressions. And I did eGrep-i because I don't care about the case. And then in quotes, I have my JPEG extension that I'm looking for. And then the pipe here means or and then I have BMP for the next extension that I'm looking for. So if you know the extensions that you're looking for, something like this will give you a list. But here we have like a BMP was found first and then I have a file that's not actually a JPEG. It's a DLL. And then I have JPEGs at the foot. So I would dump all of these into a file. I don't know on my desktop. And just call it multimedia, something like that. Multimedia list. Okay. Now from that multimedia list, let's see what's in there. I'll just get basically the full path of where I was running find from and then a filtered list of all of what looks like multimedia files. I could do the regular expression a little bit better to get rid of like these DLLs, for example. But that's pretty much how I would do it dump all of that into a list. And then if you want to sort, there's a couple of different tools where you can just sort directly based on extension, or I would dump all of the extensions into another column, like if it was a CSV sort that column and then display full path. So there's a couple of different ways to do it. It just depends on are you writing a Python script? Are you writing a bash script? Are you doing everything from command line? And what's the goal of having it like by file type? Does it matter? Also, while you're doing this, you can also use the file command. So for example, we have this zero dot JPG in Linux. If we use the file command, we can see we can find information about. It has a space. Anyway, from using the file command, we can see information about the file header, which might be a more reliable way than the file extension, because the file extension can be changed. I never use, for example, dot txt files in Linux. I always actually most of the time I don't put extensions with text files. I just have a file. But if you have the file command, you can look at the file by file header and then kind of understand what it is a little bit easier. Okay. So going back to GitHub. Yeah. So I didn't find anything much about Sorugi Linux like on GitHub. And I don't think they have an open repository, which is why. There's a lot of things called Sorugi, but it's not what we're looking for. So in that case, since I couldn't find a repository where the main creators are already maintaining it, if you think they're not going to make a repository, make one yourself if you want to update some files. In this case, I'm calling the Sorugi Linux search screenshots utility. I'm going to make it public. Let's go ahead and add or read me and then choose a license and it has to be MIT because that's what the original was. I think you could probably with MIT, I think it's pretty liberal. You can change it, but let's just keep it. All right. So then I have my repository created. I'm going to copy and paste that. And then from my development station, I'm going to do get clone and then clone in. And then if I can remember my password, nope. Oh, yeah. Okay. Now somebody's going to do analysis on that. So I'm going to have to change my password. All right. So we want to go into search screenshots. And then I just have the license file that was created and the read me file that's created, but everything else is empty. But it is a Git repository. So I've already created that's the directory. I've already pulled out the search screenshot. So I'm going to drop that in there. And that's the original version. I haven't modified it at all. So then if we look in there, we have search screenshots. If I do get status, then we see that a new file was added, but it's not been really at tracked yet. So if I do get add dot, then get status. Now it's green and saying, hey, we see the file. Okay. Now I can do my original commit. So if I do get commit dash M, and then I'll do initial search screenshot. And that is the original commit. So now I have a copy of the original. And if I mess anything up, I can just go back to it. I'm going to do get push to make sure that we push it up. And now it's pushed up to GitHub. Let's go ahead and take a look at our site. And then now we have the search screenshots. If we look at that, that's all of the original code because we haven't modified anything yet. Using Python three software license. This is exactly what we had before inside suruki. So now I can start to edit that code directly. So I'm actually going to go ahead and shut down suruki Linux because I don't usually develop in that environment. So that's more of the investigation side. Free up some resources. So I'm just going to do code. And I use MS code most of the time. And then I just did code dot. And then that's for the current directory. So basically open up the current directory in MS code. We have our license, which should be filled out for MFT. Read me, which hasn't been filled out yet. Let's say something like updated to support subdirectories. That's the read me. You should keep track of the version numbers. Change log. There is a really interesting program. Kind of standard. Let me see if I can find it real quick because it is super useful if you guys are using it for software development. I think it was like changelog standards website. Keep a changelog. I think that was it. Yep. Okay, so what I probably do, I won't do it now just to save some time, but I recommend that anyone who's doing any type of software development goes to keep a changelog.com is super, super useful standard and very easy to use. It's kind of saved me on multiple occasions. So keep a changelog.com highly recommended. Okay, so the first version we had was 2019 1201. So they just do versioning by day. Change log. I just say initial commit. Now our read me is a little bit up to date. I would probably go back and do a little bit more with that, but let's go ahead and get into the code. So we have looks like I'm missing one library CB to. Okay, so the first thing that is an issue is creating this directory. And then we have our art parse. Then we have another directory that's created. And then we just have a bunch, I have a loop with a bunch of if statements inside of it. All right, so what do we want to do? Like how do I want this program to actually work? Okay, so the first thing. Run. Let's say the help menu. So run with help menu. If there's no options or something's wrong. Two, what else do we want to do? The big thing is scan the directory of images for height with. Also scan some directories. Okay, and I also want another feature. Easy to update. Screenshot dimensions. So right now it looks like they have a height and width kind of hard coded into these if statements. Well, I mean, that's really hard to update. We would have to copy this entire block if we wanted to add another size, right? We don't want to do that. So let's go ahead and say easy to update the height and width. And then I think all of these can pretty much be a function because it looks like all of this code is the same. We'll see maybe there's a reason they weren't doing it. Okay, so from the search screenshots, I want to run with a help menu. I want to be able to scan a directory of images for height and width. Either from list or manual input. Scan some directories basically the same from a list or from manual input. And then I wanted to make it easy to update the heights and widths whenever I find new devices and have a new heights and widths. Is that it? Is that all we want to do? I have something that kind of annoyed me. Don't make directories unless actually needed. So like, only make a directory whenever you know you have everything you need to start processing an image. So maybe we find our first image. If we've confirmed that we actually want to process that image, then we create a directory to output to instead of just creating it as soon as it's created. As soon as we run it. Anything else we need to add to that? Okay, so the first thing I'm going to do is propose. What is today? 2021. That switched me over. Sorry about that. So I'm going to propose this change to the spelling error. Let's fix that. Okay. So the first thing. Go into my list again, just to make sure I always make a list like this just to make sure that I kind of stay on track. Otherwise, I'll forget a feature that I'm wanting to implement. With GitHub, normally each of these features would probably be its own branch. Feature branch that I would bounce off of. Since it's all kind of one. I mean, we're essentially refactoring almost the entire script. So I'll just do it all together now. All right, we have our imports. Destination file directory as a global. Go ahead and move all of our global variables together. It looks like this top stuff was probably tacked on afterwards. Destination file directory. I'm going to move that down with the other folder creation. So pretty. So we have our arguments. AP, arg, parse, images, list directory. So the first time that we're actually using. Okay, we get the output directory. And we create it here. Trying to think of how we would refactor this that that actually makes sense. It's kind of, it's easier to just do it without anything there trying to think about how to rearrange it as a bit. Destination file directory. Okay, so what they're doing is creating all the directories creating the destination file. CBS. And then file name right. Basically what probably looks like the titles in the CBS. And then getting the images. So what I would probably do is kind of like setting everything up. That means that we're setting everything up before we know if we need anything. We have our input directory. Open image read. And what this IMG. Okay, that's the image object image file is the data. Using image. Yep. Okay, that's what we're hashing IMG here is the image properties. And then image file is actually the data. So let's go ahead and just mark that way we know image contents, image properties. Okay. Let me print the width of the image. All right. Let's just go ahead and not write this data out. I'm going to go ahead and comment all of this. All right, so I've commented out basically all of the file creation stuff, but I still need images. Okay, so we have our images. We have our input directory. I'm going to remove the file name right. So what this will do is just print everything to the screen and we won't be writing it. Yep. We can definitely turn this into a loop. All right, so I'm going to open up a terminal. See if we can actually run it. A module named. Anyone know the package name for it? CB2. We open CB Python. Let's try that again. Okay. So now it is running and then we actually get an error message. So screenshots and that is the usage following arguments are required, the input directory. So that's actually what we would expect to happen if we give it some information without all the options and it didn't create the directory. So that's perfect. So the next thing to do, I'm thinking about moving all of these into some type of array or list and then reading them out. And maybe I'll make another file like this. And then I'm going to remove all of them except one and then kind of the width image. And that's the one that will keep those into sizes. Okay. And then I would remove that because it's going to be exactly like the other ones. And the goal here is that I'm going to take basically this code block, move it into at least one function, but maybe a couple of different functions. And then just feed the dimension into that function directly. So they do proc kill whenever they're displaying so they are showing the image and then they're basically killing the image later. Once I think the user closes the image, then they kill the process and make sure it's it's killed. I'm not sure if we actually need to show because we are moving all of the images that we find into another directory. So what we could do instead of just showing it as we find it and then blocking. So once they do show, I'm pretty sure that they're blocking all of the other commands.  I'm pretty sure it blocks stops the entire process and then until you click. So what would be better is just have an output directory. Once we extract all of the images, throw them into the output directory and then just show the directory. So let's do six and then say extract to output directory and show directory. So I won't do all of that while you guys are here. I'll just go ahead and start on the loop or the function. Sorry. One of the first functions that we need, I'm going to call, for example, setup, because it looks like we have a bunch of setups to do. So def, setup, I'll just call it setup. Then in there for now we'll just do print, create, output, folders and files. So it looks like there's the output directory and then there's the destination directory. And I think destination directory is probably for the found images themselves and the output directory is for the file that's going to contain all the metadata including the hash values. I'm not sure why they're separating them because we could just have a directory full of likely screenshots and then the metadata inside the same directory. We'll be splitting it out too. I don't think so. I think let's just keep the output directory and then get rid of destination file directory. That'll make things a little bit easier anyway. And output directory is given by the user. So we'll need to check if it's unique. If it's not unique, then we probably need to add a timestamp to it or something like that. Create output folders and files. I'm just going to put a note here. So keep output directory. Only use output directory. Check if exists. Or what are we going to do by default? Or create a random file or create a random file name. Then that's where we could use this kind of date time string that they're using here. That's the destination directory. So if not OS path exists our output directory. So if it doesn't exist, go ahead and make it. And then else would be if it exists. And what we can do is just take that code. But this is going to create it in the same folder that we're running it from. Is that something we want? We can change it. I'm going to say print. At least say what the error is. The output. Please try another name. Something like that. File name. Destination file. I still don't completely understand the difference between the output directory. Excuse me. And the destination file directory. Input directory. Image save. The output directory was where we were saving the images. And then they were just dumping the metadata into the same sub folder. I'm going to create a new variable instead of quoting this all the time. Output directory. Call that out there. And then move all of this out there. Okay. So now we have hit the argument. So we have a setup function. We have the outdoor variable that's being passed in. The outdoor variable is the output directory where we will save both the file metadata CSV file as well as all of the images that we find that we believe are screenshots. We'll check if the output directory path exists. If it doesn't exist, then we'll create it and then inside of that path, we'll create a file with a file name of year month day hour minutes seconds that CSV create that file and then write essentially a header to that file. I might need to actually. Okay, we'll come back to that, but I probably have to put that as a as a variable that we pass as well. Yep, just kind of experimenting around here. And then we probably don't need the destination file directory anymore. And then if not OS path exists, yeah, that's the same one. So I'm going to go ahead and remove that. All right. So so far we've created one function for setup. We're not running it yet. It never gets accessed because we haven't figured out where we want to run it yet. I probably want to run it the first time that I find an image that I want to process. I'm going to take the height and width of all images as they come in on a different line. So that's not great because that's going to give you a lot of text on your screen. Probably don't want that much text. Actually, you should probably do something more like print image. Something like that maybe. And then everything would be on one line and I might even add the file name or something to it later. So I'm going to go ahead and comment these out for now. Just know where I was at. Then we have our first kind of custom width and height. So if we actually do have width, args, arguments of width and height, then we will use them as an example. If not, then we will use all the built in widths and heights. So instead of doing this, if they're just doing a test like this all the time, what we can do to avoid this is just do if, like we're not guaranteed to actually have a width and height argument. And if hard parse has a, it exists. So what we can do is something like if, args. I just want to check if the height and the width was actually given by the user. What they're doing here, actually now that I think about their if statements, first the checking for the custom size. And then if it's not the custom size, they're checking for a different size. So it's not an exclusive I want to look for these sizes. It is in total give me all of these different. So essentially that needs to be checked every single time. Let's go ahead and start the function then check image. And check image will be sent the width and the height right under the def function. Let's go ahead and create another function called def check image. And then with the check sending to arguments or variables to the function check image. And then I can pretty much, I think just copy and paste everything that they have in all of these if statements. But I don't want to image show every single time. That's not going to be right. Image height, image width. And I need to check what are they testing against the actual height and width. Actually, this isn't check image. This is something like process image. I'll call it process image and we need the image data. Because I'll do the checks in a loop underneath that for loop. But again, same line.  That's the object. And dash values. File name CSB right. Probably going to image and name. I'm sleep. I'm not sure why they're sleeping. Keep that there for a second and then hide the image. We didn't display it. So I'm going to go ahead and comment all of this out for now. Okay. So this actually process image takes the place of a lot of what's going on in this if statement. Get rid of all of that. And then just to process image, we have to give it the image and file name CSB. Okay, so you can already see I basically have a check here. That is just checking the width to the user generated or user given width. And then it just processes the image. And now I can get rid of a lot of these lines of code that's completely redundant over and over again. So I'll work on that later. What else do we get the next thing I wanted to do is actually get all of these into a list. And then basically parse through this list every single time. So maybe load in either the list, load in the list and then do a for loop over it to do all the checking. And then just keep running process image whenever I find anything interesting. Rather than having all of these different if statements, you can do it with a loop and one if statements reading the list. Okay, so that would greatly reduce basically all of this code down to, you know, the loop. Loop over the list plus an if statement and then processing the image or not processing the image. We probably need to keep this if statement with custom width and height just because just because I can't see an easy way to just put it into the list or. Yeah, if it exists, we can just add it to the list and then loop over the entire list and then we can get rid of that as well. So we can pretty much move this down into like three or four lines instead of having this entire thing and it should be a little bit more efficient. It'll also be easier to manage because you can see that I've already created this sizes list and I would probably make it like a CSV file. We have width and height and then maybe even like a comment or something that way we know what's what what this width and height is for. And then what that means is without changing any of the code, I can just come into this particular file, add a new, add a new width and height. And then the next time I run the program, it'll find it. So it's much more maintainable because instead of going in finding the lines editing the code for the different heights and widths, just change one file, anyone who finds a new like common resolution that they're seeing or screenshot size can just come in and edit the file and then we can kind of collaborate a lot easier on this. So I think that'll be a much easier way to maintain everything. Right, so I have a couple problems now that I've named the file name CS and file name CSV inside this function. So the rest of the program can't get access to that file. So what I would have to do instead is create that file name CS variable in another scope outside or I would have to pass it to another function that I'm calling. So for example, specifically this process image I need to pass the file name CSV to that function. So I need to get it somehow. That's one problem I need to solve. I need to call the list, put the users widths and heights into the list. And then basically everything else is just removing this trimming it up and testing that it works. Right. That's unfortunately all I really have time for today. But I hope you guys can see. We took, we took a file that is an interesting program like it's a useful program if you can dump all of the data into a single directory call. That's the other thing I have to do thing is the recursion. But like I said, I think we can recurse at this level here. So images is just an image list. And then OS Lister args input directory all we're using is the OS Lister with the input directory, and then feeding all of those image files into the images list. Now what I think I can do is just add recursion right here. And then that solves our recursion problem so I can just point it to a suspect disk. It will recurse under all the sub directories pull out all of the images that it can find. And then, and then, yeah, we should get the list of images. I'm just thinking now. We don't actually explicitly test for images so they just kind of assume that all of the files that we're reading in our images so we might need to do a test either on the file header or by the extension. So that's something to think about there but that'll come later in the testing, right? So I think I can pretty much get this program down to, you know, less than a quarter of the lines that it used to be. It should be much faster and it should also be easier to maintain and upgrade as we find more screenshot sizes. So that's what I'm going to do right now. I'm going to go ahead and go back into GitHub. So I'm in code now. I have my screenshots. I have my new sizes file. I'm going to go ahead and commit this even though it's not completely ready. So get, add, and then I get status. So I have a couple of different files that I've modified. I'm going to just do get, commit, and then initial start of refactor. Let's do start of refactor because it was quite a bit. Okay. And then get push. And then let's check that it was actually up there, old file, go into screenshots. Let's commit 25 seconds ago. We have our sizes. We have our screenshots pie. I run into there. We should see at least those functions created. Yeah, updated date and then updated functions as well. Right. So you can kind of see what I'm doing. Like I took on a lot with the script actually because I have a lot of functions that I want to implement with it. If you just wanted to add a screenshot size, for example, so in your investigations you found that there's particular screenshot sizes, just adding another if statement to that would have been plenty and it would have been super useful for the community. I'm trying to do a lot here with kind of a total refactor to make it much easier to maintain plus add recursion and all that stuff.  I just have to create an array, loop over the array, and then run the code that's already been written. So pretty straightforward. Does anyone have any questions about that? And or how to commit or how to contribute to open source projects anyway, or how to get into digital investigations. Now's the time. Okay.  So if you liked it, please let me know. I will keep the recording up for a little bit. I'm not sure what I'll do with it for a while, but we'll see. If this was useful for you. Yeah, definitely let me know. If you like to see stuff like this, like I do this kind of thing all the time. If I find a tool that looks interesting, but it just doesn't do exactly what I want, or I'm having some issues with it. I'll usually go in and look at the source code and see if I can just change it real quick. So I do this kind of thing pretty much all the time with tools that I'm using. The next step would be if we get this tool working the way that I want it to, and it's consistent, then I would submit back to the author, Antonio, specifically, send them a copy of this and say, hey, I've updated this. It now does these things. Feel free to use it or not. But I can go ahead and use it. So now for me, this tool would be useful for me if they use it as well. It's good. If one of the things that I'm thinking about showing is how to set up a package repository. So whenever you make your own edits, thanks a lot, Jay. Whenever you make your own edits to software like this, you can create your own package repository. And then upgrade or automatically install them in Sodorugi Linux without having to go through the maintainers of the program. It's basically creating a PPA based on all of our changes. So I might talk about that a little bit later. Yep. Yeah, thank you guys so much and thanks to everyone in the chat and I hope you have a good day. Let me know if you have any questions, comment on the videos. You can also hit me up on Twitter. I'm on there quite a bit at D4 Science. So yeah, thanks a lot.


Can you work out your back without working out your biceps? Whether you want to fix your muscular imbalances or achieve a certain aesthetic Maybe even try a new exercise Not many people know that you can actually isolate your back without using your biceps my general training philosophy is time under tension high volume short rest time moderate repetition tempo and taking the muscle to failure nearly every set However, I do this with a light weight to isolate the muscle So the rep range is usually 15 to 20 for the lighter sets and 10 to 12 for the moderate weight sets With this technique, you can even perform compound exercises without using accessory muscles But we will focus on exercises that most people can execute without advanced training knowledge The most important aspect being that you are feeling the muscle contraction during the movement If you want to work out your back make sure you feel it in your back not your biceps or forearms So we have the rhomboids and inner back and the lats which are sometimes referred to as the wings of the back Then we have the lower back lumbar spine area. Some people include trapezius The muscles near your neck, but that is not what we are covering today The main exercise is a pull through this can be done with different machines The first is just a straight bar to perform this exercise You want to keep your arms static and bring the bar down Rotating your shoulders squeezing your back at the end of the movement Overhand grip tends to work the upper back rhomboids Inner back and an underhand grip focuses more on the lower back and lats You can do this on a lat pulldown machine with a much larger bar with a wider grip as opposed to a tricep machine One variation of this is on a cable crossover the same principles applying to both an overhand and underhand grip This is better for symmetry and you can even do one arm at a time if you wanted to Really make sure to stretch out the lats during the negative and squeeze your back at the end of the positive repetition To flex the muscle the rope pull through is great as well A closer rope grip will stimulate the lats Whereas the wider rope grip works the inner part of the back When using the closer grip you should feel more of an intense stretch in your lats during the negative repetition And an almost sharp contraction of the lats when performing the positive repetition The wider rope grip focuses on the rhomboids mostly and is a great isolation exercise I'm a huge fan of hyper extensions if you make sure to really squeeze your back at the top of the movement They don't just work your lower back, which is what most people believe You can stimulate the middle upper back and even some of the lats very effectively Usually I will use a 45 pound or 25 pound plate to do this But make sure you aren't stressing your glutes hamstrings or biceps a heavy weight Also makes it difficult to really bring your arms back and squeeze the upper back It's why I'm holding my arms up in the air really pulling my shoulders back to try to get that upper back contraction Deadlifts and partial deadlifts aka rack pulls are great for this as well Although they tend to stress the lower back more than hyper extensions If you restrict the deadlift to the upper half of the movement It puts more stress on the upper back as opposed to the lower back glutes and hamstrings I usually do these with 30 to 50 pound dumbbells Really trying to squeeze my upper back and pull my shoulders back at the top of the movement There is one exercise. I decided not to show here It's a dumbbell row that is done with a very light weight If you are using a light enough to moderate weight, you are able to take some stress off the biceps But this can really apply to any exercise. You could do that with just a lighter barbell row as well That being said those exercises will always stimulate the biceps to some degree So if you guys are looking to take some stress off of your arms during your back workout Perhaps you have some muscular imbalances. Maybe your biceps are big Definitely give these exercises a shot, especially if you've never done them before Thank you guys for joining me. If you could please like the video subscribe hit that bell icon and share the video if you can If you guys would like to support me further definitely check out frankies free range meat providing you with high quality nutrient-dense animal foods at an affordable price You can also go to frankies naturals dot com for minimal ingredients minimally processed hygiene and cosmetic products If you'd like to reach out to me for one-on-one consultations pertaining to fitness or health You can shoot me an email frank a to final at gmail.com Everything is linked down in the comments for you guys


That belongs to me. That belongs in a museum. Thank you very much, especially to Sean and Caleb. Pungy Spike Guide number two. You don't want to know what happened to Pungy Spike Guide number one. So thank you very much for coming. We want to start out asking the question, why does Sprockets need saving? So for those of you who haven't been around since May of 2011, it is the premier feature of Rails 3.1. And Sprockets is the asset pipeline. Sprockets actually came first before it was ever kind of wrapped up in Rails. So, whew, sorry, I got to catch my breath. That adventuring, it's like, man, you want the perils I had to go through? So a little bit about me. People call me shneems. And by people I mean me. I kind of made that my own name. It sounds a lot like schnapps. And as of today, I am not only a pretend hero, I am also a Ruby hero. Thank you. Thank you very much. What? Oh, air quotes, hero. There we go. I work for Heroku. And I do, before we want to get too carried away, I do want to say all likenesses to Disney-owned property, 100% coincidental. And it's parody, a.k.a.  You can call me by my real name, which is DHH. Send all complaints to professional race car driver care of DHHs. So also another thing I wanted to mention before we get too carried away is that you don't need to look like Indiana Jones in order to maintain open source. It just so happens that he is my sprockets spirit animal. Hey, it's OK. So from 2011 to 2016, sprockets has had 51 million downloads. And I'd like to put that in perspective. Rails has had 65 million downloads. So sprockets is pretty close. And of that entire library, one developer is responsible for 2027 commits, which happens to be about 68% of sprockets. That's one person. Compare in contrast to another Ruby hero, Rafael Franca, has 5,000 commits on Rails, which accounts for about 0.9% of Rails. Yeah, so 51 million downloads, one developer. And just one day, Josh is like, I'm cutting it. I'm out. I'm gone. So when something like this happens, what should we do? Should we, as a community, turn around? Should we abandon sprockets? There's a lot of people who said, I don't like sprockets. It's got problems. So that might say, well, hey, what are the problems? Do you know what they are? Because we can't fix what we can't define. And if we want to attempt a rewrite, then a rewrite would assume that we know better. We still have the same need to do things with assets. So we don't really know better. So yeah, I think we should stick with it. Assets are really the easy part of sprockets. There's a whole bunch of edge cases. Also, sprockets has a really well-defined and established API. So a quick show of hands. How many of you maintain code? How many of you have written code or seen code? Use any kind of code? OK, good, good. Now, how many of you want to maintain it forever? I'm glad that the number one contributor to Rails still is raising his hand. That's a good sign. That's a good sign. So I'm sorry, but losing maintainers is inevitable. And it's not always expected in this case. For those of you who don't know, this is Jim Wyrick. Jim Wyrick was the creator of a amazing library that we've all used called Rake. And in 2014, Jim passed away very suddenly. It wasn't like anybody saw this coming. He wasn't working with someone to pass on the software to for a long period of time. It was just, wow, it hit like a ton of bricks. And so whether somebody just walks away or they pass away, it hurts losing a maintainer. And we, as consumers of that, have to cope with it. And there's a lot of different ways that we do that. We might go through a period of denial and say something like, hey, they're gonna come back. We are gonna get this person back into our lives. You might be angry and say, hey, leaving is selfish.  You might also say, hey, there's tons of seats over on this side of the room. And you can totally go over there and sit down. I call that bargaining. So you might say something along the lines of maybe if we hire them, they'll work on it full time and we can get them to come back.  So the number one rule, my number one rule, because I'm on stage, I gotta make the rules, is that a maintainer does not owe you anything and not even an explanation. So if you're gonna leave a project or someone is leaving a project, it's a very personal decision. I actually reached out to Josh and said, hey man, let's talk about this. I'm giving a talk at RailsConf. I need some content, gotta help me out. And Josh didn't wanna talk about it. And I wanna respect that wish and I also wanna respect what he's done, which brings me onto the number two rule, which is that you do owe a maintainer respect. Some people will say things like, oh, but I really hate this thing. It is possible to critique a software without demonizing the creator.  And notice one word choice was intentional here is that I'm critiquing and I'm not criticizing. I aim to be productive with the words that I'm using. I want to find what is bad and then make it better. Originally, when sprockets kinda sort of fell into my lap and somebody said, hey, do you wanna be on sprockets core? I was like, sprockets. Why did that have to be sprockets? But you know what, you are not your khakis and you are not your software. You are also not your khakis. And Josh gave years of his life to this, no matter what you think of the project or you think of how it was maintained, as well as hundreds of issues closed.  All right, so I'm gonna need a lot less audience participation because I still have like 300 more slides to go through, which bring me to rule number three. Words without actions are empty. So I want you to be actionable and think about this. For example, we have a hacker news comment which says, unless they add blah, blah, blah, blah, I see this as ugly and barely usable to a Node.js release. You know, that's not really helpful. And when I read that, I'm like, boo, boo, boo, like that's not gonna make me wanna go out and help them. Instead, they easily could have said, hey, this is great, this is amazing, I love it. It looks like they don't have this thing I need. And as a matter of fact, because they don't have it, I can't use it because of this and that's actionable feedback. You can critique without criticizing. So I want you to ask yourself, is this comment adding anything? Hyperbole is like funny for like fake internet points, but it doesn't help. I want you to be honest with your critiques. I want you to be productive. Here is the Babel.js creator tweeted out the screenshot, again, coincidentally from hacker news.  I never thought I could hate something so strongly. Like, wow, that's really gonna encourage that guy to go out and fix all of your problems. You might disagree and you might have very strong opinions and those opinions might be very negative, but this software is in your life for a reason. And if you can figure out why those things, why you're having those opinions and those feelings, then it helps. Complaining by itself accomplishes nothing. So originally, I wanted to talk about how do we keep a maintainer longer? Or really, how do we wanna stay as a maintainer? If you are maintaining a product, how do you stay longer? But before that, I wanna talk about what do maintainers want? And do we really even want them to stay? Do we want them to stick around? We can either have a maintainer that just like mic drops and you like never see him again, or we can have somebody who's like passing the torch and graceful handoff. And while I'm working on sprockets, there's so many times that I'm just like, this is absolutely batshit insane. Like, this makes no sense. I'm gonna rip this all out. I'm gonna completely redo all of this and then six hours later, I'm like, wow, that was genius. And it's just, I didn't have that context. So maintainers are really historians and these maintainers, they help bring context. We try to focus on good commit messages and good pull requests. Change log entries, please, keep a change log. But none of that compares to having someone who's actually there.   Like, it's like, it doesn't store the context about the conversations around that. So maintainers are historians and we can keep those maintainers longer by giving them what do they want. Maintainers want respect, they also want help. And I know all of you are thinking, oh, this is the part where he's gonna be like, asking me to help and I really don't wanna do that. Or maybe you already are helping. Maybe you're saying like, I don't have enough time or like, ah, just fix all of the things for me. But I'm here to say that if you have five minutes to snap the face to force to gram, I had to practice that a lot. Then you have five minutes to help. You can contribute to docs. You can read the guides. You can fix typos. Maybe you found a really surprising behavior. Well, was that behavior documented? Then go ahead and add it to the guide. If you have five minutes to help, then you can submit a bug report. Like seriously, the maintainers have no clue that things are broken. You might be like, oh, there's thousands of people using Rails and all of them have reported this thing. No, like the question of like, why is Sprocket's bad? I don't know. Nobody actually gives me actionable bug reports. So if you have five minutes to help, then please let us know what your problems are in a productive way. Critique over criticism. Another thing you can do to help is sign up for a service that I wrote and maintain called Codetriage. You can go there at codetriage.com. And it will send you an issue in your inbox once a day. It's a very actionable way to get started. You can ask common questions like, oh, what version were you running on? Was this working previously? And so ask yourself the question, would you rather another maintainer spent the time either fixing bugs, or would you rather they spent the time asking for like insanely small minutiae on like GitHub issues? So if you give a minute, it might seem really small, but you were actually saving a minute of a maintainer's time. So a little bit of help can go a long way. And if you don't, then who will? It also has the benefit of exposing you to different parts of projects, which helps you grow as a developer. That's pretty good. If you have 10 minutes to help, include an example app to reproduce a problem. Example apps are amazing. I get all these bug reports that are like, well, first I run Rails new, and then I'm like, I try it, and then I'm like, couldn't reproduce. And then they're like, oh yeah, I forgot to add this other thing. And then I try it and couldn't reproduce. And then they're like, oh yeah, and can you try this other thing? And I try it and reproduce.  Instead you can ask and say, well you can say, here's an application that is going to reproduce my problem. Like I just went Rails new and here's an exact micro example of the thing that I'm experiencing. Put it on github.com, slash your name, your username, slash example app if you don't have that yet. You can even choose example app one or example app two. I'm not picky.  And I personally challenge you if you haven't already and you're not involved with an open source project, please try and make one example app this year. It is so helpful. If you have 30 minutes to help, you can try fixing a bug, anybody's bug or your bug. It's not as hard as it sounds, just time box it. Even if you don't fix it, then you're guaranteed to learn something. You're guaranteed to learn some other people's code.  And with all of this, I know you're like, okay, I don't wanna do that every time. And that brings me back to club soda. So I drink club soda at home and I don't like putting the whole thing in my refrigerator. So instead what I do is I put three or four in to get cold and then I pull one out and then I put one back in. And sometimes I run out of club soda. I'm like, how did this happen? Is somebody stealing my club soda? Is my dog drinking my club soda? You know, I'm one out, one in, pretty simple. And so what I found is that if I put two in every single time that I want to, that I actually remember, oh, I should put a club soda back in the refrigerator, I always have club soda. You got it? It all makes sense. So what I'm saying is you don't always have to contribute, you don't always have to make an example app, but just every once in a while, you have to, or please go the extra mile. So these are all different ways that we can help a maintainer, that we can make their job a little bit easier. Well, how do we transition from one maintainer to another maintainer? Like, that's a question that happened. Well, what is a maintainer? We talked about this. A maintainer is somebody who knows the stories.  They are going to take 10 minutes out of their day, 30 minutes out of their day. A maintainer is somebody who helps. And by the act of helping, we are preserving history. So maybe you see where I'm going with this. The act of helping is the answer to keeping a maintainer. Also the act of helping is also the key to creating maintainers. So whenever you actually go through that handoff process, people aren't just starting from zero. They have people who are involved with the project. So then the next question we have is, how can we foster a culture for helping? How can we get more people to help?  Like if you're using that project, you want more people helping and contributing because that makes it better. So how do we foster that culture for helping? We talked about what the maintainers want, but we never really talked about what the helpers want. Well, helpers want documentation. They want sane code. They want what regular users want. Good user experience, non-magical code, backwards compatibility, good deprecation, reliable test. These are all things that are interesting to them. So let's look at one and compare it on the sprockets chart. So documentation. Sprockets has 73% documented methods. 73% of all methods are documented. That's a lot. I don't know, that's like really high. That's like really up there. On a side note, I think that method documents are kind of like unit tests. They are very focused on one part and don't necessarily tell the whole story. So it is possible for those comments to get a little bit out of sync with reality. Yes, we've all seen code comments like this. So I also highly recommend keeping a read me. And a read me, I see as something more like an integration test. It's gonna tell a little bit more of the whole story. And if we look at sprockets, well, sprockets has about 2,500 words. That's a pretty long blog post. That's a pretty substantial read me. That's a pretty long story.  Why doesn't sprockets have anybody helping? So I put on my design research hat. I went to design research school and I learned about user stories. So we are gonna actually consider the people using our product. I want to introduce you to Pedro. This is Pedro. Pedro enjoys long walks on the beach, favorite food is bagel bites, and is building the next Uber for goldfish. Pedro is a Rails user. And Pedro cares about the Rails interface. This is gonna be, hey, how do I get one file to require another one? I wanna know what I actually have to type in my project to get it to work. Now, I don't care about all that other stuff. I just want the things I need now. So that was Pedro. We also have Pat. Pat is addicted to ES6. I know, it's tragic. Pat loves to fly fish and Pat is a plugin developer for sprockets. By the way, did you know that sprockets has plugins? Okay, well they're not called plugins. They're called processors and transformers and compressors and like 20 other things. But it does have a plugin system. And Pat cares about the processor interface. They maintain one. It looks a little bit something like this. And they wanna know that whenever we pass that hash of things to them, what is gonna be in it? And what can I do with it? What should I do with it? Pat wants this documented when Pat is working on their plugin. Conversely, Diana has a dog named Exception and hates mustard. Both of these are highly relevant to Diana's job as a Rails developer. I.e., somebody actually developing Rails. Somebody actually building Asset Pipeline or another Asset Pipeline. Diana cares about the low level interface. What does that mean? Diana cares about what are the classes I can use? What are the methods on those classes? Like all of them. I wanna be able to disable Gzip. Oh, here you go. So these are all different people with very different needs who need different documentation. Don't make them hunt down the documentation that they need. One of the things that I did when I first started Sprockets is I said, I basically kind of know nothing about the behavior. Somebody be like, is this expected? And I'm like, I don't know. You tell me, was it happening before? And through doing that research, I put together some guides. And I said, yes, this is expected behavior, but the only way I could make that fluid and make those guides make sense is if I split them out. And so we now have guides. We have a guide for building an Asset Processing Framework. So if you're building the next Rails Asset Pipeline or end user asset generation, if you are a Rails user, or extending Sprockets, if you wanna make one of those plugins. It's all right there. It's kind of right at your fingertips. And you only look at the documentation that you need when you need it. So we made it easier for developers to find what they need. Also, I mean, I was super useful for me as well. One thing I love about this is that it lives in the source and not in a wiki, because documentation is really only valid from one point in time. Like, otherwise you end up in the wiki. It's like, if you're using this version, do this. If you're using this version, do this. And it's like 20 versions and you're, it's no good. So helpers love contributing to docs. So you know what? We can make more docs. We can make our docs better. Those docs are gonna be the gateway drug to code contribution. So the next thing I wanna talk about is, is same code in real talk. Sprockets was designed to solve problems. And sometimes when it's putting out a fire, it kind of feels like it's maybe making additional problems that you didn't see before. And you don't know why it fails. And the reason you don't know why is because Sprockets isn't talking to you. Well, you know, how does code talk? So code can speak to you through errors.  No method error on nil. I'm talking about, here we have no route matches. I want my error to say this broke. I want my error to say ID key is missing. This is the thing you were missing. Hey, look here. This is the thing you should care about. Good errors are instructive. And Sprockets will have better errors. It doesn't yet. I do care about this. I am the owner of a gem called, wait for it. Sprockets better errors. That was merged into Sprockets Rails. But yes, I have some good ideas for better errors in Sprockets itself. The other way that we can speak is through deprecation. Now, deprecating something in a code comment is not enough. Right now, Sprockets is doing this. They have a little code comment and they're like, by the way, this method is now deprecated. We will just delete it and it won't be available and you never knew that because, A, no one is casually reading the method documentation. I mean, B, if they were, like who has the time? It's like every single time you upgrade every version of Sprockets, like are you gonna do that? No, and you cannot just sit there and break your API, asterisk, especially when you've got 51 million downloads. Like, that is kind of unacceptable. So your code knows when somebody's using a deprecated interface, well, guess what? We can yell at them. We have these things called deprecations. So Sprockets3.x will have deprecations before we go to Sprockets4.x. We have a branch, we've started on this. If you've not implemented deprecations in your own project, it's super simple.  Use this other thing that's not deprecated and here's where you were using it. It's kind of like a three-step process. So deprecations are gonna nudge people into the right behavior. They're gonna help get people to upgrade and they also help with API design. Because if you can't write a good deprecation, then guess what? The interface probably wasn't the best. For example, what if you just randomly changed a hash key? Well, it's like, oh, this hash key sounds better than this other hash key. Well, that's something that Sprockets did. Between two versions of Sprockets, and I don't even actually know which versions of Sprockets, these two things change. So now if you want to support both versions, you have to use both keys. And there was no warning.  You would just have to wait until your stuff broke and then maybe you would kind of figure out what was going on. Okay, this is my favorite section coming up. I hope you're paying attention. So Sprockets suffers from something that I like to call the God object problem. It has this one main class that has all of these concerns mixed in with it. It's one object with 105 methods. Like, it's using a lot of them. And you ask yourself like, where did that method come from?  Dependencies, digest utils, HTTP utils, MIME, server, resolve, loader, Bower, path utils, path dependency utils, path digest utils, digest utils, source map utils, URI utils. This is my personal favorite. Sprockets utils is mixed into Sprockets compressing, which is then mixed into Sprockets configuration, which is then included in Sprockets base, which is inherited by Sprockets environment, which is then wrapped and cached by Sprockets cached environment. So yeah, that's all I have to say about that. It's impossible to just glance at something and know how things are interacting. Like, you change this one method that you thought was only being used in this one part of the project and it's not true. For more information about how Sprockets work, I highly recommend you go to Raphael's talk, which was yesterday. So you can catch it on Confreaks. What is the solution to God Objects? Well, we can move logic over to helper classes. This is an example of a class that I introduced while I was adding new functionality. It takes a absolute path and trims it down to a relative path, or it can take a relative path and make it an absolute path. We need this for storing things in the cache. The beautiful thing about this is it has a couple of extra methods that make it a little bit cleaner that are not actually exposed to that God Object API. We can expose only the things we need. So it's gonna minimize that God Object API and it also hopefully produces small, easy to read files. You can look at that file and say, oh, you're ITAR, like, I vaguely understand a TAR can expand or compress a file. Like maybe it's related to that, for URIs. Ideally, this produces readable code and readable code also attracts helpers who read code, believe it or not. As a side note, I will say that Ruby is object oriented if you're not super comfortable with objects and classes. Please spend a little bit of time there. It's totally worth checking out. Sandy Metz has a book that I have totally not read, but she's given a ton of conference talks, which I have seen and you should watch the talks and I am sure the book is amazing as well. As well as Katrina Owen has done a ton of refactoring talks. If you wanna see, oh, how can we make this better? How can we make this more readable? As well as exorcism.io is an actual place where you can go and try out your skills. You actually refactor things there. It's pretty cool. So helping takes commitment and we do need to respect that. How are different ways that we can respect that commitment? Well, when somebody gives a pull request, even if it's not the best pull request, we as maintainers can say, hey, thanks for submitting this. You know, that person cared enough and you can help them to help you. You can say, well, explain the reasons why you're not merging it or help them to get to a place where you can merge it. You can also say, oh, you're working on this thing. I don't really care about that thing. How about you look at this other thing, which I really care about. And it's a way to get them on board. Don't, I mean, if you just like close and dismiss and like lock, that is not how you attract people to help you. So what else do people want? Maybe people want recognition. Rails has this great leaderboard. That's actually the reason why I gave, I had my first commit onto Rails is I was like, I wanna just be on the board, period. And maybe you don't have a leaderboard, but you can still give recognition. Maybe your helpers want pizza. There's a fun story where when I introduced a test, or I introduced a feature, I actually broke windows on a minor release of sprockets and I had a developer come to me and be like, hey, you broke windows and I'm like, yeah? How do I do fix this? And he said, well, here's the problem. We worked through it. We pushed out a release and I said, okay, obviously you care about sprockets. You care about windows. You have a windows machine. Can you help me get the windows tests, or the sprockets tests running on windows? And he was like, I don't know. It sounds really hard. And I'm like, I would be so appreciative. I would buy you a pizza if you did this. I will send you a pizza. I'm not joking. That's not like hyperbole. Like I will actually get like order delivery for you for a pizza to your home. And I was like, well, a couple of weeks later, he did it. He did not happen to, in those couple of weeks, reach out to me and been like, hey, by the way, I live in Germany, but he did. And if you wanna ask me questions later about sending a pizza to Germany and explaining to your credit card what open source is, I'm happy to do that. So I would also say, we mentioned acknowledgement. Well, thank you. Thank you, Daniel, for this. And sprockets is now tested on windows CI. So it's good. So those are kind of ideals and things that you might want in your project and can make helpers a little bit more comfortable. What happens when you actually have this scenario? Where are you inherited that project? Where the previous maintainer might not have done all of these things. They just might drop. What are you gonna do? Well, you can start by finding something that needs fixing. I call this bug driven development. And really the only way, I keep on talking about example apps because the only way to get started is with an example app where you can reproduce the problem. And if I didn't mention example apps are amazing and you should probably make it a goal to make one this year. So reproduce the problem and then repeat. Every single bug that you fix you're gonna learn a little bit something more about the code base. And eventually you're gonna start seeing non-bug problems. Eventually you're gonna be a lot more comfortable. Well, okay, what happens when you get a bug report? So source maps in Sprockets 4 is a thing and it was half finished when Josh stepped away. And when I got this project I was like, hey, what's a source map? And somebody would report a bug. I would try and fix that bug and it made the test break and I'm like, okay, well, are the tests that are there even reliable? I've got no clue. So where do we start? I put on my archeologist hat which was totally the inspiration for this talk. And I started research. So I looked at the Mozilla RFC. I got out a Evernote and I started taking notes. And I learned a whole lot about source maps. I eventually was able to take those notes and actually turn them into guides. So I took all that information. I said, I don't know this so other people probably don't know this. Let's not make them work. I made that as a guide and I put it in Sprocket's source tree. So even if you're interested in, it's totally rough but it works. And I can use it as a future reference. So if you wanna know what a source map is then I can tell you, go read my guide. Seriously. In this process I ended up having to borrow from some other projects where I actually used other projects from the technology, which shall not be named. So I use Uglify.js to verify my encoding and make sure that my encoding tests were valid. I use a source map to verify that my decoding was testwork correct and then I got the test to a place where they could pass. So is it finished? No. I need more bug reports and I need more actionable bug reports. So with all of that, all that being said, where do we go? Because maintainers won't be around forever. I won't be around forever. Raphael apparently will be around forever. I actually have a sneaking suspicion that Raphael is actually secretly like 100 robots. Think about it, have you seen 100 robots and Raphael in the room at the same time? It's proof right there. So I need help. And I need help maintaining the history of sprockets. I don't necessarily need you to know everything. I don't necessarily need you to go out there and fix all of the problems. You know what, sometimes I might just need you to help not say bad things about me. If somebody's like, hey, trash talking, it's like, listen, listen. Let's critique. So we can preserve these stories by getting involved. And if you don't get involved, then who will? It's open source. You can say, well, maybe somebody else will. Guess what? That somebody else is you. And we all need to step up. We can take five minutes. We can just read those guides. We can write some docs. We can open some issues. We can create example apps, which I've totally not mentioned before. Only five minutes. So I please invite you to join me and together. We can become maintainers. We can become helpers. And together we can save sprockets.