Init commit

This commit is contained in:
2021-03-26 19:39:28 +01:00
commit f82817d0f8
11 changed files with 307 additions and 0 deletions

32
src/Bot/Bot.fsproj Normal file
View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Config.fs" />
<Compile Include="Channels.fs" />
<Compile Include="WikiMathParser.fs" />
<Compile Include="BotCommands.fs" />
<Compile Include="WebsiteCheckLoop.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<Content Include="../../data/channels.dat" />
<Content Include="../../data/config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="1.0.2" />
<PackageReference Include="DSharpPlus" Version="3.2.3" />
<PackageReference Include="DSharpPlus.CommandsNext" Version="3.2.3" />
<PackageReference Include="FSharp.Data" Version="4.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
</ItemGroup>
</Project>

28
src/Bot/BotCommands.fs Normal file
View File

@@ -0,0 +1,28 @@
namespace Bot
module BotCommands =
open System.Threading.Tasks
open DSharpPlus.CommandsNext
open DSharpPlus.CommandsNext.Attributes
open Channels
type BotCommands() =
[<Command("hi")>]
member public self.hi(ctx:CommandContext) =
async { ctx.RespondAsync "Hi there" |> ignore } |> Async.StartAsTask :> Task
[<Command("echo")>]
member public self.echo(ctx:CommandContext) (message:string) =
async { ctx.RespondAsync message |> ignore } |> Async.StartAsTask :> Task
[<Command("toggle")>]
member public self.toggle(ctx:CommandContext) =
async {
toggleChannel ctx.Channel.Id
|> fun channelGotAdded ->
match channelGotAdded with
| true -> ctx.RespondAsync "This is now my channel :)" |> ignore
| false -> ctx.RespondAsync "This is now your channel (:" |> ignore
} |> Async.StartAsTask :> Task

41
src/Bot/Channels.fs Normal file
View File

@@ -0,0 +1,41 @@
namespace Bot
module Channels =
open FSharp.Data
open System.IO
let private filepath = __SOURCE_DIRECTORY__ + "/../../data/channels.dat"
let mutable channels =
File.ReadLines(filepath)
|> Seq.map (fun line -> line.ToString().AsInteger64())
|> Seq.map (uint64)
|> set
let private updateChannels newChannels =
newChannels
|> Seq.map (fun i -> i.ToString())
|> Seq.toList
|> fun lines -> File.WriteAllLines(filepath, lines)
channels <- newChannels
let private removeChannel (channelId:uint64) =
channels.Remove(channelId)
|> updateChannels
let private addChannel (channelId:uint64) =
channels.Add(channelId)
|> updateChannels
let toggleChannel (channelId:uint64) =
match channelId with
| channelId when channels.Contains(channelId) ->
removeChannel channelId
false
| channelId ->
addChannel channelId
true

14
src/Bot/Config.fs Normal file
View File

@@ -0,0 +1,14 @@
namespace Bot
module Config =
open System.IO
open Microsoft.Extensions.Configuration
let private getConfig =
let builder = new ConfigurationBuilder()
do builder.SetBasePath( Directory.GetCurrentDirectory() + "/../../data" ) |> ignore
do builder.AddJsonFile("config.json") |> ignore
builder.Build()
let config = getConfig

46
src/Bot/Program.fs Normal file
View File

@@ -0,0 +1,46 @@
namespace Bot
module core =
open System
open DSharpPlus
open DSharpPlus.CommandsNext
open System.Threading.Tasks
open Config
open WebsiteCheckLoop
open BotCommands
open WikiMathParser
let getDiscordConfig =
let conf = new DiscordConfiguration()
conf.set_Token config.["BotToken"]
conf.set_TokenType TokenType.Bot
conf.set_UseInternalLogHandler true
conf.set_LogLevel LogLevel.Debug
conf
let getCommandsConfig =
let conf = new CommandsNextConfiguration()
conf.set_StringPrefix "!"
conf
let client = new DiscordClient(getDiscordConfig)
let commands = client.UseCommandsNext(getCommandsConfig)
let mainTask =
let mutable previousResults = getStatus
async {
client.add_MessageCreated(fun e -> async { Console.WriteLine e.Message.Content } |> Async.StartAsTask :> _)
commands.RegisterCommands<BotCommands>()
client.ConnectAsync() |> Async.AwaitTask |> Async.RunSynchronously
do! scrapePeriodically (scrapeFun client previousResults
>> fun results -> previousResults <- results)
do! Async.AwaitTask(Task.Delay(-1))
}
[<EntryPoint>]
let main argv =
Async.RunSynchronously(mainTask)
0

View File

@@ -0,0 +1,47 @@
namespace Bot
module WebsiteCheckLoop =
open System
open DSharpPlus
open Channels
let private log message =
printfn "[%A] [Info] %s" DateTime.Now message
let private sendToChannelWith (client:DiscordClient) message id =
log <| sprintf "Sending message to channel: %A" id
id
|> client.GetChannelAsync
|> Async.AwaitTask
|> Async.RunSynchronously
|> fun channel ->
client.SendMessageAsync(channel, message)
|> Async.AwaitTask
|> Async.RunSynchronously
|> ignore
let private formatMessage message link =
message + "\n" + link
let scrapeFun client (previousListOfResults:List<string * string>) (listOfResults:List<string * string>) =
log "Scraping website"
match previousListOfResults, listOfResults with
| (previousListOfResults, listOfResults) when previousListOfResults = listOfResults ->
channels
|> Seq.iter (fun id -> sendToChannelWith client (formatMessage <|| Seq.head listOfResults) id)
Seq.head listOfResults
||> formatMessage
|> fun s -> s.Split("\n")
|> Seq.map (fun s -> "\t" + s)
|> Seq.fold (fun a b -> a + "\n" + b) ""
|> sprintf "Found following update: \n%s"
|> log
| (_, _) ->
log "No new content found"
listOfResults

35
src/Bot/WikiMathParser.fs Normal file
View File

@@ -0,0 +1,35 @@
namespace Bot
module WikiMathParser =
open System
open FSharp.Data
open Config
let private page = HtmlDocument.Load(sprintf "https://wiki.math.ntnu.no/%s/%s/start" config.["Class"] config.["Year"])
let private findPDFLink (node:HtmlNode) =
node.CssSelect(".mf_pdf")
|> fun l -> match l with
| l when Seq.length l = 0 -> ""
| l -> HtmlNode.attributeValue "href" (Seq.head l)
|> (+) "https://wiki.math.ntnu.no"
let getStatus =
(page.CssSelect ".level2 > ul > .level1")
|> Seq.map (fun (x:HtmlNode) -> (x.InnerText().TrimStart(), findPDFLink x ))
|> Seq.filter (fun (x:string, _) -> x.Contains("Problem Set"))
|> Seq.toList
let private timer = new Timers.Timer( config.["SecondsBetweenUpdate"].AsFloat() * 1000.0)
let private waitAPeriod = Async.AwaitEvent (timer.Elapsed) |> Async.Ignore
let scrapePeriodically (callback: List<String * String> -> unit ) =
timer.Start()
async {
while true do
Async.RunSynchronously waitAPeriod
getStatus
|> callback
}