mirror of
https://git.nerfingen.de/nerf/choirMail.git
synced 2024-11-23 23:50:17 +00:00
first working commit
This commit is contained in:
parent
2e2bbcd0d6
commit
0447d72e71
13 changed files with 465 additions and 0 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -28,3 +28,5 @@ cabal.project.local~
|
|||
result
|
||||
result-*
|
||||
|
||||
# Other Stuff
|
||||
secret/
|
||||
|
|
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Revision history for choirMail
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
29
app/Config.hs
Normal file
29
app/Config.hs
Normal file
|
@ -0,0 +1,29 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Config where
|
||||
|
||||
import qualified Toml
|
||||
import Toml(TomlCodec, (.=))
|
||||
import qualified Data.Text as T
|
||||
|
||||
data Config = Config {
|
||||
mailDomain :: String
|
||||
,mailUsername :: String
|
||||
,mailPassword :: String
|
||||
,mailTo :: T.Text
|
||||
,mailFrom :: T.Text
|
||||
} deriving Show
|
||||
|
||||
configCodec :: TomlCodec Config
|
||||
configCodec = Config
|
||||
<$> Toml.string "mailDomain" .= mailDomain
|
||||
<*> Toml.string "mailUser" .= mailUsername
|
||||
<*> Toml.string "mailPassword" .= mailPassword
|
||||
<*> Toml.text "mailTo" .= mailTo
|
||||
<*> Toml.text "mailFrom" .= mailFrom
|
||||
|
||||
parseFile :: String -> IO (Either String Config)
|
||||
parseFile path = do
|
||||
config <- Toml.decodeFileEither configCodec path
|
||||
case config of
|
||||
Left errors -> return $ Left $ unwords $ fmap show errors
|
||||
Right x -> return $ Right $ x
|
65
app/Main.hs
Normal file
65
app/Main.hs
Normal file
|
@ -0,0 +1,65 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Main where
|
||||
|
||||
import TableParser
|
||||
import Requester
|
||||
import Config
|
||||
import Data.Time.Calendar
|
||||
import Data.Time.Clock
|
||||
import qualified Data.List as L
|
||||
import System.IO
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.IO as T
|
||||
import qualified Data.Text.Lazy as LT
|
||||
import Data.Maybe
|
||||
import System.Environment
|
||||
import System.Exit
|
||||
import Data.Time.Format.ISO8601
|
||||
import Sender
|
||||
|
||||
isChoirThisWeek :: Day -> Day -> Bool
|
||||
isChoirThisWeek today day = today <= day && diffDays day today <= 6
|
||||
|
||||
getToday :: IO Day
|
||||
getToday = utctDay <$> getCurrentTime
|
||||
|
||||
reportError :: String -> IO ()
|
||||
reportError err = hPutStr stderr err
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
args <- getArgs
|
||||
if length args /= 1
|
||||
then
|
||||
die "We need exactly one argument"
|
||||
else do
|
||||
configE <- parseFile (head args)
|
||||
case configE of
|
||||
Left text -> hPutStr stderr text
|
||||
Right config -> do
|
||||
bs <- request
|
||||
let eitherTable = parseBString bs
|
||||
case eitherTable of
|
||||
Left x -> reportError x
|
||||
Right table -> do
|
||||
today <- getToday
|
||||
maybe
|
||||
(T.putStr "Keine Probe :(")
|
||||
(\record -> send (mailDomain config) (mailUsername config) (mailPassword config) (mailTo config) (mailFrom config) (mailSubject record) (mailText record))
|
||||
(L.find ((isChoirThisWeek today) . date) table)
|
||||
|
||||
mailText :: MailRecord -> LT.Text
|
||||
mailText record = LT.fromStrict $T.concat ["Guten Morgen,\n\n"
|
||||
, announcement record
|
||||
,"\n\ndiesen Donnerstag\n\nDurchsingen: "
|
||||
,song1 record
|
||||
,", "
|
||||
,song2 record
|
||||
,"\nStimmproben: "
|
||||
,voice1 record
|
||||
,", "
|
||||
,voice2 record
|
||||
,"\n\nLG\nMalte\n" ]
|
||||
|
||||
mailSubject :: MailRecord -> T.Text
|
||||
mailSubject record = T.concat ["Donnerstag ", T.pack $ iso8601Show $ date record]
|
0
app/NetworkGet.hs
Normal file
0
app/NetworkGet.hs
Normal file
19
app/Requester.hs
Normal file
19
app/Requester.hs
Normal file
|
@ -0,0 +1,19 @@
|
|||
{-# LANGUAGE OverloadedStrings, DataKinds#-}
|
||||
module Requester(request) where
|
||||
|
||||
import Network.HTTP.Req
|
||||
-- import qualified Data.Text as T
|
||||
import Control.Monad.IO.Class(MonadIO)
|
||||
import qualified Data.ByteString as B
|
||||
import Text.URI
|
||||
|
||||
url :: Url 'Https
|
||||
url = https "md.darmstadt.ccc.de" /: "mathechor-probenplanung" /: "download"
|
||||
|
||||
|
||||
|
||||
requestRunner :: (MonadIO m)=> m BsResponse
|
||||
requestRunner = runReq defaultHttpConfig $ req GET url NoReqBody bsResponse mempty
|
||||
|
||||
request :: (MonadIO m) => m B.ByteString
|
||||
request = fmap responseBody requestRunner
|
18
app/Sender.hs
Normal file
18
app/Sender.hs
Normal file
|
@ -0,0 +1,18 @@
|
|||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Sender where
|
||||
|
||||
import Network.Mail.Mime
|
||||
import Network.Mail.SMTP
|
||||
import qualified Data.Text as ST
|
||||
import qualified Data.Text.Lazy as LT
|
||||
|
||||
|
||||
generateMail :: ST.Text -> ST.Text -> ST.Text -> LT.Text -> Mail
|
||||
generateMail to from subj = simpleMail' (Address Nothing to) (Address Nothing from) subj
|
||||
|
||||
-- domain -> Username -> password -> To -> From -> Subject -> Body
|
||||
send :: String -> String -> String -> ST.Text -> ST.Text -> ST.Text -> LT.Text -> IO ()
|
||||
send domain user pass to from subj body = sendMailWithLoginTLS domain user pass mail
|
||||
where
|
||||
mail = generateMail to from subj body
|
||||
|
88
app/TableParser.hs
Normal file
88
app/TableParser.hs
Normal file
|
@ -0,0 +1,88 @@
|
|||
module TableParser(MailRecord(..), parseBString, parseTable) where
|
||||
|
||||
import qualified Text.Parsec as P
|
||||
-- import qualified Text.Parsec.Char as P
|
||||
import qualified Text.Parsec.Text as P
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as T
|
||||
import qualified Data.Time.Calendar as D
|
||||
import qualified Data.ByteString as B
|
||||
import Control.Monad
|
||||
|
||||
|
||||
data MailRecord = MailRecord {
|
||||
date :: D.Day,
|
||||
voice1 :: T.Text,
|
||||
voice2 :: T.Text,
|
||||
song1 :: T.Text,
|
||||
song2 :: T.Text,
|
||||
announcement :: T.Text
|
||||
} deriving (Show)
|
||||
|
||||
seperator :: Char
|
||||
seperator = '\t'
|
||||
|
||||
sepParser :: P.Parser ()
|
||||
sepParser = void $ P.char seperator
|
||||
|
||||
textCellParser :: P.Parser T.Text
|
||||
textCellParser = fmap T.pack $ P.many $ P.noneOf [seperator,'\n','\r']
|
||||
|
||||
dateCellParser :: P.Parser D.Day
|
||||
dateCellParser = do
|
||||
year <- parseYear
|
||||
_ <- P.char '-'
|
||||
month <- parseMonth
|
||||
_ <- P.char '-'
|
||||
day <- parseDay
|
||||
return $ D.fromGregorian year month day
|
||||
|
||||
parseYear :: P.Parser Integer
|
||||
parseYear = read <$> P.count 4 P.digit
|
||||
|
||||
parseMonth :: P.Parser Int
|
||||
parseMonth = read <$> P.count 2 P.digit
|
||||
|
||||
parseDay :: P.Parser Int
|
||||
parseDay = read <$> P.count 2 P.digit
|
||||
|
||||
parseRow :: P.Parser MailRecord
|
||||
parseRow = do
|
||||
date <- dateCellParser
|
||||
sepParser
|
||||
voice1 <- textCellParser
|
||||
sepParser
|
||||
voice2 <- textCellParser
|
||||
sepParser
|
||||
song1 <- textCellParser
|
||||
sepParser
|
||||
song2 <- textCellParser
|
||||
sepParser
|
||||
announcments <- textCellParser
|
||||
return $ MailRecord date voice1 voice2 song1 song2 announcments
|
||||
|
||||
parseFirstRow :: P.Parser ()
|
||||
parseFirstRow = void (P.string "Datum\tStimmprobe 1\tStimmprobe 2\tLied 1\tLied 2\tWeitere Ansagen" >> P.endOfLine)
|
||||
|
||||
parseTable :: P.Parser [MailRecord]
|
||||
parseTable = do
|
||||
parseFirstRow
|
||||
x <- parseRow `P.sepEndBy` P.endOfLine
|
||||
P.eof
|
||||
return x
|
||||
|
||||
textToMailRecord :: T.Text -> Either String [MailRecord]
|
||||
textToMailRecord t = case P.parse parseTable "" t of
|
||||
Left x -> Left $ show x
|
||||
Right x -> Right x
|
||||
|
||||
|
||||
parseBString :: B.ByteString -> Either String [MailRecord]
|
||||
parseBString t = do
|
||||
text <- toText t
|
||||
textToMailRecord text
|
||||
|
||||
toText :: B.ByteString -> Either String T.Text
|
||||
toText t = case T.decodeUtf8' t of
|
||||
Left x -> Left $ show x
|
||||
Right x -> Right x
|
95
choirMail.cabal
Normal file
95
choirMail.cabal
Normal file
|
@ -0,0 +1,95 @@
|
|||
cabal-version: 3.0
|
||||
-- The cabal-version field refers to the version of the .cabal specification,
|
||||
-- and can be different from the cabal-install (the tool) version and the
|
||||
-- Cabal (the library) version you are using. As such, the Cabal (the library)
|
||||
-- version used must be equal or greater than the version stated in this field.
|
||||
-- Starting from the specification version 2.2, the cabal-version field must be
|
||||
-- the first thing in the cabal file.
|
||||
|
||||
-- Initial package description 'choirMail' generated by
|
||||
-- 'cabal init'. For further documentation, see:
|
||||
-- http://haskell.org/cabal/users-guide/
|
||||
--
|
||||
-- The name of the package.
|
||||
name: choirMail
|
||||
|
||||
-- The package version.
|
||||
-- See the Haskell package versioning policy (PVP) for standards
|
||||
-- guiding when and how versions should be incremented.
|
||||
-- https://pvp.haskell.org
|
||||
-- PVP summary: +-+------- breaking API changes
|
||||
-- | | +----- non-breaking API additions
|
||||
-- | | | +--- code changes with no API change
|
||||
version: 0.1.0.0
|
||||
|
||||
-- A short (one-line) description of the package.
|
||||
-- synopsis:
|
||||
|
||||
-- A longer description of the package.
|
||||
-- description:
|
||||
|
||||
-- URL for the project homepage or repository.
|
||||
homepage: "https://git.nerfingen.de/nerf/choirMail"
|
||||
|
||||
-- The license under which the package is released.
|
||||
license: GPL-3.0-or-later
|
||||
|
||||
-- The file containing the license text.
|
||||
license-file: LICENSE
|
||||
|
||||
-- The package author(s).
|
||||
author: Dennis Frieberg
|
||||
|
||||
-- An email address to which users can send suggestions, bug reports, and patches.
|
||||
maintainer: nerfingen@yahoo.de
|
||||
|
||||
-- A copyright notice.
|
||||
-- copyright:
|
||||
category: Web
|
||||
build-type: Simple
|
||||
|
||||
-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
|
||||
extra-doc-files: CHANGELOG.md
|
||||
|
||||
-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
|
||||
-- extra-source-files:
|
||||
|
||||
common warnings
|
||||
ghc-options: -Wall
|
||||
|
||||
executable choirMail
|
||||
-- Import common warning flags.
|
||||
import: warnings
|
||||
|
||||
-- .hs or .lhs file containing the Main module.
|
||||
main-is: Main.hs
|
||||
|
||||
-- Modules included in this executable, other than Main.
|
||||
other-modules: Config
|
||||
, TableParser
|
||||
, Requester
|
||||
, Sender
|
||||
|
||||
-- LANGUAGE extensions used by modules in this package.
|
||||
-- other-extensions:
|
||||
|
||||
-- Other library packages from which modules are imported.
|
||||
-- ^>=4.15.1.0
|
||||
build-depends: base ^>=4.15.1.0
|
||||
,tomland >= 1.3.3.0
|
||||
,smtp-mail
|
||||
,optparse-applicative
|
||||
,req
|
||||
,parsec
|
||||
,text
|
||||
,time
|
||||
,bytestring
|
||||
,mime-mail
|
||||
,modern-uri
|
||||
|
||||
|
||||
-- Directories containing source files.
|
||||
hs-source-dirs: app
|
||||
|
||||
-- Base language which the package is written in.
|
||||
default-language: Haskell2010
|
17
choirMail.nix
Normal file
17
choirMail.nix
Normal file
|
@ -0,0 +1,17 @@
|
|||
{ mkDerivation, base, bytestring, lib, mime-mail, modern-uri
|
||||
, optparse-applicative, parsec, req, smtp-mail, text, time, tomland
|
||||
}:
|
||||
mkDerivation {
|
||||
pname = "choirMail";
|
||||
version = "0.1.0.0";
|
||||
src = ./.;
|
||||
isLibrary = false;
|
||||
isExecutable = true;
|
||||
executableHaskellDepends = [
|
||||
base bytestring mime-mail modern-uri optparse-applicative parsec
|
||||
req smtp-mail text time tomland
|
||||
];
|
||||
homepage = ""https://git.nerfingen.de/nerf/choirMail"";
|
||||
license = lib.licenses.gpl3Plus;
|
||||
mainProgram = "choirMail";
|
||||
}
|
2
default.nix
Normal file
2
default.nix
Normal file
|
@ -0,0 +1,2 @@
|
|||
{ pkgs , compiler ? "ghc902"}:
|
||||
pkgs.haskell.packages.${compiler}.callPackage ./choirMail.nix { }
|
43
flake.lock
Normal file
43
flake.lock
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1667639549,
|
||||
"narHash": "sha256-frqZKSG/933Ctwl9voSZnXDwo8CqddXcjQhnCzwNqaM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cae3751e9f74eea29c573d6c2f14523f41c2821a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
82
flake.nix
Normal file
82
flake.nix
Normal file
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
description = "choir Mail automatisation";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable;
|
||||
flake-utils.url = github:numtide/flake-utils;
|
||||
};
|
||||
|
||||
outputs = {self, nixpkgs, flake-utils} :
|
||||
let
|
||||
# name to be used as identifier for editor environments and such
|
||||
name = "Application";
|
||||
compiler = "ghc902";
|
||||
|
||||
in
|
||||
flake-utils.lib.eachDefaultSystem ( system:
|
||||
let
|
||||
pkgs = import nixpkgs {inherit system;};
|
||||
hpkgs = pkgs.haskell.packages.${compiler};
|
||||
in {
|
||||
packages = { default = (import ./default.nix) {inherit pkgs compiler;};};
|
||||
|
||||
|
||||
devShells =
|
||||
rec {
|
||||
# This sets the default devShell
|
||||
default = kakoune;
|
||||
kakoune =
|
||||
let
|
||||
haskell-language-server = hpkgs.haskell-language-server;
|
||||
myKakoune =
|
||||
let
|
||||
# this could also be done by generating toml with the
|
||||
# nixpkgs lib, but I'm lazy
|
||||
kak-lsp-config = pkgs.writeTextFile {
|
||||
name = "kak-lsp-config.toml";
|
||||
text = ''
|
||||
[language.haskell]
|
||||
filetypes = ["haskell"]
|
||||
roots = ["Setup.hs", "stack.yaml", "*.cabal"]
|
||||
command = "haskell-language-server-wrapper"
|
||||
args = ["--lsp"]
|
||||
'';
|
||||
};
|
||||
config = pkgs.writeTextFile (rec {
|
||||
name = "kakrc.kak";
|
||||
destination = "/share/kak/autoload/${name}";
|
||||
text = ''
|
||||
colorscheme solarized-dark
|
||||
set global tabstop 2
|
||||
set global indentwidth 2
|
||||
# eval %sh{kak-lsp --kakoune --session $kak_session -c ${kak-lsp-config}}
|
||||
eval %sh{kak-lsp --kakoune --session $kak_session -c ${kak-lsp-config} --log /tmp/kak-lpsLog -vvvv}
|
||||
hook global WinSetOption filetype=(haskell|nix) %{
|
||||
lsp-auto-hover-enable
|
||||
lsp-enable-window
|
||||
}
|
||||
add-highlighter global/ number-lines
|
||||
map global normal <c-p> ': fzf-mode<ret>'
|
||||
'';
|
||||
});
|
||||
in
|
||||
pkgs.kakoune.override {
|
||||
plugins = with pkgs.kakounePlugins; [fzf-kak kak-lsp config];
|
||||
};
|
||||
in
|
||||
pkgs.mkShell {
|
||||
inputsFrom = [self.outputs.packages.${system}.default];
|
||||
packages = [myKakoune haskell-language-server pkgs.git pkgs.fzf hpkgs.cabal2nix pkgs.cabal-install pkgs.zlib.dev];
|
||||
# TODO only try to start the kakoune session if no session with that
|
||||
# name exists
|
||||
shellHook = ''
|
||||
alias ..="cd .."
|
||||
export KAKOUNE_CONFIG_DIR="/dev/null/"
|
||||
kak -d -s ${name} &
|
||||
alias vim="kak -c ${name}"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue