Skip to content

Instantly share code, notes, and snippets.

@Daniel-Worrall
Created June 21, 2020 04:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Daniel-Worrall/dfd4de93cb117beea8a49609ba3c98d9 to your computer and use it in GitHub Desktop.
Save Daniel-Worrall/dfd4de93cb117beea8a49609ba3c98d9 to your computer and use it in GitHub Desktop.
twist.moe Downloader Crystal
require "./twist"
CHUNK_SIZE = 1024
name = ""
path = ""
Dir.mkdir_p(path)
Twist.get_sources(name).each do |source|
filename = "#{path}/#{name} E#{source.number.to_s.rjust(2, '0')}.mp4"
print filename
response = HTTP::Client.head("https://twist.moe#{source.link}")
while response.status_code == 302
response = HTTP::Client.get(response.headers.get("Location").first, headers: HTTP::Headers{"Referer" => "https://twist.moe/"}) do |r|
next r if r.status_code == 302
puts " - #{r.headers.get("Content-Length").first.to_i.humanize}B"
io = r.body_io
chunk = Slice(UInt8).new(CHUNK_SIZE)
File.open(filename, "w") do |file|
while (count = io.read(chunk)) > 0
if count == CHUNK_SIZE
file.write(chunk)
else
file.write chunk[0...count]
end
end
end
r
end
end
end
require "http/client"
require "json"
require "openssl"
module Twist
TOKEN = "1rj2vRtegS8Y60B3w3qNZm5T2Q0TN2NR"
KEY = "LXgIVP&PorO68Rq7dTx8N^lP!Fa5sGJ^*XK"
alias Sources = Array(Source)
struct Anime
include JSON::Serializable
property id : Int32
property title : String
property alt_title : Nil
property season : Int32
property ongoing : Int32
property description : String
property hb_id : Int32
property created_at : String
property updated_at : String
property hidden : Int32
property mal_id : Nil
property slug : Source
property episodes : Array(Source)
end
struct Source
include JSON::Serializable
property id : Int32
property source : String?
property number : Int32?
property anime_id : Int32
property created_at : String
property updated_at : String
property slug : String?
def link
raise "No source" unless source
cipher = OpenSSL::Cipher.new("aes-256-cbc")
encrypted = Base64.decode_string(source.not_nil!)
salt = encrypted.to_slice[8, 8]
my_data = KEY + String.new(salt)
final_key = key = String.build { |str| str.write(OpenSSL::MD5.hash(my_data).to_slice) }
while final_key.to_slice.size < 48
key = String.build { |str| str.write(OpenSSL::MD5.hash(key + my_data).to_slice) }
final_key += key
end
cipher.key = String.new(final_key.to_slice[0...32])
cipher.iv = String.new(final_key.to_slice[32...48])
cipher.decrypt
String.build do |str|
str.write(cipher.update(encrypted.to_slice[16..]))
str.write(cipher.final)
end
end
end
def self.get_anime(anime) : Anime
Anime.from_json(HTTP::Client.get("https://twist.moe/api/anime/#{anime}", headers: HTTP::Headers{"x-access-token" => TOKEN}).body)
end
def self.get_sources(anime) : Sources
Sources.from_json(HTTP::Client.get("https://twist.moe/api/anime/#{anime}/sources", headers: HTTP::Headers{"x-access-token" => TOKEN}).body)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment