diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..643c053 --- /dev/null +++ b/.gitignore @@ -0,0 +1,434 @@ + +# Created by https://www.gitignore.io/api/linux,macos,windows,visualstudio,visualstudiocode +# Edit at https://www.gitignore.io/?templates=linux,macos,windows,visualstudio,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# End of https://www.gitignore.io/api/linux,macos,windows,visualstudio,visualstudiocode \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/.dockerignore b/src/PasslickDevelopment.LearnwebNotifier/.dockerignore new file mode 100644 index 0000000..3729ff0 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/.dockerignore @@ -0,0 +1,25 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Communication/ServiceResponse.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Communication/ServiceResponse.cs new file mode 100644 index 0000000..09a8bf4 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Communication/ServiceResponse.cs @@ -0,0 +1,25 @@ +using LearnwebNotifier.Library.Domain.Models; +using System; +using System.Collections.Generic; + +namespace LearnwebNotifier.Library.Domain.Communication +{ + public class ServiceResponse + { + public Guid ExecId { get; } + public DateTime ExecDate { get; } + public ServiceStatus Status { get; } + public List Activities { get; } + + public ServiceResponse(ServiceStatus status, List activities) + { + ExecId = Guid.NewGuid(); + ExecDate = DateTime.UtcNow; + Status = status; + Activities = activities; + } + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Communication/ServiceStatus.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Communication/ServiceStatus.cs new file mode 100644 index 0000000..e106219 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Communication/ServiceStatus.cs @@ -0,0 +1,27 @@ +namespace LearnwebNotifier.Library.Domain.Communication +{ + public enum ServiceStatus + { + Info = 1000, + InfoRequested = 1001, + OK = 2000, + NewActivities = 2001, + NoRecentActivities = 2002, + PushSent = 2003, + Valid = 2005, + Exists = 2006, + Warning = 3000, + WidgetNotFound = 3001, + Fail = 4000, + PushFailed = 4003, + NotFound = 4004, + Invalid = 4005, + Exception = 5000, + Unauthorized = 5001, + CriticalException = 6000, + UnhandledException = 7000 + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/Activity.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/Activity.cs new file mode 100644 index 0000000..aec644e --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/Activity.cs @@ -0,0 +1,25 @@ +using System; + +namespace LearnwebNotifier.Library.Domain.Models +{ + public class Activity + { + public DateTime? Date { get; } + public Course Course { get; } + public string Type { get; } + public string Name { get; } + public string Url { get; } + + public Activity(DateTime? date, Course course, string type, string name, string url) + { + Date = date; + Course = course; + Type = type; + Name = name; + Url = url; + } + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/ConfigStruct.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/ConfigStruct.cs new file mode 100644 index 0000000..5087b9f --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/ConfigStruct.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace LearnwebNotifier.Library.Domain.Models +{ + public class ConfigStruct + { + [JsonPropertyName("service")] + public ServiceConfig Service { get; set; } = new ServiceConfig(5); + [JsonPropertyName("learnweb")] + public LearnwebConfig Learnweb { get; set; } = new LearnwebConfig("", "", new List { "" }); + [JsonPropertyName("pushover")] + public PushoverConfig Pushover { get; set; } = new PushoverConfig("", ""); + [JsonPropertyName("sentry")] + public SentryConfig Sentry { get; set; } = new SentryConfig(false, ""); + + public ConfigStruct(LearnwebConfig learnweb, PushoverConfig pushover) + { + Learnweb = learnweb; + Pushover = pushover; + } + public ConfigStruct() { } + + + public class ServiceConfig + { + [JsonPropertyName("refresh")] + public uint Refresh { get; set; } + + public ServiceConfig(uint refresh) + { + Refresh = refresh; + } + public ServiceConfig() { } + } + + public class LearnwebConfig + { + [JsonPropertyName("user")] + public string Username { get; set; } + [JsonPropertyName("password")] + public string Password { get; set; } + [JsonPropertyName("courses")] + public List Courses { get; set; } + + public LearnwebConfig(string username, string password, List courses) + { + Username = username; + Password = password; + Courses = courses; + } + public LearnwebConfig() { } + } + + public class PushoverConfig + { + [JsonPropertyName("token")] + public string Token { get; set; } + [JsonPropertyName("recipient")] + public string Recipient { get; set; } + + public PushoverConfig(string token, string recipient) + { + Token = token; + Recipient = recipient; + } + public PushoverConfig() { } + } + + public class SentryConfig + { + [JsonPropertyName("activate")] + public bool Activate { get; set; } + [JsonPropertyName("dsn")] + public string Dsn { get; set; } + + public SentryConfig(bool activate, string dsn) + { + Activate = activate; + Dsn = dsn; + } + public SentryConfig() { } + } + + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/Course.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/Course.cs new file mode 100644 index 0000000..1efefde --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Models/Course.cs @@ -0,0 +1,21 @@ +namespace LearnwebNotifier.Library.Domain.Models +{ + public class Course + { + public string Id { get; } + public string Name { get; set; } + public string Abbrev { get; set; } + public string Url { get; set; } + + public Course(string id) + { + Id = id; + Name = "N/A"; + Abbrev = "N/A"; + Url = $"https://sso.uni-muenster.de/LearnWeb/learnweb2/course/view.php?id={Id}&lang=en"; + } + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IActivityService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IActivityService.cs new file mode 100644 index 0000000..90ec3fb --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IActivityService.cs @@ -0,0 +1,16 @@ +using LearnwebNotifier.Library.Domain.Communication; + +namespace LearnwebNotifier.Library.Domain.Services +{ + public interface IActivityService + { + public ServiceStatus Init(); + public ServiceResponse FetchActivities(string courseId); + public ServiceStatus RefreshSession(); + public void ClearCookies(); + public void Dispose(); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IClientService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IClientService.cs new file mode 100644 index 0000000..34568fa --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IClientService.cs @@ -0,0 +1,18 @@ +using LearnwebNotifier.Library.Domain.Communication; +using System.Net.Http; + +namespace LearnwebNotifier.Library.Domain.Services +{ + public interface IClientService + { + public ServiceStatus Init(); + public ServiceStatus Authorize(); + public ServiceStatus RefreshSession(); + public HttpClient GetClient(); + public void ClearCookies(); + public void Dispose(); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IConfigService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IConfigService.cs new file mode 100644 index 0000000..adb0632 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/IConfigService.cs @@ -0,0 +1,17 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Library.Domain.Models; + +namespace LearnwebNotifier.Library.Domain.Services +{ + public interface IConfigService + { + public ServiceStatus LoadConfig(); + public ServiceStatus GenerateConfig(ConfigStruct config); + public ServiceStatus CheckConfigExistence(); + public (ServiceStatus status, string value) GetLearnwebPassword(); + public ServiceStatus ConfigAssistant(); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/ICrawlerService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/ICrawlerService.cs new file mode 100644 index 0000000..1291ad1 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Domain/Services/ICrawlerService.cs @@ -0,0 +1,14 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Library.Domain.Models; +using System.Collections.Generic; + +namespace LearnwebNotifier.Library.Domain.Services +{ + public interface ICrawlerService + { + public (ServiceStatus status, List activities) ParseCourse(Course course); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Helper/HelperFunctions.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Helper/HelperFunctions.cs new file mode 100644 index 0000000..ee01c57 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Helper/HelperFunctions.cs @@ -0,0 +1,71 @@ +using System; +using System.Reflection; + +namespace LearnwebNotifier.Library.Helper +{ + public static class HelperFunctions + { + + public static void ConsoleWrite(string text, ConsoleColor fgcolor = ConsoleColor.Gray, ConsoleColor bgcolor = ConsoleColor.Black) + { + Console.ForegroundColor = fgcolor; + Console.BackgroundColor = bgcolor; + Console.Write(text); + Console.ResetColor(); + } + + + public static void ConsoleWriteLine(string text, ConsoleColor fgcolor = ConsoleColor.Gray, ConsoleColor bgcolor = ConsoleColor.Black) + { + Console.ForegroundColor = fgcolor; + Console.BackgroundColor = bgcolor; + Console.WriteLine(text); + Console.ResetColor(); + } + + + public static string ReadPassword() + { + string password = string.Empty; + ConsoleKey key; + do + { + var keyInfo = Console.ReadKey(true); + key = keyInfo.Key; + + if (key == ConsoleKey.Backspace && password.Length > 0) + { + Console.Write("\b \b"); + password = password[0..^1]; + } + else if (!char.IsControl(keyInfo.KeyChar)) + { + Console.Write("∗"); + password += keyInfo.KeyChar; + } + } while (key != ConsoleKey.Enter); + return password; + } + + + public static void PrintConsoleHeader() + { + Console.Clear(); + Console.WriteLine("\n***************************************************************************\n"); + Console.WriteLine($" WWU Learnweb Notifier Service v{Assembly.GetEntryAssembly().GetName().Version}"); + Console.WriteLine($" (c) Passlick Development {DateTime.UtcNow.Year}. All rights reserved."); + Console.WriteLine("\n***************************************************************************\n"); + } + + + // Hardcoded encryption key for weak string protection against easy plaintext read -- does not serve safety-critical purpose + private static readonly string encryptionKey = "d7z8jhL5uvPFbsMzyfobhwHdoEeovkUGlztcTVFrdp86NwItxK9VN6kvNDhaAtIo"; + + public static string EncryptString(string value) => SimpleAES.AES256.Encrypt(value, encryptionKey); + public static string DecryptString(string value) => SimpleAES.AES256.Decrypt(value, encryptionKey); + + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/LearnwebNotifier.Library.csproj b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/LearnwebNotifier.Library.csproj new file mode 100644 index 0000000..ff0506d --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/LearnwebNotifier.Library.csproj @@ -0,0 +1,32 @@ + + + + netcoreapp3.1 + lwnotif-lib + Library + Passlick Development + WWU Learnweb Notifier + Copyright © Passlick Development 2020. All rights reserved. + en-US + passlickdev.ico + 1.3.0.0 + 1.3.0.0 + 1.3.0 + false + + + + false + + + + + + + + + + + + + diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Properties/launchSettings.json b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Properties/launchSettings.json new file mode 100644 index 0000000..d5d5af5 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "LearnwebNotifier.Library": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ActivityService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ActivityService.cs new file mode 100644 index 0000000..5b35133 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ActivityService.cs @@ -0,0 +1,86 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Library.Domain.Models; +using LearnwebNotifier.Library.Domain.Services; +using Microsoft.Extensions.Logging; +using System; + +namespace LearnwebNotifier.Library.Services +{ + public class ActivityService : IActivityService + { + + private readonly ILoggerFactory loggerFactory; + private readonly ILogger logger; + private readonly ClientService clientService; + private CrawlerService crawlerService; + + public ActivityService(ILoggerFactory _loggerFactory) + { + loggerFactory = _loggerFactory; + logger = loggerFactory.CreateLogger("Library.ActivityService"); + clientService = new ClientService(loggerFactory); + } + + + /// + /// Initializes ActivityService with CrawlerService + /// + /// ServiceStatus + public ServiceStatus Init() + { + if (clientService.Authorize() == ServiceStatus.OK) + { + crawlerService = new CrawlerService(clientService, loggerFactory); + return ServiceStatus.OK; + } + else return ServiceStatus.Fail; + } + + + /// + /// Fetches activities by inheriting CrawlerService for courseId + /// + /// Course ID + /// ServiceResponse object + public ServiceResponse FetchActivities(string courseId) + { + try + { + Course course = new Course(courseId); + if (crawlerService != null) + { + var (status, activities) = crawlerService.ParseCourse(course); + return new ServiceResponse(status, activities); + } + else return new ServiceResponse(ServiceStatus.Exception, null); + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return new ServiceResponse(ServiceStatus.UnhandledException, null); + } + } + + + /// + /// Refreshes ClientService session + /// + /// ServiceStatus + public ServiceStatus RefreshSession() => clientService.RefreshSession(); + + + /// + /// Clears ClientService cookies + /// + public void ClearCookies() => clientService.ClearCookies(); + + + /// + /// Disposes ClientService connection + /// + public void Dispose() => clientService.Dispose(); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ClientService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ClientService.cs new file mode 100644 index 0000000..63c018f --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ClientService.cs @@ -0,0 +1,163 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Library.Domain.Services; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; + +namespace LearnwebNotifier.Library.Services +{ + public class ClientService : IClientService + { + + private readonly ILoggerFactory loggerFactory; + private readonly ILogger logger; + private readonly ConfigService configService; + private static readonly HttpClientHandler handler = new HttpClientHandler(); + private readonly HttpClient client = new HttpClient(handler); + private static CookieContainer cookies = new CookieContainer(); + private readonly Uri baseUrl = new Uri("https://uni-muenster.de/LearnWeb/learnweb2/?lang=en"); + + public ClientService(ILoggerFactory _loggerFactory) + { + loggerFactory = _loggerFactory; + logger = loggerFactory.CreateLogger("Library.ClientService"); + configService = new ConfigService(); + + // Basic settings + handler.AllowAutoRedirect = true; + handler.CookieContainer = cookies; + client.BaseAddress = baseUrl; + client.Timeout = TimeSpan.FromSeconds(60); + + // Request headers + client.DefaultRequestHeaders.Add("Connection", "keep-alive"); + client.DefaultRequestHeaders.Add("Cache-Control", "max-age=0"); + client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1"); + client.DefaultRequestHeaders.Add("DNT", "1"); + client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Safari/537.36"); + client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"); + client.DefaultRequestHeaders.Add("Sec-Fetch-Site", "same-origin"); + client.DefaultRequestHeaders.Add("Sec-Fetch-Mode", "navigate"); + client.DefaultRequestHeaders.Add("Sec-Fetch-User", "?1"); + client.DefaultRequestHeaders.Add("Sec-Fetch-Dest", "document"); + } + + + /// + /// Initializes the HttpClient with basic data and validates connection to Learnweb + /// + /// ServiceStatus Enum + public ServiceStatus Init() + { + try + { + var res = client.GetAsync(baseUrl); + var statusCode = res.Result; + if (statusCode.IsSuccessStatusCode) return ServiceStatus.OK; + else + { + ClearCookies(); + logger.LogWarning($"Could not establish connection to Learnweb ({statusCode})"); + return ServiceStatus.Exception; + } + } + catch (Exception ex) + { + ClearCookies(); + logger.LogError(ex.Message); + return ServiceStatus.UnhandledException; + } + } + + + /// + /// Authorizes to Learnweb and populates cookie container with session cookies + /// + /// ServiceStatus Enum + public ServiceStatus Authorize() + { + string username = configService.Config.Learnweb.Username; + string password = configService.GetLearnwebPassword().value; + + if (Init() == ServiceStatus.OK) + { + List> reqBody = new List> + { + new KeyValuePair("httpd_username", username), + new KeyValuePair("httpd_password", password) + }; + + try + { + var res = client.PostAsync("https://sso.uni-muenster.de/LearnWeb/learnweb2/?lang=en", new FormUrlEncodedContent(reqBody)); + if (res.Result.IsSuccessStatusCode) return ServiceStatus.OK; + else + { + ClearCookies(); + logger.LogError($"Authentication for '{username}' failed ({res.Result.StatusCode})"); + return ServiceStatus.Exception; + } + } + catch (Exception ex) + { + ClearCookies(); + logger.LogError(ex.Message); + return ServiceStatus.UnhandledException; + } + } + else + { + ClearCookies(); + logger.LogWarning("Learnweb initialization failed. Authentication not possible."); + return ServiceStatus.Fail; + } + } + + + /// + /// Returns HttpClient + /// + /// HttpClient + public HttpClient GetClient() => client; + + + /// + /// Refreshes Learnweb session without loosing authorization + /// + /// Status + public ServiceStatus RefreshSession() + { + foreach (Cookie cookie in cookies.GetCookies(new Uri("https://sso.uni-muenster.de"))) + { + if (cookie.Name == "MoodleSessionLearnweb2prod") + { + cookie.Expired = true; + return ServiceStatus.OK; + } + } + logger.LogWarning("Could not refresh session (cookie not found)"); + return ServiceStatus.Fail; + } + + + /// + /// Clears cookies by setting new CookieContainer + /// + public void ClearCookies() => cookies = new CookieContainer(); + + + /// + /// Disposes HttpClient and HttpClientHandler + /// + public void Dispose() + { + client.Dispose(); + handler.Dispose(); + } + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ConfigService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ConfigService.cs new file mode 100644 index 0000000..15c4655 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/ConfigService.cs @@ -0,0 +1,210 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Library.Domain.Models; +using LearnwebNotifier.Library.Domain.Services; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using static LearnwebNotifier.Library.Helper.HelperFunctions; + +namespace LearnwebNotifier.Library.Services +{ + public class ConfigService : IConfigService + { + + private readonly ILoggerFactory loggerFactory; + private readonly ILogger logger; + public ConfigStruct Config; + + public ConfigService() + { + loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddConsole(); + }); + logger = loggerFactory.CreateLogger("Library.ConfigService"); + + if (CheckConfigExistence() == ServiceStatus.Exists) + { + LoadConfig(); + if (Config.Sentry.Activate) + { + loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddConsole() + .AddSentry(o => + { + o.Debug = false; + o.Dsn = Config.Sentry.Dsn; + o.MaxBreadcrumbs = 150; + o.MinimumBreadcrumbLevel = LogLevel.Information; + o.MinimumEventLevel = LogLevel.Warning; + }); + }); + logger = loggerFactory.CreateLogger("Library.ConfigService"); + } + } + } + + + /// + /// Loads config file + /// + /// Service Status Code + public ServiceStatus LoadConfig() + { + try + { + Config = JsonSerializer.Deserialize(File.ReadAllText("config.json")); + return ServiceStatus.OK; + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return ServiceStatus.UnhandledException; + } + } + + + /// + /// Generates config file for the service in the working directory + /// + /// Service Status Code + public ServiceStatus GenerateConfig(ConfigStruct config) + { + try + { + config.Learnweb.Password = EncryptString(config.Learnweb.Password); + + JsonSerializerOptions options = new JsonSerializerOptions + { + WriteIndented = true + }; + + string configJson = JsonSerializer.Serialize(config, options); + File.WriteAllText("config.json", configJson); + return ServiceStatus.OK; + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return ServiceStatus.UnhandledException; + } + } + + + /// + /// Checks the config file existence + /// + /// Service Status Code + public ServiceStatus CheckConfigExistence() + { + try + { + if (File.Exists("config.json")) + return ServiceStatus.Exists; + else + return ServiceStatus.NotFound; + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return ServiceStatus.UnhandledException; + } + } + + + /// + /// Returns decrypted Learnweb password + /// + /// Decrypted Learnweb password + public (ServiceStatus status, string value) GetLearnwebPassword() + { + try + { + string password = DecryptString(Config.Learnweb.Password); + return (ServiceStatus.OK, password); + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return (ServiceStatus.UnhandledException, null); + } + } + + + /// + /// Configuration Assistant + /// + /// Service Status Code + public ServiceStatus ConfigAssistant() + { + try + { + + ConsoleWriteLine("\n --- LEARNWEB NOTIFIER CONFIGURATION ASSISTANT --- \n\n", ConsoleColor.Black, ConsoleColor.Green); + + Console.WriteLine("The service will create a configuration file which is necessary for the execution of the service. " + + "Please enter all the data below and confirm each by pressing 'Enter'. Your password will be encrypted and only stored on your machine."); + + ConsoleWrite("\nLearnweb User (a_bcde01):", ConsoleColor.Black, ConsoleColor.Gray); + Console.Write(" "); + string inputLearnwebUser = Console.ReadLine(); + + ConsoleWrite("Learnweb Password:", ConsoleColor.Black, ConsoleColor.Gray); + Console.Write(" "); + string inputLearnwebPassword = ReadPassword(); + + Console.WriteLine("\n\n\nEnter the course IDs for the courses which should be monitored by the service. " + + "You can add one or more courses. If you want to add more than one course, please separate the IDs with a comma, but without spaces (12345,67890).\n"); + + ConsoleWrite("Learnweb Course IDs:", ConsoleColor.Black, ConsoleColor.Gray); + Console.Write(" "); + List inputCourses = Console.ReadLine().Split(',').ToList(); + + Console.WriteLine("\n\nEnter the data for the Pushover notification service. " + + "You need an API token and a token for the receiver, which can be a single user or a usergroup.\n"); + + ConsoleWrite("Pushover API Token:", ConsoleColor.Black, ConsoleColor.Gray); + Console.Write(" "); + string inputPushoverToken = Console.ReadLine(); + + ConsoleWrite("Pushover Recipient Token:", ConsoleColor.Black, ConsoleColor.Gray); + Console.Write(" "); + string inputPushoverRecipient = Console.ReadLine(); + Console.WriteLine(""); + + + ConfigStruct newConfig = new ConfigStruct( + new ConfigStruct.LearnwebConfig(inputLearnwebUser, inputLearnwebPassword, inputCourses), + new ConfigStruct.PushoverConfig(inputPushoverToken, inputPushoverRecipient) + ); + + if (GenerateConfig(newConfig) == ServiceStatus.OK) + ConsoleWriteLine("\nConfiguration file generated, please restart the service now!", ConsoleColor.Green); + else + ConsoleWriteLine("\nConfiguration file generation failed! Aborting...", ConsoleColor.Red); + + Console.WriteLine("Press any key to quit...\n"); + Console.ReadKey(true); + Environment.Exit(0); + return ServiceStatus.OK; + + } + catch (Exception ex) + { + logger.LogError(ex.Message); + Environment.Exit(0); + return ServiceStatus.UnhandledException; + } + } + + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/CrawlerService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/CrawlerService.cs new file mode 100644 index 0000000..8cae72e --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/Services/CrawlerService.cs @@ -0,0 +1,187 @@ +using HtmlAgilityPack; +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Library.Domain.Models; +using LearnwebNotifier.Library.Domain.Services; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net.Http; + +namespace LearnwebNotifier.Library.Services +{ + public class CrawlerService : ICrawlerService + { + + private readonly ILoggerFactory loggerFactory; + private readonly ILogger logger; + private readonly ClientService clientService; + private readonly HttpClient client; + + public CrawlerService(ClientService _clientService, ILoggerFactory _loggerFactory) + { + loggerFactory = _loggerFactory; + logger = loggerFactory.CreateLogger("Library.CrawlerService"); + clientService = _clientService; + client = _clientService.GetClient(); + } + + + /// + /// Gets course HTML document from Learnweb + /// + /// Course + /// Status and course HTML + private (ServiceStatus status, HtmlDocument courseHtml) GetCourse(Course course) + { + try + { + var res = client.GetAsync(course.Url); + if (res.Result.IsSuccessStatusCode) + { + clientService.RefreshSession(); + string htmlString = res.Result.Content.ReadAsStringAsync().Result; + HtmlDocument courseHtml = new HtmlDocument(); + courseHtml.LoadHtml(htmlString); + return (ServiceStatus.OK, courseHtml); + } + else + { + logger.LogError($"Could not load course with id '{course.Id}' ({res.Result.StatusCode})"); + return (ServiceStatus.Exception, null); + } + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return (ServiceStatus.UnhandledException, null); + } + } + + + /// + /// Validates course HTML document for activities and activity widget + /// + /// Course + /// Course HTML document + /// Status + private ServiceStatus ValidateCourse(Course course, HtmlDocument courseHtml) + { + HtmlNode htmlNode = courseHtml.DocumentNode; + HtmlNode activityHeader = htmlNode.SelectSingleNode("//div[@class='activityhead']"); + HtmlNode activity = htmlNode.SelectSingleNode("//p[@class='activity']"); + + if (activityHeader == null) + { + logger.LogWarning($"No activity widget found for course id '{course.Id}'"); + return ServiceStatus.WidgetNotFound; + } + else if (activity == null) + { + logger.LogInformation($"No recent activity found for course id '{course.Id}'"); + return ServiceStatus.NoRecentActivities; + } + else if (activity != null) + { + logger.LogInformation($"New activities found for course id '{course.Id}'"); + return ServiceStatus.NewActivities; + } + else + { + logger.LogError($"Course validation error for course id '{course.Id}'"); + return ServiceStatus.Exception; + } + } + + + /// + /// Populates an Course object with relevant basic information for the course + /// + /// Course + /// Course HTML document + /// Status and populated Course object + private (ServiceStatus status, Course populatedCourse) PopulateCourse(Course course, HtmlDocument courseHtml) + { + HtmlNode htmlNode = courseHtml.DocumentNode; + HtmlNode courseName = htmlNode.SelectSingleNode("//*[@id='region-main']/h1"); + HtmlNode courseAbbrev = htmlNode.SelectSingleNode("//*[@id='page-navbar']/nav/ol/li[4]/a"); + + if (htmlNode != null && courseName != null && courseAbbrev != null) + { + course.Name = courseName.InnerText; + course.Abbrev = courseAbbrev.InnerText; + return (ServiceStatus.OK, course); + } + else + { + logger.LogWarning($"Could not populate course with course id '{course.Id}'"); + return (ServiceStatus.Fail, course); + } + } + + + /// + /// Parses an course HTML document for activities + /// + /// Course + /// Status and list of parsed activities + public (ServiceStatus status, List activities) ParseCourse(Course course) + { + try + { + var courseGet = GetCourse(course); + ServiceStatus courseValidation = + (courseGet.status == ServiceStatus.OK) + ? ValidateCourse(course, courseGet.courseHtml) + : ServiceStatus.Fail; + + if (courseValidation == ServiceStatus.NewActivities) + { + HtmlDocument courseHtml = courseGet.courseHtml; + HtmlNode documentNode = courseHtml.DocumentNode; + course = PopulateCourse(course, courseHtml).populatedCourse; + + HtmlNode activityHeader = documentNode.SelectSingleNode("//div[@class='activityhead']"); + string activityDateString = activityHeader.InnerText.Replace("Activity since ", "").Replace(",", ""); + DateTime activityDate = DateTime.Parse(activityDateString, new CultureInfo("de-DE")); + + List activities = new List(); + foreach (HtmlNode node in documentNode.SelectNodes("//p[@class='activity']")) + { + int nodeCount = node.ChildNodes.Count; + string activityType = "N/A"; + string activityName = null; + string activityUrl = null; + + if (nodeCount > 0) + activityType = node.ChildNodes[0].InnerText; + if (nodeCount > 2) + { + activityName = node.ChildNodes[2].InnerText; + activityUrl = node.ChildNodes[2].Attributes["href"].Value; + } + + Activity activity = new Activity(activityDate, course, activityType, activityName, activityUrl); + activities.Add(activity); + } + return (ServiceStatus.NewActivities, activities); + } + else if (courseValidation == ServiceStatus.NoRecentActivities) + return (ServiceStatus.NoRecentActivities, null); + else + { + logger.LogError($"Course parsing error for course id '{course.Id}'"); + return (ServiceStatus.Exception, null); + } + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return (ServiceStatus.UnhandledException, null); + } + } + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/passlickdev.ico b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/passlickdev.ico new file mode 100644 index 0000000..d58ecc6 Binary files /dev/null and b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Library/passlickdev.ico differ diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Dockerfile b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Dockerfile new file mode 100644 index 0000000..49779c0 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/dotnet/core/runtime:3.1-buster-slim AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build +WORKDIR /src +COPY ["LearnwebNotifier.Push/LearnwebNotifier.Push.csproj", "LearnwebNotifier.Push/"] +COPY ["LearnwebNotifier.Library/LearnwebNotifier.Library.csproj", "LearnwebNotifier.Library/"] +RUN dotnet restore "LearnwebNotifier.Push/LearnwebNotifier.Push.csproj" +COPY . . +WORKDIR "/src/LearnwebNotifier.Push" +RUN dotnet build "LearnwebNotifier.Push.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "LearnwebNotifier.Push.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "lwnotif-push.dll"] \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Models/PushNotification.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Models/PushNotification.cs new file mode 100644 index 0000000..c27424a --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Models/PushNotification.cs @@ -0,0 +1,36 @@ +using System.Text.Json.Serialization; + +namespace LearnwebNotifier.Push.Domain.Models +{ + public class PushNotification + { + [JsonPropertyName("token")] + public string Token { get; set; } + [JsonPropertyName("user")] + public string UserToken { get; set; } + [JsonPropertyName("title")] + public string Title { get; set; } + [JsonPropertyName("message")] + public string Message { get; set; } + [JsonPropertyName("url")] + public string Url { get; set; } + [JsonPropertyName("url_title")] + public string UrlTitle { get; set; } + [JsonPropertyName("priority")] + public string Priority { get; set; } + + public PushNotification(string token, string userToken, string title, string message, string url, string urlTitle, string priority) + { + Token = token; + UserToken = userToken; + Title = title; + Message = message; + Url = url; + UrlTitle = urlTitle; + Priority = priority; + } + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Services/IClientService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Services/IClientService.cs new file mode 100644 index 0000000..d44abf0 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Services/IClientService.cs @@ -0,0 +1,15 @@ +using LearnwebNotifier.Library.Domain.Communication; +using System.Net.Http; + +namespace LearnwebNotifier.Push.Domain.Services +{ + public interface IClientService + { + public ServiceStatus Init(); + public HttpClient GetClient(); + public void Dispose(); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Services/IPushService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Services/IPushService.cs new file mode 100644 index 0000000..7884011 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Domain/Services/IPushService.cs @@ -0,0 +1,15 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Push.Domain.Models; + +namespace LearnwebNotifier.Push.Domain.Services +{ + public interface IPushService + { + public ServiceStatus Init(); + public ServiceStatus SendPush(PushNotification pushNotif); + public void Dispose(); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/LearnwebNotifier.Push.csproj b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/LearnwebNotifier.Push.csproj new file mode 100644 index 0000000..4d26890 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/LearnwebNotifier.Push.csproj @@ -0,0 +1,35 @@ + + + + netcoreapp3.1 + dotnet-LearnwebNotifier.Push-C173D3A2-C7CF-4FFE-9B29-866D45C325E2 + lwnotif-push + passlickdev.ico + Passlick Development + Passlick Development + WWU Learnweb Notifier + Copyright © Passlick Development 2020. All rights reserved. + en-US + 1.3.0.0 + 1.3.0.0 + 1.3.0 + Linux + + + + false + + + + + + + + + + + + + + + diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Program.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Program.cs new file mode 100644 index 0000000..8b62463 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Program.cs @@ -0,0 +1,44 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Library.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Sentry; +using static LearnwebNotifier.Library.Helper.HelperFunctions; + +namespace LearnwebNotifier.Push +{ + public class Program + { + + public static void Main(string[] args) + { + ConfigService configService = new ConfigService(); + + PrintConsoleHeader(); + if (configService.CheckConfigExistence() == ServiceStatus.NotFound || (args.Length > 0 && args[0] == "--config")) + configService.ConfigAssistant(); + else + { + if (configService.Config.Sentry.Activate) + { + using (SentrySdk.Init(configService.Config.Sentry.Dsn)) + CreateHostBuilder(args).Build().Run(); + } + else + CreateHostBuilder(args).Build().Run(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSystemd() + .UseWindowsService() + .ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + }); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Properties/launchSettings.json b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Properties/launchSettings.json new file mode 100644 index 0000000..d831fb8 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "LearnwebNotifier.Push": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker" + } + } +} \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Services/ClientService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Services/ClientService.cs new file mode 100644 index 0000000..9880c37 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Services/ClientService.cs @@ -0,0 +1,68 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Push.Domain.Services; +using Microsoft.Extensions.Logging; +using System; +using System.Net.Http; + +namespace LearnwebNotifier.Push.Services +{ + public class ClientService : IClientService + { + + private readonly ILoggerFactory loggerFactory; + private readonly ILogger logger; + public HttpClient client = new HttpClient(); + private readonly Uri baseUrl = new Uri("https://api.pushover.net/"); + + public ClientService(ILoggerFactory _loggerFactory) + { + loggerFactory = _loggerFactory; + logger = loggerFactory.CreateLogger("Push.ClientService"); + + // Basic settings + client.BaseAddress = baseUrl; + client.Timeout = TimeSpan.FromSeconds(20); + } + + + /// + /// Initializes the HttpClient with basic data and validates connection to Pushover API + /// + /// ServiceStatus Enum + public ServiceStatus Init() + { + try + { + var res = client.GetAsync(baseUrl); + var statusCode = res.Result; + if (statusCode.IsSuccessStatusCode) return ServiceStatus.OK; + else + { + logger.LogWarning($"Could not establish connection to Pushover API ({statusCode})"); + return ServiceStatus.Exception; + } + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return ServiceStatus.UnhandledException; + } + } + + + /// + /// Returns HttpClient + /// + /// HttpClient + public HttpClient GetClient() => client; + + + /// + /// Disposes HttpClient + /// + public void Dispose() => client.Dispose(); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Services/PushService.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Services/PushService.cs new file mode 100644 index 0000000..0af9dfd --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Services/PushService.cs @@ -0,0 +1,116 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Push.Domain.Models; +using LearnwebNotifier.Push.Domain.Services; +using Microsoft.Extensions.Logging; +using System; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace LearnwebNotifier.Push.Services +{ + public class PushService : IPushService + { + + private readonly ILoggerFactory loggerFactory; + private readonly ILogger logger; + private readonly JsonSerializerOptions jsonOptions; + private readonly ClientService clientService; + + public PushService(ILoggerFactory _loggerFactory) + { + loggerFactory = _loggerFactory; + logger = loggerFactory.CreateLogger("Push.PushService"); + + jsonOptions = new JsonSerializerOptions + { + IgnoreNullValues = true + }; + + clientService = new ClientService(loggerFactory); + } + + + /// + /// Initializes ClientService + /// + /// ServiceStatus + public ServiceStatus Init() => clientService.Init(); + + + /// + /// Send push notification using Pushover API + /// + /// Push notification object + /// ServiceStatus + public ServiceStatus SendPush(PushNotification pushNotif) + { + try + { + string pushJson = JsonSerializer + .Serialize(pushNotif, jsonOptions); + StringContent reqBody = new StringContent(pushJson, Encoding.UTF8, "application/json"); + var res = clientService + .client + .PostAsync("https://api.pushover.net/1/messages.json", reqBody); + + if (res.Result.IsSuccessStatusCode) return ServiceStatus.PushSent; + else + { + logger.LogWarning("Failed to send push notification, retry attempt..."); + return RetryPush(pushNotif); + } + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return ServiceStatus.UnhandledException; + } + } + + + /// + /// Retry push notification delivery after failed first attempt using Pushover API + /// + /// Push notification object + /// ServiceStatus + private ServiceStatus RetryPush(PushNotification pushNotif) + { + try + { + string pushJson = JsonSerializer + .Serialize(pushNotif, jsonOptions); + StringContent reqBody = new StringContent(pushJson, Encoding.UTF8, "application/json"); + + for (int i = 0; i < 5; i++) + { + var res = clientService + .client + .PostAsync("https://api.pushover.net/1/messages.json", reqBody); + + if (res.Result.IsSuccessStatusCode) return ServiceStatus.PushSent; + else logger.LogWarning($"Failed to send push notification on attempt #{i + 1}"); + Task.Delay(5000); + } + + logger.LogError("Failed to send push notification on final attempt"); + return ServiceStatus.PushFailed; + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return ServiceStatus.UnhandledException; + } + } + + + /// + /// Disposes ClientService connection + /// + public void Dispose() => clientService.Dispose(); + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Worker.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Worker.cs new file mode 100644 index 0000000..be33dfa --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/Worker.cs @@ -0,0 +1,182 @@ +using LearnwebNotifier.Library.Domain.Communication; +using LearnwebNotifier.Library.Domain.Models; +using LearnwebNotifier.Library.Services; +using LearnwebNotifier.Push.Domain.Models; +using LearnwebNotifier.Push.Services; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace LearnwebNotifier.Push +{ + public class Worker : BackgroundService + { + + private readonly ILoggerFactory loggerFactory; + private readonly ILogger logger; + private readonly ConfigService configService; + private readonly ActivityService activityService; + private readonly PushService pushService; + + private List courseIds; + private string pushoverToken; + private string pushoverUser; + private uint refreshDuration; + + public Worker() + { + configService = new ConfigService(); + + if (configService.Config.Sentry.Activate) + { + loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddConsole() + .AddSentry(o => + { + o.Debug = false; + o.Dsn = configService.Config.Sentry.Dsn; + o.MaxBreadcrumbs = 150; + o.MinimumBreadcrumbLevel = LogLevel.Information; + o.MinimumEventLevel = LogLevel.Warning; + }); + }); + } + else + { + loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddConsole(); + }); + } + logger = loggerFactory.CreateLogger("Push.Worker"); + + activityService = new ActivityService(loggerFactory); + pushService = new PushService(loggerFactory); + } + + + /// + /// StartAsync Service + /// + /// + /// + public override Task StartAsync(CancellationToken cancellationToken) + { + try + { + courseIds = configService.Config.Learnweb.Courses; + pushoverToken = configService.Config.Pushover.Token; + pushoverUser = configService.Config.Pushover.Recipient; + refreshDuration = configService.Config.Service.Refresh; + + ServiceStatus activityStatus = activityService.Init(); + ServiceStatus pushStatus = pushService.Init(); + + if (activityStatus == ServiceStatus.OK && pushStatus == ServiceStatus.OK) + return base.StartAsync(cancellationToken); + else + { + logger.LogError("Could not initialize 'Learnweb Notifier Service'"); + return base.StopAsync(cancellationToken); + } + } + catch (Exception ex) + { + logger.LogError(ex.Message); + return base.StopAsync(cancellationToken); + } + } + + + /// + /// StopAsync Service + /// + /// + /// + public override Task StopAsync(CancellationToken cancellationToken) + { + activityService.Dispose(); + pushService.Dispose(); + return base.StopAsync(cancellationToken); + } + + + /// + /// ExecuteAsync Service + /// + /// + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + logger.LogInformation("Executing 'Learnweb Notifier Service' at {time}", DateTimeOffset.Now); + try + { + foreach (string courseId in courseIds) + { + var res = activityService.FetchActivities(courseId); + if (res.Status == ServiceStatus.NewActivities) + { + logger.LogInformation("Sending push notifications..."); + foreach (Activity activity in res.Activities) + { + string courseAbbrev = activity.Course.Abbrev; + string activityType = activity.Type; + string activityName = activity.Name; + string activityUrl = activity.Url; + + if (activityName == null) + { + PushNotification notif = new PushNotification( + pushoverToken, + pushoverUser, + "New course activity", + $"There is new activity in {courseAbbrev}\n{activityType}", + activityUrl, + "View activity in Learnweb", + "0" + ); + pushService.SendPush(notif); + } + else + { + PushNotification notif = new PushNotification( + pushoverToken, + pushoverUser, + "New course activity", + $"There is new activity in {courseAbbrev}\n{activityType}: {activityName}", + activityUrl, + "View activity in Learnweb", + "0" + ); + pushService.SendPush(notif); + } + await Task.Delay(1000); + } + } + else if (res.Status >= ServiceStatus.Fail) logger.LogWarning("Worker execution aborted (could not fetch activities)"); + await Task.Delay(1000); + } + } + catch (Exception ex) + { + logger.LogError(ex.Message); + } + + logger.LogInformation("Finished execution 'Learnweb Notifier Service' at {time}", DateTimeOffset.Now); + logger.LogInformation("Next execution: {time}", DateTimeOffset.Now.AddMinutes(refreshDuration)); + await Task.Delay(TimeSpan.FromMinutes(refreshDuration), stoppingToken); + } + } + } +} + + +// (c) Passlick Development 2020. All rights reserved. diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/appsettings.Development.json b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/appsettings.json b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/appsettings.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/passlickdev.ico b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/passlickdev.ico new file mode 100644 index 0000000..d58ecc6 Binary files /dev/null and b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Push/passlickdev.ico differ diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Api/.gitkeep b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Api/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/LearnwebNotifier.Test.csproj b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/LearnwebNotifier.Test.csproj new file mode 100644 index 0000000..d54c5fd --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/LearnwebNotifier.Test.csproj @@ -0,0 +1,37 @@ + + + + netcoreapp3.1 + + false + + LearnwebNotifier.Test.Playground.MainPlayground + + lwnotif-test + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Library/.gitkeep b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Library/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Playground/MainPlayground.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Playground/MainPlayground.cs new file mode 100644 index 0000000..3a0aaa9 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Playground/MainPlayground.cs @@ -0,0 +1,19 @@ +namespace LearnwebNotifier.Test.Playground +{ + class MainPlayground + { + + + + static void Main(string[] args) + { + + // Code + + } + + + } + + +} \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Playground/ReadJsonTest.cs b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Playground/ReadJsonTest.cs new file mode 100644 index 0000000..342564a --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Playground/ReadJsonTest.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; + +namespace LearnwebNotifier.Test.Playground +{ + class ReadJsonTest + { + + static void Main(string[] args) + { + List courses = new List(); + + courses.Add("43023"); // CACS-2020_1 + courses.Add("42869"); // CSOS-2020 + courses.Add("43472"); // Da-2020_1 + courses.Add("43562"); // PM-2020_1 + courses.Add("43547"); // Si-2020_1 + courses.Add("27915"); // WI-Koord + + string jsonString = JsonSerializer.Serialize(courses); + Console.WriteLine(jsonString); + File.WriteAllText("courses.json", jsonString); + + + string unmarshalString; + List uCourses; + + unmarshalString = File.ReadAllText("courses.json"); + uCourses = JsonSerializer.Deserialize>(unmarshalString); + Console.WriteLine(unmarshalString); + foreach (string s in uCourses) + { + Console.WriteLine(s); + } + + } + + } +} diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Properties/launchSettings.json b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Properties/launchSettings.json new file mode 100644 index 0000000..607fb45 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "LearnwebNotifier.Test": { + "commandName": "Project", + "environmentVariables": { + "PUSHOVER_TOKEN": "aan6h4kab2sucuck2g264rpxpkq76x", + "PUSHOVER_USR": "ubgwi2c91nzsw8s87bj2gvopt237dr", + "LEARNWEB_PWD": "test", + "LEARNWEB_USR": "n_pass02" + } + } + } +} \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Push/.gitkeep b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Push/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/ASSIGNMENT_42869_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/ASSIGNMENT_42869_1 new file mode 100644 index 0000000..bfe83ba --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/ASSIGNMENT_42869_1 @@ -0,0 +1,2075 @@ + + + + Course: Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger + + + + + + + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + +
+
+ + +
+

Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger

+
+

Topic outline

+ + +
+
+
+ +
+
+ +
+
+
+ + + +
+
+ + + +
You are logged in as John Doe (Log out)
+
+ + + + + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FOLDER_43472_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FOLDER_43472_1 new file mode 100644 index 0000000..2648134 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FOLDER_43472_1 @@ -0,0 +1,2038 @@ + + + + Course: Datenanalyse SS 2020, Ingolf Terveer + + + + + + + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + +
+
+ + +
+

Datenanalyse SS 2020, Ingolf Terveer

+
+

Topic outline

  • General

  • Topic 2

    • Topic 3

      • Topic 4

        • Topic 5

          + + +
          +
          +
          + +
          +
          + +
          +
          +
          + + + +
          +
          + + + +
          You are logged in as John Doe (Log out)
          +
          + + + + + + + + + + +
          +
          +
          +
          + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FORUM_42869_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FORUM_42869_1 new file mode 100644 index 0000000..c4b744d --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FORUM_42869_1 @@ -0,0 +1,2058 @@ + + + + Course: Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger + + + + + + + + + + +
          + + + + + + + + +
          +
          + + +
          + + + + + + +
          +
          + + +
          +

          Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger

          +
          +

          Topic outline

          + + +
          +
          +
          + +
          +
          + +
          +
          +
          + + + +
          +
          + + + +
          You are logged in as John Doe (Log out)
          +
          + + + + + + + + + + +
          +
          +
          +
          + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FORUM_43023_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FORUM_43023_1 new file mode 100644 index 0000000..0e56d3f --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/FORUM_43023_1 @@ -0,0 +1,2018 @@ + + + + Course: Communication and Collaboration Systems 2020 + + + + + + + + + + +
          + + + + + + + + +
          +
          + + +
          + + + + + + +
          +
          + + +
          +

          Communication and Collaboration Systems 2020

          +
          +

          Topic outline

          • General

          • This topic

            Kick-Off Session

          • Part 1: Technological Foundations of Communication Systems

            Instructor: Jens Lechtenbörger

            +

            For part 1, each student must work through self-study presentations ahead of class meetings. During our meetings, we + discuss students' questions as well as review questions. In parallel to part 1, students work in groups to collaborate on answers to selected review questions in a Git repository.

            +

            The self-study presentations are maintained as Open Educational Resources in a public Git repository. Any form of communication and collaboration (feedback, suggestions, improvements) is very welcome!

          + + +
          +
          +
          + +
          +
          + +
          +
          +
          + + + +
          +
          + + + +
          You are logged in as John Doe (Log out)
          +
          + + + + + + + + + + +
          +
          +
          +
          + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_42869_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_42869_1 new file mode 100644 index 0000000..fc50c05 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_42869_1 @@ -0,0 +1,2075 @@ + + + + Course: Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger + + + + + + + + + + +
          + + + + + + + + +
          +
          + + +
          + + + + + + +
          +
          + + +
          +

          Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger

          +
          +

          Topic outline

          + + +
          +
          +
          + +
          +
          + +
          +
          +
          + + + +
          +
          + + + +
          You are logged in as John Doe (Log out)
          +
          + + + + + + + + + + +
          +
          +
          +
          + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43023_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43023_1 new file mode 100644 index 0000000..29dfa79 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43023_1 @@ -0,0 +1,2043 @@ + + + + Course: Communication and Collaboration Systems 2020 + + + + + + + + + + +
          + + + + + + + + +
          +
          + + +
          + + + + + + +
          +
          + + +
          +

          Communication and Collaboration Systems 2020

          +
          +

          Topic outline

          • General

          • Kick-Off Session

          • This topic

            Part 1: Technological Foundations of Communication Systems

            Instructor: Jens Lechtenbörger

            +

            For part 1, each student must work through self-study presentations ahead of class meetings. During our meetings, we + discuss students' questions as well as review questions. In parallel to part 1, students work in groups to collaborate on answers to selected review questions in a Git repository.

            +

            The self-study presentations are maintained as Open Educational Resources in a public Git repository. Any form of communication and collaboration (feedback, suggestions, improvements) is very welcome!

          • Part 2: Theoretical Foundations of Communication Systems

            Instructor: Russell Haines

            For part 2, each student must read the referenced articles and complete the tasks about the different topics. In place of attending lectures during this two-week period, each student must participate in one session of the online "crisis response" exercise.

          • Part 3: Outcomes of Using Communication Systems

            +

            Instructor: Russell Haines

            +

            By 8:00 PM on the day before the session, each student not in a group presenting that day must read the referenced articles and submit at least one thoughtful question or comment via Learnweb about issues that you would like + to raise as part of the class discussion. 

            +

            +

            By 8:00 PM on the day before the session when they are presenting, each group must upload their presentation slides via Learnweb. Groups will normally give their presentations at the beginning of the lecture.

          • Part 4: Social Organizations Using Communication Systems

            Instructor: Russell Haines

            By 8:00 PM on the day before the session, each student not in a group presenting that day must read the referenced articles and submit at least one thoughtful question or comment via Learnweb about issues that you would like to raise as part of the class discussion. 

            By 8:00 PM on the day before the session when they are presenting, each group must upload their presentation slides via Learnweb. Groups will normally give their presentations at the beginning of the lecture.

          + + +
          +
          +
          + +
          +
          + +
          +
          +
          + + + +
          +
          + + + +
          You are logged in as John Doe (Log out)
          +
          + + + + + + + + + + +
          +
          +
          +
          + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43472_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43472_1 new file mode 100644 index 0000000..40aa896 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43472_1 @@ -0,0 +1,2038 @@ + + + + Course: Datenanalyse SS 2020, Ingolf Terveer + + + + + + + + + + +
          + + + + + + + + +
          +
          + + +
          + + + + + + +
          +
          + + +
          +

          Datenanalyse SS 2020, Ingolf Terveer

          +
          +

          Topic outline

          • General

          • Topic 2

            • Topic 3

              • Topic 4

                • Topic 5

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43547_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43547_1 new file mode 100644 index 0000000..25c0a96 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43547_1 @@ -0,0 +1,2029 @@ + + + + Course: Simulation SS 2020, Stephan Meisel, Tanja Merfeld + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Simulation SS 2020, Stephan Meisel, Tanja Merfeld

                  +
                  +

                  Topic outline

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43562_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43562_1 new file mode 100644 index 0000000..5ecb142 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NOACTIVITY_43562_1 @@ -0,0 +1,2229 @@ + + + + Course: Project Management SS 2020 + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Project Management SS 2020

                  +
                  +

                  Topic outline

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_42869_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_42869_1 new file mode 100644 index 0000000..603945a --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_42869_1 @@ -0,0 +1,2050 @@ + + + + Course: Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger

                  +
                  +

                  Topic outline

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_42869_2 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_42869_2 new file mode 100644 index 0000000..91c26b9 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_42869_2 @@ -0,0 +1,2050 @@ + + + + Course: Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Computer Structures and Operating Systems SS 2020, Gottfried Vossen, Jens Lechtenbörger

                  +
                  +

                  Topic outline

                  • General Information

                    Due to the Corona Virus, classes will not start with a traditional lecture on April 9. See here for University information on the Corona Virus.

                    Do not worry, you did not miss anything in this course yet. Further information will be posted in the course forum (whose messages are sent out via e-mail to course subscribers). Until then, stay safe and all the best!

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_1 new file mode 100644 index 0000000..ad0275a --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_1 @@ -0,0 +1,1989 @@ + + + + Course: Communication and Collaboration Systems 2020 + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Communication and Collaboration Systems 2020

                  +
                  +

                  Topic outline

                  • General

                  • This topic

                    Kick-Off Session

                  • Part 1: Technological Foundations of Communication Systems

                    Instructor: Jens Lechtenbörger

                    +

                    For part 1, each student must work through self-study presentations ahead of class meetings. During our meetings, we + discuss students' questions as well as review questions. In parallel to part 1, students work in groups to collaborate on answers to selected review questions in a Git repository.

                    +

                    The self-study presentations are maintained as Open Educational Resources in a public Git repository. Any form of communication and collaboration (feedback, suggestions, improvements) is very welcome!

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_2 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_2 new file mode 100644 index 0000000..09ad39a --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_2 @@ -0,0 +1,2018 @@ + + + + Course: Communication and Collaboration Systems 2020 + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Communication and Collaboration Systems 2020

                  +
                  +

                  Topic outline

                  • General

                  • This topic

                    Kick-Off Session

                  • Part 1: Technological Foundations of Communication Systems

                    Instructor: Jens Lechtenbörger

                    +

                    For part 1, each student must work through self-study presentations ahead of class meetings. During our meetings, we + discuss students' questions as well as review questions. In parallel to part 1, students work in groups to collaborate on answers to selected review questions in a Git repository.

                    +

                    The self-study presentations are maintained as Open Educational Resources in a public Git repository. Any form of communication and collaboration (feedback, suggestions, improvements) is very welcome!

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_3 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_3 new file mode 100644 index 0000000..cbfa8c5 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_3 @@ -0,0 +1,2024 @@ + + + + Course: Communication and Collaboration Systems 2020 + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Communication and Collaboration Systems 2020

                  +
                  +

                  Topic outline

                  • General

                  • This topic

                    Kick-Off Session

                  • Part 1: Technological Foundations of Communication Systems

                    Instructor: Jens Lechtenbörger

                    +

                    For part 1, each student must work through self-study presentations ahead of class meetings. During our meetings, we + discuss students' questions as well as review questions. In parallel to part 1, students work in groups to collaborate on answers to selected review questions in a Git repository.

                    +

                    The self-study presentations are maintained as Open Educational Resources in a public Git repository. Any form of communication and collaboration (feedback, suggestions, improvements) is very welcome!

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_4 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_4 new file mode 100644 index 0000000..2de60fd --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43023_4 @@ -0,0 +1,2043 @@ + + + + Course: Communication and Collaboration Systems 2020 + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Communication and Collaboration Systems 2020

                  +
                  +

                  Topic outline

                  • General

                  • Kick-Off Session

                  • This topic

                    Part 1: Technological Foundations of Communication Systems

                    Instructor: Jens Lechtenbörger

                    +

                    For part 1, each student must work through self-study presentations ahead of class meetings. During our meetings, we + discuss students' questions as well as review questions. In parallel to part 1, students work in groups to collaborate on answers to selected review questions in a Git repository.

                    +

                    The self-study presentations are maintained as Open Educational Resources in a public Git repository. Any form of communication and collaboration (feedback, suggestions, improvements) is very welcome!

                  • Part 2: Theoretical Foundations of Communication Systems

                    Instructor: Russell Haines

                    For part 2, each student must read the referenced articles and complete the tasks about the different topics. In place of attending lectures during this two-week period, each student must participate in one session of the online "crisis response" exercise.

                  • Part 3: Outcomes of Using Communication Systems

                    +

                    Instructor: Russell Haines

                    +

                    By 8:00 PM on the day before the session, each student not in a group presenting that day must read the referenced articles and submit at least one thoughtful question or comment via Learnweb about issues that you would like + to raise as part of the class discussion. 

                    +

                    +

                    By 8:00 PM on the day before the session when they are presenting, each group must upload their presentation slides via Learnweb. Groups will normally give their presentations at the beginning of the lecture.

                  • Part 4: Social Organizations Using Communication Systems

                    Instructor: Russell Haines

                    By 8:00 PM on the day before the session, each student not in a group presenting that day must read the referenced articles and submit at least one thoughtful question or comment via Learnweb about issues that you would like to raise as part of the class discussion. 

                    By 8:00 PM on the day before the session when they are presenting, each group must upload their presentation slides via Learnweb. Groups will normally give their presentations at the beginning of the lecture.

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43562_1 b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43562_1 new file mode 100644 index 0000000..6e4f898 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/LearnwebNotifier.Test/Resources/Course Pages/NORMAL_43562_1 @@ -0,0 +1,2024 @@ + + + + Course: Project Management SS 2020 + + + + + + + + + + +
                  + + + + + + + + +
                  +
                  + + +
                  + + + + + + +
                  +
                  + + +
                  +

                  Project Management SS 2020

                  +
                  +

                  Topic outline

                  + + +
                  +
                  +
                  + +
                  +
                  + +
                  +
                  +
                  + + + +
                  +
                  + + + +
                  You are logged in as John Doe (Log out)
                  +
                  + + + + + + + + + + +
                  +
                  +
                  +
                  + + + + + + \ No newline at end of file diff --git a/src/PasslickDevelopment.LearnwebNotifier/PasslickDevelopment.LearnwebNotifier.sln b/src/PasslickDevelopment.LearnwebNotifier/PasslickDevelopment.LearnwebNotifier.sln new file mode 100644 index 0000000..3437747 --- /dev/null +++ b/src/PasslickDevelopment.LearnwebNotifier/PasslickDevelopment.LearnwebNotifier.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30002.166 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearnwebNotifier.Library", "LearnwebNotifier.Library\LearnwebNotifier.Library.csproj", "{60CA46E5-BA20-440C-B726-2B9D31C94607}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearnwebNotifier.Test", "LearnwebNotifier.Test\LearnwebNotifier.Test.csproj", "{D9FEDA03-ECF2-492F-A27F-8A7DAC1A150B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearnwebNotifier.Push", "LearnwebNotifier.Push\LearnwebNotifier.Push.csproj", "{D1129973-9489-4CDF-A9E4-EBC7B7121547}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {60CA46E5-BA20-440C-B726-2B9D31C94607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60CA46E5-BA20-440C-B726-2B9D31C94607}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60CA46E5-BA20-440C-B726-2B9D31C94607}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60CA46E5-BA20-440C-B726-2B9D31C94607}.Release|Any CPU.Build.0 = Release|Any CPU + {D9FEDA03-ECF2-492F-A27F-8A7DAC1A150B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9FEDA03-ECF2-492F-A27F-8A7DAC1A150B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9FEDA03-ECF2-492F-A27F-8A7DAC1A150B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9FEDA03-ECF2-492F-A27F-8A7DAC1A150B}.Release|Any CPU.Build.0 = Release|Any CPU + {D1129973-9489-4CDF-A9E4-EBC7B7121547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1129973-9489-4CDF-A9E4-EBC7B7121547}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1129973-9489-4CDF-A9E4-EBC7B7121547}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1129973-9489-4CDF-A9E4-EBC7B7121547}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E4EDC382-A423-42A3-9ED8-7BD976FEE702} + EndGlobalSection +EndGlobal