12 |
13 |
--------------------------------------------------------------------------------
/test/shady_urls_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb.ErrorViewTest do
2 | use ShadyUrlsWeb.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(ShadyUrlsWeb.ErrorView, "404.html", []) == "Not Found"
9 | end
10 |
11 | test "renders 500.html" do
12 | assert render_to_string(ShadyUrlsWeb.ErrorView, "500.html", []) == "Internal Server Error"
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/config/test.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :shady_urls, ShadyUrlsWeb.Endpoint,
6 | http: [ip: {127, 0, 0, 1}, port: 4002],
7 | secret_key_base: "JGWJ93TzZac5S9VBkm5eE/CsqnRwJZLrs6wSzGxSGK9cZ8WZs6EunxQSe+qsaZOl",
8 | server: false
9 |
10 | # Print only warnings and errors during test
11 | config :logger, level: :warn
12 |
13 | # Initialize plugs at runtime for faster test compilation
14 | config :phoenix, :plug_init_mode, :runtime
15 |
--------------------------------------------------------------------------------
/lib/shady_urls_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb.ErrorView do
2 | use ShadyUrlsWeb, :view
3 |
4 | # If you want to customize a particular status code
5 | # for a certain format, you may uncomment below.
6 | # def render("500.html", _assigns) do
7 | # "Internal Server Error"
8 | # end
9 |
10 | # By default, Phoenix returns the status message from
11 | # the template name. For example, "404.html" becomes
12 | # "Not Found".
13 | def template_not_found(template, _assigns) do
14 | Phoenix.Controller.status_message_from_template(template)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shady URLs
2 |
3 | Have you ever felt that your links look serious or just too legit?
4 |
5 | Shady URLs allow you to convert normal links into absolute beauties like this one:
6 |
7 | `https://0x4a69726b612.xyz/free_money-39244-dark_net-6b49a-click_here-69916-download-fa6a-paypal.com-e74a.xls.bat`
8 |
9 |
10 | # Want to host your own instance?
11 |
12 | First of all, why would you want that?
13 |
14 | Try to give it a second thought, perhaps go outside, touch the grass.
15 |
16 | If you still want to host the project, install elixir toolchain, run `mix deps.get` and finally start the local dev server `mix phx.server`.
17 |
--------------------------------------------------------------------------------
/lib/shady_urls/database.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrls.Database do
2 | use Agent
3 |
4 | def start_link(_) do
5 | Agent.start_link(fn -> %{} end, name: __MODULE__)
6 | end
7 |
8 | @spec insert_redirect(String.t(), String.t(), boolean()) :: no_return()
9 | def insert_redirect(path, redirect, preview) when is_binary(path) and is_binary(redirect) and is_boolean(preview) do
10 | Agent.update(__MODULE__, fn db -> Map.put(db, path, {redirect, preview}) end)
11 | end
12 |
13 | @spec lookup_redirect(String.t()) :: {:ok, String.t(), boolean()} | :not_found
14 | def lookup_redirect(path) when is_binary(path) do
15 | result = Agent.get(__MODULE__, fn db -> db[path] end)
16 |
17 | case result do
18 | nil -> :not_found
19 | {redirect, preview} -> {:ok, redirect, preview}
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/shady_urls_web/templates/page/index.html.heex:
--------------------------------------------------------------------------------
1 | .
2 |
--------------------------------------------------------------------------------
/lib/shady_urls_web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | use Phoenix.HTML
7 |
8 | @doc """
9 | Generates tag for inlined form input errors.
10 | """
11 | def error_tag(form, field) do
12 | Enum.map(Keyword.get_values(form.errors, field), fn error ->
13 | content_tag(:span, translate_error(error),
14 | class: "invalid-feedback",
15 | phx_feedback_for: input_name(form, field)
16 | )
17 | end)
18 | end
19 |
20 | @doc """
21 | Translates an error message.
22 | """
23 | def translate_error({msg, opts}) do
24 | # Because the error messages we show in our forms and APIs
25 | # are defined inside Ecto, we need to translate them dynamically.
26 | Enum.reduce(opts, msg, fn {key, value}, acc ->
27 | String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
28 | end)
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | shady_urls-*.tar
24 |
25 | # Ignore assets that are produced by build tools.
26 | /priv/static/assets/
27 |
28 | # Ignore digested assets cache.
29 | /priv/static/cache_manifest.json
30 |
31 | # In case you use Node.js/npm, you want to ignore these.
32 | npm-debug.log
33 | /assets/node_modules/
34 |
35 | # Mnesia persistence files
36 | .mnesia
--------------------------------------------------------------------------------
/lib/shady_urls/application.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrls.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | use Application
7 |
8 | @impl true
9 | def start(_type, _args) do
10 | children = [
11 | # Start the Telemetry supervisor
12 | ShadyUrlsWeb.Telemetry,
13 | # Start the PubSub system
14 | {Phoenix.PubSub, name: ShadyUrls.PubSub},
15 | # Start the Endpoint (http/https)
16 | ShadyUrlsWeb.Endpoint,
17 | # Start a worker by calling: ShadyUrls.Worker.start_link(arg)
18 | # {ShadyUrls.Worker, arg},
19 | ShadyUrls.Database
20 | ]
21 |
22 | # See https://hexdocs.pm/elixir/Supervisor.html
23 | # for other strategies and supported options
24 | opts = [strategy: :one_for_one, name: ShadyUrls.Supervisor]
25 | Supervisor.start_link(children, opts)
26 | end
27 |
28 | # Tell Phoenix to update the endpoint configuration
29 | # whenever the application is updated.
30 | @impl true
31 | def config_change(changed, _new, removed) do
32 | ShadyUrlsWeb.Endpoint.config_change(changed, removed)
33 | :ok
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build common data structures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | we enable the SQL sandbox, so changes done to the database
12 | are reverted at the end of every test. If you are using
13 | PostgreSQL, you can even run database tests asynchronously
14 | by setting `use ShadyUrlsWeb.ConnCase, async: true`, although
15 | this option is not recommended for other databases.
16 | """
17 |
18 | use ExUnit.CaseTemplate
19 |
20 | using do
21 | quote do
22 | # Import conveniences for testing with connections
23 | import Plug.Conn
24 | import Phoenix.ConnTest
25 | import ShadyUrlsWeb.ConnCase
26 |
27 | alias ShadyUrlsWeb.Router.Helpers, as: Routes
28 |
29 | # The default endpoint for testing
30 | @endpoint ShadyUrlsWeb.Endpoint
31 | end
32 | end
33 |
34 | setup _tags do
35 | {:ok, conn: Phoenix.ConnTest.build_conn()}
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/shady_urls_web/templates/layout/root.html.heex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= live_title_tag assigns[:page_title] || "Shady URLs" %>
9 |
10 |
11 |
12 |
13 |
14 |
Shady URLs
15 |
16 |
17 | Please note that this is just a fun side project and that your redirects can stop working at any time (I store them in memory using elixir agents lmao)
18 |
19 |
20 |
21 | <%= @inner_content %>
22 |
23 |
24 |
--------------------------------------------------------------------------------
/lib/shady_urls_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb.PageController do
2 | use ShadyUrlsWeb, :controller
3 |
4 | alias ShadyUrls.Database
5 | alias ShadyUrls.Generator
6 |
7 | @spec index(Plug.Conn.t(), any) :: Plug.Conn.t()
8 | def index(conn, _params) do
9 | render(conn, "index.html")
10 | end
11 |
12 | @spec handle(Plug.Conn.t(), map) :: Plug.Conn.t()
13 | def handle(conn, %{ "path" => path }) do
14 | case Database.lookup_redirect(path) do
15 | :not_found -> redirect(conn, to: Routes.page_path(ShadyUrlsWeb.Endpoint, :index))
16 | {:ok, url, true} -> redirect(conn, external: url)
17 | {:ok, url, false} ->
18 | conn
19 | |> put_root_layout({ ShadyUrlsWeb.LayoutView, "redirect.html"})
20 | |> assign(:redirect, url)
21 | |> render("redirect.html")
22 | end
23 | end
24 |
25 | @spec generate(Plug.Conn.t(), map) :: Plug.Conn.t()
26 | def generate(conn, %{ "url" => url, "preview" => preview }) do
27 | url = Generator.normalize_url(url)
28 | path = Generator.generate_shady_url(url)
29 | link = Routes.page_url(ShadyUrlsWeb.Endpoint, :handle, path)
30 | preview = (preview == "true")
31 |
32 | Database.insert_redirect(path, url, preview)
33 |
34 | conn
35 | |> assign(:original, url)
36 | |> assign(:generated, link)
37 | |> render("result.html")
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 |
7 | # General application configuration
8 | import Config
9 |
10 | # Configures the endpoint
11 | config :shady_urls, ShadyUrlsWeb.Endpoint,
12 | url: [host: "localhost"],
13 | render_errors: [view: ShadyUrlsWeb.ErrorView, accepts: ~w(html json), layout: false],
14 | pubsub_server: ShadyUrls.PubSub,
15 | live_view: [signing_salt: "uRdT7CCC"]
16 |
17 | # Configure esbuild (the version is required)
18 | config :esbuild,
19 | version: "0.14.29",
20 | default: [
21 | args:
22 | ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
23 | cd: Path.expand("../assets", __DIR__),
24 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
25 | ]
26 |
27 | # Configures Elixir's Logger
28 | config :logger, :console,
29 | format: "$time $metadata[$level] $message\n",
30 | metadata: [:request_id]
31 |
32 | # Use Jason for JSON parsing in Phoenix
33 | config :phoenix, :json_library, Jason
34 |
35 | # Import environment specific config. This must remain at the bottom
36 | # of this file so it overrides the configuration defined above.
37 | import_config "#{config_env()}.exs"
38 |
--------------------------------------------------------------------------------
/lib/shady_urls_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb.Router do
2 | use ShadyUrlsWeb, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_live_flash
8 | plug :put_root_layout, {ShadyUrlsWeb.LayoutView, :root}
9 | plug :protect_from_forgery
10 | plug :put_secure_browser_headers
11 | end
12 |
13 | pipeline :api do
14 | plug :accepts, ["json"]
15 | end
16 |
17 | scope "/", ShadyUrlsWeb do
18 | pipe_through :browser
19 |
20 | get "/", PageController, :index
21 | get "/:path", PageController, :handle
22 |
23 | post "/generate", PageController, :generate
24 | end
25 |
26 | # Other scopes may use custom stacks.
27 | # scope "/api", ShadyUrlsWeb do
28 | # pipe_through :api
29 | # end
30 |
31 | # Enables LiveDashboard only for development
32 | #
33 | # If you want to use the LiveDashboard in production, you should put
34 | # it behind authentication and allow only admins to access it.
35 | # If your application does not have an admins-only section yet,
36 | # you can use Plug.BasicAuth to set up some basic authentication
37 | # as long as you are also using SSL (which you should anyway).
38 | if Mix.env() in [:dev, :test] do
39 | import Phoenix.LiveDashboard.Router
40 |
41 | scope "/" do
42 | pipe_through :browser
43 |
44 | live_dashboard "/dashboard", metrics: ShadyUrlsWeb.Telemetry
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/shady_urls_web/telemetry.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb.Telemetry do
2 | use Supervisor
3 | import Telemetry.Metrics
4 |
5 | def start_link(arg) do
6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
7 | end
8 |
9 | @impl true
10 | def init(_arg) do
11 | children = [
12 | # Telemetry poller will execute the given period measurements
13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
15 | # Add reporters as children of your supervision tree.
16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
17 | ]
18 |
19 | Supervisor.init(children, strategy: :one_for_one)
20 | end
21 |
22 | def metrics do
23 | [
24 | # Phoenix Metrics
25 | summary("phoenix.endpoint.stop.duration",
26 | unit: {:native, :millisecond}
27 | ),
28 | summary("phoenix.router_dispatch.stop.duration",
29 | tags: [:route],
30 | unit: {:native, :millisecond}
31 | ),
32 |
33 | # VM Metrics
34 | summary("vm.memory.total", unit: {:byte, :kilobyte}),
35 | summary("vm.total_run_queue_lengths.total"),
36 | summary("vm.total_run_queue_lengths.cpu"),
37 | summary("vm.total_run_queue_lengths.io")
38 | ]
39 | end
40 |
41 | defp periodic_measurements do
42 | [
43 | # A module, function and arguments to be invoked periodically.
44 | # This function must call :telemetry.execute/3 and a metric must be added above.
45 | # {ShadyUrlsWeb, :count_users, []}
46 | ]
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/shady_urls_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :shady_urls
3 |
4 | # The session will be stored in the cookie and signed,
5 | # this means its contents can be read but not tampered with.
6 | # Set :encryption_salt if you would also like to encrypt it.
7 | @session_options [
8 | store: :cookie,
9 | key: "_shady_urls_key",
10 | signing_salt: "vK9FtGOy"
11 | ]
12 |
13 | socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
14 |
15 | # Serve at "/" the static files from "priv/static" directory.
16 | #
17 | # You should set gzip to true if you are running phx.digest
18 | # when deploying your static files in production.
19 | plug Plug.Static,
20 | at: "/",
21 | from: :shady_urls,
22 | gzip: false,
23 | only: ~w(assets fonts images favicon.ico robots.txt)
24 |
25 | # Code reloading can be explicitly enabled under the
26 | # :code_reloader configuration of your endpoint.
27 | if code_reloading? do
28 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
29 | plug Phoenix.LiveReloader
30 | plug Phoenix.CodeReloader
31 | end
32 |
33 | plug Phoenix.LiveDashboard.RequestLogger,
34 | param_key: "request_logger",
35 | cookie_key: "request_logger"
36 |
37 | plug Plug.RequestId
38 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
39 |
40 | plug Plug.Parsers,
41 | parsers: [:urlencoded, :multipart, :json],
42 | pass: ["*/*"],
43 | json_decoder: Phoenix.json_library()
44 |
45 | plug Plug.MethodOverride
46 | plug Plug.Head
47 | plug Plug.Session, @session_options
48 | plug ShadyUrlsWeb.Router
49 | end
50 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrls.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :shady_urls,
7 | version: "0.1.0",
8 | elixir: "~> 1.12",
9 | elixirc_paths: elixirc_paths(Mix.env()),
10 | compilers: Mix.compilers(),
11 | start_permanent: Mix.env() == :prod,
12 | aliases: aliases(),
13 | deps: deps()
14 | ]
15 | end
16 |
17 | # Configuration for the OTP application.
18 | #
19 | # Type `mix help compile.app` for more information.
20 | def application do
21 | [
22 | mod: {ShadyUrls.Application, []},
23 | extra_applications: [:logger, :runtime_tools]
24 | ]
25 | end
26 |
27 | # Specifies which paths to compile per environment.
28 | defp elixirc_paths(:test), do: ["lib", "test/support"]
29 | defp elixirc_paths(_), do: ["lib"]
30 |
31 | # Specifies your project dependencies.
32 | #
33 | # Type `mix help deps` for examples and options.
34 | defp deps do
35 | [
36 | {:phoenix, "~> 1.6.11"},
37 | {:phoenix_html, "~> 3.0"},
38 | {:phoenix_live_reload, "~> 1.2", only: :dev},
39 | {:phoenix_live_view, "~> 0.17.5"},
40 | {:floki, ">= 0.30.0", only: :test},
41 | {:phoenix_live_dashboard, "~> 0.6"},
42 | {:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
43 | {:telemetry_metrics, "~> 0.6"},
44 | {:telemetry_poller, "~> 1.0"},
45 | {:jason, "~> 1.2"},
46 | {:plug_cowboy, "~> 2.5"}
47 | ]
48 | end
49 |
50 | # Aliases are shortcuts or tasks specific to the current project.
51 | # For example, to install project dependencies and perform other setup tasks, run:
52 | #
53 | # $ mix setup
54 | #
55 | # See the documentation for `Mix` for more info on aliases.
56 | defp aliases do
57 | [
58 | setup: ["deps.get"],
59 | "assets.deploy": ["esbuild default --minify", "phx.digest"]
60 | ]
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # For production, don't forget to configure the url host
4 | # to something meaningful, Phoenix uses this information
5 | # when generating URLs.
6 | #
7 | # Note we also include the path to a cache manifest
8 | # containing the digested version of static files. This
9 | # manifest is generated by the `mix phx.digest` task,
10 | # which you should run after static files are built and
11 | # before starting your production server.
12 | config :shady_urls, ShadyUrlsWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
13 |
14 | # Do not print debug messages in production
15 | config :logger, level: :info
16 |
17 | # ## SSL Support
18 | #
19 | # To get SSL working, you will need to add the `https` key
20 | # to the previous section and set your `:url` port to 443:
21 | #
22 | config :shady_urls, ShadyUrlsWeb.Endpoint,
23 | force_ssl: [rewrite_on: [:x_forwarded_proto]]
24 | # https: [
25 | # ...,
26 | # port: 443,
27 | # cipher_suite: :strong,
28 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
29 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
30 | # ]
31 | #
32 | # The `cipher_suite` is set to `:strong` to support only the
33 | # latest and more secure SSL ciphers. This means old browsers
34 | # and clients may not be supported. You can set it to
35 | # `:compatible` for wider support.
36 | #
37 | # `:keyfile` and `:certfile` expect an absolute path to the key
38 | # and cert in disk or a relative path inside priv, for example
39 | # "priv/ssl/server.key". For all supported SSL configuration
40 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
41 | #
42 | # We also recommend setting `force_ssl` in your endpoint, ensuring
43 | # no data is ever sent via http, always redirecting to https:
44 | #
45 | # config :shady_urls, ShadyUrlsWeb.Endpoint,
46 | # force_ssl: [hsts: true]
47 | #
48 | # Check `Plug.SSL` for all available options in `force_ssl`.
49 |
--------------------------------------------------------------------------------
/config/runtime.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # config/runtime.exs is executed for all environments, including
4 | # during releases. It is executed after compilation and before the
5 | # system starts, so it is typically used to load production configuration
6 | # and secrets from environment variables or elsewhere. Do not define
7 | # any compile-time configuration in here, as it won't be applied.
8 | # The block below contains prod specific runtime configuration.
9 |
10 | # ## Using releases
11 | #
12 | # If you use `mix release`, you need to explicitly enable the server
13 | # by passing the PHX_SERVER=true when you start it:
14 | #
15 | # PHX_SERVER=true bin/shady_urls start
16 | #
17 | # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
18 | # script that automatically sets the env var above.
19 | if System.get_env("PHX_SERVER") do
20 | config :shady_urls, ShadyUrlsWeb.Endpoint, server: true
21 | end
22 |
23 | if config_env() == :prod do
24 | # The secret key base is used to sign/encrypt cookies and other secrets.
25 | # A default value is used in config/dev.exs and config/test.exs but you
26 | # want to use a different value for prod and you most likely don't want
27 | # to check this value into version control, so we use an environment
28 | # variable instead.
29 | secret_key_base =
30 | System.get_env("SECRET_KEY_BASE") ||
31 | raise """
32 | environment variable SECRET_KEY_BASE is missing.
33 | You can generate one by calling: mix phx.gen.secret
34 | """
35 |
36 | host = System.get_env("PHX_HOST") || "0x4a69726b612.xyz"
37 | port = String.to_integer(System.get_env("PORT") || "4000")
38 |
39 | config :shady_urls, ShadyUrlsWeb.Endpoint,
40 | url: [host: host, port: 443, scheme: "https"],
41 | http: [
42 | # Enable IPv6 and bind on all interfaces.
43 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
44 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
45 | # for details about using IPv6 vs IPv4 and loopback vs public addresses.
46 | ip: {0, 0, 0, 0, 0, 0, 0, 0},
47 | port: port
48 | ],
49 | secret_key_base: secret_key_base
50 | end
51 |
--------------------------------------------------------------------------------
/config/dev.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with esbuild to bundle .js and .css sources.
9 | config :shady_urls, ShadyUrlsWeb.Endpoint,
10 | # Binding to loopback ipv4 address prevents access from other machines.
11 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
12 | http: [ip: {127, 0, 0, 1}, port: 4000],
13 | check_origin: false,
14 | code_reloader: true,
15 | debug_errors: true,
16 | secret_key_base: "4RWs10jQ2tzMobJ/ElFqent+Y7h+nzt7sIdxO2+DYTmGFGbrAbjh+RjQCmfGep8d",
17 | watchers: []
18 |
19 | # ## SSL Support
20 | #
21 | # In order to use HTTPS in development, a self-signed
22 | # certificate can be generated by running the following
23 | # Mix task:
24 | #
25 | # mix phx.gen.cert
26 | #
27 | # Note that this task requires Erlang/OTP 20 or later.
28 | # Run `mix help phx.gen.cert` for more information.
29 | #
30 | # The `http:` config above can be replaced with:
31 | #
32 | # https: [
33 | # port: 4001,
34 | # cipher_suite: :strong,
35 | # keyfile: "priv/cert/selfsigned_key.pem",
36 | # certfile: "priv/cert/selfsigned.pem"
37 | # ],
38 | #
39 | # If desired, both `http:` and `https:` keys can be
40 | # configured to run both http and https servers on
41 | # different ports.
42 |
43 | # Watch static and templates for browser reloading.
44 | config :shady_urls, ShadyUrlsWeb.Endpoint,
45 | live_reload: [
46 | patterns: [
47 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
48 | ~r"lib/shady_urls_web/(live|views)/.*(ex)$",
49 | ~r"lib/shady_urls_web/templates/.*(eex)$"
50 | ]
51 | ]
52 |
53 | # Do not include metadata nor timestamps in development logs
54 | config :logger, :console, format: "[$level] $message\n"
55 |
56 | # Set a higher stacktrace during development. Avoid configuring such
57 | # in production as building large stacktraces may be expensive.
58 | config :phoenix, :stacktrace_depth, 20
59 |
60 | # Initialize plugs at runtime for faster development compilation
61 | config :phoenix, :plug_init_mode, :runtime
62 |
--------------------------------------------------------------------------------
/lib/shady_urls_web.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrlsWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, views, channels and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use ShadyUrlsWeb, :controller
9 | use ShadyUrlsWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: ShadyUrlsWeb
23 |
24 | import Plug.Conn
25 | alias ShadyUrlsWeb.Router.Helpers, as: Routes
26 | end
27 | end
28 |
29 | def view do
30 | quote do
31 | use Phoenix.View,
32 | root: "lib/shady_urls_web/templates",
33 | namespace: ShadyUrlsWeb
34 |
35 | # Import convenience functions from controllers
36 | import Phoenix.Controller,
37 | only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
38 |
39 | # Include shared imports and aliases for views
40 | unquote(view_helpers())
41 | end
42 | end
43 |
44 | def live_view do
45 | quote do
46 | use Phoenix.LiveView,
47 | layout: {ShadyUrlsWeb.LayoutView, "live.html"}
48 |
49 | unquote(view_helpers())
50 | end
51 | end
52 |
53 | def live_component do
54 | quote do
55 | use Phoenix.LiveComponent
56 |
57 | unquote(view_helpers())
58 | end
59 | end
60 |
61 | def component do
62 | quote do
63 | use Phoenix.Component
64 |
65 | unquote(view_helpers())
66 | end
67 | end
68 |
69 | def router do
70 | quote do
71 | use Phoenix.Router
72 |
73 | import Plug.Conn
74 | import Phoenix.Controller
75 | import Phoenix.LiveView.Router
76 | end
77 | end
78 |
79 | def channel do
80 | quote do
81 | use Phoenix.Channel
82 | end
83 | end
84 |
85 | defp view_helpers do
86 | quote do
87 | # Use all HTML functionality (forms, tags, etc)
88 | use Phoenix.HTML
89 |
90 | # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
91 | import Phoenix.LiveView.Helpers
92 |
93 | # Import basic rendering functionality (render, render_layout, etc)
94 | import Phoenix.View
95 |
96 | import ShadyUrlsWeb.ErrorHelpers
97 | alias ShadyUrlsWeb.Router.Helpers, as: Routes
98 | end
99 | end
100 |
101 | @doc """
102 | When used, dispatch to the appropriate controller/view/etc.
103 | """
104 | defmacro __using__(which) when is_atom(which) do
105 | apply(__MODULE__, which, [])
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/lib/shady_urls/generator.ex:
--------------------------------------------------------------------------------
1 | defmodule ShadyUrls.Generator do
2 |
3 | @words [
4 | # Thanks to http://urlify.io/ and https://verylegit.link for providing inspiration <3
5 | "facebook.com", "amazon.com", "paypal.com", "Win a free iPad", "ISIS registration form", "Bush did 9 11", "trojan",
6 | "Trojan.Win32", "Genocide", "KKK", "Ku Klux Klan", "Heroin", "Cocaine", "Meth", "Weed", "Download", "Free",
7 | "Hentai", "Porn", "weeb", "Twitter Hack", "Facebook Hack", "Crypto", "Bitcoin", "Stolen credit cards",
8 | "Phishing", "White Power", "Tentacle fun time", "Windows Crack", "Free Money", "Webhost000", "Invoice",
9 | "Java Exploit", "Warez", "Cracked", "Botnet", "Dark Net", "xxx", "Adolf will rise", "IP Stealer",
10 | "Token grabber", "Discord hack", "Leaked nudes", "OnlyFans", "License key", "Secret conspiracy",
11 | "Illegal substances", "Midget porn", "Furry", "420", "Hot moms nearby", "Hot single women",
12 | "Penis enlargement pills", "Tinder", "~home", "Click Here", "FBI", "CIA", "NSA", "Donald Trump",
13 | "Android hack", "Malware", "Keylogger", "Root", "Win $100 000 000", "Hidden service", "Tor",
14 | "Onion", "Terrorism", "Yiff", "Tesla", "e621.net", "nhentai.net", "Trial version", "Try not to cum", "Challenge",
15 | "CSGO cheats", "Minecraft", "Minecraft cheat", "Free Minecraft", "Nitro", "Free Nitro", "Steam gift cards",
16 | "Google Play gift cards", "Netflix credentials", "Roblox", "Robux", "Free Robux", "Hacked Roblox Client",
17 | "Hacked Minecraft Client", "Send Nudes"
18 | ]
19 |
20 | @extensions [
21 | "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx",
22 | "txt", "bin", "png", "jpg", "gif", "webp", "mp4", "mp3", "ics"
23 | ]
24 |
25 | @ending_extensions [
26 | "zip", "rar", "exe", "msi", "jar", "bat", "7z", "ps1"
27 | ]
28 |
29 | @spec normalize_url(String.t()) :: String.t()
30 | def normalize_url(source_url) when is_binary(source_url) do
31 | url = source_url
32 | |> String.trim()
33 | |> String.trim("/")
34 | |> String.replace(~r/^https?:\/\//, "")
35 |
36 | "https://#{url}"
37 | end
38 |
39 | @spec generate_shady_url(String.t()) :: String.t()
40 | def generate_shady_url(source_url) when is_binary(source_url) do
41 | # Seed the PRNG based off the provided source url
42 | :rand.seed(:default, :erlang.phash2(source_url))
43 |
44 | words = select_shady_words()
45 | extension = Enum.random(@extensions)
46 | ending_extension = Enum.random(@ending_extensions)
47 |
48 | path = words
49 | |> Enum.map(fn word -> String.downcase(word) end)
50 | |> Enum.map(fn word -> String.replace(word, ~r/\s+/, "_") end)
51 | |> Enum.map(&generate_random_suffix/1)
52 | |> Enum.join("-")
53 |
54 | "#{path}.#{extension}.#{ending_extension}"
55 | end
56 |
57 | @spec select_shady_words() :: [String.t()]
58 | defp select_shady_words() do
59 | @words
60 | |> Enum.shuffle()
61 | |> Enum.take(4 + :rand.uniform(2))
62 | |> Enum.to_list()
63 | end
64 |
65 | @spec generate_random_suffix(String.t()) :: String.t()
66 | defp generate_random_suffix(word) when is_binary(word) do
67 | length = 2 + :rand.uniform(2)
68 | suffix = for _ <- 0..length, into: "", do: <>
69 |
70 | word <> "-" <> suffix
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "amnesia": {:hex, :amnesia, "0.2.8", "81199a1c4c8db886cfb8ea159f746d5ffdb188bee96cb944f63bdb4465b09fa0", [:mix], [{:exquisite, "~> 0.1.7", [hex: :exquisite, repo: "hexpm", optional: false]}], "hexpm", "6037898c974457809ffa1f9a74cccab8f48c99b206aee9b3fc7bb3af73b06b14"},
3 | "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"},
4 | "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
5 | "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
6 | "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
7 | "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
8 | "exquisite": {:hex, :exquisite, "0.1.10", "e3ca4f8b812696a40a6da3bcd4e9861cef879ee2eb239d5485b0f96885fc9fba", [:mix], [], "hexpm", "0af9319851f1e21dd4adab812c82247f05e11cc19820fa17cc74afd696e2313c"},
9 | "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
10 | "floki": {:hex, :floki, "0.33.1", "f20f1eb471e726342b45ccb68edb9486729e7df94da403936ea94a794f072781", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "461035fd125f13fdf30f243c85a0b1e50afbec876cbf1ceefe6fddd2e6d712c6"},
11 | "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
12 | "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
13 | "memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"},
14 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
15 | "phoenix": {:hex, :phoenix, "1.6.11", "29f3c0fd12fa1fc4d4b05e341578e55bc78d96ea83a022587a7e276884d397e4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1664e34f80c25ea4918fbadd957f491225ef601c0e00b4e644b1a772864bfbc2"},
16 | "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
17 | "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"},
18 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
19 | "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.11", "205f6aa5405648c76f2abcd57716f42fc07d8f21dd8ea7b262dd12b324b50c95", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7177791944b7f90ed18f5935a6a5f07f760b36f7b3bdfb9d28c57440a3c43f99"},
20 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
21 | "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"},
22 | "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},
23 | "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
24 | "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
25 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
26 | "tailwind": {:hex, :tailwind, "0.1.8", "3762defebc8e328fb19ff1afb8c37723e53b52be5ca74f0b8d0a02d1f3f432cf", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "40061d1bf2c0505c6b87be7a3ed05243fc10f6e1af4bac3336db8358bc84d4cc"},
27 | "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
28 | "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
29 | "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
30 | }
31 |
--------------------------------------------------------------------------------