summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-07-27 19:03:49 -0700
committerFuwn <[email protected]>2024-07-27 19:03:49 -0700
commitefeff94b550651ab8f134e705b07b80c1c8faeba (patch)
treed345d0a0d5602cc01ee164a02a08b07bf97ecfd8 /server
downloadyuna-main.tar.xz
yuna-main.zip
feat: initial development buildHEADmain
Diffstat (limited to 'server')
-rw-r--r--server/gleam.toml22
-rw-r--r--server/manifest.toml25
-rw-r--r--server/src/server.gleam200
3 files changed, 247 insertions, 0 deletions
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")
+ }
+}