Last active
December 29, 2022 13:54
-
-
Save selfawaresoup/cd77d682575a3985004d4c91dac3c41d to your computer and use it in GitHub Desktop.
Bulk domain blocks for Mastodon
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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