Skip to content

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

Just for fun, I decided to import a subset of my MF2-JSON data into Postgres and see if I can maybe improve Kittybox with it.

CREATE DATABASE mf2_test;
CREATE TABLE vika.mf2_json (url TEXT NOT NULL PRIMARY KEY, mf2 JSONB NOT NULL);
for post in $(ssh root@primrose "ls /persist/containers/kittybox/kittybox/data/fireburn.ru/posts/*.json"); do
    json="$(ssh root@primrose cat "$post")"
    echo "INSERT INTO vika.mf2_json (url, mf2) VALUES ('$(echo "$json" | jq -r ".properties.uid[]")', '$(echo "$json" | sed -e "s/'/''/g")') ON CONFLICT DO NOTHING;"
done | psql mf2_test --single-transaction
-- See which categories I am using for posts
SELECT DISTINCT jsonb_strip_nulls(jsonb_path_query(mf2['properties']['category'], '$[*]'))  #>> '{}' AS tag FROM vika.mf2_json ORDER BY tag ASC;

-- Index the post corpus for full-text search
CREATE INDEX fulltext ON vika.mf2_json USING GIN (to_tsvector('english', mf2['properties']['content'][]));

-- Run a full-text search -- takes 90ms w/o index, 2ms with index!
SELECT url, mf2['properties']['content'][]['value'] FROM mf2_json WHERE to_tsvector('english', mf2['properties']['content'][]) @@ to_tsquery('Kittybox') ORDER BY mf2['properties']['published'][] DESC;

This makes it possible to run a lot of data analysis on posts. Maybe I'll finally have some stuff to populate my widget on the right of the main page with.

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

Oh, looks like Quill has been updated to indicate other social networks' character limits, because of one particular social network's downfall. Neat.

I don't remember the last time I opened that particular social network though...

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

Noticed that in Ethereum, major DeFi tokens all correlate to the price of Ethereum, maybe a bit too much. Perhaps that’s why everyone’s calling crypto a Ponzi scheme β€” how could different assets have such correlation in prices?

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

I have remembered an old easter egg in my website, and decided to try recreating it using CSS counters, which I have recently learned about. Here’s some CSS:

@counter-style rainbow-hearts {
    system: cyclic;
    symbols: "❀️" "🧑" "πŸ’›" "πŸ’š" "πŸ’™" "πŸ’œ";
}
body {
    counter-reset: like-icons;
}
span.like-icon::before {
    counter-increment: like-icons;
    content: "" counter(like-icons, rainbow-hearts);
}
span.like-icon-label {
    display: none;
}

Using it with the following HTML will do the trick:

<span class="like-icon" aria-label="liked"><span class="like-icon-label" aria-hidden="true">❀️</span></span>

I tried to make it compatible with screen readers and allow for a fallback icons for browsers not supporting counters (apparently text browsers don’t do a lot of CSS). This also means the CSS snippet can be excluded from the CSS sent to the browser if you don’t like having fun.

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

Are cars even worth it if they move slower than pedestrians? I ordered a taxi and the amount of time I spent waiting for it and the amount of time I will spend inside the car could’ve gotten me to my destination on foot, if not for the damn ice on the sidewalks that nobody cares to break...

Because the city only cares about cars and car owners. And pedestrians are always treated as second-class citizens...

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

I want to be free from the confines of this hyper-capitalist world we are living in.

I want to see the Internet that I never got to see β€” an open, free ecosystem run by enthusiasts from their closets.

I wonder what would be of it in our current highspeed era if it were not killed by corporations and silos.

Perhaps we would have something absolutely beautiful on our hands. Perhaps...we could still have it?

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

The word "sensitive content" makes me want to destroy this world because usually it gets applied by social networking silos to anything that is not capitalist-praising distilled fake-smile covered in glitter simulacrum and usually is an excuse to non-transparently suppress content undesirable to capitalists.

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

I often forget that my new authentication system has short-lived tokens that live a week, and long-term refresh tokens that last up to two months.

And Quill doesn’t seem to use refresh tokens...

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

One last thing remains. Moving to a new domain. Soonβ„’

I just need to rent a cloud server, because I am literally hosting this server under my bed, and it might be dangerous now.

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

I’ve put a HTTP 451 page on my website in response to my country’s prohibition on LGBTQ+ content. Since I am transgender, publicizing the fact of my own existence is now quite literally illegal.

Fuck Russia.

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0

A certain comment on Hacker News perfectly conveys how I see all financial systems and crypto, and seem so skeptical about the high price of Ethereum.

The US dollar is a medium of exchange, not an asset or an investment. You use dollars to buy things, you don’t own dollars just to own dollars.

Same could be said about cryptocurrency, about sticks and stones and even squirrel pelts (that were used as a currency in ancient times, if my memory serves me).

And this is why I am so annoyed with cryptobros and venture capitalists who dabble in cryptocurrencies β€” they view what should be a medium of exchange as an investment platform, resulting in the blockchain getting cramped.

I’m so grateful for the FTX crash right now. I hope it would scare capitalist leeches off.

Webmention counters:

  • 0
  • πŸ’¬0
  • πŸ”„0
  • πŸ”–0