Skip to content

Instantly share code, notes, and snippets.

@oliveira-andre
Created June 16, 2026 01:03
Show Gist options
  • Select an option

  • Save oliveira-andre/fcc3efd73d3399f72927190488db3e6d to your computer and use it in GitHub Desktop.

Select an option

Save oliveira-andre/fcc3efd73d3399f72927190488db3e6d to your computer and use it in GitHub Desktop.
require 'mechanize'
require 'byebug'
require 'json'
require 'open3'
require 'base64'
require 'net/http'
require 'open-uri'
require 'fileutils'
require 'active_support/core_ext/string/inflections'
require 'watir'
require 'telegram/bot'
TELEGRAM_KEY_TOKEN = ''.freeze
TELEGRAM_CHAT_ID = ''.freeze
# Only these chat ids may trigger commands through the listener. Defaults to the
# configured TELEGRAM_CHAT_ID; add more ids as needed.
TELEGRAM_ALLOWED_CHAT_IDS = [TELEGRAM_CHAT_ID].reject(&:empty?).map(&:to_i).freeze
class Mp3Free
attr_reader :url, :last_index
HELP_TEXT = <<~TEXT.freeze
Commands:
/download <url> [count] β€” download a song, or a playlist (count = number of songs)
/metadata <url> β€” fetch playlist/song metadata
TEXT
def initialize(url, last_index = nil, chat_id: TELEGRAM_CHAT_ID)
@url = url
@last_index = last_index&.to_i
@chat_id = chat_id
end
# Long-polling listener (no webhook). Keeps the process alive and dispatches the
# /download and /metadata commands sent through Telegram.
def self.listen!
if TELEGRAM_KEY_TOKEN.empty?
abort 'Set TELEGRAM_KEY_TOKEN at the top of the file to use listen mode'
end
puts 'Listening for commands on Telegram (Ctrl-C to stop)...'
Telegram::Bot::Client.run(TELEGRAM_KEY_TOKEN) do |bot|
bot.listen { |message| handle_message(bot, message) }
end
end
def self.handle_message(bot, message)
return unless message.respond_to?(:text) && message.text
chat_id = message.chat.id
unless TELEGRAM_ALLOWED_CHAT_IDS.include?(chat_id)
puts "Ignoring command from unauthorized chat_id=#{chat_id}"
return
end
command, args = message.text.strip.split(/\s+/, 2)
case command
when '/start', '/help'
bot.api.send_message(chat_id: chat_id, text: HELP_TEXT)
when '/metadata'
handle_metadata(bot, chat_id, args)
when '/download'
handle_download(bot, chat_id, args)
end
end
def self.handle_metadata(bot, chat_id, url)
return bot.api.send_message(chat_id: chat_id, text: 'Usage: /metadata <url>') unless url
bot.api.send_message(chat_id: chat_id, text: "πŸ”Ž Getting metadata: #{url}")
new(url, chat_id: chat_id).write_metadata
bot.api.send_message(chat_id: chat_id, text: 'Metadata got successfully')
rescue StandardError => e
puts "Metadata error: #{e.class}: #{e.message}"
bot.api.send_message(chat_id: chat_id, text: "Metadata failed: #{url}")
end
def self.handle_download(bot, chat_id, args)
url, last_index = args&.split(/\s+/, 2)
return bot.api.send_message(chat_id: chat_id, text: 'Usage: /download <url> [count]') unless url
bot.api.send_message(chat_id: chat_id, text: "▢️ Starting download: #{url}")
new(url, last_index, chat_id: chat_id).download_with_watir
rescue StandardError => e
puts "Download error: #{e.class}: #{e.message}"
end
def download
agent = Mechanize.new
agent.user_agent_alias = 'Mac Safari'
# page = agent.get('https://youtubemp3free.com/en/')
page = agent.get("https://youtubemp3free.com/index.php?link=#{url}")
public_ip = Net::HTTP.get(URI('https://api.ipify.org')).strip
i_param = Base64.strict_encode64(public_ip)
metadata = self.metadata
index = url.match(/&index=/)&.last
id = if index
metadata['entries'][index.to_i - 1]['id']
else
metadata['id']
end
slug = metadata['title'].parameterize
first_url = "https://sern.info/youtube-to-mp3/#{slug}_#{id}.html?t=#{Time.now.to_i}&i=#{i_param}&utm_source=ytmp3&utm_medium=sync&utm_campaign=#{id}"
page = agent.get(first_url)
# The page fires an XHR that produces the cid. Mechanize doesn't run JS, so
# we scan the HTML/JS for the endpoint and replay it ourselves. Cookies
# persist via the agent; the Referer header is set explicitly so the server
# treats this as in-context.
xhr_endpoint = page.body[%r{["'](https?://[^"']+\.php[^"']*)["']}, 1] ||
page.body[%r{["'](/[^"']+\.php[^"']*)["']}, 1]
xhr_endpoint = URI.join(first_url, xhr_endpoint).to_s if xhr_endpoint&.start_with?('/')
agent.get(xhr_endpoint, [], first_url, { 'X-Requested-With' => 'XMLHttpRequest' }) if xhr_endpoint
page = agent.get('https://sern.info/youtu.be', [], first_url)
download_href = page.body[/download-href=["']([^"']+)["']/, 1]
debugger
puts download_href
end
def download_with_watir
metadata = self.metadata
index = url.split(/&index=/)&.last
current_album = File.join('tmp', (metadata['title'] || 'downloads').gsub(%r{[/\\:*?"<>|]}, '_'))
FileUtils.mkdir_p(current_album)
if last_index
current_index = index.to_i - 1
current_last_index = last_index - 1
(current_index..current_last_index).each do |i|
if i > current_index
id = metadata['entries'][i]['id']
new_url = url.gsub(/v=[^&]+/, "v=#{id}").gsub(/index=\d+/, "index=#{i + 1}")
else
new_url = url
end
with_retries(label: "track #{i + 1} (#{new_url})") do
browser = Watir::Browser.new :chrome, headless: false
begin
browser.goto('https://youtubemp3free.com/en')
browser.text_field(name: 'link').set(new_url)
browser.button(type: 'submit').click
browser.wait_until(timeout: 90) { |b| b.html.include?('download-href=') }
sleep 5
title = browser.h1.text
clickable = browser.a(id: 'download-url').href
thumb_src = (browser.img(class: 'thumb').src if i.zero?)
ensure
browser.close
end
save_album_image(thumb_src, current_album) if thumb_src
filename = File.join(current_album, "#{title.gsub(%r{[/\\:*?"<>|]}, '_')}.mp3")
URI.parse(clickable).open do |source|
File.open(filename, 'wb') { |f| IO.copy_stream(source, f) }
end
puts "Downloaded: #{filename}"
notify("βœ… Downloaded: <b>#{title}</b> (#{i + 1}/#{current_last_index + 1}). Ready for the next one.")
end
end
notify("πŸŽ‰ Playlist finished: <b>#{metadata['title']}</b>. Ready to download another one.")
else
with_retries(label: url) do
browser = Watir::Browser.new :chrome, headless: false
begin
browser.goto('https://youtubemp3free.com/en')
browser.text_field(name: 'link').set(url)
browser.button(type: 'submit').click
browser.wait_until(timeout: 90) { |b| b.html.include?('download-href=') }
sleep 5
title = browser.h1.text
clickable = browser.a(id: 'download-url').href
thumb_src = browser.img(class: 'thumb').src
ensure
browser.close
end
save_album_image(thumb_src, current_album)
filename = File.join(current_album, "#{title.gsub(%r{[/\\:*?"<>|]}, '_')}.mp3")
URI.parse(clickable).open do |source|
File.open(filename, 'wb') { |f| IO.copy_stream(source, f) }
end
puts "Downloaded: #{filename}"
notify("βœ… Downloaded: <b>#{title}</b>. Ready to download another one.")
end
end
end
def metadata
@metadata ||= if File.exist?(metadata_file)
puts "Loading metadata from #{metadata_file}"
JSON.parse(File.read(metadata_file))
else
fetch_metadata
end
end
def write_metadata
FileUtils.mkdir_p(File.dirname(metadata_file))
data = fetch_metadata
File.write(metadata_file, JSON.pretty_generate(data))
puts "Metadata written to #{metadata_file}"
data
end
def metadata_file
File.join('tmp', 'current_metadata.json')
end
private
def with_retries(max: 5, label: nil)
attempts = 0
begin
yield
rescue StandardError => e
attempts += 1
if attempts < max
puts "Attempt #{attempts} failed: #{e.message}. Retrying (#{attempts}/#{max - 1})..."
retry
end
notify("❌ Download failed after #{max} attempts#{label ? " for #{label}" : ''}: #{e.message}")
raise
end
end
def notify(text)
return if @chat_id.to_s.empty? || TELEGRAM_KEY_TOKEN.empty?
telegram_bot.send_message(chat_id: @chat_id, text: text, parse_mode: 'HTML')
rescue StandardError => e
puts "Telegram notify failed: #{e.class}: #{e.message}"
end
def telegram_bot
@telegram_bot ||= Telegram::Bot::Api.new(TELEGRAM_KEY_TOKEN)
end
def save_album_image(src, album_dir)
ext = File.extname(URI.parse(src).path)
ext = '.jpg' if ext.empty?
path = File.join(album_dir, "album#{ext}")
URI.parse(src).open { |source| File.open(path, 'wb') { |f| IO.copy_stream(source, f) } }
puts "Saved album image: #{path}"
end
def fetch_metadata
raise ArgumentError, "Invalid URL: #{url.inspect}" unless valid_http_url?(url)
# `--` stops yt-dlp from treating a `-`-prefixed url as a flag (argv injection).
stdout, _stderr, _status = Open3.capture3('yt-dlp', '-J', '--skip-download', '--', url)
JSON.parse(stdout)
end
def valid_http_url?(value)
return false if value.nil? || value.start_with?('-')
URI.parse(value).is_a?(URI::HTTP)
rescue URI::InvalidURIError
false
end
end
action = ARGV[0]
case action
when 'metadata'
url = ARGV[1]
abort 'Usage: ruby mp3free.rb metadata <url>' unless url
Mp3Free.new(url).write_metadata
when 'listen', nil
# No args: stay alive and accept downloads through Telegram (no webhook).
Mp3Free.listen!
else
# One-shot: ruby mp3free.rb <url> [last_index]
url = ARGV[0]
last_index = ARGV[1]
Mp3Free.new(url, last_index).download_with_watir
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment