From 83971fccc9000cef94869577ac39a243e033a2e8 Mon Sep 17 00:00:00 2001 From: hashirama Date: Wed, 15 Oct 2025 00:15:03 -0400 Subject: [PATCH 1/3] working! (text) only --- .gitignore | 13 +++++++++ CHANGELOG.md | 5 ++++ app/Main.hs | 16 +++++++++++ lastfm-hs.cabal | 50 +++++++++++++++++++++++++++++++++++ src/Lastfm/Core.hs | 63 ++++++++++++++++++++++++++++++++++++++++++++ src/Lastfm/Crypto.hs | 5 ++++ 6 files changed, 152 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 app/Main.hs create mode 100644 lastfm-hs.cabal create mode 100644 src/Lastfm/Core.hs create mode 100644 src/Lastfm/Crypto.hs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7a92b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# .gitignore + + +*~ +*# +.#* +*rej +*orig +pinentry* +*.hi +*.o +*.a +dist-newstyle \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a5b41d0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for lastfm-hs + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/app/Main.hs b/app/Main.hs new file mode 100644 index 0000000..6c40b07 --- /dev/null +++ b/app/Main.hs @@ -0,0 +1,16 @@ +{-# LANGUAGE OverloadedStrings #-} + +import Lastfm.Core +import Control.Monad (forM_) + +main :: IO () +main = do + let username = "h4shirama" + apiKey = "dcf893bc386af9044e505e5b78531573" + putStrLn $ "Fetching artists for " ++ username ++ "..." + result <- fetchArtists apiKey username + case result of + Left err -> putStrLn $ "Error: " ++ err + Right root -> do + forM_ (artist (artists root)) $ \a -> do + putStrLn $ name a ++ " (" ++ show (playcount a) ++ " plays)" diff --git a/lastfm-hs.cabal b/lastfm-hs.cabal new file mode 100644 index 0000000..dbaa970 --- /dev/null +++ b/lastfm-hs.cabal @@ -0,0 +1,50 @@ +cabal-version: 2.4 +-- Cabal file for lastfm-hs CLI + +name: lastfm-hs +version: 0.1.0.0 +synopsis: Last.fm CLI tool with encrypted API key storage +description: A Haskell CLI application that stores Last.fm API key securely and fetches user library. +category: Web +author: Your Name +maintainer: your.email@example.com +license: MIT +license-file: LICENSE +build-type: Simple + + + +library + + exposed-modules: + Lastfm.Crypto + Lastfm.Core + + build-depends: + base >=4.9 && <5, + bytestring >=0.10, + directory >=1.3, + filepath >=1.3, + cryptonite , + Rasterific == 0.7.5.4, + JuicyPixels == 3.3.9, + http-conduit, + aeson >= 2.2.3.0, + text >=1.2, + process >=1.6, + containers + + hs-source-dirs: src + default-language: GHC2021 + +executable lastfm-hs + main-is: Main.hs + hs-source-dirs: app + ghc-options: -Wall -threaded + default-language: Haskell2010 + build-depends: + base ^>=4.16.4.0, + lastfm-hs + + + -- Optional: extra-libraries or extra-ghc-options can be added if needed diff --git a/src/Lastfm/Core.hs b/src/Lastfm/Core.hs new file mode 100644 index 0000000..eb00246 --- /dev/null +++ b/src/Lastfm/Core.hs @@ -0,0 +1,63 @@ +{-# LANGUAGE DeriveGeneric, OverloadedStrings #-} + +module Lastfm.Core + ( Image(..) + , Artist(..) + , Artists(..) + , Root(..) + , fetchArtists + ) where + + +import Data.Aeson +import GHC.Generics +import qualified Data.ByteString.Lazy as BL +import Network.HTTP.Simple +import qualified Data.Map as M +import Control.Monad (forM_) + +-- Image +data Image = Image + { size :: String + , text :: String + } deriving (Show, Generic) + +instance FromJSON Image where + parseJSON = withObject "Image" $ \v -> + Image <$> v .: "size" <*> v .: "#text" + +-- Artist +data Artist = Artist + { name :: String + , playcount :: Int + , url :: String + , image :: [Image] + } deriving (Show, Generic) + +instance FromJSON Artist where + parseJSON = withObject "Artist" $ \v -> + Artist <$> v .: "name" + <*> (read <$> v .: "playcount") + <*> v .: "url" + <*> v .: "image" + +newtype Artists = Artists { artist :: [Artist] } + deriving (Show, Generic) + +instance FromJSON Artists + +newtype Root = Root { artists :: Artists } + deriving (Show, Generic) + +instance FromJSON Root + +-- | Fetch artists from Last.fm API +fetchArtists :: String -> String -> IO (Either String Root) +fetchArtists apiKey username = do + let url = "https://ws.audioscrobbler.com/2.0/?method=library.getartists" + ++ "&user=" ++ username + ++ "&api_key=" ++ apiKey + ++ "&format=json" + response <- httpLBS (parseRequest_ url) + let body = getResponseBody response + return $ eitherDecode body diff --git a/src/Lastfm/Crypto.hs b/src/Lastfm/Crypto.hs new file mode 100644 index 0000000..bac49ca --- /dev/null +++ b/src/Lastfm/Crypto.hs @@ -0,0 +1,5 @@ +module Lastfm.Crypto where + +test2 :: IO () +test2 = do + putStrLn "hello 2" From b1fd04f9a6befa705c405af3ccded036fe3871a6 Mon Sep 17 00:00:00 2001 From: hashirama Date: Wed, 15 Oct 2025 00:16:31 -0400 Subject: [PATCH 2/3] readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..07d623e --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +## lastfm-hs + +keep track of the things you've listened to! + +TODO: image processing + + +### build: +```shell +$ cabal run +``` + From 36ac5731a7da97dcec9a578f5a191cd8c452bcba Mon Sep 17 00:00:00 2001 From: hashirama Date: Wed, 15 Oct 2025 00:21:25 -0400 Subject: [PATCH 3/3] initial commit --- .gitignore | 13 +++++++++ CHANGELOG.md | 5 ++++ README.md | 12 +++++++++ app/Main.hs | 16 +++++++++++ lastfm-hs.cabal | 50 +++++++++++++++++++++++++++++++++++ src/Lastfm/Core.hs | 63 ++++++++++++++++++++++++++++++++++++++++++++ src/Lastfm/Crypto.hs | 5 ++++ 7 files changed, 164 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 app/Main.hs create mode 100644 lastfm-hs.cabal create mode 100644 src/Lastfm/Core.hs create mode 100644 src/Lastfm/Crypto.hs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7a92b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# .gitignore + + +*~ +*# +.#* +*rej +*orig +pinentry* +*.hi +*.o +*.a +dist-newstyle \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a5b41d0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for lastfm-hs + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/README.md b/README.md new file mode 100644 index 0000000..07d623e --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +## lastfm-hs + +keep track of the things you've listened to! + +TODO: image processing + + +### build: +```shell +$ cabal run +``` + diff --git a/app/Main.hs b/app/Main.hs new file mode 100644 index 0000000..bb3c7ab --- /dev/null +++ b/app/Main.hs @@ -0,0 +1,16 @@ +{-# LANGUAGE OverloadedStrings #-} + +import Lastfm.Core +import Control.Monad (forM_) + +main :: IO () +main = do + let username = "your user" + apiKey = "your key" + putStrLn $ "Fetching artists for " ++ username ++ "..." + result <- fetchArtists apiKey username + case result of + Left err -> putStrLn $ "Error: " ++ err + Right root -> do + forM_ (artist (artists root)) $ \a -> do + putStrLn $ name a ++ " (" ++ show (playcount a) ++ " plays)" diff --git a/lastfm-hs.cabal b/lastfm-hs.cabal new file mode 100644 index 0000000..dbaa970 --- /dev/null +++ b/lastfm-hs.cabal @@ -0,0 +1,50 @@ +cabal-version: 2.4 +-- Cabal file for lastfm-hs CLI + +name: lastfm-hs +version: 0.1.0.0 +synopsis: Last.fm CLI tool with encrypted API key storage +description: A Haskell CLI application that stores Last.fm API key securely and fetches user library. +category: Web +author: Your Name +maintainer: your.email@example.com +license: MIT +license-file: LICENSE +build-type: Simple + + + +library + + exposed-modules: + Lastfm.Crypto + Lastfm.Core + + build-depends: + base >=4.9 && <5, + bytestring >=0.10, + directory >=1.3, + filepath >=1.3, + cryptonite , + Rasterific == 0.7.5.4, + JuicyPixels == 3.3.9, + http-conduit, + aeson >= 2.2.3.0, + text >=1.2, + process >=1.6, + containers + + hs-source-dirs: src + default-language: GHC2021 + +executable lastfm-hs + main-is: Main.hs + hs-source-dirs: app + ghc-options: -Wall -threaded + default-language: Haskell2010 + build-depends: + base ^>=4.16.4.0, + lastfm-hs + + + -- Optional: extra-libraries or extra-ghc-options can be added if needed diff --git a/src/Lastfm/Core.hs b/src/Lastfm/Core.hs new file mode 100644 index 0000000..eb00246 --- /dev/null +++ b/src/Lastfm/Core.hs @@ -0,0 +1,63 @@ +{-# LANGUAGE DeriveGeneric, OverloadedStrings #-} + +module Lastfm.Core + ( Image(..) + , Artist(..) + , Artists(..) + , Root(..) + , fetchArtists + ) where + + +import Data.Aeson +import GHC.Generics +import qualified Data.ByteString.Lazy as BL +import Network.HTTP.Simple +import qualified Data.Map as M +import Control.Monad (forM_) + +-- Image +data Image = Image + { size :: String + , text :: String + } deriving (Show, Generic) + +instance FromJSON Image where + parseJSON = withObject "Image" $ \v -> + Image <$> v .: "size" <*> v .: "#text" + +-- Artist +data Artist = Artist + { name :: String + , playcount :: Int + , url :: String + , image :: [Image] + } deriving (Show, Generic) + +instance FromJSON Artist where + parseJSON = withObject "Artist" $ \v -> + Artist <$> v .: "name" + <*> (read <$> v .: "playcount") + <*> v .: "url" + <*> v .: "image" + +newtype Artists = Artists { artist :: [Artist] } + deriving (Show, Generic) + +instance FromJSON Artists + +newtype Root = Root { artists :: Artists } + deriving (Show, Generic) + +instance FromJSON Root + +-- | Fetch artists from Last.fm API +fetchArtists :: String -> String -> IO (Either String Root) +fetchArtists apiKey username = do + let url = "https://ws.audioscrobbler.com/2.0/?method=library.getartists" + ++ "&user=" ++ username + ++ "&api_key=" ++ apiKey + ++ "&format=json" + response <- httpLBS (parseRequest_ url) + let body = getResponseBody response + return $ eitherDecode body diff --git a/src/Lastfm/Crypto.hs b/src/Lastfm/Crypto.hs new file mode 100644 index 0000000..bac49ca --- /dev/null +++ b/src/Lastfm/Crypto.hs @@ -0,0 +1,5 @@ +module Lastfm.Crypto where + +test2 :: IO () +test2 = do + putStrLn "hello 2"