First commit
This commit is contained in:
commit
da366208b6
10 changed files with 11564 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Delete verse.lua to compile from source
|
||||||
|
#verse.lua
|
||||||
|
config_private.lua
|
||||||
|
services.json
|
17
README.md
Normal file
17
README.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Lua XMPP Privacy Bot
|
||||||
|
|
||||||
|
This bot replaces links to popular sites such as youtube with privacy respecting front ends such as individuous. It is written in 100% pure lua
|
||||||
|
|
||||||
|
# How to run
|
||||||
|
|
||||||
|
Make sure `make`, `tar`, `gzip`, `lua`, and `luarocks` are installed.
|
||||||
|
|
||||||
|
Then do `luarocks install luasocket luaexpat luasec`
|
||||||
|
|
||||||
|
Next configure the bot to your liking in `config.lua`. Also don't forget to copy `config_private_example.lua` to `config_private.lua` and fill that out as well.
|
||||||
|
|
||||||
|
Then run the `./run` script to run the bot. It will download the farside `services.json` list and compile the `verse.lua` xmpp library.
|
||||||
|
|
||||||
|
# List of supported front ends
|
||||||
|
|
||||||
|
**TODO**
|
6
clean
Executable file
6
clean
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env lua
|
||||||
|
|
||||||
|
print("Deleting compiled verse")
|
||||||
|
os.remove("verse.lua")
|
||||||
|
print("Deleting services.json")
|
||||||
|
os.remove("services.json")
|
73
config.lua
Normal file
73
config.lua
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
-- Main privacy bot configuration file
|
||||||
|
config = {
|
||||||
|
-- Log verbosity
|
||||||
|
verbosity = 1,
|
||||||
|
-- Bot nickname
|
||||||
|
name = "Privacy Link Bot",
|
||||||
|
--[[
|
||||||
|
This will set the type of url to replace the service domain with. Can be:
|
||||||
|
- clearnet
|
||||||
|
- onion
|
||||||
|
- eepsite
|
||||||
|
- yggdrasil
|
||||||
|
- TODO: make it work and more types?
|
||||||
|
]]--
|
||||||
|
prefered_website_medium = "clearnet",
|
||||||
|
-- Choose random frontend instead of fallback one, will force clearnet
|
||||||
|
random_frontend = true,
|
||||||
|
-- List of desired frontends to extract from `services.json`
|
||||||
|
sites = {
|
||||||
|
-- Key is domain pattern
|
||||||
|
["reddit[.]com"] = {
|
||||||
|
-- Specify which frontents should be used
|
||||||
|
frontends = { "libreddit", "redlib" }
|
||||||
|
},
|
||||||
|
["instagram[.]com"] = {
|
||||||
|
frontends = { "proxigram" }
|
||||||
|
},
|
||||||
|
["github[.]com"] = {
|
||||||
|
frontends = { "gothub" }
|
||||||
|
},
|
||||||
|
["google[.]com"] = {
|
||||||
|
frontends = { "searxng" }
|
||||||
|
},
|
||||||
|
["youtube[.]com"] = {
|
||||||
|
frontends = { "piped", "invidious"}
|
||||||
|
},
|
||||||
|
["www[.]youtube[.]com"] = {
|
||||||
|
frontends = { "piped", "invidious"}
|
||||||
|
},
|
||||||
|
["youtu[.]be"] = {
|
||||||
|
frontends = { "piped", "invidious", }
|
||||||
|
},
|
||||||
|
["twitter[.]com"] = {
|
||||||
|
frontends = { "nitter", }
|
||||||
|
},
|
||||||
|
["x[.]com"] = {
|
||||||
|
frontends = { "nitter", }
|
||||||
|
},
|
||||||
|
["wikipedia[.]org"] = {
|
||||||
|
frontends = { "wikiless", }
|
||||||
|
},
|
||||||
|
["medium[.]com"] = {
|
||||||
|
frontends = { "scribe", }
|
||||||
|
},
|
||||||
|
["imgur[.]com"] = {
|
||||||
|
frontends = { "rimgo", }
|
||||||
|
},
|
||||||
|
["translate[.]google[.]com"] = {
|
||||||
|
frontends = { "lingva", }
|
||||||
|
},
|
||||||
|
["tiktok[.]com"] = {
|
||||||
|
frontends = { "proxitok", }
|
||||||
|
},
|
||||||
|
["fandom[.]com"] = {
|
||||||
|
frontends = { "breezewiki", }
|
||||||
|
},
|
||||||
|
-- TODO: the rest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Load config file with private information
|
||||||
|
dofile("config_private.lua")
|
||||||
|
|
12
config_private_example.lua
Normal file
12
config_private_example.lua
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
-- JID for the bot
|
||||||
|
config.jid = "bot@server.net"
|
||||||
|
-- Bot password, stored in plaintext
|
||||||
|
config.password = "password"
|
||||||
|
|
||||||
|
-- Rooms the bot will attempt to join
|
||||||
|
config.rooms_to_join = {
|
||||||
|
-- Normal room
|
||||||
|
{jid = "room@server.net",},
|
||||||
|
-- Password protected room, password stored in plaintext
|
||||||
|
{jid = "protected@server.net", password = "pass"},
|
||||||
|
}
|
6
get_frontends
Executable file
6
get_frontends
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env lua
|
||||||
|
|
||||||
|
local farside_instance_json_url = "https://git.sr.ht/~benbusby/farside/blob/HEAD/services.json"
|
||||||
|
|
||||||
|
os.remove("services.json")
|
||||||
|
os.execute(string.format("wget \"%s\"", farside_instance_json_url))
|
65
main.lua
Executable file
65
main.lua
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
-- Get the verse lib
|
||||||
|
local verse = require("verse")
|
||||||
|
-- Setup logging and config
|
||||||
|
require("utils")
|
||||||
|
local log = setup_log(string.format("%s_main", config.name))
|
||||||
|
log("info", "Log initialized")
|
||||||
|
-- Get the client lib
|
||||||
|
local client_lib = verse.init("client")
|
||||||
|
-- Make the client
|
||||||
|
local client = client_lib.new()
|
||||||
|
-- Load client plugins
|
||||||
|
client:add_plugin("groupchat")
|
||||||
|
client:add_plugin("version")
|
||||||
|
-- Client hooks
|
||||||
|
client:hook("disconnected", function()
|
||||||
|
log("error", "XMPP connetion lost. Quitting...")
|
||||||
|
os.exit(1)
|
||||||
|
end)
|
||||||
|
client:hook("authentication-failure", function()
|
||||||
|
log("error", "Failed to authenticate with XMPP Server. Quitting...")
|
||||||
|
os.exit(1)
|
||||||
|
end)
|
||||||
|
client:hook("authentication-success", function()
|
||||||
|
log("info", "XMPP authentication sucessful!")
|
||||||
|
end)
|
||||||
|
client:hook("ready", function()
|
||||||
|
log("info", "Client ready")
|
||||||
|
|
||||||
|
-- Main code goes here
|
||||||
|
for _, room in pairs(config.rooms_to_join) do
|
||||||
|
local room, err = client:join_room(room.jid, config.name, {}, config.password)
|
||||||
|
|
||||||
|
if room then
|
||||||
|
-- Run on message events
|
||||||
|
log("info", "Joined room \"%s\"", room.jid)
|
||||||
|
room:hook("message", function(event)
|
||||||
|
if event.stanza.attr.type == "groupchat" and not string.find(event.stanza.attr.from, "/" .. config.name) then
|
||||||
|
local body = event.stanza:get_child_text("body")
|
||||||
|
if body then
|
||||||
|
for site, services in pairs(config.sites) do
|
||||||
|
local instance = choose_instance(services.frontends)
|
||||||
|
-- TODO: make it reply using XEP-0461
|
||||||
|
for match in string.gmatch(body, string.format("%%s(%s/%%S+)", site)) do
|
||||||
|
room:send_message(string.format("> %s\nPrivate frontend: %s", match, string.gsub(match, site, instance)))
|
||||||
|
end
|
||||||
|
for match in string.gmatch(body, string.format("(https?://%s/%%S+)", site)) do
|
||||||
|
room:send_message(string.format("> %s\nPrivate frontend: %s", match, string.gsub(match, site, instance)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
log("error", "Error joining room \"%s\": %s", room.jid, err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
log("info", "Connecting to server...")
|
||||||
|
|
||||||
|
client:connect_client(config.jid, config.password)
|
||||||
|
|
||||||
|
verse.loop()
|
||||||
|
|
||||||
|
log("info", "Verse loop exited. Quitting...")
|
51
run
Executable file
51
run
Executable file
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env lua
|
||||||
|
|
||||||
|
-- TODO: luarocks?
|
||||||
|
|
||||||
|
-- Download frontends list
|
||||||
|
if not os.execute(string.format("ls services.json 2>/dev/null >/dev/null")) then
|
||||||
|
dofile("get_frontends")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Squish commit hash
|
||||||
|
local squish_version = "tip"
|
||||||
|
-- Squish script url
|
||||||
|
local squish_script = string.format("http://code.matthewwild.co.uk/squish/raw-file/%s/squish.lua", squish_version)
|
||||||
|
|
||||||
|
-- Verse commit hash
|
||||||
|
local verse_version = "98dc1750584d"
|
||||||
|
-- Location for various verse files
|
||||||
|
local verse_folder_name = string.format("verse-%s", verse_version)
|
||||||
|
local verse_archive_filename = string.format("%s.tar.gz", verse_version)
|
||||||
|
local verse_dist = string.format("http://code.matthewwild.co.uk/verse/archive/%s", verse_archive_filename)
|
||||||
|
|
||||||
|
-- Options for verse `./configure` script
|
||||||
|
local verse_config_opts = ""
|
||||||
|
-- Options for verse `make`
|
||||||
|
local verse_make_opts = ""
|
||||||
|
|
||||||
|
-- Check if verse module exists and compile if it doesn't
|
||||||
|
if not os.execute(string.format("ls verse.lua 2>/dev/null >/dev/null")) then
|
||||||
|
-- Download source archive
|
||||||
|
os.execute(string.format("wget \"%s\"", verse_dist))
|
||||||
|
-- Extract the source archive
|
||||||
|
os.execute(string.format("tar -xf \"%s\"", verse_archive_filename))
|
||||||
|
-- Delete the source archive
|
||||||
|
os.remove(verse_archive_filename)
|
||||||
|
-- Compile library
|
||||||
|
os.execute(string.format(
|
||||||
|
[[sh -c "cd \"%s\" && # Go to library dir
|
||||||
|
wget \"%s\" -O ./buildscripts/squish && # Replace squish with stripped down version that actually works
|
||||||
|
./configure %s && # Configure the library
|
||||||
|
make %s # Compile"]],
|
||||||
|
verse_folder_name, squish_script, verse_config_opts, verse_make_opts
|
||||||
|
))
|
||||||
|
-- Copy file
|
||||||
|
os.execute(string.format("cp \"%s/verse.lua\" .", verse_folder_name))
|
||||||
|
-- Delete folder
|
||||||
|
os.execute(string.format("rm -rf \"%s\"", verse_folder_name))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Load main module
|
||||||
|
require("main")
|
100
utils.lua
Normal file
100
utils.lua
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
-- Various utility functions used by the bot
|
||||||
|
|
||||||
|
function log_callback(source, level, message, ...)
|
||||||
|
local output = string.format(
|
||||||
|
"%s %s [%s]: %s",
|
||||||
|
os.date(),
|
||||||
|
source,
|
||||||
|
level,
|
||||||
|
string.format(message, ...)
|
||||||
|
)
|
||||||
|
if config.verbosity == 0 then
|
||||||
|
if level ~= "debug" then
|
||||||
|
print(output)
|
||||||
|
end
|
||||||
|
elseif config.verbosity == 1 then
|
||||||
|
print(output)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Make a new logger with `name` and setup a handler based on the log level `v`
|
||||||
|
TODO: `v` needs to be figured out, for now it's 1 to print debug messages 0 to not print them or any other value to print nothing
|
||||||
|
]]--
|
||||||
|
function setup_log(name)
|
||||||
|
local log_module = require("util.logger")
|
||||||
|
log_module.add_level_sink("debug", log_callback)
|
||||||
|
log_module.add_level_sink("info", log_callback)
|
||||||
|
log_module.add_level_sink("warn", log_callback)
|
||||||
|
log_module.add_level_sink("error", log_callback)
|
||||||
|
return log_module.init(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Read a file in it's entirety, returning an empty string on failure
|
||||||
|
function read_all_text(file)
|
||||||
|
local file = io.open(file, "rb")
|
||||||
|
if not file then
|
||||||
|
print(string.format("Failed to open file \"%s\"!", file))
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
local text = file:read("*all")
|
||||||
|
file:close()
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Choose instance from available services
|
||||||
|
function choose_instance(services)
|
||||||
|
-- Choose a random service
|
||||||
|
local service = services[math.random(#services)]
|
||||||
|
-- Get list of instances for service
|
||||||
|
local service_instances
|
||||||
|
for _, service_instance_list in pairs(config.instances) do
|
||||||
|
if service_instance_list.type == service then
|
||||||
|
-- Based on config choose instance
|
||||||
|
if config.random_frontend then
|
||||||
|
-- TODO: cache this?
|
||||||
|
local usable_instances = {}
|
||||||
|
for _, instance_url_list in pairs(service_instance_list.instances) do
|
||||||
|
-- Instance URLs are split by pipes
|
||||||
|
for instance in string.gmatch(instance_url_list, "[^|]+") do
|
||||||
|
if instance.match(instance, "[.]onion$") then
|
||||||
|
if config.prefered_website_medium == "onion" then
|
||||||
|
table.insert(usable_instances, instance)
|
||||||
|
end
|
||||||
|
elseif instance.match(instance, "[.]i2p$") then
|
||||||
|
if config.prefered_website_medium == "eepsite" then
|
||||||
|
table.insert(usable_instances, instance)
|
||||||
|
end
|
||||||
|
elseif instance.match(instance, "[[][%d:]+[]]") then
|
||||||
|
if config.prefered_website_medium == "yggdrasil" then
|
||||||
|
table.insert(usable_instances, instance)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Assume clearnet
|
||||||
|
if config.prefered_website_medium == "clearnet" then
|
||||||
|
table.insert(usable_instances, instance)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return string.gsub(usable_instances[math.random(#usable_instances)], "https?://", "")
|
||||||
|
else
|
||||||
|
return string.gsub(service_instance_list.fallback, "https?://", "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return string.format("%s-no-instances-available", service)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Config file
|
||||||
|
dofile("config.lua")
|
||||||
|
|
||||||
|
-- Load subsitutions
|
||||||
|
local services_text = read_all_text("services.json")
|
||||||
|
local json = require("util.json")
|
||||||
|
local services, err = json.decode(services_text)
|
||||||
|
if services then
|
||||||
|
config.instances = services
|
||||||
|
else
|
||||||
|
print(string.format("Error loading \"services.json\": %s", err))
|
||||||
|
end
|
Loading…
Reference in a new issue