summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore11
-rw-r--r--LICENSE.txt17
-rw-r--r--client/gleam.toml13
-rw-r--r--client/index.html17
-rw-r--r--client/manifest.toml49
-rw-r--r--client/src/client.gleam56
-rw-r--r--flake.lock98
-rw-r--r--flake.nix51
-rw-r--r--justfile33
-rw-r--r--server/gleam.toml22
-rw-r--r--server/manifest.toml25
-rw-r--r--server/src/server.gleam200
12 files changed, 592 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..212a225
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# Gleam & BEAM
+*.beam
+*.ez
+build
+erl_crash.dump
+
+# Lustre
+priv
+
+# Visual Studio Code
+.vscode
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..ef6e8e2
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,17 @@
+All Rights Reserved
+
+Copyright (c) 2024 Fuwn
+
+THE CONTENTS OF THIS PROJECT ARE PROPRIETARY AND CONFIDENTIAL.
+UNAUTHORIZED COPYING, TRANSFERRING OR REPRODUCTION OF THE CONTENTS OF THIS PROJECT, VIA ANY MEDIUM IS STRICTLY PROHIBITED.
+
+The receipt or possession of the source code and/or any parts thereof does not convey or imply any right to use them
+for any purpose other than the purpose for which they were provided to you.
+
+The software is provided "AS IS", without warranty of any kind, express or implied, including but not limited to
+the warranties of merchantability, fitness for a particular purpose and non infringement.
+In no event shall the authors or copyright holders be liable for any claim, damages or other liability,
+whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software
+or the use or other dealings in the software.
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the software. \ No newline at end of file
diff --git a/client/gleam.toml b/client/gleam.toml
new file mode 100644
index 0000000..97730a1
--- /dev/null
+++ b/client/gleam.toml
@@ -0,0 +1,13 @@
+name = "client"
+version = "1.0.0"
+target = "javascript"
+
+[dependencies]
+gleam_json = "1.0.1"
+gleam_stdlib = "~> 0.36"
+lustre = "~> 4.0"
+lustre_ui = "~> 0.4"
+
+[dev-dependencies]
+gleeunit = "~> 1.0"
+lustre_dev_tools = "~> 1.0"
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..87796c6
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
+ <title>yuna.news</title>
+
+ <!-- Uncomment this if you add the TailwindCSS integration -->
+ <!-- <link rel="stylesheet" href="/priv/static/client.css> -->
+ <script type="module" src="/priv/static/client.mjs"></script>
+ </head>
+
+ <body>
+ <div id="app"></div>
+ </body>
+</html>
diff --git a/client/manifest.toml b/client/manifest.toml
new file mode 100644
index 0000000..b9b8209
--- /dev/null
+++ b/client/manifest.toml
@@ -0,0 +1,49 @@
+# This file was generated by Gleam
+# You typically do not need to edit this file
+
+packages = [
+ { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
+ { name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" },
+ { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" },
+ { name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
+ { name = "fs", version = "8.6.1", build_tools = ["rebar3"], requirements = [], otp_app = "fs", source = "hex", outer_checksum = "61EA2BDAEDAE4E2024D0D25C63E44DCCF65622D4402DB4A2DF12868D1546503F" },
+ { name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" },
+ { name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" },
+ { name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
+ { name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
+ { name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
+ { name = "gleam_httpc", version = "2.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "CF76C71002DEECF6DC5D9CA83D962728FAE166B57926BE442D827004D3C7DF1B" },
+ { name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" },
+ { name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
+ { name = "gleam_package_interface", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_package_interface", source = "hex", outer_checksum = "CF3BFC5D0997750D9550D8D73A90F4B8D71C6C081B20ED4E70FFBE1E99AFC3C2" },
+ { name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
+ { name = "glearray", version = "0.2.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "9C207E05F38D724F464FA921378DB3ABC2B0A2F5821116D8BC8B2CACC68930D5" },
+ { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
+ { name = "glint", version = "0.18.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "5FB54D7732B4105E4AF4D89A7EE6D5E8CF33DA13A3575D0C6ECE470B97958454" },
+ { name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
+ { name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" },
+ { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
+ { name = "logging", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "FCB111401BDB4703A440A94FF8CC7DA521112269C065F219C2766998333E7738" },
+ { name = "lustre", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "43642C0602D3E2D6FEC3E24173D68A1F8E646969B53A2B0A5EB61238DDA739C4" },
+ { name = "lustre_dev_tools", version = "1.3.4", build_tools = ["gleam"], requirements = ["argv", "filepath", "fs", "gleam_community_ansi", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_package_interface", "gleam_stdlib", "glint", "glisten", "mist", "simplifile", "spinner", "term_size", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "FB056F18870EA7FE2A070264A598CFCDB8F6F24D65FF989F18F3F46C9ABEEE31" },
+ { name = "lustre_ui", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "lustre_ui", source = "hex", outer_checksum = "FA1F9E89D89CDD5DF376ED86ABA8A38441CB2E664CD4D402F22A49DA4D7BB56D" },
+ { name = "marceau", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "5188D643C181EE350D8A20A3BDBD63AF7B6C505DE333CFBE05EF642ADD88A59B" },
+ { name = "mist", version = "1.2.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "109B4D64E68C104CC23BB3CC5441ECD479DD7444889DA01113B75C6AF0F0E17B" },
+ { name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
+ { name = "repeatedly", version = "2.1.1", build_tools = ["gleam"], requirements = [], otp_app = "repeatedly", source = "hex", outer_checksum = "38808C3EC382B0CD981336D5879C24ECB37FCB9C1D1BD128F7A80B0F74404D79" },
+ { name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
+ { name = "snag", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "54D32E16E33655346AA3E66CBA7E191DE0A8793D2C05284E3EFB90AD2CE92BCC" },
+ { name = "spinner", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "glearray", "repeatedly"], otp_app = "spinner", source = "hex", outer_checksum = "200BA3D4A04D468898E63C0D316E23F526E02514BC46454091975CB5BAE41E8F" },
+ { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" },
+ { name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" },
+ { name = "tom", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0831C73E45405A2153091226BF98FB485ED16376988602CC01A5FD086B82D577" },
+ { name = "wisp", version = "0.14.0", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "9F5453AF1F9275E6F8707BC815D6A6A9DF41551921B16FBDBA52883773BAE684" },
+]
+
+[requirements]
+gleam_json = { version = "1.0.1" }
+gleam_stdlib = { version = "~> 0.36" }
+gleeunit = { version = "~> 1.0" }
+lustre = { version = "~> 4.0" }
+lustre_dev_tools = { version = "~> 1.0" }
+lustre_ui = { version = "~> 0.4" }
diff --git a/client/src/client.gleam b/client/src/client.gleam
new file mode 100644
index 0000000..f685665
--- /dev/null
+++ b/client/src/client.gleam
@@ -0,0 +1,56 @@
+import gleam/list
+import lustre
+import lustre/attribute
+import lustre/element/html
+import lustre/ui
+
+pub fn main() {
+ let categories = ["All", "Anime", "Manga", "Light Novels"]
+ let app =
+ lustre.element(
+ ui.cluster([], [
+ html.a([attribute.href("/")], [html.text("yuna.news")]),
+ html.div([], [
+ html.a([attribute.href("/?search=")], [html.text("Search")]),
+ html.a([attribute.href("/followings")], [html.text("Following")]),
+ html.a([attribute.href("/settings")], [html.text("Settings")]),
+ ]),
+ html.div([], [
+ html.div([], [
+ html.div([], [
+ html.a([attribute.href("/?period=today&before=")], [
+ html.text("Today"),
+ ]),
+ html.a([attribute.href("/?period=yesterday&before=&after=")], [
+ html.text("Yesterday"),
+ ]),
+ ]),
+ html.div([], [
+ html.a([attribute.href("/?view=GroupView")], [
+ html.text("GroupView"),
+ ]),
+ html.a([attribute.href("/?view=ListCompactView")], [
+ html.text("ListCompactView"),
+ ]),
+ html.a([attribute.href("/?view=ListExpandedView")], [
+ html.text("ListExpandedView"),
+ ]),
+ ]),
+ ]),
+ html.div(
+ [],
+ categories
+ |> list.map(fn(category) {
+ html.a([attribute.href("/?folder=" <> category)], [
+ html.text(category),
+ ])
+ }),
+ ),
+ ]),
+ ]),
+ )
+
+ let assert Ok(_) = lustre.start(app, "#app", Nil)
+
+ Nil
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..edfe36e
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,98 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "nix-gleam": {
+ "locked": {
+ "lastModified": 1717670865,
+ "narHash": "sha256-+dAPiKAwCzlKWtx3aIHXfR6jydh1JyangewqZKJx500=",
+ "owner": "arnarg",
+ "repo": "nix-gleam",
+ "rev": "c69abe0e57a01991654d3de63dcd93a8b91b98ff",
+ "type": "github"
+ },
+ "original": {
+ "owner": "arnarg",
+ "repo": "nix-gleam",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1720957393,
+ "narHash": "sha256-oedh2RwpjEa+TNxhg5Je9Ch6d3W1NKi7DbRO1ziHemA=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "693bc46d169f5af9c992095736e82c3488bf7dbb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "gitignore": "gitignore",
+ "nix-gleam": "nix-gleam",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..c92d0d8
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,51 @@
+{
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
+ nix-gleam.url = "github:arnarg/nix-gleam";
+
+ gitignore = {
+ url = "github:hercules-ci/gitignore.nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs =
+ { self
+ , nixpkgs
+ , flake-utils
+ , nix-gleam
+ , gitignore
+ , ...
+ }: flake-utils.lib.eachDefaultSystem (system:
+ let
+ pkgs = import nixpkgs {
+ inherit system;
+
+ overlays = [
+ nix-gleam.overlays.default
+ ];
+ };
+
+ inherit (gitignore.lib) gitignoreSource;
+ in
+ {
+ packages.default = pkgs.buildGleamApplication {
+ src = gitignoreSource ./.;
+
+ rebar3Package = pkgs.rebar3WithPlugins {
+ plugins = with pkgs.beamPackages; [
+ pc
+ ];
+ };
+ };
+
+ devShell = pkgs.mkShell {
+ buildInputs = [
+ pkgs.gleam
+ pkgs.rebar3
+ ];
+ };
+ }
+ );
+}
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..2b85db7
--- /dev/null
+++ b/justfile
@@ -0,0 +1,33 @@
+run directory:
+ cd '{{directory}}' && gleam run
+
+watch directory:
+ #!/usr/bin/env sh
+
+ cd '{{directory}}'
+
+ start_gleam() {
+ gleam run &
+ }
+
+ stop_beam() {
+ BEAM_PID=$(pgrep -f beam.smp)
+
+ if [ -n "${BEAM_PID}" ]; then
+ kill "${BEAM_PID}"
+ wait "${BEAM_PID}" 2>/dev/null
+ fi
+ }
+
+ if ! command -v inotifywait >/dev/null; then
+ echo "error: inotifywait could not be found. please install inotify-tools."
+ exit 1
+ fi
+
+ start_gleam
+
+ while inotifywait -r -e modify,create,delete,move src; do
+ stop_beam
+ sleep 1
+ start_gleam
+ done
diff --git a/server/gleam.toml b/server/gleam.toml
new file mode 100644
index 0000000..d97c632
--- /dev/null
+++ b/server/gleam.toml
@@ -0,0 +1,22 @@
+name = "server"
+version = "1.0.0"
+
+# Fill out these fields if you intend to generate HTML documentation or publish
+# your project to the Hex package manager.
+#
+# description = ""
+# licences = ["Apache-2.0"]
+# repository = { type = "github", user = "username", repo = "project" }
+# links = [{ title = "Website", href = "https://gleam.run" }]
+#
+# For a full reference of all the available options, you can have a look at
+# https://gleam.run/writing-gleam/gleam-toml/.
+
+[dependencies]
+gleam_stdlib = ">= 0.34.0 and < 2.0.0"
+xmlm = ">= 1.0.0 and < 2.0.0"
+gleam_http = ">= 3.6.0 and < 4.0.0"
+gleam_hackney = ">= 1.2.0 and < 2.0.0"
+
+[dev-dependencies]
+gleeunit = ">= 1.0.0 and < 2.0.0"
diff --git a/server/manifest.toml b/server/manifest.toml
new file mode 100644
index 0000000..3911bd6
--- /dev/null
+++ b/server/manifest.toml
@@ -0,0 +1,25 @@
+# This file was generated by Gleam
+# You typically do not need to edit this file
+
+packages = [
+ { name = "certifi", version = "2.12.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C" },
+ { name = "gleam_hackney", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "hackney"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "066B1A55D37DBD61CC72A1C4EDE43C6015B1797FAF3818C16FE476534C7B6505" },
+ { name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
+ { name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
+ { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
+ { name = "hackney", version = "1.20.1", build_tools = ["rebar3"], requirements = ["certifi", "idna", "metrics", "mimerl", "parse_trans", "ssl_verify_fun", "unicode_util_compat"], otp_app = "hackney", source = "hex", outer_checksum = "FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3" },
+ { name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" },
+ { name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" },
+ { name = "mimerl", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "A1E15A50D1887217DE95F0B9B0793E32853F7C258A5CD227650889B38839FE9D" },
+ { name = "parse_trans", version = "3.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "parse_trans", source = "hex", outer_checksum = "620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A" },
+ { name = "ssl_verify_fun", version = "1.1.7", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8" },
+ { name = "unicode_util_compat", version = "0.7.0", build_tools = ["rebar3"], requirements = [], otp_app = "unicode_util_compat", source = "hex", outer_checksum = "25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521" },
+ { name = "xmlm", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "xmlm", source = "hex", outer_checksum = "295F483E1A930DC4C45ED0F07C3466C0A7381972C3C2FC64D66BD2FBB307ED6A" },
+]
+
+[requirements]
+gleam_hackney = { version = ">= 1.2.0 and < 2.0.0"}
+gleam_http = { version = ">= 3.6.0 and < 4.0.0" }
+gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
+gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
+xmlm = { version = ">= 1.0.0 and < 2.0.0" }
diff --git a/server/src/server.gleam b/server/src/server.gleam
new file mode 100644
index 0000000..2ea7b3e
--- /dev/null
+++ b/server/src/server.gleam
@@ -0,0 +1,200 @@
+import gleam/dict.{type Dict}
+import gleam/hackney
+import gleam/http
+import gleam/http/request
+import gleam/io
+import gleam/list
+import gleam/option.{type Option, None, Some}
+import gleam/result
+import gleam/string
+import gleam/uri
+import xmlm
+
+type RssFeed {
+ RssFeed(title: String, link: String, items: List(RssItem))
+}
+
+type RssItem {
+ RssItem(title: String, link: String, description: String)
+}
+
+// iterate through top level signals element to form rss feed.
+// top level is the RssFeed
+// top level's children are RssItem
+fn signals_to_rss_feed(signals: #(List(xmlm.Signal), xmlm.Input)) {
+ let _ = io.debug(list.last(signals.0))
+ let _ = io.debug(signals.1)
+
+ case xmlm.signal(signals.1) {
+ Ok(#(xmlm.ElementStart(e), input)) -> {
+ io.debug(e)
+ io.debug(input)
+
+ Nil
+ }
+ _ -> {
+ io.debug("error")
+
+ Nil
+ }
+ }
+
+ ""
+}
+
+pub fn main() {
+ let feeds = [
+ "https://www.reddit.com/.rss", "https://www.reddit.com/r/programming/.rss",
+ ]
+
+ let _body =
+ list.map(feeds, fn(feed) {
+ let assert Ok(uri) = uri.parse(feed)
+ let assert Ok(request) = request.from_uri(uri)
+
+ case hackney.send(request) {
+ Ok(response) -> {
+ let _ = io.debug(parse_rss(response.body))
+
+ case
+ response.body
+ |> xmlm.from_string
+ |> xmlm.with_stripping(True)
+ |> xmlm.signals
+ {
+ Ok(signals) -> {
+ signals_to_rss_feed(signals)
+ }
+ Error(error) -> {
+ io.debug(error)
+
+ ""
+ }
+ }
+
+ ""
+ }
+ Error(error) -> {
+ io.debug(error)
+
+ ""
+ }
+ }
+ })
+}
+
+fn parse_rss(xml: String) -> Result(RssFeed, String) {
+ let root = parse_element(xml)
+ case find_element(root, "channel") {
+ Some(channel) -> {
+ let title = find_element_text(channel, "title") |> result.unwrap_both
+ let link = find_element_text(channel, "link") |> result.unwrap_both
+ let items =
+ find_elements(channel, "item")
+ |> list.map(parse_item)
+ |> result.all
+ case items {
+ Ok(items) -> Ok(RssFeed(title, link, items))
+ Error(e) -> Error(e)
+ }
+ }
+ None -> Error("Channel element not found")
+ }
+}
+
+fn parse_item(item: Element) -> Result(RssItem, String) {
+ let title = find_element_text(item, "title") |> result.unwrap_both
+ let link = find_element_text(item, "link") |> result.unwrap_both
+ let description = find_element_text(item, "description") |> result.unwrap_both
+ Ok(RssItem(title, link, description))
+}
+
+type Element {
+ Element(
+ name: String,
+ attributes: Dict(String, String),
+ children: List(Element),
+ text: String,
+ )
+}
+
+fn parse_element(xml: String) -> Element {
+ // Very basic parsing implementation
+ // This won't handle all XML cases, just a simple subset for RSS
+ let name = extract_tag_name(xml)
+ let text = extract_text(xml)
+ let children = extract_children(xml)
+ Element(name, dict.new(), children, text)
+}
+
+fn string_find(s: String, sub: String) -> Int {
+ case
+ string.split(s, "")
+ |> list.index_map(fn(x, i) { #(i, x) })
+ |> list.filter(fn(a) { a.1 == sub })
+ |> list.first
+ {
+ Ok(i) -> i.0
+ _ -> 0
+ }
+}
+
+fn string_rfind(s: String, sub: String, default) -> Int {
+ case
+ string.split(s, "")
+ |> list.index_map(fn(x, i) { #(i, x) })
+ |> list.filter(fn(a) { a.1 == sub })
+ |> list.last
+ {
+ Ok(i) -> i.0
+ _ -> default
+ }
+}
+
+fn extract_tag_name(xml: String) -> String {
+ // Extract the tag name from the XML string
+ // This is a simple implementation
+ let start = string_find(xml, "<")
+ let end = string_find(xml, ">")
+ string.slice(xml, start + 1, end)
+}
+
+fn extract_text(xml: String) -> String {
+ // Extract the text content from the XML string
+ // This is a simple implementation
+ let start = string_find(xml, ">")
+ let end = string_rfind(xml, "<", string.length(xml))
+ string.slice(xml, start + 1, end)
+}
+
+fn extract_children(xml: String) -> List(Element) {
+ // Extract child elements from the XML string
+ // This is a simple implementation
+ let children_xml =
+ string.split(xml, "</")
+ |> list.filter(fn(part) { string.contains(part, "<") })
+ |> list.map(parse_element)
+ children_xml
+}
+
+fn find_element(element: Element, tag: String) -> Option(Element) {
+ // element.children
+ // |> list.find(fn(child) { child.name == tag })
+
+ case list.find(element.children, fn(child) { child.name == tag }) {
+ Ok(child) -> Some(child)
+ _ -> None
+ }
+}
+
+fn find_elements(element: Element, tag: String) -> List(Element) {
+ element.children
+ |> list.filter(fn(child) { child.name == tag })
+}
+
+fn find_element_text(element: Element, tag: String) -> Result(String, String) {
+ case find_element(element, tag) {
+ Some(child) -> Ok(child.text)
+ None -> Error("Element not found")
+ }
+}