Problem: you want to encode arbitrary binary data as
a hashtag on Mastodon or another popular platform.
A solution: transform to a hex string and shift the
0..f up to a..p so all the bytes are lowercase Latin characters.
First, load up {tidyverse}:
library(tidyverse)
I want to transform a string like “Hello” to a hex string:
chars2hexstr <- function(t) {
t |>
charToRaw() |>
as.character() |>
paste(collapse = "")
}
Testing:
chars2hexstr("Hello")
[1] "48656c6c6f"
Consulting an ASCII
table, looks good. “H” = 0x48, “e” = 0x65, “l” = 0x6c, and “o” =
0x6f.
Let’s undo it:
hexstr2chars <- function(hexstr) {
substring(hexstr,
seq(1, nchar(hexstr) - 1, 2),
seq(2, nchar(hexstr), 2)) |>
strtoi(base = 16) |>
as.raw() |>
rawToChar()
}
And a test:
hexstr2chars("48656c6c6f")
[1] "Hello"
Next, I want to shift 0..f up to a..p. I’m sure there’s an easier
way. Here’s how I’m doing it. First, setup a mapping:
charmap <- data.frame(
hex = c(0:9, letters[1:6]),
let = letters[1:16])
charmap
These two functions translate back and forth between 0..f (hex) and
a..p (I’m going to call it a lethex), using a strsplit
and
join
:
hex2lethex <- function(hstr) {
data.frame(hex = (hstr |> strsplit(""))[[1]]) |>
left_join(charmap, by = "hex") |>
pull(let) |>
paste(collapse = "")
}
lethex2hex <- function(lethex) {
data.frame(let = (lethex |> strsplit(""))[[1]]) |>
left_join(charmap, by = "let") |>
pull(hex) |>
paste(collapse = "")
}
Try it with “Hello”. Here’s the hex string:
hello_hex <- chars2hexstr("Hello")
hello_hex
[1] "48656c6c6f"
Now as a lethex:
hello_lethex <- hello_hex |> hex2lethex()
hello_lethex
[1] "eigfgmgmgp"
And back again:
hello_lethex |> lethex2hex()
[1] "48656c6c6f"
Finally, a couple of wrappers, which take a string to a lethex string
and back again:
str2hashtag <- function(s) {
s |> chars2hexstr() |> hex2lethex()
}
hashtag2str <- function(h) {
h |> lethex2hex() |> hexstr2chars()
}
str2hashtag("Hello")
[1] "eigfgmgmgp"
hashtag2str("eigfgmgmgp")
[1] "Hello"
Finally:
hashtag2str("gihehehahddkcpcphhhhhhcohjgphfhehfgcgfcogdgpgncphhgbhegdgidphgdngefbhhdehhdjfhghfigdfb")
LS0tDQp0aXRsZTogIkVuY29kZSBieXRlcyBhcyBoYXNodGFncyINCmF1dGhvcjogIkBhbmRpQHRlY2gubGdidCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIGNvZGVfZm9sZGluZzogbm9uZQ0KLS0tDQoNCioqUHJvYmxlbToqKiB5b3Ugd2FudCB0byBlbmNvZGUgYXJiaXRyYXJ5IGJpbmFyeSBkYXRhIGFzIGEgaGFzaHRhZyBvbiBNYXN0b2RvbiBvciBhbm90aGVyIHBvcHVsYXIgcGxhdGZvcm0uDQoNCioqQSBzb2x1dGlvbjoqKiB0cmFuc2Zvcm0gdG8gYSBoZXggc3RyaW5nIGFuZCBzaGlmdCB0aGUgMC4uZiB1cCB0byBhLi5wIHNvIGFsbCB0aGUgYnl0ZXMgYXJlIGxvd2VyY2FzZSBMYXRpbiBjaGFyYWN0ZXJzLg0KDQoNCkZpcnN0LCBsb2FkIHVwIHt0aWR5dmVyc2V9Og0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KDQpJIHdhbnQgdG8gdHJhbnNmb3JtIGEgc3RyaW5nIGxpa2UgIkhlbGxvIiB0byBhIGhleCBzdHJpbmc6DQoNCmBgYHtyfQ0KY2hhcnMyaGV4c3RyIDwtIGZ1bmN0aW9uKHQpIHsNCiAgdCB8Pg0KICAgIGNoYXJUb1JhdygpIHw+DQogICAgYXMuY2hhcmFjdGVyKCkgfD4NCiAgICBwYXN0ZShjb2xsYXBzZSA9ICIiKQ0KfQ0KYGBgDQoNClRlc3Rpbmc6DQoNCmBgYHtyfQ0KY2hhcnMyaGV4c3RyKCJIZWxsbyIpDQpgYGANCg0KQ29uc3VsdGluZyBhbiBbQVNDSUkgdGFibGVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0FTQ0lJI1ByaW50YWJsZV9jaGFyYWN0ZXJzKSwgbG9va3MgZ29vZC4gIkgiID0gMHg0OCwgImUiID0gMHg2NSwgImwiID0gMHg2YywgYW5kICJvIiA9IDB4NmYuDQoNCg0KTGV0J3MgdW5kbyBpdDoNCg0KYGBge3J9DQpoZXhzdHIyY2hhcnMgPC0gZnVuY3Rpb24oaGV4c3RyKSB7DQogIHN1YnN0cmluZyhoZXhzdHIsDQogICAgICAgICAgICBzZXEoMSwgbmNoYXIoaGV4c3RyKSAtIDEsIDIpLA0KICAgICAgICAgICAgc2VxKDIsIG5jaGFyKGhleHN0ciksIDIpKSB8Pg0KICBzdHJ0b2koYmFzZSA9IDE2KSB8Pg0KICBhcy5yYXcoKSB8Pg0KICByYXdUb0NoYXIoKQ0KfQ0KYGBgDQoNCg0KQW5kIGEgdGVzdDoNCg0KYGBge3J9DQpoZXhzdHIyY2hhcnMoIjQ4NjU2YzZjNmYiKQ0KYGBgDQoNCk5leHQsIEkgd2FudCB0byBzaGlmdCAwLi5mIHVwIHRvIGEuLnAuIEknbSBzdXJlIHRoZXJlJ3MgYW4gZWFzaWVyIHdheS4gSGVyZSdzIGhvdyBJJ20gZG9pbmcgaXQuIEZpcnN0LCBzZXR1cCBhIG1hcHBpbmc6DQoNCmBgYHtyfQ0KY2hhcm1hcCA8LSBkYXRhLmZyYW1lKA0KICBoZXggPSBjKDA6OSwgbGV0dGVyc1sxOjZdKSwNCiAgbGV0ID0gbGV0dGVyc1sxOjE2XSkNCmNoYXJtYXANCmBgYA0KDQpUaGVzZSB0d28gZnVuY3Rpb25zIHRyYW5zbGF0ZSBiYWNrIGFuZCBmb3J0aCBiZXR3ZWVuIDAuLmYgKGhleCkgYW5kIGEuLnAgKEknbSBnb2luZyB0byBjYWxsIGl0IGEgbGV0aGV4KSwgdXNpbmcgYSBgc3Ryc3BsaXRgIGFuZCBgam9pbmA6DQoNCmBgYHtyfQ0KaGV4MmxldGhleCA8LSBmdW5jdGlvbihoc3RyKSB7DQogIGRhdGEuZnJhbWUoaGV4ID0gKGhzdHIgfD4gc3Ryc3BsaXQoIiIpKVtbMV1dKSB8Pg0KICAgIGxlZnRfam9pbihjaGFybWFwLCBieSA9ICJoZXgiKSB8Pg0KICAgIHB1bGwobGV0KSB8Pg0KICAgIHBhc3RlKGNvbGxhcHNlID0gIiIpDQp9DQoNCmxldGhleDJoZXggPC0gZnVuY3Rpb24obGV0aGV4KSB7DQogIGRhdGEuZnJhbWUobGV0ID0gKGxldGhleCB8PiBzdHJzcGxpdCgiIikpW1sxXV0pIHw+DQogICAgbGVmdF9qb2luKGNoYXJtYXAsIGJ5ID0gImxldCIpIHw+DQogICAgcHVsbChoZXgpIHw+DQogICAgcGFzdGUoY29sbGFwc2UgPSAiIikNCn0NCmBgYA0KDQpUcnkgaXQgd2l0aCAiSGVsbG8iLiBIZXJlJ3MgdGhlIGhleCBzdHJpbmc6DQoNCmBgYHtyfQ0KaGVsbG9faGV4IDwtIGNoYXJzMmhleHN0cigiSGVsbG8iKQ0KaGVsbG9faGV4DQpgYGANCk5vdyBhcyBhIGxldGhleDoNCg0KYGBge3J9DQpoZWxsb19sZXRoZXggPC0gaGVsbG9faGV4IHw+IGhleDJsZXRoZXgoKQ0KaGVsbG9fbGV0aGV4DQpgYGANCg0KQW5kIGJhY2sgYWdhaW46DQoNCmBgYHtyfQ0KaGVsbG9fbGV0aGV4IHw+IGxldGhleDJoZXgoKQ0KYGBgDQoNCkZpbmFsbHksIGEgY291cGxlIG9mIHdyYXBwZXJzLCB3aGljaCB0YWtlIGEgc3RyaW5nIHRvIGEgbGV0aGV4IHN0cmluZyBhbmQgYmFjayBhZ2FpbjoNCg0KYGBge3J9DQpzdHIyaGFzaHRhZyA8LSBmdW5jdGlvbihzKSB7DQogIHMgfD4gY2hhcnMyaGV4c3RyKCkgfD4gaGV4MmxldGhleCgpDQp9DQoNCmhhc2h0YWcyc3RyIDwtIGZ1bmN0aW9uKGgpIHsNCiAgaCB8PiBsZXRoZXgyaGV4KCkgfD4gaGV4c3RyMmNoYXJzKCkNCn0NCmBgYA0KDQoNCmBgYHtyfQ0Kc3RyMmhhc2h0YWcoIkhlbGxvIikNCmBgYA0KDQpgYGB7cn0NCmhhc2h0YWcyc3RyKCJlaWdmZ21nbWdwIikNCmBgYA0KDQpGaW5hbGx5Og0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KaGFzaHRhZzJzdHIoImdpaGVoZWhhaGRka2NwY3BoaGhoaGhjb2hqZ3BoZmhlaGZnY2dmY29nZGdwZ25jcGhoZ2JoZWdkZ2lkcGhnZG5nZWZiaGhkZWhoZGpmaGdoZmlnZGZiIikNCmBgYA0K