• Member Since 6th Jul, 2015
  • online

Silk Rose


Go read The Sky Is Your Bed!, by 6-D Pegasus | Rainbow Dash is best pony! | Bio updates daily!

More Blog Posts79

  • Thursday
    There

    I knew exactly what the tone was going to be when I thought of the name. I liked this one a lot. I think it did pretty well for not getting featured.

    Both stories for May are written and just need proofread. I'm very excited for both of them!

    Read More

    1 comments · 47 views
  • 2 weeks
    Ticking Love

    This story was a speed write from a few months back. Finally finished it up and got it posted. I think it turned out good.

    I'm liking this two story a month release schedule. It's going good, and I feel like I can handle it so far. My next story is written and just needs to be proofread.

    Thanks for reading!
    :rainbowdetermined2:

    0 comments · 36 views
  • 4 weeks
    The Exploding Story Code Overview

    EThis Story Did Not Explode
    Phew! That was a close one.
    PseudoBob Delightus · 6.6k words  ·  287  14 · 6.5k views

    Prelude

    Read More

    7 comments · 136 views
  • 5 weeks
    New Translation, Readings, and Auto-Updating Bio

    As you might have noticed, my bio on my profile has started changing each day. This is because I've written code to automatically recommend other author's stories. The stories are not ones I have picked, but the author themselves. I simply asked for their most proud work and their best pony.

    Read More

    3 comments · 70 views
  • 6 weeks
    Plush Pondering

    I had fun writing this story, a nice refresher after Cardboard Casket.
    The plush in the cover is mine, I've had her since 2013.
    And yes, I do strap her in a seatbelt when she comes with me.

    Read More

    1 comments · 48 views
Apr
6th
2024

The Exploding Story Code Overview · 1:15am April 6th

EThis Story Did Not Explode
Phew! That was a close one.
PseudoBob Delightus · 6.6k words  ·  287  14 · 6.5k views

Prelude

Before I start the code overview, I wanted to say thanks to everypony who commented on or read the story. It was really fun to work on with PseudoBob Delightus, we both really appreciate the response the story has gotten.

I should also explain what happened, in case you weren't there for the event.

On April 1st, 2024, PseudoBob released a story with a simple title: This Story will Explode in 24:00. And every minute the title counted down, 23:59 → 23:58 → 23:57, etc.

On every hour, a new chapter was released, and the cover was updated. The description and short description were also updated regularly. If you paid close attention, you might have even found some Easter eggs.
:raritywink:

I had a lot of fun writing the code for this story, and even more fun watching it play out. I'm also really thankful that the code I wrote never failed. The code I wrote for this won't even be thrown away, the whole time I knew this code would be repurposed into an automatic story poster, so look forward to that.

Another thing to look forward to: next year. We are already plotting and scheming our next art installation. I'm excited for it already, and we haven't even decided what it is.

Last thing before we get into the code talk, if you want to read more about Bob's side of the story, check out his sister blog to this one here. And if you have questions, feel free to ask either of us questions here in our Q&A forum post.

Code Overview

The code has four mane packages to make it work:

  1. FIMFiction Cookie: to get a cookie for use with the cover updater.
  2. FIMFiction Cover: to update the story's cover.
  3. Clock Timer 2 (wiwi): to keep all the events running at the right time.
  4. FIMFiction April Fools: to bring it all together and send out API requests.

We will go over these one at a time.

FIMFiction Cookie

This one is really straightforward, it uses a browser emulator to log in to your FIMFiction account and saves a cookies.json file in the root of the project directory.

There are no command line arguments, it asks for your username or email, and password in the console.

Please note: the cookies.json file should be kept secret, for it can be used to log in to your account.

This package was written in Typescript and is 59 lines of code.

FIMFiction Cover

This one is also pretty straightforward, but a little more complex. It uses the cookies.json file and opens a page in a browser emulator, going to the stories manage page before hitting the Browse... button, then entering the file path of the new cover. After that, it clicks the Save Changes button, and waits for the page to refresh before closing.

It has the following command line arguments:

  1. Story ID.
  2. File path to the new cover.
  3. File path to the cookies.json file.

This package was written in Typescript and is 92 lines of code.

Clock Timer 2 (wiwi)

Internally, a clock timer stores a time (ex. 1 Apr 2024 at midnight UTC), and an interval amount (ex. 1 minute), and exposes a way to get the next time in the interval. Every time it's called, it adds the interval amount to the stored time (ex. 00:00 + 1 minute = 00:01), and checks if the current time is before this new calculated time. If it's already past, then it returns to this new time without waiting. Otherwise, it'll wait until he calculated time is reached, then returns it. What this achieves is a way to halt the program until certain time intervals. The program can then do things with this time information, like call FIMFiction API every minute to update the title.

~ Meadowsys

This module is a part of the crate wiwi, was written in Rust, and is 186 lines of code.

FIMFiction April Fools

This part is where I will go into greater detail, since this is the mane part of the whole event.

Let's start with the command line arguments:

  1. API Access Token.
  2. Path to arguments.json file.
  3. Path to events.json file.

API Access Token

This is required to make any call to the FIMFiction API. To get a token, you must authenticate with an application on the API. Getting an application requires knighty's approval, and can take time.

Please note: your access token should be kept secret, for it can be used to do stuff on your account, and they never expire.

Arguments.json

This file contains the mane arguments so that the command wasn't so long.

This file contains the following data:

  1. Story ID: number

    • Self explanatory. ID can be found in the URL of your story.
  2. Start time: number

    • The Unix Epoch time of when you want it to start the event.
  3. Skip past events: boolean

    • Whether to skip past events, like if the program was interrupted.
  4. Duration hours: number

    • The number of hours the entire event lasts.
  5. Interval minutes: number

    • How often in minutes the clock ticks.
  6. Countdown duration hours: number

    • The hours to countdown in the title.
  7. Covers directory: string

    • where all the cover files are stored.
  8. Cover mane.js: string

    • The mane script file for FIMFiction Cover.
  9. cookies.json path: string

    • The cookies.json file path gotten from FIMFiction Cookie.

Events.json

This file is where we store all the event data for when to update the cover, release chapters, change the description, short description, and title, and set the story status to complete.

This file contains an array of these events. Each of these events is an object.

Think of an object like a box, it contains stuff you want to use later. Think of an array as a box of boxes. It contains a collection of boxes of stuff you want to use later.

Here is an example of what one of these event object's looks like:

{
	"release_hour": 2,
	"release_minute": 30,
	"chapter_id": 1738301,
	"cover": "cover-2-30.png",
	"description": "I agree with you, Pinkie is super cute!\n\nI love to give Pinkie lots of hugs!",
	"short_description": "Pinkie is cute!"
}

In this example, when the elapsed time is 2 hours and 30 minutes, it will release the chapter, change the cover, and update both the description and short description.

It is worth noting that the only required fields are release hour and release minute. So, it's very customizable for how you want to do your events.

Now that that is explained, we can move onto the mane function.

Code Overview

The first step is to initialize our variables, all the stuff we just talked about gets read into the application and stored.

After this, we use the story ID to set the URL for use with the API. We set the chapter URL, but for now, it's just static, since the chapter URLs we use for the API will be constructed later.

We need to create an HTTP client next, but we use a crate to do this for us. Crates are code written by other people for use in any project. We are using the reqwest crate for the HTTP client.

An HTTP client is like a phone, it can send and receive requests. We will be using it to send requests to the API to update the story.

Now that we have a client, we need to construct our headers. Headers are instructions for your requests. An example would be, this side up labels on the side of packages being shipped.

Another header we create is for our access token, so the API knows we are who we say we are.

We construct the timer next. We create a timer using the wiwi crate. A part of creating the timer is setting its start time, duration, and tick interval.

Once we have the timer setup, we wait for the first tick. If the program is started before the start time, it waits for the first tick.

Each tick is a cycle of the timer, waiting for the time to be passed, then firing the code we put in the loop.

We make this loop using the timer, telling the code to wait for the timer to do each loop.

In this loop, we determine if the countdown is still active with this code:

let countdown = tick.remaining().num_minutes()
	>= (args.duration_hours - args.countdown_duration_hours) * 60;

The timer's tick variable gives us a lot of useful information, such as elapsed and remaining time in hours and minutes, and more.

Inside this loop, we call the handle_events function. We pass it all the data it needs to handle events, including the countdown variable from above.

The first thing we do in this function is some math:

let elapsed = tick.elapsed();
let remaining = tick.remaining();

let remaining_hours =
	(remaining.num_minutes() / 60) - (args.duration_hours - 
	args.countdown_duration_hours);

let remaining_minutes = match remaining.num_minutes() % 60 == 0 {
	true => 0,
	false => {
		remaining.num_minutes()
		- ((args.duration_hours - args.countdown_duration_hours) * 60)
		- (remaining_hours * 60)
	}
};

We use this math to determine the time left for the countdown timer.

The next thing we do is check if countdown is true or not, if it is, we set the title:

let title = match countdown {
	true => Some(format!(
		"This Story will Explode in {:0>2}:{:0>2}",
		remaining_hours, remaining_minutes
	)),
	false => None,
};

The :0>2 bit pads the numbers so they are always 2 digits long, using 0 as the padding digit.

Now we are going to filter the events array from earlier. We want to find all events that match with the current tick of the timer.

let events = events
	.iter()
	.filter(|event| {
		event.release_hour == elapsed.num_hours() as u32
			&& event.release_minute
				== (elapsed.num_minutes() - (elapsed.num_hours() * 60)) as u32
	})
	.collect::<Vec<_>>();

If this events array is empty, we simply update the title if countdown is still true; otherwise it does nothing. If it does find matching events, then it executes each of them.

Title, description, short description, and completion status all can be sent in one request, so we gather all of those present into an object.

We send this object as the body of the HTTP request to the stories URL from earlier.

If chapter ID is present, we make a separate request to the chapter's URL, adding the ID to the end of the URL. We construct the chapter object like so:

fn chapter_json(id: u32) -> Value {
	json!({
		"data": {
			"id": id,
			"attributes": {
				"published": true
			}
		}
	})
}

If cover is present, we construct the node command to update it like so:

let command = format!(
	"node {} {} {} {}",
	args.cover_mane_js, args.story_id, cover, args.fimfic_cookie_json
);

In this command, one of the things that I forgot to do was include double quotes around each {}, this would prevent word splitting, which caused file paths with spaces to pass the wrong arguments to the program.

This was the only bug encountered with the code during the whole event. After the first cover didn't update, we discovered why, and Bob just moved the covers to a new path without spaces in it.

That's essentially it, the program just loops every interval and runs the relevant code for any events on that tick.

This application was written in Rust and is 254 lines of code.

Thanks for reading, and let me know your thoughts in the comments.

If you have any questions, you can ask them in our Q&N post here. Remember to check out Bob's blog here.

Stay safe as always!
:twilightsmile:

Report Silk Rose · 136 views · #Update
Comments ( 7 )

I love how you were consistent with changing "main" to "mane".:rainbowlaugh:
Not gonna pretend I understood it all, but I appreciate the explanation. Thank you so much for being a part of this, Silk Rose.

Rego #3 · 4 weeks ago · · ·

Me trying to read the explanation:
derpicdn.net/img/view/2020/10/14/2466770.png
Amazing work either way, even if I don't have the patience to understand what went into it. I appreciate everything you did! Great job, Silk.

It's ok Silk, I understood it. And it's still impressive!

:yay:

I, as a programming hobbyist, am truly very impressed! :raritystarry:

I'm sorry I missed this one on the day but will absolutely go back and check it out.

Very neat piece of code. :)

I know just enough about computers to understand maybe 20% of what I just read, but most of it goes way over my head. (Hey, that rhymes! Fun times.)

I just wanted to say Thank You for your part in this madness awesome experience. Have a cookie and a Spikestache.
🍪:moustache:

Login or register to comment