Skip to content

Today I realized that a function that always returns true can technically be considered a bloom filter with a false-positive rate of one. If you're willing to stretch your definition of bloom filter, of course.

The best thing about this bloom filter is its memory usage. It uses zero memory.

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

TIL that ip rule supports a uidrange condition that allows to route traffic for specific users. This is useful in combination with Tailscale exit nodes or other VPNs to allow setting a different default route per-service (you do run your services as separate UIDs, right?)

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

Somehow I ended up playing with SIMD and I wrote an IPv6 96-bit prefix comparison function that's 250 picoseconds faster than doing lhs.segments()[0..6] == rhs.segments()[0..6].

I have no idea what to do with these 250 picoseconds I am saving every time I need to compare 96-bit IPv6 prefixes.

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

while studying cargo index format, I realized a cursed thing: I could probably get this thing working with Nix to fetch crates without using Cargo...

i'm probably reinventing the wheel (the wheel being naersk), but:

let
  # assuming cwd = `git clone github.com/rust-lang/crates.io-index`
  fetchCrateVersions = name: builtins.readFile ./${builtins.substring 0 2 name}/${builtins.substring 2 4 name}/${name};
  fetchCrateMetadata = name: version: builtins.getAttr version (builtins.listToAttrs (builtins.map (c: { name = c.vers; value = c; }) (builtins.map builtins.fromJSON (builtins.filter (n: n != "") (nixpkgs.lib.strings.splitString "\n" (fetchCrateVersions name))))));
in
# `fetchCrateMetadata`'s output contains `cksum` attribute matching SHA256 hash of the crate, allowing for a fixed-output derivation.
# Using recursive calls of a hypothetical function, all dependencies of a certain crate could be found, and a list for calling `fetchurl` created.
# Creating such a recursive function is an exercise for the reader.

Thus, the entire crates.io registry becomes accessible, and a hypothetical Cargo.lock file could be used as a starting point to discover dependencies for a project, without the requirement for any sort of hashes.

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

This is your reminder that when switching from Chrome, you should avoid Chromium reskins.

Especially Brave with its hypocrisy around its own shitcoin, BAT, which doesn't allow non-custodial withdrawals (therefore is not even real crypto). Not to mention the widely known homophobic views of its CEO.

...also brave sucks for me as a web developer, because they disable some genuinely useful features, all in the name of "privacy". At this point it's better to use Firefox instead.

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

I've never found social networks and their algorithms "addicting", like how many fearmongers claim them to be.

Instead, I found them simply frustrating.

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

Cryptocurrencies, in relation to constructing hardware wallets, have a singular fatal flaw.

Most popular ones seem to use a very specific elliptic curve for implementing their cryptography, secp256k1. Ban secure element chips that implement that curve — and bam, you've successfully curbed any efforts to construct a hardware wallet.

Perhaps you could even attempt to ban software that implements it. That is actually much harder to do, but a government could attempt to do it.

That doesn't seem too secure to me in context of covert cryptocurrency usage.

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

Fucking Rercury is in the metrograde again or something and there's also a full moon and TikTok is now full of magic practicioners manifesting things and spamming posts with affirmations

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

Brave Browser isn't just a crypto scam, it's a double crypto scam. Because if you actually want to participate in its crypto scam, you can only do it from certain countries which have a partnered centralized exchange...and you have to go through KYC. Which defeats the purpose of cryptocurrency entirely.

You cannot self-custody your way out of it, which makes it double the scam it actually is.

Switch to Firefox, it respects your privacy and doesn't feature any crypto.

Though I do have a fair share of concerns about where Mozilla is going, Firefox is good enough for me to overlook these...for now

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

Thinking about creating a Microsub server, I remembered that my new shiny Postgres backend can easily answer if I already left a like on a post or not:

kittybox=# CREATE INDEX likes ON kittybox.mf2_json USING GIN ((mf2['properties']['like-of']));
CREATE INDEX
kittybox=# EXPLAIN SELECT EXISTS (SELECT uid FROM kittybox.mf2_json WHERE mf2['properties']['like-of'] ? 'https://aaronparecki.com/2018/12/25/17/');
                                                        QUERY PLAN                                                        
--------------------------------------------------------------------------------------------------------------------------
 Result  (cost=11.28..11.29 rows=1 width=1)
   InitPlan 1 (returns $0)
     ->  Bitmap Heap Scan on mf2_json  (cost=8.09..43.20 rows=11 width=0)
           Recheck Cond: (mf2['properties'::text]['like-of'::text] ? 'https://aaronparecki.com/2018/12/25/17/'::text)
           ->  Bitmap Index Scan on likes  (cost=0.00..8.08 rows=11 width=0)
                 Index Cond: (mf2['properties'::text]['like-of'::text] ? 'https://aaronparecki.com/2018/12/25/17/'::text)
(6 rows)

Nice. Indexes are awesome.

Webmention counters:

  • 1
  • 💬0
  • 🔄0
  • 🔖0

Here's a short Rust program using the microformats crate that checks the presence of a webmention on a certain page, properly resolving all URLs and even scanning HTML content in entry["properties"]["content"].

use std::cell::{RefCell, Ref};
use std::rc::Rc;

use clap::Parser;
use microformats::types::PropertyValue;
use microformats::html5ever;
use microformats::html5ever::tendril::TendrilSink;

#[derive(thiserror::Error, Debug)]
enum Error {
    #[error("http request error: {0}")]
    Http(#[from] reqwest::Error),
    #[error("microformats error: {0}")]
    Microformats(#[from] microformats::Error),
    #[error("json error: {0}")]
    Json(#[from] serde_json::Error),
    #[error("url parse error: {0}")]
    UrlParse(#[from] url::ParseError),
}

#[derive(Debug)]
enum MentionType {
    Reply,
    Like,
    Repost,
    Bookmark,
    Mention
}

fn check_mention(document: impl AsRef<str>, base_url: &url::Url, link: &url::Url) -> Result<Option<MentionType>, Error> {
    // First, check the document for MF2 markup
    let document = microformats::from_html(document.as_ref(), base_url.clone())?;

    // Get an iterator of all items
    let items_iter = document.items.iter()
        .map(AsRef::as_ref)
        .map(RefCell::borrow);

    for item in items_iter {
        let props = item.properties.borrow();
        for (prop, interaction_type) in [
            ("in-reply-to", MentionType::Reply), ("like-of", MentionType::Like),
            ("bookmark-of", MentionType::Bookmark), ("repost-of", MentionType::Repost)
        ] {
            if let Some(propvals) = props.get(prop) {
                for val in propvals {
                    if let PropertyValue::Url(url) = val {
                        if url == link {
                            return Ok(Some(interaction_type))
                        }
                    }
                }
            }
        }
        // Process `content`
        if let Some(PropertyValue::Fragment(content)) = props.get("content")
            .map(Vec::as_slice)
            .unwrap_or_default()
            .first()
        {
            let root = html5ever::parse_document(html5ever::rcdom::RcDom::default(), Default::default())
                .from_utf8()
                .one(content.html.to_owned().as_bytes())
                .document;

            // This is a trick to unwrap recursion into a loop
            //
            // A list of unprocessed node is made. Then, in each
            // iteration, the list is "taken" and replaced with an
            // empty list, which is populated with nodes for the next
            // iteration of the loop.
            //
            // Empty list means all nodes were processed.
            let mut unprocessed_nodes: Vec<Rc<html5ever::rcdom::Node>> = root.children.borrow().iter().cloned().collect();
            while unprocessed_nodes.len() > 0 {
                // "Take" the list out of its memory slot, replace it with an empty list
                let nodes = std::mem::take(&mut unprocessed_nodes);
                for node in nodes.into_iter() {
                    // Add children nodes to the list for the next iteration
                    unprocessed_nodes.extend(node.children.borrow().iter().cloned());

                    if let html5ever::rcdom::NodeData::Element { ref name, ref attrs, .. } = node.data {
                        // If it's not `<a>`, skip it
                        if name.local != *"a" { continue; }
                        for attr in attrs.borrow().iter() {
                            // if it's not `<a href="...">`, skip it 
                            if attr.name.local != *"href" { continue; }
                            // Be forgiving in parsing URLs, and resolve them against the base URL
                            if let Ok(url) = base_url.join(attr.value.as_ref()) {
                                if &url == link {
                                    return Ok(Some(MentionType::Mention));
                                }
                            }
                        }
                    }
                }
            }
            
        }
    }

    Ok(None)
}

#[derive(Parser, Debug)]
#[clap(
    name = "kittybox-check-webmention",
    author = "Vika <vika@fireburn.ru>",
    version = env!("CARGO_PKG_VERSION"),
    about = "Verify an incoming webmention"
)]
struct Args {
    #[clap(value_parser)]
    url: url::Url,
    #[clap(value_parser)]
    link: url::Url
}

#[tokio::main]
async fn main() -> Result<(), self::Error> {
    let args = Args::parse();
    
    let http: reqwest::Client = {
        #[allow(unused_mut)]
        let mut builder = reqwest::Client::builder()
            .user_agent(concat!(
                env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")
            ));

        builder.build().unwrap()
    };

    let response = http.get(args.url.clone()).send().await?;
    let text = response.text().await?;
    
    if let Some(mention_type) = check_mention(text, &args.url, &args.link)? {
        println!("{:?}", mention_type);

        Ok(())
    } else {
        std::process::exit(1)
    }
}

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

bluesky is Twitter all over again now, but decentralized and even more chaotic.

And I still don't know half the twitter culture, so I feel lost. And there are already more cultural things like the hellthread? My head is spinning I should probably get breakfast!

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0

Assuming the default bluesky feeds are open source, it would be easy to check if posts with links get downranked. This was the case at Twitter before when I was posse-ing — posts with links gained less impressions, and considering my account size, it was pretty much catastrophic.

Webmention counters:

  • 0
  • 💬0
  • 🔄0
  • 🔖0