diff options
| -rw-r--r-- | .gitignore | 11 | ||||
| -rw-r--r-- | LICENSE.txt | 17 | ||||
| -rw-r--r-- | client/gleam.toml | 13 | ||||
| -rw-r--r-- | client/index.html | 17 | ||||
| -rw-r--r-- | client/manifest.toml | 49 | ||||
| -rw-r--r-- | client/src/client.gleam | 56 | ||||
| -rw-r--r-- | flake.lock | 98 | ||||
| -rw-r--r-- | flake.nix | 51 | ||||
| -rw-r--r-- | justfile | 33 | ||||
| -rw-r--r-- | server/gleam.toml | 22 | ||||
| -rw-r--r-- | server/manifest.toml | 25 | ||||
| -rw-r--r-- | server/src/server.gleam | 200 |
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") + } +} |