add bittorrent url encoding

This commit is contained in:
CJ_Clippy 2025-02-01 20:00:49 -08:00
parent 5ebea988fa
commit 1e539d908d
27 changed files with 613 additions and 150 deletions

View File

@ -6,7 +6,7 @@ services:
dockerfile: dockerfiles/opentracker.dockerfile
container_name: opentracker
environment:
- WHITELIST_FEED_URL=http://bright:4000/torrents/whitelist?type=json
- WHITELIST_FEED_URL=http://bright:4000/torrents
ports:
- "6969:6969/tcp"
- "6969:6969/udp"
@ -63,6 +63,7 @@ services:
SUPERSTREAMER_URL: http://superstreamer-api:52001
PUBLIC_S3_ENDPOINT: https://fp-dev.b-cdn.net
BT_TRACKER_URL: https://tracker.futureporn.net/announce
BT_TRACKER_ACCESSLIST_URL: http://opentracker:8666
SITE_URL: https://futureporn.net
env_file:
- .kamal/secrets.development

View File

@ -30,7 +30,8 @@ config :bright,
superstreamer_auth_token: System.get_env("SUPERSTREAMER_AUTH_TOKEN"),
public_s3_endpoint: System.get_env("PUBLIC_S3_ENDPOINT"),
s3_cdn_endpoint: System.get_env("PUBLIC_S3_ENDPOINT"),
bittorrent_tracker_url: System.get_env("BT_TRACKER_URL"),
bt_tracker_url: System.get_env("BT_TRACKER_URL"),
bt_tracker_accesslist_url: System.get_env("BT_TRACKER_ACCESSLIST_URL"),
site_url: System.get_env("SITE_URL")
config :bright, :buckets,

View File

@ -0,0 +1,39 @@
defmodule Bright.BittorrentUrlEncode do
@moduledoc """
URL encoding for Bittorrent Info hash v1. https://www.bittorrent.org/beps/bep_0003.html
Contains deviations from Elixir's URI.encode(). https://github.com/elixir-lang/elixir/blob/78f63d08313677a680868685701ae79a2459dcc1/lib/elixir/lib/uri.ex#L395
Contains deviations from RFC3986 Section 2.2. https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
Designed to be compatible with qBittorrent's percent encoding.
"""
import Bitwise
@spec encode(binary()) :: binary()
def encode(string) when is_binary(string) do
string = Base.decode16!(string, case: :lower)
enc(string, &char_allowed?/1)
end
@spec enc(binary, (byte -> as_boolean(term))) :: binary
defp enc(string, predicate)
when is_binary(string) and is_function(predicate, 1) do
for <<byte <- string>>, into: "", do: percent(byte, predicate)
end
defp char_allowed?(character) do
character in ?0..?9 or character in ?a..?z or character in ?A..?Z or character in ~c"~_-.!"
end
defp percent(char, predicate) do
if predicate.(char) do
<<char>>
else
<<"%", hex(bsr(char, 4)), hex(band(char, 15))>>
end
end
defp hex(n) when n <= 9, do: n + ?0
defp hex(n), do: n + ?a - 10
end

View File

@ -9,8 +9,9 @@ defmodule Bright.ObanWorkers.CreateTorrent do
B2,
Images,
Cache,
Torrent,
Tracker
Torrents,
Tracker,
Torrentfile
}
require Logger
import Ecto.Query, warn: false
@ -18,13 +19,16 @@ defmodule Bright.ObanWorkers.CreateTorrent do
def perform(%Oban.Job{args: %{"vod_id" => vod_id}}) do
IO.puts "hello this is Oban.Job and we are testing to see if we can get env vars here. Application.fetch_env!(:bright, :bt_tracker_accesslist_url)=#{Application.fetch_env!(:bright, :bt_tracker_accesslist_url)} System.get_env('BT_TRACKER_ACCESSLIST_URL')=#{System.get_env("BT_TRACKER_ACCESSLIST_URL")}"
vod = Streams.get_vod!(vod_id)
with {:ok, filename} <- B2.get(vod),
{:ok, torrent} <- Torrent.create_torrent(vod),
{:ok, %{cdn_url: cdn_url}} <- B2.put(torrent.local_path, torrent.basename),
:ok <- Tracker.whitelist_info_hash(torrent.info_hash),
:ok <- Tracker.announce_torrent(torrent.magnet_link),
{:ok, updated_vod} <- Streams.update_vod(vod, %{torrent: cdn_url, magnet_link: torrent.magnet_link}) do
{:ok, tf} <- Torrentfile.create(vod, filename),
{:ok, %{cdn_url: cdn_url}} <- B2.put(tf.save_path, Path.basename(tf.save_path)),
{:ok, torrent} <- Torrents.create_torrent(%{info_hash_v1: tf.btih, info_hash_v2: tf.btmh, cdn_url: cdn_url, magnet: tf.magnet}),
:ok <- Tracker.whitelist_info_hash(torrent.info_hash_v1),
:ok <- Tracker.whitelist_info_hash(torrent.info_hash_v2),
:ok <- Tracker.announce_torrent(torrent.magnet),
{:ok, updated_vod} <- Streams.update_vod(vod, %{torrent_id: torrent.id}) do
{:ok, updated_vod}
end
end

View File

@ -1,44 +0,0 @@
defmodule Bright.Torrent do
alias Bright.Streams.Vod
alias Bright.{Cache,Torrentfile,B2}
def bittorrent_tracker_url do
Application.fetch_env!(:bright, :bittorrent_tracker_url)
end
def site_url do
Application.fetch_env!(:bright, :site_url)
end
def create_torrent(input_path, output_path, web_seed_url, vod_id) do
IO.puts "site_url=#{site_url()}"
IO.puts "bittorrent_tracker_url=#{bittorrent_tracker_url()}"
tracker_url = bittorrent_tracker_url()
source_url = URI.append_path(URI.parse(site_url()), "/vods/#{vod_id}") |> URI.to_string()
comment = site_url()
meta_version = 3 # hybrid BT v1 & v2
{:ok, %{btih: btih, btmh: btmh, magnet: magnet, save_path: save_path} = torrentfile} = Torrentfile.create(input_path, output_path, tracker_url, source_url, comment, web_seed_url, meta_version)
# upload to s3
basename = Path.basename(save_path)
{:ok, asset} = B2.put(save_path, basename)
{:ok, %{basename: basename, local_path: save_path, magnet_link: magnet, info_hash_v1: btih, info_hash_v2: btmh}}
end
end

View File

@ -4,6 +4,16 @@ defmodule Bright.Torrentfile do
"""
alias Bright.Cache
alias Bright.Streams.Vod
def bittorrent_tracker_url do
Application.fetch_env!(:bright, :bt_tracker_url)
end
def site_url do
Application.fetch_env!(:bright, :site_url)
end
# @spec execute(command :: Command.t) :: {:ok, binary()} | {:error, {Collectable.t, exit_status :: non_neg_integer}}
# def execute(%Command{} = command) do
@ -66,7 +76,19 @@ defmodule Bright.Torrentfile do
defp extract_last(nil), do: nil
defp extract_last(list) when is_list(list), do: List.last(list)
def create(%Vod{} = vod, input_path) do
output_path = Cache.generate_filename("vod-#{vod.id}", "torrent")
tracker_url = bittorrent_tracker_url()
site_url = site_url()
comment = site_url
source_url = URI.parse(site_url) |> URI.append_path("/vods/#{vod.id}") |> URI.to_string()
web_seed_url = vod.s3_cdn_url
meta_version = 3
create(input_path, output_path, tracker_url, comment, source_url, web_seed_url, meta_version)
end
def create(input_path, output_path, tracker_url, source_url, comment, web_seed_url, meta_version) do
IO.puts "Torrentfile.create called with args input_path=#{input_path}, output_path=#{output_path}, tracker_url=#{tracker_url}, source_url=#{source_url}, comment=#{comment}, web_seed_url=#{web_seed_url}, meta_version=#{meta_version}"
case Rambo.run(torrentfile_path(), [
"--magnet",
"--prog", "0",

View File

@ -4,20 +4,24 @@ defmodule Bright.Torrents do
"""
import Ecto.Query, warn: false
alias Bright.Repo
alias Bright.Streams.Vod
alias Bright.{Repo,Cache,Torrentfile,B2}
alias Bright.Torrents.Torrent
@doc """
Returns the list of torrent.
## Examples
iex> list_torrent()
iex> list_torrents()
[%Torrent{}, ...]
"""
def list_torrent do
def list_torrents do
Repo.all(Torrent)
end
@ -37,6 +41,7 @@ defmodule Bright.Torrents do
"""
def get_torrent!(id), do: Repo.get!(Torrent, id)
@doc """
Creates a torrent.
@ -55,6 +60,10 @@ defmodule Bright.Torrents do
|> Repo.insert()
end
@doc """
Updates a torrent.
@ -101,4 +110,31 @@ defmodule Bright.Torrents do
def change_torrent(%Torrent{} = torrent, attrs \\ %{}) do
Torrent.changeset(torrent, attrs)
end
# @doc """
# Generates a torrent file on disk, then uploads it to S3
# """
# def generate_torrent_file(input_path, output_path, web_seed_url, vod_id) do
# s3_asset_key = Cache.deterministic_filename("vod", attrs.vod_id, "mp4")
# output_path = Cache.generate_filename(s3_asset_key, "torrent")
# input_path = Cache.get(s3_asset_key).local_path
# tracker_url = bittorrent_tracker_url()
# source_url = URI.append_path(URI.parse(site_url()), "/vods/#{vod_id}") |> URI.to_string()
# comment = site_url()
# meta_version = 3 # hybrid BT v1 & v2
# {:ok, %{btih: btih, btmh: btmh, magnet: magnet, save_path: save_path} = torrentfile} = Torrentfile.create(input_path, output_path, tracker_url, source_url, comment, web_seed_url, meta_version)
# # upload to s3
# {:ok, asset} = B2.put(save_path, s3_asset_key)
# # {:ok, %{basename: basename, local_path: save_path, magnet_link: magnet, info_hash_v1: btih, info_hash_v2: btmh}}
# end
end

View File

@ -1,25 +1,117 @@
defmodule Bright.Tracker do
@unreserved_chars 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'
alias Bright.Streams.Vod
alias Bright.{Cache,Torrentfile,B2}
def tracker_url do
Application.get_env!(:bright, :bt_tracker_url)
def bt_tracker_url do
Application.fetch_env!(:bright, :bt_tracker_url)
end
def bt_tracker_accesslist_url do
Application.fetch_env!(:bright, :bt_tracker_accesslist_url)
end
# We start with the `Info hash v1` of the torrent, as copied from qBittorrent.
info_hash_v1 = "acc3b2e433d7c7475abb5941b5681cb7a1ea26e2"
# Next we decode into binary.
binary = Base.decode16!(info_hash_v1, case: :lower)
# Next we
@doc """
Encodes `string` as BEP3's weird URL encoded string.
## Example
iex> bep3_encode("a88fda5954e89178c372716a6a78b8180ed4dad3")
"%A8%8F%DAYT%E8%91x%C3rqjjx%B8%18%0E%D4%DA%D3"
"""
@spec bep3_encode(binary) :: binary
def bep3_encode(string) when is_binary(string) do
string = Base.decode16!(string, case: :lower)
URI.encode(string, &URI.char_unreserved?/1)
end
# @spec bep3_encode(binary) :: binary
# def bep3_encode(string) when is_binary(string) do
# string = Base.decode16!(string, case: :lower)
# IO.puts inspect(string)
# for <<byte <- string>>, into: "" do
# percent(byte, &URI.char_unreserved?/1)
# end
# end
# defp percent(char, predicate) do
# if predicate.(char) do
# <<char>>
# else
# <<"%", hex(Bitwise.bsr(char, 4)), hex(Bitwise.band(char, 15))>>
# end
# end
# defp hex(n) when n <= 9, do: n + ?0
# defp hex(n), do: n + ?A - 10
# def url_encode_info_hash(info_hash) do
# info_hash |> Base.decode16!(case: :lower) |> URI.encode_www_form() |> String.replace("+", "%20")
# end
def announce_torrent(info_hash) do
encoded_info_hash = bep3_encode(info_hash)
url = bt_tracker_url() |> URI.parse() |> URI.append_query("info_hash=#{encoded_info_hash}") |> URI.to_string()
body = []
headers = []
case HTTPoison.get(url, body, headers) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
IO.puts inspect(Bento.decode(body))
{:ok, body}
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
{:error, %{status: status, body: body}}
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}
failed ->
Logger.error("Failed to POST. #{inspect(failed)}")
{:error, :failed}
end
end
def whitelist_info_hash(info_hash) do
server = "tcp://ncat:8666"
port = 8666
IO.puts "bt_tracker_accesslist_url=#{bt_tracker_accesslist_url()}"
{host, port} = case URI.parse(bt_tracker_accesslist_url()) do
%URI{host: host, port: port} when not is_nil(host) and not is_nil(port) ->
{host, port}
_ ->
raise "Invalid bt_tracker_accesslist_url: #{bt_tracker_accesslist_url()}"
end
host = String.to_charlist(host)
# Open a TCP connection
{:ok, socket} = :gen_tcp.connect(server, port, [:binary, packet: :raw, active: false])
{:ok, socket} = :gen_tcp.connect(host, port, [:binary, packet: :raw, active: false])
# Send the "hello world" data to the server
:gen_tcp.send(socket, "#{info_hash}\n")
@ -28,12 +120,6 @@ defmodule Bright.Tracker do
:gen_tcp.close(socket)
# url = "http://ncat:6868"
# body = [
# ]
# headers = []
# HTTPoison.post(url, body, headers)
end
end

View File

@ -0,0 +1,62 @@
defmodule BrightWeb.TorrentController do
use BrightWeb, :controller
alias Bright.Torrents
alias Bright.Torrents.Torrent
def index(conn, _params) do
torrent = Torrents.list_torrents()
render(conn, :index, torrents: torrent)
end
def new(conn, _params) do
changeset = Torrents.change_torrent(%Torrent{})
render(conn, :new, changeset: changeset)
end
def create(conn, %{"torrent" => torrent_params}) do
case Torrents.create_torrent(torrent_params) do
{:ok, torrent} ->
conn
|> put_flash(:info, "Torrent created successfully.")
|> redirect(to: ~p"/torrent/#{torrent}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
torrent = Torrents.get_torrent!(id)
render(conn, :show, torrent: torrent)
end
def edit(conn, %{"id" => id}) do
torrent = Torrents.get_torrent!(id)
changeset = Torrents.change_torrent(torrent)
render(conn, :edit, torrent: torrent, changeset: changeset)
end
def update(conn, %{"id" => id, "torrent" => torrent_params}) do
torrent = Torrents.get_torrent!(id)
case Torrents.update_torrent(torrent, torrent_params) do
{:ok, torrent} ->
conn
|> put_flash(:info, "Torrent updated successfully.")
|> redirect(to: ~p"/torrent/#{torrent}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :edit, torrent: torrent, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
torrent = Torrents.get_torrent!(id)
{:ok, _torrent} = Torrents.delete_torrent(torrent)
conn
|> put_flash(:info, "Torrent deleted successfully.")
|> redirect(to: ~p"/torrent")
end
end

View File

@ -0,0 +1,13 @@
defmodule BrightWeb.TorrentHTML do
use BrightWeb, :html
embed_templates "torrent_html/*"
@doc """
Renders a torrent form.
"""
attr :changeset, Ecto.Changeset, required: true
attr :action, :string, required: true
def torrent_form(assigns)
end

View File

@ -0,0 +1,8 @@
<.header>
Edit Torrent {@torrent.id}
<:subtitle>Use this form to manage torrent records in the database.</:subtitle>
</.header>
<.torrent_form changeset={@changeset} action={~p"/torrent/#{@torrent}"} />
<.back navigate={~p"/torrent"}>Back to torrent</.back>

View File

@ -0,0 +1,26 @@
<.header>
Listing Torrent
<:actions>
<.link href={~p"/torrent/new"}>
<.button>New Torrent</.button>
</.link>
</:actions>
</.header>
<.table id="torrent" rows={@torrents} row_click={&JS.navigate(~p"/torrent/#{&1}")}>
<:col :let={torrent} label="Info hash v1">{torrent.info_hash_v1}</:col>
<:col :let={torrent} label="Info hash v2">{torrent.info_hash_v2}</:col>
<:col :let={torrent} label="Cdn url">{torrent.cdn_url}</:col>
<:col :let={torrent} label="Magnet">{torrent.magnet}</:col>
<:action :let={torrent}>
<div class="sr-only">
<.link navigate={~p"/torrent/#{torrent}"}>Show</.link>
</div>
<.link navigate={~p"/torrent/#{torrent}/edit"}>Edit</.link>
</:action>
<:action :let={torrent}>
<.link href={~p"/torrent/#{torrent}"} method="delete" data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>

View File

@ -0,0 +1,8 @@
<.header>
New Torrent
<:subtitle>Use this form to manage torrent records in the database.</:subtitle>
</.header>
<.torrent_form changeset={@changeset} action={~p"/torrent"} />
<.back navigate={~p"/torrent"}>Back to torrent</.back>

View File

@ -0,0 +1,18 @@
<.header>
Torrent {@torrent.id}
<:subtitle>This is a torrent record from the database.</:subtitle>
<:actions>
<.link href={~p"/torrent/#{@torrent}/edit"}>
<.button>Edit torrent</.button>
</.link>
</:actions>
</.header>
<.list>
<:item title="Info hash v1">{@torrent.info_hash_v1}</:item>
<:item title="Info hash v2">{@torrent.info_hash_v2}</:item>
<:item title="Cdn url">{@torrent.cdn_url}</:item>
<:item title="Magnet">{@torrent.magnet}</:item>
</.list>
<.back navigate={~p"/torrent"}>Back to torrent</.back>

View File

@ -0,0 +1,12 @@
<.simple_form :let={f} for={@changeset} action={@action}>
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:info_hash_v1]} type="text" label="Info hash v1" />
<.input field={f[:info_hash_v2]} type="text" label="Info hash v2" />
<.input field={f[:cdn_url]} type="text" label="Cdn url" />
<.input field={f[:magnet]} type="text" label="Magnet" />
<:actions>
<.button>Save Torrent</.button>
</:actions>
</.simple_form>

View File

@ -0,0 +1,26 @@
defmodule BrightWeb.TorrentJSON do
alias Bright.Torrents.Torrent
@doc """
Renders a list of torrents.
"""
def index(%{torrents: torrents}) do
%{data: for(torrent <- torrents, do: data(torrent))}
end
@doc """
Renders a single torrent.
"""
def show(%{torrent: torrent}) do
%{data: data(torrent)}
end
defp data(%Torrent{} = torrent) do
%{
id: torrent.id,
info_hash_v1: torrent.info_hash_v1,
info_hash_v2: torrent.info_hash_v2,
cdn_url: torrent.cdn_url,
magnet: torrent.magnet
}
end
end

View File

@ -88,7 +88,7 @@ defmodule BrightWeb.Router do
post("/join", UserController, :join)
post("/join", UserController, :join)
resources("/orders", OrderController, only: [:create, :show])
resources("/torrents", TorrentController, only: [:index, :show])
get("/streams", StreamController, :index)
get("/streams/:id", StreamController, :show)

View File

@ -67,6 +67,7 @@ defmodule Bright.MixProject do
{:sweet_xml, "~> 0.6"},
{:ex_m3u8, "~> 0.14.2"},
{:atomex, "~> 0.3.0"},
{:bento, "~> 1.0"},
# {:membrane_core, "~> 1.0"},
# {:membrane_mpeg_ts_plugin, "~> 1.0.3"},
# {:membrane_file_plugin, "~> 0.17.2"},

View File

@ -2,6 +2,7 @@
"atomex": {:hex, :atomex, "0.3.0", "19b5d1a2aef8706dbd307385f7d5d9f6f273869226d317492c396c7bacf26402", [:mix], [{:xml_builder, "~> 2.0.0", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "025dbc3a3e99380894791a093019f535d0ef6cf1916f6ec1b778ac107fcfc3e4"},
"bandit": {:hex, :bandit, "1.6.6", "f2019a95261d400579075df5bc15641ba8e446cc4777ede6b4ec19e434c3340d", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "ceb19bf154bc2c07ee0c9addf407d817c48107e36a66351500846fc325451bf9"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"},
"bento": {:hex, :bento, "1.0.0", "5097e6f02e4980b72d08bf0270026f6f5c9bf5d8ca606b43d41321b549d49de8", [:mix], [], "hexpm", "b921b335a555f7570adfac0cc41864c76377f6852617cf1f25fbb88578b993c8"},
"bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"},
"bulma": {:hex, :bulma, "1.0.2", "50dfffe8d28b0bd527418560223b407f9e80e990e187e1653b17eff818f8fcbe", [:mix], [], "hexpm", "27745727ff7f451d140a2438c0ca4448bc8ca73e0a6d2d4f24e1b5b9ced8a774"},
"bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"},

View File

@ -0,0 +1,55 @@
defmodule Bright.BittorrentUrlEncodeTest do
use ExUnit.Case
doctest Bright.BittorrentUrlEncode
alias Bright.BittorrentUrlEncode
test "The WIRED CD - Rip. Sample. Mash. Share a8 8f da 59 54 e8 91 78 c3 72 71 6a 6a 78 b8 18 0e d4 da d3" do
actual = BittorrentUrlEncode.encode("a88fda5954e89178c372716a6a78b8180ed4dad3")
expected = "%a8%8f%daYT%e8%91x%c3rqjjx%b8%18%0e%d4%da%d3"
assert actual === expected
end
test "tails-amd64-6.10-img 07 b4 51 63 36 e4 af e9 23 2c 73 bc 31 26 42 59 0a 7d 7e 95" do
actual = BittorrentUrlEncode.encode("07b4516336e4afe9232c73bc312642590a7d7e95")
expected = "%07%b4Qc6%e4%af%e9%23%2cs%bc1%26BY%0a%7d~%95"
assert actual === expected
end
test "linuxmint-22-mate-64bit.iso e0 a4 05 8e 40 7d dd ad 1c ac f8 c9 ce db 0b 27 21 c0 7f 92" do
actual = BittorrentUrlEncode.encode("e0a4058e407dddad1cacf8c9cedb0b2721c07f92")
expected = "%e0%a4%05%8e%40%7d%dd%ad%1c%ac%f8%c9%ce%db%0b%27!%c0%7f%92"
assert actual === expected
end
test "debian-12.8.0-amd64-DVD-1.iso 56 3e 72 81 c0 00 e1 80 91 e5 c0 d3 9d 09 8c ff 13 5d ab 26" do
actual = BittorrentUrlEncode.encode("563e7281c000e18091e5c0d39d098cff135dab26")
expected = "V%3er%81%c0%00%e1%80%91%e5%c0%d3%9d%09%8c%ff%13%5d%ab%26"
assert actual === expected
end
test "KNOPPIX_V9.1DVD-2021-01-25-EN c0 3b b7 09 bd 7e fe 79 68 87 75 c4 fc 92 51 41 e4 1d b2 87" do
actual = BittorrentUrlEncode.encode("c03bb709bd7efe79688775c4fc925141e41db287")
expected = "%c0%3b%b7%09%bd~%feyh%87u%c4%fc%92QA%e4%1d%b2%87"
assert actual === expected
end
test "Peppermint-7-20160616-i386.iso 72 c3 7d bf e4 39 0f 1b 74 b9 b4 25 07 e8 97 01 b4 9e c7 a3" do
actual = BittorrentUrlEncode.encode("72c37dbfe4390f1b74b9b42507e89701b49ec7a3")
expected = "r%c3%7d%bf%e49%0f%1bt%b9%b4%25%07%e8%97%01%b4%9e%c7%a3"
assert actual === expected
end
test "crunchbang-11-20130506-i686.iso 88 fa 85 16 ca 2b 3d 3d 41 86 75 20 84 f9 3a 59 91 8f a3 51" do
actual = BittorrentUrlEncode.encode("88fa8516ca2b3d3d4186752084f93a59918fa351")
expected = "%88%fa%85%16%ca%2b%3d%3dA%86u%20%84%f9%3aY%91%8f%a3Q"
assert actual === expected
end
test "kali-linux-2024.3-installer-amd64.iso 7b 14 90 47 4e 51 53 6e 1a 7a c0 df ec 24 67 e8 6d a2 32 a4" do
actual = BittorrentUrlEncode.encode("7b1490474e51536e1a7ac0dfec2467e86da232a4")
expected = "%7b%14%90GNQSn%1az%c0%df%ec%24g%e8m%a22%a4"
assert actual === expected
end
end

View File

@ -14,19 +14,11 @@ defmodule Bright.CacheTest do
## To implement this cache before the system works is pre-mature optimization!
# describe "cache k/v" do
# test "get/1 with string cache key" do
# test "get/1 with string cache key" do
# end
# end
# test "get/1 with %Vod{}" do
# stream = stream_fixture()
# vod = vod_fixture(%{stream_id: stream.id})
# Cache.get(vod)
# end
# test "put/2" do
# end
# end
describe "cache" do

View File

@ -20,7 +20,7 @@ defmodule Bright.CreateTorrentTest do
@tag :integration
test "torrent creation" do
stream = stream_fixture()
vod = vod_fixture(%{torrent: nil, stream_id: stream.id, origin_temp_input_url: @test_video_url})
vod = vod_fixture(%{torrent: nil, stream_id: stream.id, s3_cdn_url: @test_video_url})
{:ok, %Vod{torrent: torrent, magnet_link: magnet_link, info_hash_v1: info_hash_v1, info_hash_v2: info_hash_v2}}
= perform_job(Bright.ObanWorkers.CreateTorrent, %{vod_id: vod.id})
assert Regex.match?(~r/^https:\/\/.*\.torrent$/, torrent)

View File

@ -1,49 +0,0 @@
defmodule Bright.TorrentTest do
use Bright.DataCase
alias Bright.Torrent
describe "torrent" do
import Bright.StreamsFixtures
alias Bright.{Downloader,Cache}
@test_fixture "https://futureporn-b2.b-cdn.net/test-fixture.ts"
# @tag :integration
# test "create_torrent/1" do
# stream = stream_fixture()
# vod = vod_fixture(%{stream_id: stream.id, s3_cdn_url: "https://futureporn-b2.b-cdn.net/test-fixture.ts"})
# {:ok, _} = Torrent.create_torrent(vod)
# assert :ok
# end
@tag :integration
test "create_torrent/7" do
stream = stream_fixture()
vod = vod_fixture(%{stream_id: stream.id, s3_cdn_url: "https://futureporn-b2.b-cdn.net/test-fixture.ts"})
input_path = Path.absname("./test/fixtures/test-fixture.ts")
output_path = Cache.generate_filename("test", "torrent")
tracker_url = "https://tracker.futureporn.net/announce"
source_url = "https://futureporn.net/vods/69"
comment = "https://futureporn.net"
web_seed_url = @test_fixture
meta_version = 3
IO.puts "input_path=#{input_path} output_path=#{output_path} tracker_url=#{tracker_url} source_url=#{source_url}"
{:ok, %{local_path: local_path, magnet_link: magnet_link, basename: basename, info_hash_v1: info_hash_v1, info_hash_v2: info_hash_v2}}
= Torrent.create_torrent(input_path, output_path, web_seed_url, vod.id)
assert :ok
assert local_path === output_path
assert File.exists?(output_path)
assert String.starts_with?(magnet_link, "magnet:")
assert String.ends_with?(basename, ".torrent")
assert is_binary(info_hash_v1)
assert is_binary(info_hash_v2)
end
end
end

View File

@ -23,6 +23,15 @@ defmodule Bright.TorrentfileTest do
assert Regex.match?(~r"\/torrentfile", Torrentfile.torrentfile_path())
end
test "create/2" do
{:ok, output} = Torrentfile.create(vod, input_path)
assert :ok
assert is_binary(output.save_path)
assert output.save_path === output_path
assert is_binary(output.btih)
assert is_binary(output.btmh)
assert File.exists?(output_path)
end
test "create/7" do
input_path = @test_ts_fixture
@ -30,9 +39,9 @@ defmodule Bright.TorrentfileTest do
tracker_url = @test_tracker_url
comment = @test_comment
source_url = @test_source_url
web_Seed_url = @test_web_seed_url
web_seed_url = @test_web_seed_url
meta_version = 3
{:ok, output} = Torrentfile.create(input_path, output_path, tracker_url, comment, source_url, web_Seed_url, meta_version)
{:ok, output} = Torrentfile.create(input_path, output_path, tracker_url, comment, source_url, web_seed_url, meta_version)
assert :ok
assert is_binary(output.save_path)

View File

@ -2,17 +2,21 @@ defmodule Bright.TorrentsTest do
use Bright.DataCase
alias Bright.Torrents
alias Bright.{Downloader,Cache}
describe "torrent" do
@test_fixture "https://futureporn-b2.b-cdn.net/test-fixture.ts"
describe "torrents" do
alias Bright.Torrents.Torrent
import Bright.TorrentsFixtures
@invalid_attrs %{info_hash_v1: nil, info_hash_v2: nil, cdn_url: nil, magnet: nil}
test "list_torrent/0 returns all torrent" do
test "list_torrents/0 returns all torrent" do
torrent = torrent_fixture()
assert Torrents.list_torrent() == [torrent]
assert Torrents.list_torrents() == [torrent]
end
test "get_torrent!/1 returns the torrent with given id" do
@ -61,5 +65,32 @@ defmodule Bright.TorrentsTest do
torrent = torrent_fixture()
assert %Ecto.Changeset{} = Torrents.change_torrent(torrent)
end
@tag :integration
test "generate_torrent_file/7" do
stream = stream_fixture()
vod = vod_fixture(%{stream_id: stream.id, s3_cdn_url: "https://futureporn-b2.b-cdn.net/test-fixture.ts"})
input_path = Path.absname("./test/fixtures/test-fixture.ts")
output_path = Cache.generate_filename("test", "torrent")
tracker_url = "https://tracker.futureporn.net/announce"
source_url = "https://futureporn.net/vods/69"
comment = "https://futureporn.net"
web_seed_url = @test_fixture
meta_version = 3
IO.puts "input_path=#{input_path} output_path=#{output_path} tracker_url=#{tracker_url} source_url=#{source_url}"
{:ok, %{local_path: local_path, magnet_link: magnet_link, basename: basename, info_hash_v1: info_hash_v1, info_hash_v2: info_hash_v2}}
= Torrent.create_torrent(input_path, output_path, web_seed_url, vod.id)
assert :ok
assert local_path === output_path
assert File.exists?(output_path)
assert String.starts_with?(magnet_link, "magnet:")
assert String.ends_with?(basename, ".torrent")
assert is_binary(info_hash_v1)
assert is_binary(info_hash_v2)
end
end
end

View File

@ -2,39 +2,60 @@ defmodule Bright.TrackerTest do
use Bright.DataCase
alias Bright.Tracker
alias Bright.URLEncoder
describe "tracker" do
import Bright.StreamsFixtures
@info_hash_fixture "723886c0b0d9d41bfaa5276a9b2552d84ba09dd8a77d9ddcab5c9fa16cdb9770" # test-fixture.ts (BT info_hash v2)
@info_hash_v2_fixture "723886c0b0d9d41bfaa5276a9b2552d84ba09dd8a77d9ddcab5c9fa16cdb9770" # test-fixture.ts (BT info_hash v2)
@info_hash_v1_fixture "157835a64d398fd63d83b5fd6dac5612bd60b6c6" # test-fixture.ts (BT info_hash v1)
@tag :integration
test "whitelist_info_hash/1 using a string info_hash" do
:ok = Tracker.whitelist_info_hash(@info_hash_fixture)
assert :ok
end
@tag :integration
test "whitelist_info_hash/1 using a %Vod{}" do
stream = stream_fixture()
vod = vod_fixture(%{stream_id: stream.id})
:ok = Tracker.whitelist_info_hash(vod)
:ok = Tracker.whitelist_info_hash(@info_hash_v2_fixture)
assert :ok
end
@tag :integration
test "announce_torrent/1 using a string info_hash" do
:ok = Tracker.announce_torrent(@info_hash_fixture)
{:ok, body} = Tracker.announce_torrent(@info_hash_v1_fixture)
assert :ok
end
@tag :integration
test "announce_torrent/1 using a %Vod{}" do
stream = stream_fixture()
vod = vod_fixture(%{stream_id: stream.id, info_hash: @info_hash_fixture})
:ok = Tracker.announce_torrent(vod)
assert :ok
@tag :unit
@tag :bep3
test "bep3_encode/1 projekt-melody.jpg" do
## projekt-melody.jpg
info_hash_v1 = "4a7620a29789ca70c6e461a11da810bd1d253fc6"
expected_output = "Jv%20%a2%97%89%cap%c6%e4a%a1%1d%a8%10%bd%1d%25%3f%c6"
assert Tracker.bep3_encode(info_hash_v1) === expected_output
end
@tag :unit
@tag :bep3
test "bep3_encode/1 StackOverflow" do
## @see https://stackoverflow.com/a/40000337/1004931
info_hash_v1 = "acc3b2e433d7c7475abb5941b5681cb7a1ea26e2"
expected_output = "%ac%c3%b2%e43%d7%c7GZ%bbYA%b5h%1c%b7%a1%ea%26%e2"
assert Tracker.bep3_encode(info_hash_v1) === expected_output
end
@tag :unit
@tag :bep3
test "bep3_encode/1 WIRED CD" do
info_hash_v1 = "a88fda5954e89178c372716a6a78b8180ed4dad3"
expected_output = "%A8%8F%DAYT%E8%91x%C3rqjjx%B8%18%0E%D4%DA%D3"
actual = Tracker.bep3_encode(info_hash_v1)
IO.puts actual
assert actual === expected_output
end
end

View File

@ -0,0 +1,84 @@
defmodule BrightWeb.TorrentControllerTest do
use BrightWeb.ConnCase
import Bright.TorrentsFixtures
@create_attrs %{info_hash_v1: "some info_hash_v1", info_hash_v2: "some info_hash_v2", cdn_url: "some cdn_url", magnet: "some magnet"}
@update_attrs %{info_hash_v1: "some updated info_hash_v1", info_hash_v2: "some updated info_hash_v2", cdn_url: "some updated cdn_url", magnet: "some updated magnet"}
@invalid_attrs %{info_hash_v1: nil, info_hash_v2: nil, cdn_url: nil, magnet: nil}
describe "index" do
test "lists all torrent", %{conn: conn} do
conn = get(conn, ~p"/torrent")
assert html_response(conn, 200) =~ "Listing Torrent"
end
end
describe "new torrent" do
test "renders form", %{conn: conn} do
conn = get(conn, ~p"/torrent/new")
assert html_response(conn, 200) =~ "New Torrent"
end
end
describe "create torrent" do
test "redirects to show when data is valid", %{conn: conn} do
conn = post(conn, ~p"/torrent", torrent: @create_attrs)
assert %{id: id} = redirected_params(conn)
assert redirected_to(conn) == ~p"/torrent/#{id}"
conn = get(conn, ~p"/torrent/#{id}")
assert html_response(conn, 200) =~ "Torrent #{id}"
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post(conn, ~p"/torrent", torrent: @invalid_attrs)
assert html_response(conn, 200) =~ "New Torrent"
end
end
describe "edit torrent" do
setup [:create_torrent]
test "renders form for editing chosen torrent", %{conn: conn, torrent: torrent} do
conn = get(conn, ~p"/torrent/#{torrent}/edit")
assert html_response(conn, 200) =~ "Edit Torrent"
end
end
describe "update torrent" do
setup [:create_torrent]
test "redirects when data is valid", %{conn: conn, torrent: torrent} do
conn = put(conn, ~p"/torrent/#{torrent}", torrent: @update_attrs)
assert redirected_to(conn) == ~p"/torrent/#{torrent}"
conn = get(conn, ~p"/torrent/#{torrent}")
assert html_response(conn, 200) =~ "some updated info_hash_v1"
end
test "renders errors when data is invalid", %{conn: conn, torrent: torrent} do
conn = put(conn, ~p"/torrent/#{torrent}", torrent: @invalid_attrs)
assert html_response(conn, 200) =~ "Edit Torrent"
end
end
describe "delete torrent" do
setup [:create_torrent]
test "deletes chosen torrent", %{conn: conn, torrent: torrent} do
conn = delete(conn, ~p"/torrent/#{torrent}")
assert redirected_to(conn) == ~p"/torrent"
assert_error_sent 404, fn ->
get(conn, ~p"/torrent/#{torrent}")
end
end
end
defp create_torrent(_) do
torrent = torrent_fixture()
%{torrent: torrent}
end
end