Skip to content

Instantly share code, notes, and snippets.

@selfawaresoup
Last active December 29, 2022 13:54
Show Gist options
  • Save selfawaresoup/cd77d682575a3985004d4c91dac3c41d to your computer and use it in GitHub Desktop.
Save selfawaresoup/cd77d682575a3985004d4c91dac3c41d to your computer and use it in GitHub Desktop.
Bulk domain blocks for Mastodon
#!/usr/bin/env ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'dotenv'
gem 'httparty'
end
require 'dotenv'
require 'httparty'
require 'csv'
require 'json'
Dotenv.load
BASE_URL=ENV['BASE_URL']
ACCESS_TOKEN=ENV['ACCESS_TOKEN']
CSV_FILENAME="domain-blocks.csv"
def request(method, path, data = {})
return unless [:GET, :POST, :PUT, :DELETE].include? method
headers = {
"Authorization" => "Bearer #{ACCESS_TOKEN}",
"Content-Type" => "application/json"
}
case method
when :GET
blocks = []
url = "#{BASE_URL}/api/v1/#{path}?"
loop do
puts "GET #{url}"
res = HTTParty.get( url, headers: headers)
puts "#{res.code}, #{res.length} entries"
blocks += res
next_urls = res.headers.dig("link").filter_map do |h|
m = h.match /<(?<url>http[^>]+)>; rel="next"/
next if m.nil?
m[:url]
end
url = next_urls.first()
break if url.nil?
end
return blocks
when :POST
return HTTParty.post( "#{BASE_URL}/api/v1/#{path}", headers: headers, body: JSON.generate(data))
when :PUT
return HTTParty.put( "#{BASE_URL}/api/v1/#{path}", headers: headers, body: JSON.generate(data))
when :DELETE
return HTTParty.delete( "#{BASE_URL}/api/v1/#{path}", headers: headers)
end
end
def entry_equal(a, b)
compare_fields = ['id', 'domain', 'severity', 'reject_media', 'reject_reports', 'private_comment', 'public_comment', 'obfuscate']
compare_fields.each do |k|
return false unless a[k] == b[k]
end
end
command = ARGV[0]
def dry_run
return ARGV.include? "--dry-run"
end
if command.nil?
puts <<~HELP
Usage:
./domain-blocks [command]
Commands:
dump fetches current production block list into CSV file
apply applies block list CSV file to production
HELP
exit
end
csv_options = {
headers: true,
}
if command == 'dump'
blocks = request(:GET, 'admin/domain_blocks')
puts "Fetched #{blocks.length} domain blocks"
CSV.open(CSV_FILENAME, "w", **csv_options) do |csv|
csv << ['id', 'domain', 'created_at', 'severity', 'reject_media', 'reject_reports', 'private_comment', 'public_comment', 'obfuscate']
for b in blocks
csv << b.values_at('id', 'domain', 'created_at', 'severity', 'reject_media', 'reject_reports', 'private_comment', 'public_comment', 'obfuscate')
end
end
exit
end
if command == 'apply'
remote_blocks = request(:GET, 'admin/domain_blocks')
saved_blocks = []
CSV.foreach(CSV_FILENAME, **csv_options) do |row|
entry = row.to_h
entry["reject_media"] = entry["reject_media"] == "true"
entry["reject_reports"] = entry["reject_reports"] == "true"
entry["obfuscate"] = entry["obfuscate"] == "true"
entry.delete "created_at"
saved_blocks << entry
end
saved_blocks.uniq! { |entry| entry["domain"] }
to_delete = remote_blocks.filter do |remote|
# only delete if the dmomain can't be found in the saved data at all
saved_blocks.none? do |saved|
saved["domain"] == remote["domain"]
end
end
to_create = []
to_update = []
saved_blocks.each do |saved|
if remote_blocks.none? { |remote| saved["id"] == remote["id"] }
to_create << saved
elsif remote_blocks.none? { |remote| entry_equal(saved, remote) }
to_update << saved
end
end
puts "To delete: #{to_delete.length}"
puts to_delete.map {|e| e["domain"]}.join("\n")
puts
puts "To create: #{to_create.length}"
puts to_create.map {|e| e["domain"]}.join("\n")
request(:POST, "admin/domain_blocks")
puts
puts "To update: #{to_update.length}"
puts to_update.map {|e| e["domain"]}.join("\n")
puts
if dry_run
puts "\nDry run, no actual changes are made.\n\n"
exit
end
to_delete.each do |entry|
puts "Deleting #{entry["domain"]}"
res = request(:DELETE, "admin/domain_blocks/#{entry["id"]}")
puts res.code
end
to_create.each do |entry|
puts "Creating #{entry["domain"]}"
entry.delete("id")
res = request(:POST, "admin/domain_blocks", entry)
pp res
puts res.code
end
to_update.each do |entry|
puts "Updating #{entry["domain"]}"
res = request(:PUT, "admin/domain_blocks/#{entry["id"]}", entry)
puts res.code
end
exit
end
#!/usr/bin/env ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'dotenv'
gem 'httparty'
end
require 'dotenv'
require 'httparty'
require 'csv'
require 'json'
INSTANCE_HOSTNAME=ARGV[0]
URL="https://#{INSTANCE_HOSTNAME}/api/v1/instance/domain_blocks"
CSV_FILENAME="import-#{INSTANCE_HOSTNAME}.csv"
csv_options = {
headers: true,
}
blocks = HTTParty.get(URL)
if blocks.code == 404
puts "No block list found at #{URL}"
end
exit(1) unless blocks.code == 200
n = 0
CSV.open(CSV_FILENAME, "w", **csv_options) do |csv|
csv << ['id', 'domain', 'created_at', 'severity', 'reject_media', 'reject_reports', 'private_comment', 'public_comment', 'obfuscate']
for b in blocks
pp b
next if b["domain"]&.include? "*" # ignore obfuscated domains
csv << ["", b['domain'], "", b["severity"], false, false, "", b["comment"], false]
n += 1
end
end
puts "Fetched #{n} domain blocks from #{INSTANCE_HOSTNAME}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment