<Brooke> <Codes> https://brooke.codes The Tech Blog of Brooke. Tue, 09 Dec 2025 09:01:35 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 Delete All Twitter (X) Content https://brooke.codes/2025/12/07/delete-all-twitter-x-content/ Sun, 07 Dec 2025 19:55:00 +0000 https://brooke.codes/?p=1781 This weekend, I decided to finally take the plunge and delete all content in my Twitter account. While deleting the account itself would have been a simpler solution, I have had issues in the past where my former username was used to impersonate me and spam others. The first search results are for Twitter apps […]]]>

This weekend, I decided to finally take the plunge and delete all content in my Twitter account. While deleting the account itself would have been a simpler solution, I have had issues in the past where my former username was used to impersonate me and spam others.

The first search results are for Twitter apps with… questionable… marketing practices. The apps claim to delete up to 500 tweets for free, but don’t actually offer those plans. This is likely in part due to X’s API changes, but nonetheless, I wanted to post about a free way to delete all tweets and remove followers.

Safety First: Request Archive

The first step is to request an archive if there is anything you want to save. Note that it may take a couple of days to prepare your backup. So do this first.

Prepare your console.

For safety, pasting into the browser console is disabled by default in most major browsers like Firefox and Chrome; you may need to type allow pasting to paste JavaScript into the console. This is your reminder to be careful with pasting scripts in general!

Delete All Tweets

Chris Smith’s method of deleting all tweets worked well for me. I had to run it a few times since I had a few thousand tweets. To run the script, you visit /profile while logged in and then run the script in the console.

Remove all Following

The unfollow script worked well, but had a really low rate limit. I had to run the script lots of times, about every 100 followers or so. However, you visit /profile/following/ and run the script.

Remove all Followers

Before I wanted to delete my account, I didn’t even know it was possible to remove followers. This may be a newer feature since I left the site. It should be noted that this doesn’t prevent folks from re-following, but it does help with emptying out an account. If you wanted to prevent refollow, you could block instead of unfollow. To accomplish the unfollow, I vibe-coded this script. I’m sure it could be improved, but it did what I needed it to. Like the remove following script, I did run into rate limits.

In Conclusion

I hope this helps others looking to reduce their Twitter/X footprint.

]]>
Quick Tip: Stop Forum Spam https://brooke.codes/2025/12/02/quick-tip-stop-forum-spam/ Tue, 02 Dec 2025 21:38:00 +0000 https://brooke.codes/?p=1769 Akismet is a great tool for fighting spam, however due to licensing and call limits I was looking to decrease the number of calls to Akismet. In my search for alternatives I stumbled upon Stop Forum Spam. For a free project, I have been impressed with their accuracy. While primary focused on, well forums, they […]]]>

Akismet is a great tool for fighting spam, however due to licensing and call limits I was looking to decrease the number of calls to Akismet. In my search for alternatives I stumbled upon Stop Forum Spam. For a free project, I have been impressed with their accuracy. While primary focused on, well forums, they have an API.

Other alternatives exist but for now I have been happy with the combination of Aksimet and Stop Form Spam combined with judicial word and IP blocks when needed.

Spam Strategy

While my exact spam strategy is somewhat depending on the content type. I often use a PHP library for Akismet in combination with a word block and/or IP block list.

For my most recent project the strategy looked like this:

  • First check the input against an IP list. While IP blocks are a cat and mouse game, I use it to block known spammers. I’m talking about IPs that have made more than five known spam attempts in the last fourteen days get blocked for two weeks.
  • Next, check the content against a known word block list. Again this is the low hanging fruit for the crypto and link spammers who often uses the same words or phrases in their spam.
  • Once content passes both of those, the data is checked against the Stop Forum Spam database.
  • Finally, if that passes, then check the content with Akismet.

I found this catches about 98% of spam. All entries require manual approval, but catching spam means less moderation.

PHP Example code

Here is an example using the Stop Forum Spam API with Symfony HTTP Client. A similar method should be possible with any Request package. For Akismet I’m using the Omines Akismet Package as I can use the same Request package.

One quirk of the API is that the blocklists sets the frequency field to 255, and the lastseen date to the current time (UTC). I am being somewhat conservative here and checking if the lastseen is within the last hour.

/**
     * Check IP / email / username against Stop ForumSpam database
     *
     * @param string | null $ip IP address to check
     * @param string | null $email Email to check
     * @param string | null $username Username to check
     * @param int $threshold Confidence threshold(0 - 100, default: 75)
     * @param bool $checkTor include TOR exit nodes in spam detection(default: false)
     * @param bool $checkBlacklist include blacklisted entries(default: true)
     * @return bool | string false if clean, or string with reason if spam detected('confidence' | 'tor' | 'blacklist') {
     */
    private static function stopForumSpamLookup($ip = null, $email = null, $username = null, $threshold = 75, $checkTor = true, $checkBlacklist = true)
    {
        $client = HttpClient::create();

        $params = ['json' => '1', 'confidence' => '1'];

        if ($checkTor) {
            $params['badtorexit'] = '1';
        }
        if ($checkBlacklist) {
            $params['nobaduser'] = '0';
        }

        if ($ip) {
            $params['ip'] = $ip;
        }
        if ($email) {
            $params['email'] = $email;
        }
        if ($username) {
            $params['username'] = $username;
        }

        try {
            $response = $client->request('GET', 'http://api.stopforumspam.org/api', [
                'query' => $params
            ]);

            $data = json_decode($response->getContent(), true);

            if (!isset($data['success']) || $data['success'] != 1) {
                return false;
            }

            foreach (['ip', 'email', 'username'] as $field) {
                if (isset($data[$field]) && $data[$field]['appears'] == 1) {
                    if ($checkBlacklist && isset($data[$field]['frequency']) && $data[$field]['frequency'] == 255 && isset($data[$field]['lastseen'])) {
                        $lastSeen = strtotime($data[$field]['lastseen']);
                        $hourAgo = time() - 3600;
                        if ($lastSeen >= $hourAgo) {
                            return 'sfs_blacklist';
                        }
                    }

                    if ($checkTor && $field === 'ip' && isset($data[$field]['torexit']) && $data[$field]['torexit'] == 1) {
                        return 'sfs_tor';
                    }

                    if (isset($data[$field]['confidence']) && $data[$field]['confidence'] >= $threshold) {
                        return 'sfs_confidence';
                    }
                }
            }

            return false;
        } catch (Exception $e) {
            return false;
        }
    }

]]>
Some Reflection https://brooke.codes/2025/03/28/some-reflection/ Sat, 29 Mar 2025 06:08:18 +0000 https://brooke.codes/?p=1695 I spent some time this week moving this site from WordPress.com to a self-hosted WordPress instance. As part of that process I ended up reviewing this blog which has content going back from 2011. The older content is a lot of code snippets, and plugin releases which has me thinking about how the internet has […]]]>

I spent some time this week moving this site from WordPress.com to a self-hosted WordPress instance. As part of that process I ended up reviewing this blog which has content going back from 2011.

The older content is a lot of code snippets, and plugin releases which has me thinking about how the internet has changed so much over the past 15ish years. We have moved to systems like GitHub and Reddit and that long form blogging is mostly a thing of the past. I understand the appeal of having all the content in one place, but it brings back the age old question of who owns and controls our data.

For me, I remain a fan of open technologies (like git itself) and plan to bring back this blog. In fact, the process revealed I have lots of posts in draft form here, so more to come, stay tuned!

]]>
Introducing Soapberry for Ackee https://brooke.codes/2019/12/29/introducing-ackee-wp/ Sun, 29 Dec 2019 23:18:40 +0000 https://brooke.codes/?p=1081 Update: this plugin was originally called Ackee WP but has been renamed to Soapberry to comply with the WordPress.org trademark policy for plugins. The plugin can now be found on WordPress.org under the slug Soapberry. As part of my desire to own my data, I haven’t used Google Analytics for the past few years. In […]]]>

Note: This post refers to code and a project from many years ago 😱. The content was edited in March of 2025 to remove dead links, improve clarity, or fix formatting, but no other edits were made. Enjoy this time capsule into the past.

Update: this plugin was originally called Ackee WP but has been renamed to Soapberry to comply with the WordPress.org trademark policy for plugins. The plugin can now be found on WordPress.org under the slug Soapberry.

As part of my desire to own my data, I haven’t used Google Analytics for the past few years. In that time I’ve been curious about my site statistics but knew when I resumed collecting data I wanted to do so in a way that respected the privacy of my visitors.

This year I started a search for self-hosted tracking solutions and came across a lightweight node application, Ackee. After looking at a few other options I decided on Ackee for its care in anonymizing user data. Through hashing the user’s IP, a unique domain ID, and a salt which changes daily, site visits can be tracked without tracking the individual visitors.

For my needs, I want to know how many visits my sites are getting, where visitors are coming from, and how long they stay on the site. However, I do not have a need or desire to track individual visitors.

Using Ackee with WordPress

After setting up an Ackee instance and adding the tracking script to a few static sites I wanted to bring the functionality to my WordPress sites. At first, I just edited the theme’s footer.php file which worked well enough as a quick way to insert the script. Next, I hooked into wp_footer() so it would be easier to exclude logged in visits from the analytics.

While both of these methods work they do require a bit of WordPress know-how and do not carry over when switching themes. Wanting a better solution, I got to work writing Soapberry a WordPress plugin that adds the Ackee tracking script and data attributes to the site’s footer based on settings saved on a WP Admin page.

Keeping things simple at first, this first version of the plugin only has the ability to exclude all logged-in visitors and does not take into account the personal data options provided by Ackee. In the future, you may be able to exclude visits by role and enable opt-in tracking for personal information.

Exploring Ackee Alternatives

If after looking at Ackee you don’t think its right for you that’s okay. Ackee won’t be right for everyone. The good news is there are other options when looking to move away from Google Analytics, Facebook Pixel, or other third-party tools.

Chris Wiegman wrote a post on anonymizing and tracking visits at the server level to avoid the JavaScript requirement of many trackers. The folks over at Awesome Open Source also list several other Analytics tools that can be explored. If you find a tool you like let me know what you are using in the comments.

]]>
WordCamp Seattle 2017 Slides https://brooke.codes/2017/11/05/wordcamp-seattle-2017-slides/ Sun, 05 Nov 2017 18:55:42 +0000 https://brooke.codes/?p=1006 This weekend I have the pleasure of speaking at WordCamp Seattle offering some advice from all parts of support. Here are the slides from my #wcsea talk, Help Us Help You, things you should know before contacting support. Once the video is on WordPress.tv that will be posted here as well.]]>

This weekend I have the pleasure of speaking at WordCamp Seattle offering some advice from all parts of support. Here are the slides from my #wcsea talk, Help Us Help You, things you should know before contacting support. Once the video is on WordPress.tv that will be posted here as well.

]]>
Light up the sky https://brooke.codes/2017/07/02/light-up-the-sky/ Mon, 03 Jul 2017 03:12:36 +0000 https://brooke.codes/?p=740 I recently added a WRGB LED strip to the top of my bookshelf using WS2811 LEDs or as my friends at Adafruit call them NeoPixels. I chose RGBW LEDs so I could get a nicer white and use less power than a traditional RGB strip. Parts List: Required: Optional: Setting up the Hardware The first step […]]]>

Note: This post refers to code and a project from many years ago 😱. The content was edited in March of 2025 to remove dead links, improve clarity, or fix formatting, but no other edits were made. Enjoy this time capsule into the past.

I recently added a WRGB LED strip to the top of my bookshelf using WS2811 LEDs or as my friends at Adafruit call them NeoPixels. I chose RGBW LEDs so I could get a nicer white and use less power than a traditional RGB strip.

Parts List:

Required:

  • NeoPixel Strip ( https://www.adafruit.com/product/2837  )
  • Particle Photon ( https://www.particle.io/products/hardware/photon-wifi-dev-kit )
  • AC/DC Power converter with 5V output. Lots of options here, make sure it’s powerful enough for your strips’ length. I used https://www.amazon.com/gp/product/B01B1QKLR8/ )
  • 470Ω Resistor
  • 1000µF (6.3V or higher) capacitor
  • Wires.
  • Breadboard or blank circuit board

Optional:

  • Seeed Particle Photon Base Shield ( https://www.seeedstudio.com/Particle-Photon-Base-Shield-p-2598.html )
  • Grove Connectors

Setting up the Hardware

The first step will be to wire up the power and data pin to the LED strip. You can use a traditional breadboard or for a more permanent solution solder directly to a blank circuit board. I’m using a Grove Photon shield and Grove connector. I find this gives me the clean connections I’m looking for without requiring direct soldering onto the Photon.

Adafruit has put together a great guide on NeoPixels so I won’t be going into too much detail here. While the whole guide is great I particularly recommend the part on Powering NeoPixels which will help you determine how large a power supply you need and other safety concerns. 

I used D2 but any available data pin will work. Going to the strip the capacitor will be placed between the Ground and VCC ( + ). This makes sure that the strip doesn’t get too much power.

The resistor is placed between the D2 pin and the strip. Like the capacitor this prevents the board from getting too much power and overloading.

One important note is to make sure you are sharing the common ground between the Photon and the LED strip if you forget this step the strip won’t be able to be controlled by your board.

photon_LED_bb.png

Here’s what my final board looks like. With the Grove connector between the ground and data connections.

IMG_20170702_1713562

Here’s an image of the Photon. My particular device had its USB port snapped off (oops) so I used a Photon Power Shield to add back in the USB power. This also gives me the option of powering the device directly from my DC Power supply if I wish.

Photon

I also went ahead and added a connector to the end of the LED strip and shink wrapped the connections. This makes for a nice clean setup.

Setting up the Firmware

I am using the NeoPixel Library by Adafruit and the Particle Cloud API to send and receive data  to/from the strip. You’ll find the full code on GitHub.  You’ll want to flash this onto the Photon. Feel free to customize the firmware to meet your needs. The main things you may want to change are the defines at the top:

// Input pin for LED Strip
#define PIXEL_PIN 2

//Total number of pixels
#define PIXEL_COUNT 58

// Your Pixel Type (in my case RGBW)
#define PIXEL_TYPE SK6812RGBW

Web Interface

I wrote a custom web interface called MissionControl that I may write more on later which controls several IoT Devices in my home including the LED Strip. Here I’ll go over some of the basics as they pertain to the LED strip itself.

Screen Shot 2017-05-28 at 1.38.47 PM

Cloud Variables and Functions

I’m using two particle.functions ( POST )  and two particle.variables ( GET ). One for the color and one for the brightness.

Functions
hex  — Accepts an eight digit hex value (RGBW) to be used to set the color. For example: FF000000 is Red.
bri — Accepts an int value 0-255 where 0 is off and 255 is 100% bright.

You’ll also need to send your access_token  to make the POST request.

// POST Request URLS
https://api.particle.io/v1/devices/DEVICE_ID/hex/
https://api.particle.io/v1/devices/DEVICE_ID/bri/

Variables
curBri — Used to retrieve the value of bri returning a 0-255 value.
curHex — Returns the value of hex which is an 8 character string.

// GET Request URLs
https://api.particle.io/v1/devices/DEVICE_ID/curBri/
https://api.particle.io/v1/devices/DEVICE_ID/curHex/

Usage:

If you’re using jQuery like me your code may look something like this:

//Partical.io Photon setup
particleDeviceID = "1234567890";
particleDeviceSecret = "ABC123DEFG4567";
particleAPIServer = "https://api.particle.io/v1/devices";

function setLedColor(id,token,hexValue,successMessage){
var postURL = particleAPIServer + "/" + id + "/hex/";

$.ajax({
type: 'POST',
url: postURL,
data: {
args: hexValue,
access_token: token
},
dataType: "json",
success:(function(data){
if (data['return_value'] == "1"){
ajaxSuccess(successMessage);
}
}),
error :(function (){ ajaxFailed();}),
complete :(function(data){
if (data['return_value'] == "1"){
ajaxSuccess(successMessage);
}
}),
});
}

function setLedBrightness(id,token,brightness,successMessage){
var postURL = particleAPIServer + "/" + id + "/bri/";

$.ajax({
type: 'POST',
url: postURL,
data: {
args: brightness,
access_token: token
},
dataType: "json",
success:(function(data){
if (data['return_value'] == "1"){
ajaxSuccess(successMessage);
}
}),
error :(function (){ ajaxFailed();}),
complete :(function(data){
if (data['return_value'] == "1"){
ajaxSuccess(successMessage);
}
}),
});
}

function getLedBrightness(id,token){
var getURL = particleAPIServer + "/" + id + "/curBri/?access_token=" + token;
var result = "";
$.ajax({
type: 'GET',
url: getURL,
async: false,
success:(function(data){
result = data['result'];
}),
error :(function (){ ajaxFailed();}),
});
return result;
}
//returned on fail
function ajaxFailed() {
$(".results").stop().fadeIn().html("Error Occured, Please Try Again.").removeClass().addClass("results alert-error").delay(3000).fadeOut(2000);
}
//returned on success
function ajaxSuccess(status){
$(".results").fadeIn().html( status ).removeClass().addClass("results alert-success").delay(3000).fadeOut(2000);
}

Later in your JavaScript you’ll uses the above functions like the following:

GET the Brightness

startBrighness = parseInt(getLedBrightness(particleDeviceID,particleDeviceSecret));

SET the Brightness

startBrighness = parseInt(getLedBrightness(particleDeviceID,particleDeviceSecret));

SET the Color

setLedColor( particleDeviceID,particleDeviceSecret, '#FF000000' ),&quot;Setting lights to: &quot; + Red );

Hope that helps get you started and let me know if you have any questions in the commets 🙂

]]>
O Christmas Tree! O Christmas Tree! https://brooke.codes/2016/12/20/o-christmas-tree-o-christmas-tree/ Wed, 21 Dec 2016 04:58:44 +0000 https://brooke.codes/?p=710 Last year while making popcorn and cranberry chains the only thing missing from the tree was a star. One was made posthaste. However, it was missing a very important feature, lighting up. We couldn’t let that happen two years in a row so this year we added a color changing light to our homemade star. […]]]>

Last year while making popcorn and cranberry chains the only thing missing from the tree was a star. One was made posthaste. However, it was missing a very important feature, lighting up. We couldn’t let that happen two years in a row so this year we added a color changing light to our homemade star.

The hardware is a Trinket and single Flora NeoPixel both from Adafruit.

The software is a slightly modified rainbow effect from the NeoPixel stand test included in the library.

Happy Holidays!

IMG_20161220_1914410.jpg
]]>
I want to ride my bike https://brooke.codes/2016/03/17/i-want-to-ride-my-bike/ Thu, 17 Mar 2016 17:48:43 +0000 https://brooke.codes/?p=626 BikeBlink Part III Once I started to wire the bike light into my 3D Box I quickly learned that while I learned a lot my box was clunky and would have too many wires. That’s around the same time I learned about custom circuit boards. The first thing I did was watch a few videos […]]]>

BikeBlink Part III

Once I started to wire the bike light into my 3D Box I quickly learned that while I learned a lot my box was clunky and would have too many wires. That’s around the same time I learned about custom circuit boards.

The first thing I did was watch a few videos and download the free version of EAGLE. I had the advantage of working with this circuit for a while and pretty much having it memorized. Once In EAGLE I created the schematic and board layout. From there it came down to printing and making though my pull though components would work as expected.  I also used the Trinket EAGLE file found on GitHub.

I then ordered the boards from OSH Park which I highly recommend. Their website is easy to use and provides a board preview which really helped me make sure my screen printing was in order. The boards arrived and everything works as expected.

Now that I have a much more compact cleaner version of my board layout next up will be revisiting the enclosure. For the curious, I’ve released the boards as Open Source Hardware.

 

 

]]>
I want to ride my bicycle https://brooke.codes/2016/03/14/i-want-to-ride-my-bicycle/ Mon, 14 Mar 2016 17:44:39 +0000 https://brooke.codes/?p=611 BikeBlink Part II Now that I had working code I started thinking about waterproofing. I decided to order a waterproof cable and print a 3D Box. As this was my first time 3D Printing I had no idea what I was doing. I designed a box in AutoDesk 3D and headed to my local maker […]]]>

BikeBlink Part II

Now that I had working code I started thinking about waterproofing. I decided to order a waterproof cable and print a 3D Box. As this was my first time 3D Printing I had no idea what I was doing. I designed a box in AutoDesk 3D and headed to my local maker space to have it printed. Over all I’m pretty happy with how the box turned out. My original idea was to solder everything into the box. However, I learned this was not as easily done as I was hoping so I’ll be going another route in terms of enclosures.

3Dcasebox_solder

]]>
Bicycle Bicycle https://brooke.codes/2016/03/12/bicycle-bicycle/ Sat, 12 Mar 2016 19:29:57 +0000 https://brooke.codes/?p=541 BikeBlink Part I About 10 months ago I had a friend who got hit by a car while on a bike. This lead to stepping up the visibility game. One of the improvements were bike spoke lights. They serve to increase side visibility while riding. When I first saw this I was just getting into the […]]]>

BikeBlink Part I

Note: This post refers to code and a project from many years ago 😱. The content was edited in March of 2025 to remove dead links, improve clarity, or fix formatting, but no other edits were made. Enjoy this time capsule into the past.

m204_cover1_2015-478x480
Monkeylight m204 spoke light from Monkeylectric

About 10 months ago I had a friend who got hit by a car while on a bike. This lead to stepping up the visibility game. One of the improvements were bike spoke lights. They serve to increase side visibility while riding.

When I first saw this I was just getting into the maker space and said to myself, perhaps stupidly, “I can make that.” Then went to work.

I started researching and landed on the Adafruit Trinket as my microcontroller of choice. It’s small, powerful enough and has everything I needed to run my LEDs.

adafruit_products_trinket_clear_products_1500_ORIG.jpg

The Trinket has ~5.25K bytes of space available for use, and 512 bytes of ram. These limitations would be a good to work with as a challenge to write more efficient code.

breadboard

After some time I got a working test which flashed though 4 different LED modes switching modes at a press of the button. As an added bonus I added the mode into E2PROM(non-volatile memory). This allows the device to turn on in the mode it was using when power was disconnected.

The code is up on GitHub.

 

]]>