From a97669710e59e868b8f8ca9b0a6d8b1bb220169e Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Thu, 2 Mar 2023 13:20:54 -0600 Subject: [PATCH 01/33] Init C# program --- .gitignore | 402 +++++++++++++++++- .../.idea/.gitignore | 13 + .../.idea/indexLayout.xml | 8 + .../.idea/vcs.xml | 6 + .idea/inspectionProfiles/Project_Default.xml | 5 + BookmarkSync.CLI/BookmarkSync.CLI.csproj | 10 + BookmarkSync.CLI/Program.cs | 11 + mastodon-bookmark-sync.sln | 22 + 8 files changed, 471 insertions(+), 6 deletions(-) create mode 100644 .idea/.idea.mastodon-bookmark-sync/.idea/.gitignore create mode 100644 .idea/.idea.mastodon-bookmark-sync/.idea/indexLayout.xml create mode 100644 .idea/.idea.mastodon-bookmark-sync/.idea/vcs.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 BookmarkSync.CLI/BookmarkSync.CLI.csproj create mode 100644 BookmarkSync.CLI/Program.cs create mode 100644 mastodon-bookmark-sync.sln diff --git a/.gitignore b/.gitignore index 48dc7d8..8dd4607 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,398 @@ -# macOS crap -.DS_Store +## 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/main/VisualStudio.gitignore -# Compiled binary -mastodon-bookmark-sync +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates -# Config files / secrets -config.yaml \ No newline at end of file +# 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/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# 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/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.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 + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# 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 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# 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/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml \ No newline at end of file diff --git a/.idea/.idea.mastodon-bookmark-sync/.idea/.gitignore b/.idea/.idea.mastodon-bookmark-sync/.idea/.gitignore new file mode 100644 index 0000000..de76ff1 --- /dev/null +++ b/.idea/.idea.mastodon-bookmark-sync/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +/.idea.mastodon-bookmark-sync.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.mastodon-bookmark-sync/.idea/indexLayout.xml b/.idea/.idea.mastodon-bookmark-sync/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.mastodon-bookmark-sync/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.mastodon-bookmark-sync/.idea/vcs.xml b/.idea/.idea.mastodon-bookmark-sync/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.mastodon-bookmark-sync/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..e13319d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,5 @@ + + + + diff --git a/BookmarkSync.CLI/BookmarkSync.CLI.csproj b/BookmarkSync.CLI/BookmarkSync.CLI.csproj new file mode 100644 index 0000000..82b88b8 --- /dev/null +++ b/BookmarkSync.CLI/BookmarkSync.CLI.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + disable + enable + + + diff --git a/BookmarkSync.CLI/Program.cs b/BookmarkSync.CLI/Program.cs new file mode 100644 index 0000000..0a52f26 --- /dev/null +++ b/BookmarkSync.CLI/Program.cs @@ -0,0 +1,11 @@ +using System; + +namespace BookmarkSync.CLI; + +public class Program +{ + public static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } +} diff --git a/mastodon-bookmark-sync.sln b/mastodon-bookmark-sync.sln new file mode 100644 index 0000000..6d66988 --- /dev/null +++ b/mastodon-bookmark-sync.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B696875F-E32C-4227-920E-4AFD03B7A5FC}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + .gitignore = .gitignore + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.CLI", "BookmarkSync.CLI\BookmarkSync.CLI.csproj", "{696935C2-0383-4409-A2B4-B80A8C905DD2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {696935C2-0383-4409-A2B4-B80A8C905DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {696935C2-0383-4409-A2B4-B80A8C905DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {696935C2-0383-4409-A2B4-B80A8C905DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {696935C2-0383-4409-A2B4-B80A8C905DD2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 4637fcd4adbbf8614952576e9562c25c09c3a0a6 Mon Sep 17 00:00:00 2001 From: prplecake Date: Thu, 2 Mar 2023 16:00:31 -0600 Subject: [PATCH 02/33] Add ConfigManager and related config objects --- .gitignore | 3 ++ BookmarkSync.CLI/BookmarkSync.CLI.csproj | 15 ++++++ BookmarkSync.CLI/Program.cs | 39 ++++++++++++-- BookmarkSync.CLI/appsettings.Example.json | 15 ++++++ BookmarkSync.Core/BookmarkSync.Core.csproj | 16 ++++++ .../Configuration/ConfigManager.cs | 52 +++++++++++++++++++ BookmarkSync.Core/Entities/Account.cs | 8 +++ BookmarkSync.Core/Entities/Bookmark.cs | 10 ++++ BookmarkSync.Core/Entities/Config/App.cs | 10 ++++ .../Entities/Config/Bookmarking.cs | 7 +++ .../Entities/Config/ConfigurationBase.cs | 8 +++ BookmarkSync.Core/Entities/Config/Instance.cs | 10 ++++ BookmarkSync.Core/Entities/Config/Pinboard.cs | 6 +++ mastodon-bookmark-sync.sln | 6 +++ 14 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 BookmarkSync.CLI/appsettings.Example.json create mode 100644 BookmarkSync.Core/BookmarkSync.Core.csproj create mode 100644 BookmarkSync.Core/Configuration/ConfigManager.cs create mode 100644 BookmarkSync.Core/Entities/Account.cs create mode 100644 BookmarkSync.Core/Entities/Bookmark.cs create mode 100644 BookmarkSync.Core/Entities/Config/App.cs create mode 100644 BookmarkSync.Core/Entities/Config/Bookmarking.cs create mode 100644 BookmarkSync.Core/Entities/Config/ConfigurationBase.cs create mode 100644 BookmarkSync.Core/Entities/Config/Instance.cs create mode 100644 BookmarkSync.Core/Entities/Config/Pinboard.cs diff --git a/.gitignore b/.gitignore index 8dd4607..35bd12e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ ## ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore +# Secrets +appsettings.json + # User-specific files *.rsuser *.suo diff --git a/BookmarkSync.CLI/BookmarkSync.CLI.csproj b/BookmarkSync.CLI/BookmarkSync.CLI.csproj index 82b88b8..5bce381 100644 --- a/BookmarkSync.CLI/BookmarkSync.CLI.csproj +++ b/BookmarkSync.CLI/BookmarkSync.CLI.csproj @@ -7,4 +7,19 @@ enable + + + + + + + + + + + + Always + + + diff --git a/BookmarkSync.CLI/Program.cs b/BookmarkSync.CLI/Program.cs index 0a52f26..f8b57d3 100644 --- a/BookmarkSync.CLI/Program.cs +++ b/BookmarkSync.CLI/Program.cs @@ -1,11 +1,44 @@ using System; +using System.Threading.Tasks; +using BookmarkSync.Core.Configuration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; namespace BookmarkSync.CLI; public class Program { - public static void Main(string[] args) + private static IConfiguration Configuration; + public async static Task Main(string[] args) { - Console.WriteLine("Hello, World!"); + Configuration = SetupConfiguration(args); + IConfigManager configManager = new ConfigManager(Configuration); + + using var host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + services.AddSingleton(configManager); + }) + .Build(); + await host.StartAsync(); + var lifetime = host.Services.GetRequiredService(); + + // Do work here + Console.WriteLine("Hello, world!"); + + // Shutdown + configManager.SaveToFile(); + + lifetime.StopApplication(); + await host.WaitForShutdownAsync(); + } + private static IConfiguration SetupConfiguration(string[] args) + { + return new ConfigurationBuilder() + .AddJsonFile("appsettings.json", false, true) + .AddEnvironmentVariables() + .AddCommandLine(args) + .Build(); } -} +} \ No newline at end of file diff --git a/BookmarkSync.CLI/appsettings.Example.json b/BookmarkSync.CLI/appsettings.Example.json new file mode 100644 index 0000000..df410ff --- /dev/null +++ b/BookmarkSync.CLI/appsettings.Example.json @@ -0,0 +1,15 @@ +{ + "Instances": [ + { + "Uri": "https://compostintraining.club", + "AccessToken": "", + "DeleteBookmarks": true + } + ], + "App": { + "Bookmarking": { + "Service": "Pinboard", + "ApiToken": "" + } + } +} \ No newline at end of file diff --git a/BookmarkSync.Core/BookmarkSync.Core.csproj b/BookmarkSync.Core/BookmarkSync.Core.csproj new file mode 100644 index 0000000..e47591a --- /dev/null +++ b/BookmarkSync.Core/BookmarkSync.Core.csproj @@ -0,0 +1,16 @@ + + + + net7.0 + disable + enable + + + + + + + + + + diff --git a/BookmarkSync.Core/Configuration/ConfigManager.cs b/BookmarkSync.Core/Configuration/ConfigManager.cs new file mode 100644 index 0000000..b0d8ec0 --- /dev/null +++ b/BookmarkSync.Core/Configuration/ConfigManager.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using BookmarkSync.Core.Entities.Config; +using CiT.Common.Exceptions; +using Microsoft.Extensions.Configuration; + +namespace BookmarkSync.Core.Configuration; + +public interface IConfigManager +{ + IConfiguration Configuration { get; set; } + Instance[] Instances { get; set; } + App App { get; set; } + string GetConfigValue(string key); + void SaveToFile(); +} +public class ConfigManager : IConfigManager +{ + public ConfigManager( + IConfiguration configuration) + { + Configuration = configuration; + List instance = Configuration.GetSection("Instances").Get>(); + App app = Configuration.GetSection("App").Get(); + var instances = Configuration.GetSection("Instances").Get(); + App = app; + + if (!App.IsValid()) + { + throw new InvalidConfigurationException(); + } + } + public IConfiguration Configuration { get; set; } + public Instance[] Instances { get; set; } + public App App { get; set; } + public string GetConfigValue(string key) + { + string? value = Configuration[key]; + if (string.IsNullOrWhiteSpace(value)) + { + throw new ConfigurationErrorsException( + $"An invalid key was provided: key: {key}"); + } + return value; + } + public void SaveToFile() + { + Console.WriteLine(Directory.GetCurrentDirectory()); + } +} diff --git a/BookmarkSync.Core/Entities/Account.cs b/BookmarkSync.Core/Entities/Account.cs new file mode 100644 index 0000000..09a5515 --- /dev/null +++ b/BookmarkSync.Core/Entities/Account.cs @@ -0,0 +1,8 @@ +namespace BookmarkSync.Core.Entities; + +public class Account +{ + public string Name { get; set; } + /// + public override string ToString() => Name; +} diff --git a/BookmarkSync.Core/Entities/Bookmark.cs b/BookmarkSync.Core/Entities/Bookmark.cs new file mode 100644 index 0000000..2707cdf --- /dev/null +++ b/BookmarkSync.Core/Entities/Bookmark.cs @@ -0,0 +1,10 @@ +namespace BookmarkSync.Core.Entities; + +public class Bookmark +{ + public Account Account { get; set; } + public string Content { get; set; } + public string Id { get; set; } + public string Uri { get; set; } + public string Visibility { get; set; } +} diff --git a/BookmarkSync.Core/Entities/Config/App.cs b/BookmarkSync.Core/Entities/Config/App.cs new file mode 100644 index 0000000..669ae68 --- /dev/null +++ b/BookmarkSync.Core/Entities/Config/App.cs @@ -0,0 +1,10 @@ +using System; +using CiT.Common.Attributes; + +namespace BookmarkSync.Core.Entities.Config; + +public class App : ConfigurationBase +{ + [ConfigRequired] public Bookmarking Bookmarking { get; set; } + public DateTime LastSynced { get; set; } +} diff --git a/BookmarkSync.Core/Entities/Config/Bookmarking.cs b/BookmarkSync.Core/Entities/Config/Bookmarking.cs new file mode 100644 index 0000000..969f398 --- /dev/null +++ b/BookmarkSync.Core/Entities/Config/Bookmarking.cs @@ -0,0 +1,7 @@ +namespace BookmarkSync.Core.Entities.Config; + +public class Bookmarking : ConfigurationBase +{ + public string Service { get; set; } + public string ApiToken { get; set; } +} diff --git a/BookmarkSync.Core/Entities/Config/ConfigurationBase.cs b/BookmarkSync.Core/Entities/Config/ConfigurationBase.cs new file mode 100644 index 0000000..b699fdd --- /dev/null +++ b/BookmarkSync.Core/Entities/Config/ConfigurationBase.cs @@ -0,0 +1,8 @@ +using CiT.Common.Validations; + +namespace BookmarkSync.Core.Entities.Config; + +public class ConfigurationBase +{ + public bool IsValid() => !this.IsAnyNullOrEmpty(); +} diff --git a/BookmarkSync.Core/Entities/Config/Instance.cs b/BookmarkSync.Core/Entities/Config/Instance.cs new file mode 100644 index 0000000..0b592cb --- /dev/null +++ b/BookmarkSync.Core/Entities/Config/Instance.cs @@ -0,0 +1,10 @@ +namespace BookmarkSync.Core.Entities.Config; + +public class Instance : ConfigurationBase +{ + public string AccessToken { get; set; } + public bool DeleteBookmarks { get; set; } + public string Uri { get; set; } + /// + public override string ToString() => Uri; +} diff --git a/BookmarkSync.Core/Entities/Config/Pinboard.cs b/BookmarkSync.Core/Entities/Config/Pinboard.cs new file mode 100644 index 0000000..1aa1551 --- /dev/null +++ b/BookmarkSync.Core/Entities/Config/Pinboard.cs @@ -0,0 +1,6 @@ +namespace BookmarkSync.Core.Entities.Config; + +public class Pinboard : ConfigurationBase +{ + +} diff --git a/mastodon-bookmark-sync.sln b/mastodon-bookmark-sync.sln index 6d66988..29992c0 100644 --- a/mastodon-bookmark-sync.sln +++ b/mastodon-bookmark-sync.sln @@ -8,6 +8,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.CLI", "BookmarkSync.CLI\BookmarkSync.CLI.csproj", "{696935C2-0383-4409-A2B4-B80A8C905DD2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Core", "BookmarkSync.Core\BookmarkSync.Core.csproj", "{0391DE45-C449-4AEF-93A6-808261868807}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {696935C2-0383-4409-A2B4-B80A8C905DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {696935C2-0383-4409-A2B4-B80A8C905DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU {696935C2-0383-4409-A2B4-B80A8C905DD2}.Release|Any CPU.Build.0 = Release|Any CPU + {0391DE45-C449-4AEF-93A6-808261868807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0391DE45-C449-4AEF-93A6-808261868807}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0391DE45-C449-4AEF-93A6-808261868807}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0391DE45-C449-4AEF-93A6-808261868807}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From eb8a3ee24b4f758b4fbca07c7c0fec205f798d35 Mon Sep 17 00:00:00 2001 From: prplecake Date: Thu, 2 Mar 2023 16:05:25 -0600 Subject: [PATCH 03/33] Init bookmarking services --- BookmarkSync.CLI/BookmarkSync.CLI.csproj | 1 + BookmarkSync.CLI/Program.cs | 6 +++++- .../Interfaces/IBookmarkingService.cs | 6 ++++++ .../BookmarkSync.Infrastructure.csproj | 17 +++++++++++++++++ .../Services/PinboardBookmarkingService.cs | 8 ++++++++ mastodon-bookmark-sync.sln | 6 ++++++ 6 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 BookmarkSync.Core/Interfaces/IBookmarkingService.cs create mode 100644 BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj create mode 100644 BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs diff --git a/BookmarkSync.CLI/BookmarkSync.CLI.csproj b/BookmarkSync.CLI/BookmarkSync.CLI.csproj index 5bce381..b964d88 100644 --- a/BookmarkSync.CLI/BookmarkSync.CLI.csproj +++ b/BookmarkSync.CLI/BookmarkSync.CLI.csproj @@ -14,6 +14,7 @@ + diff --git a/BookmarkSync.CLI/Program.cs b/BookmarkSync.CLI/Program.cs index f8b57d3..0cb0073 100644 --- a/BookmarkSync.CLI/Program.cs +++ b/BookmarkSync.CLI/Program.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; using BookmarkSync.Core.Configuration; +using BookmarkSync.Core.Interfaces; +using BookmarkSync.Infrastructure.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -19,6 +21,8 @@ public async static Task Main(string[] args) .ConfigureServices(services => { services.AddSingleton(configManager); + + services.AddTransient(); }) .Build(); await host.StartAsync(); @@ -41,4 +45,4 @@ private static IConfiguration SetupConfiguration(string[] args) .AddCommandLine(args) .Build(); } -} \ No newline at end of file +} diff --git a/BookmarkSync.Core/Interfaces/IBookmarkingService.cs b/BookmarkSync.Core/Interfaces/IBookmarkingService.cs new file mode 100644 index 0000000..8ec9389 --- /dev/null +++ b/BookmarkSync.Core/Interfaces/IBookmarkingService.cs @@ -0,0 +1,6 @@ +namespace BookmarkSync.Core.Interfaces; + +public interface IBookmarkingService +{ + +} diff --git a/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj b/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj new file mode 100644 index 0000000..9d63ce0 --- /dev/null +++ b/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + disable + enable + + + + + + + + + + + diff --git a/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs b/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs new file mode 100644 index 0000000..767cedf --- /dev/null +++ b/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs @@ -0,0 +1,8 @@ +using BookmarkSync.Core.Interfaces; + +namespace BookmarkSync.Infrastructure.Services; + +public class PinboardBookmarkingService : IBookmarkingService +{ + +} diff --git a/mastodon-bookmark-sync.sln b/mastodon-bookmark-sync.sln index 29992c0..b92a263 100644 --- a/mastodon-bookmark-sync.sln +++ b/mastodon-bookmark-sync.sln @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.CLI", "Bookmar EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Core", "BookmarkSync.Core\BookmarkSync.Core.csproj", "{0391DE45-C449-4AEF-93A6-808261868807}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Infrastructure", "BookmarkSync.Infrastructure\BookmarkSync.Infrastructure.csproj", "{D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {0391DE45-C449-4AEF-93A6-808261868807}.Debug|Any CPU.Build.0 = Debug|Any CPU {0391DE45-C449-4AEF-93A6-808261868807}.Release|Any CPU.ActiveCfg = Release|Any CPU {0391DE45-C449-4AEF-93A6-808261868807}.Release|Any CPU.Build.0 = Release|Any CPU + {D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From db591ad60bba63a12e214c7a4a8f0801b3f0c0b8 Mon Sep 17 00:00:00 2001 From: prplecake Date: Thu, 2 Mar 2023 16:28:26 -0600 Subject: [PATCH 04/33] Remove Go --- .github/workflows/codeql-analysis.yml | 71 ---------- .github/workflows/go.yml | 25 ---- .github/workflows/release.yml | 26 ---- bookmarks.go | 44 ------ config.example.yaml | 8 -- config.go | 75 ----------- go.mod | 14 -- go.sum | 22 --- mastodon-bookmark-sync.go | 184 -------------------------- 9 files changed, 469 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml delete mode 100644 .github/workflows/go.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 bookmarks.go delete mode 100644 config.example.yaml delete mode 100644 config.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 mastodon-bookmark-sync.go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 24d83e9..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '25 23 * * 6' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index ac4b2d7..0000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Go - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.17 - - - name: Build - run: go build -v ./... - - #- name: Test - # run: go test -v ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index d77ddab..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Release Go Binary - -on: - release: - types: [published] - workflow_dispatch: - -jobs: - releases-matrix: - name: Release Go Binary - runs-on: ubuntu-latest - strategy: - matrix: - goos: [linux, windows, darwin] - goarch: [amd64, arm64] - exclude: - - goarch: arm64 - goos: windows - steps: - - uses: actions/checkout@v2 - - uses: wangyoucao577/go-release-action@v1.32 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - goos: ${{ matrix.goos }} - goarch: ${{ matrix.goarch }} - extra_files: LICENSE README.md config.example.yaml diff --git a/bookmarks.go b/bookmarks.go deleted file mode 100644 index 03a96df..0000000 --- a/bookmarks.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "log" -) - -type bookmark struct { - ID string - URL string - Content string - Account account - Visibility string -} - -type account struct { - Acct string -} - -func processBookmarks(data []byte) ([]bookmark, error) { - var rawBookmarks []bookmark - if err := json.Unmarshal(data, &rawBookmarks); err != nil { - return nil, err - } - if debug { - var jsonResult bytes.Buffer - _ = json.Indent(&jsonResult, data, "", " ") - log.Print(jsonResult.String()) - } - - var bookmarks []bookmark - for _, bookmark := range rawBookmarks { - if bookmark.Visibility == "private" || bookmark.Visibility == "direct" { - if debug { - log.Printf("Removing bookmark %s due to visibility setting", bookmark.URL) - } - } else { - bookmarks = append(bookmarks, bookmark) - } - } - - return bookmarks, nil -} diff --git a/config.example.yaml b/config.example.yaml deleted file mode 100644 index 1baa088..0000000 --- a/config.example.yaml +++ /dev/null @@ -1,8 +0,0 @@ -pinboard: - # https://pinboard.in/settings/password - apitoken: -instances: - # https://tools.splat.soy/pleroma-access-token -- accesstoken: - instanceurl: https://compostintraining.club - deletebookmarks: true diff --git a/config.go b/config.go deleted file mode 100644 index e5ce10c..0000000 --- a/config.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "log" - "os" - "time" - - "gopkg.in/yaml.v2" -) - -const ( - HttpUserAgent = "mastodon-bookmark-sync/1.0" -) - -var ( - configFile string -) - -type config struct { - Instances []instanceConfig - Pinboard pinboardConfig - LastSynced time.Time - HttpConfig httpConfig -} - -type instanceConfig struct { - AccessToken, InstanceURL string - DeleteBookmarks bool -} - -type pinboardConfig struct { - APIToken string -} - -type httpConfig struct { - UserAgent string -} - -func readConfig(fileName string) *config { - log.Println("reading config...") - configFile = fileName - config := new(config) - cf, err := os.ReadFile(configFile) - if err != nil { - log.Fatalln("Failed to read config: ", err) - } - err = yaml.Unmarshal(cf, &config) - if err != nil { - log.Panic(err) - } - if debug { - log.Printf("Config:\n\n%v", config) - } - config.HttpConfig.UserAgent = HttpUserAgent - return config -} - -func (cf *config) updateLastSynced() { - log.Println("updating LastSynced key...") - cf.LastSynced = time.Now() -} - -func (cf *config) Save() error { - log.Println("saving config to file...") - cfBytes, err := yaml.Marshal(cf) - if err != nil { - log.Fatalln("Failed to marshal config: ", err.Error()) - } - err = os.WriteFile(configFile, cfBytes, 0644) - if err != nil { - log.Fatalf("Failed to save config to file. Error: %s", err.Error()) - } - - return nil -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 663f25d..0000000 --- a/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/prplecake/mastodon-bookmark-sync - -go 1.17 - -require ( - github.com/microcosm-cc/bluemonday v1.0.18 - golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 - gopkg.in/yaml.v2 v2.4.0 -) - -require ( - github.com/aymerick/douceur v0.2.0 // indirect - github.com/gorilla/css v1.0.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 522f8b8..0000000 --- a/go.sum +++ /dev/null @@ -1,22 +0,0 @@ -github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo= -github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/mastodon-bookmark-sync.go b/mastodon-bookmark-sync.go deleted file mode 100644 index 4c13299..0000000 --- a/mastodon-bookmark-sync.go +++ /dev/null @@ -1,184 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io" - "log" - "net/http" - "net/url" - "strings" - - "github.com/microcosm-cc/bluemonday" - "golang.org/x/net/html" -) - -var ( - conf *config - debug bool - PinboardDescMaxLength = 255 - PinboardApiUrl = "https://api.pinboard.in/v1/posts/add" -) - -func main() { - var ( - configFile string - ) - flag.StringVar(&configFile, "c", "config.yaml", "the configuration file to use") - flag.BoolVar(&debug, "debug", false, "enable debugging") - flag.Parse() - - conf = readConfig(configFile) - - if debug { - log.Println(conf) - } - - log.Println("mastodon-bookmark-sync starting up...") - log.Println("initializing bluemonday") - p := bluemonday.StripTagsPolicy() - - for _, instance := range conf.Instances { - log.Printf("Fetching bookmarks from instance: [%s]...", instance.InstanceURL) - apiURL := instance.InstanceURL + "/api/v1/bookmarks" - - var req *http.Request - req, err := http.NewRequest("GET", apiURL, nil) - if err != nil { - log.Fatal(err) - } - req.Header.Set("Authorization", "Bearer "+instance.AccessToken) - req.Header.Set("User-Agent", conf.HttpConfig.UserAgent) - - c := &http.Client{} - - resp, err := c.Do(req) - if err != nil { - log.Fatal(err) - } - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - } - bookmarks, err := processBookmarks(body) - if err != nil { - log.Fatal(err) - } - - for i := len(bookmarks) - 1; i >= 0; i-- { - bookmarks[i].Content = html.UnescapeString(p.Sanitize( - strings.Replace(bookmarks[i].Content, "

", "\n\n", -1), - )) - if debug { - log.Printf("%d:\n%s\n%s\n%s\n\n", - i, bookmarks[i].URL, - bookmarks[i].Content, - bookmarks[i].Account.Acct, - ) - } - - log.Printf("Saving URL: %s", bookmarks[i].URL) - - var ( - descriptionTrimmed = false - trimmedDescription string - extended string - tags = fmt.Sprintf( - "%s %s", - "via:@"+bookmarks[i].Account.Acct, - "via:mastodon-bookmark-sync", - ) - ) - - if len(bookmarks[i].Content) > PinboardDescMaxLength { - trimmedDescription = bookmarks[i].Content[PinboardDescMaxLength:] - descriptionTrimmed = true - } - - if descriptionTrimmed { - extended = bookmarks[i].Content - bookmarks[i].Content = trimmedDescription - } - - data := url.Values{} - data.Set("auth_token", conf.Pinboard.APIToken) - data.Set("description", bookmarks[i].Content) - data.Set("url", bookmarks[i].URL) - data.Set("shared", "no") - data.Set("extended", extended) - data.Set("tags", tags) - fullURL := PinboardApiUrl + "?" + data.Encode() - - if debug { - log.Print(fullURL) - } - - req, err := http.NewRequest("GET", fullURL, nil) - if err != nil { - log.Fatal(err) - } - req.Header.Set("User-Agent", conf.HttpConfig.UserAgent) - - c := &http.Client{} - - resp, err := c.Do(req) - if err != nil { - log.Fatal(err) - } - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - log.Printf("resp.StatusCode: %d", resp.StatusCode) - - if debug { - buf := new(strings.Builder) - _, err = io.Copy(buf, resp.Body) - if err != nil { - log.Fatal(err) - } - log.Printf("resp.Body: %s", buf.String()) - } - - if instance.DeleteBookmarks && resp.StatusCode == 200 && bookmarks[i].Visibility != "private" && bookmarks[i].Visibility != "direct" { - // only delete bookmarks if successfully saved by Pinboard and - // don't delete bookmarks of private or direct statuses - instance.deleteBookmark(bookmarks[i]) - } - } - } -} - -func (instance *instanceConfig) deleteBookmark(status bookmark) { - apiURL := instance.InstanceURL + "/api/v1/statuses/" + status.ID + "/unbookmark" - - req, err := http.NewRequest("POST", apiURL, nil) - if err != nil { - log.Fatal(err) - } - - req.Header.Set("Authorization", "Bearer "+instance.AccessToken) - - c := &http.Client{} - - resp, err := c.Do(req) - if err != nil { - log.Fatal(err) - } - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - if debug { - buf := new(strings.Builder) - _, err = io.Copy(buf, resp.Body) - if err != nil { - log.Fatal(err) - } - log.Printf("resp.Body: %s", buf.String()) - } -} From ce0d80188492697bdc6868b1f9d99f9b1140c16a Mon Sep 17 00:00:00 2001 From: prplecake Date: Thu, 2 Mar 2023 16:31:31 -0600 Subject: [PATCH 05/33] Add dotnet CI workflow --- .github/workflows/dotnet.yml | 66 ++++++++++++++++++++++++++++++++++++ mastodon-bookmark-sync.sln | 1 + 2 files changed, 67 insertions(+) create mode 100644 .github/workflows/dotnet.yml diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..491235d --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,66 @@ +name: .NET + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.x + - name: Restore dependencies + run: | + dotnet nuget add source --username prplecake --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/CompostInTraining/index.json" + dotnet restore + - name: Build + run: dotnet build --no-restore + + - name: Test + run: dotnet test --no-build --verbosity normal --logger "trx" --results-directory "./TestResults" + + - uses: dorny/test-reporter@v1 + if: always() + with: + name: .NET Test Results + path: TestResults/*.trx + reporter: dotnet-trx + + publish: + strategy: + matrix: + rid: [osx-x64, linux-x64, win-x64] + + runs-on: ubuntu-latest + name: publish-${{matrix.rid}} + needs: build + if: ${{ github.event_name != 'pull_request' }} # don't upload artifacts for PRs + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.x + - name: Add NuGet source + run: dotnet nuget add source --username prplecake --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/CompostInTraining/index.json" + - name: Publish CLI (${{matrix.rid}} + run: dotnet publish BookmarkSync.CLI/BookmarkSync.CLI.csproj -c Release -r ${{matrix.rid}} --self-contained + - name: Upload CLI artifact + uses: actions/upload-artifact@v3 + with: + name: BookmarkSync.CLI-${{matrix.rid}} + path: BookmarkSync.CLI/bin/Release/net7.0/${{matrix.rid}}/publish/ \ No newline at end of file diff --git a/mastodon-bookmark-sync.sln b/mastodon-bookmark-sync.sln index b92a263..eba9b9e 100644 --- a/mastodon-bookmark-sync.sln +++ b/mastodon-bookmark-sync.sln @@ -4,6 +4,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject README.md = README.md .gitignore = .gitignore + .github\workflows\dotnet.yml = .github\workflows\dotnet.yml EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.CLI", "BookmarkSync.CLI\BookmarkSync.CLI.csproj", "{696935C2-0383-4409-A2B4-B80A8C905DD2}" From 0e2af4c27df35251e4bb33f0c86d0aa5a44342b4 Mon Sep 17 00:00:00 2001 From: prplecake Date: Thu, 2 Mar 2023 18:03:12 -0600 Subject: [PATCH 06/33] Resolve CodeFactor errors and cleanup code --- .github/workflows/dotnet.yml | 4 ++-- BookmarkSync.CLI/Program.cs | 19 ++++++++----------- .../Configuration/ConfigManager.cs | 6 +++--- .../Entities/Config/Bookmarking.cs | 2 +- BookmarkSync.Core/Entities/Config/Pinboard.cs | 5 +---- .../Interfaces/IBookmarkingService.cs | 5 +---- .../Services/PinboardBookmarkingService.cs | 5 +---- 7 files changed, 17 insertions(+), 29 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 491235d..ba2cbd3 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -41,8 +41,8 @@ jobs: publish: strategy: matrix: - rid: [osx-x64, linux-x64, win-x64] - + rid: [ osx-x64, linux-x64, win-x64 ] + runs-on: ubuntu-latest name: publish-${{matrix.rid}} needs: build diff --git a/BookmarkSync.CLI/Program.cs b/BookmarkSync.CLI/Program.cs index 0cb0073..8d54aaa 100644 --- a/BookmarkSync.CLI/Program.cs +++ b/BookmarkSync.CLI/Program.cs @@ -27,22 +27,19 @@ public async static Task Main(string[] args) .Build(); await host.StartAsync(); var lifetime = host.Services.GetRequiredService(); - + // Do work here Console.WriteLine("Hello, world!"); - + // Shutdown configManager.SaveToFile(); - + lifetime.StopApplication(); await host.WaitForShutdownAsync(); } - private static IConfiguration SetupConfiguration(string[] args) - { - return new ConfigurationBuilder() - .AddJsonFile("appsettings.json", false, true) - .AddEnvironmentVariables() - .AddCommandLine(args) - .Build(); - } + private static IConfiguration SetupConfiguration(string[] args) => new ConfigurationBuilder() + .AddJsonFile("appsettings.json", false, true) + .AddEnvironmentVariables() + .AddCommandLine(args) + .Build(); } diff --git a/BookmarkSync.Core/Configuration/ConfigManager.cs b/BookmarkSync.Core/Configuration/ConfigManager.cs index b0d8ec0..692459e 100644 --- a/BookmarkSync.Core/Configuration/ConfigManager.cs +++ b/BookmarkSync.Core/Configuration/ConfigManager.cs @@ -10,9 +10,9 @@ namespace BookmarkSync.Core.Configuration; public interface IConfigManager { + App App { get; set; } IConfiguration Configuration { get; set; } Instance[] Instances { get; set; } - App App { get; set; } string GetConfigValue(string key); void SaveToFile(); } @@ -22,8 +22,8 @@ public ConfigManager( IConfiguration configuration) { Configuration = configuration; - List instance = Configuration.GetSection("Instances").Get>(); - App app = Configuration.GetSection("App").Get(); + var instance = Configuration.GetSection("Instances").Get>(); + var app = Configuration.GetSection("App").Get(); var instances = Configuration.GetSection("Instances").Get(); App = app; diff --git a/BookmarkSync.Core/Entities/Config/Bookmarking.cs b/BookmarkSync.Core/Entities/Config/Bookmarking.cs index 969f398..b028f53 100644 --- a/BookmarkSync.Core/Entities/Config/Bookmarking.cs +++ b/BookmarkSync.Core/Entities/Config/Bookmarking.cs @@ -2,6 +2,6 @@ namespace BookmarkSync.Core.Entities.Config; public class Bookmarking : ConfigurationBase { - public string Service { get; set; } public string ApiToken { get; set; } + public string Service { get; set; } } diff --git a/BookmarkSync.Core/Entities/Config/Pinboard.cs b/BookmarkSync.Core/Entities/Config/Pinboard.cs index 1aa1551..e15f31c 100644 --- a/BookmarkSync.Core/Entities/Config/Pinboard.cs +++ b/BookmarkSync.Core/Entities/Config/Pinboard.cs @@ -1,6 +1,3 @@ namespace BookmarkSync.Core.Entities.Config; -public class Pinboard : ConfigurationBase -{ - -} +public class Pinboard : ConfigurationBase { } diff --git a/BookmarkSync.Core/Interfaces/IBookmarkingService.cs b/BookmarkSync.Core/Interfaces/IBookmarkingService.cs index 8ec9389..0b6bba6 100644 --- a/BookmarkSync.Core/Interfaces/IBookmarkingService.cs +++ b/BookmarkSync.Core/Interfaces/IBookmarkingService.cs @@ -1,6 +1,3 @@ namespace BookmarkSync.Core.Interfaces; -public interface IBookmarkingService -{ - -} +public interface IBookmarkingService { } diff --git a/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs b/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs index 767cedf..44894a1 100644 --- a/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs +++ b/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs @@ -2,7 +2,4 @@ namespace BookmarkSync.Infrastructure.Services; -public class PinboardBookmarkingService : IBookmarkingService -{ - -} +public class PinboardBookmarkingService : IBookmarkingService { } From 9a20b8e797e580c99299cd2be5aaa354c11f3007 Mon Sep 17 00:00:00 2001 From: prplecake Date: Thu, 2 Mar 2023 18:03:39 -0600 Subject: [PATCH 07/33] Add basic tests --- .../BookmarkSync.Core.Tests.csproj | 23 ++++ .../Configuration/ConfigManagerTests.cs | 52 +++++++++ .../Entities/AccountTests.cs | 30 +++++ .../Entities/BookmarkTests.cs | 22 ++++ .../Entities/Config/AppTests.cs | 21 ++++ .../Entities/Config/BookmarkingTests.cs | 21 ++++ .../Entities/Config/InstanceTests.cs | 34 ++++++ .../Entities/Config/PinboardTests.cs | 19 ++++ .../Helpers/TestHelpers.cs | 106 ++++++++++++++++++ BookmarkSync.Core.Tests/Usings.cs | 2 + mastodon-bookmark-sync.sln | 6 + 11 files changed, 336 insertions(+) create mode 100644 BookmarkSync.Core.Tests/BookmarkSync.Core.Tests.csproj create mode 100644 BookmarkSync.Core.Tests/Configuration/ConfigManagerTests.cs create mode 100644 BookmarkSync.Core.Tests/Entities/AccountTests.cs create mode 100644 BookmarkSync.Core.Tests/Entities/BookmarkTests.cs create mode 100644 BookmarkSync.Core.Tests/Entities/Config/AppTests.cs create mode 100644 BookmarkSync.Core.Tests/Entities/Config/BookmarkingTests.cs create mode 100644 BookmarkSync.Core.Tests/Entities/Config/InstanceTests.cs create mode 100644 BookmarkSync.Core.Tests/Entities/Config/PinboardTests.cs create mode 100644 BookmarkSync.Core.Tests/Helpers/TestHelpers.cs create mode 100644 BookmarkSync.Core.Tests/Usings.cs diff --git a/BookmarkSync.Core.Tests/BookmarkSync.Core.Tests.csproj b/BookmarkSync.Core.Tests/BookmarkSync.Core.Tests.csproj new file mode 100644 index 0000000..e05c2a9 --- /dev/null +++ b/BookmarkSync.Core.Tests/BookmarkSync.Core.Tests.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/BookmarkSync.Core.Tests/Configuration/ConfigManagerTests.cs b/BookmarkSync.Core.Tests/Configuration/ConfigManagerTests.cs new file mode 100644 index 0000000..5bb81f4 --- /dev/null +++ b/BookmarkSync.Core.Tests/Configuration/ConfigManagerTests.cs @@ -0,0 +1,52 @@ +using BookmarkSync.Core.Configuration; +using Microsoft.Extensions.Configuration; + +namespace BookmarkSync.Core.Tests.Configuration; + +[TestClass] +public class ConfigManagerTests +{ + [TestInitialize] + public void Init() + { + var inMemoryConfig = new Dictionary + { + { + "App:Bookmarking:Service", "Pinboard" + }, + { + "Instances:0:Uri", "https://compostintraining.club" + } + }; + _config = new ConfigurationBuilder() + .AddInMemoryCollection(inMemoryConfig) + .Build(); + + _configManager = new ConfigManager(_config); + } + private IConfigManager _configManager; + private IConfiguration _config; + [TestMethod] + public void ConfigManager_HasProperties() + { + // Assert + Assert.AreEqual(3, _configManager.PropertyCount()); + Assert.IsTrue(_configManager.HasProperty("Configuration")); + Assert.IsTrue(_configManager.HasProperty("Instances")); + Assert.IsTrue(_configManager.HasProperty("App")); + + Assert.IsTrue(_configManager.HasMethod("GetConfigValue")); + } + [TestMethod] + public void GetConfigValue_Success() + { + // Arrange + var expected = "Pinboard"; + + // Act + string actual = _configManager.GetConfigValue("App:Bookmarking:Service"); + + // Assert + Assert.AreEqual(expected, actual); + } +} diff --git a/BookmarkSync.Core.Tests/Entities/AccountTests.cs b/BookmarkSync.Core.Tests/Entities/AccountTests.cs new file mode 100644 index 0000000..b817f43 --- /dev/null +++ b/BookmarkSync.Core.Tests/Entities/AccountTests.cs @@ -0,0 +1,30 @@ +using BookmarkSync.Core.Entities; + +namespace BookmarkSync.Core.Tests.Entities; + +[TestClass] +public class AccountTests +{ + [TestMethod] + public void Account_HasProperties() + { + // Arrange + Account obj = new(); + + // Assert + Assert.AreEqual(1, obj.PropertyCount()); + Assert.IsTrue(obj.HasProperty("Name")); + } + [TestMethod] + public void Account_ToString_Is_Name() + { + // Arrange + Account obj = new() + { + Name = "@prplecake@compostintraining.club" + }; + + // Assert + Assert.AreEqual(obj.ToString(), obj.Name); + } +} diff --git a/BookmarkSync.Core.Tests/Entities/BookmarkTests.cs b/BookmarkSync.Core.Tests/Entities/BookmarkTests.cs new file mode 100644 index 0000000..c38f612 --- /dev/null +++ b/BookmarkSync.Core.Tests/Entities/BookmarkTests.cs @@ -0,0 +1,22 @@ +using BookmarkSync.Core.Entities; + +namespace BookmarkSync.Core.Tests.Entities; + +[TestClass] +public class BookmarkTests +{ + [TestMethod] + public void App_HasProperties() + { + // Arrange + Bookmark obj = new(); + + // Assert + Assert.AreEqual(5, obj.PropertyCount()); + Assert.IsTrue(obj.HasProperty("Account")); + Assert.IsTrue(obj.HasProperty("Content")); + Assert.IsTrue(obj.HasProperty("Id")); + Assert.IsTrue(obj.HasProperty("Uri")); + Assert.IsTrue(obj.HasProperty("Visibility")); + } +} diff --git a/BookmarkSync.Core.Tests/Entities/Config/AppTests.cs b/BookmarkSync.Core.Tests/Entities/Config/AppTests.cs new file mode 100644 index 0000000..e979d08 --- /dev/null +++ b/BookmarkSync.Core.Tests/Entities/Config/AppTests.cs @@ -0,0 +1,21 @@ +using BookmarkSync.Core.Entities.Config; + +namespace BookmarkSync.Core.Tests.Entities.Config; + +[TestClass] +public class AppTests +{ + [TestMethod] + public void App_HasProperties() + { + // Arrange + App obj = new(); + + // Assert + Assert.AreEqual(2, obj.PropertyCount()); + Assert.IsTrue(obj.HasProperty("Bookmarking")); + Assert.IsTrue(obj.HasProperty("LastSynced")); + + Assert.IsTrue(obj.HasMethod("IsValid")); + } +} diff --git a/BookmarkSync.Core.Tests/Entities/Config/BookmarkingTests.cs b/BookmarkSync.Core.Tests/Entities/Config/BookmarkingTests.cs new file mode 100644 index 0000000..d200847 --- /dev/null +++ b/BookmarkSync.Core.Tests/Entities/Config/BookmarkingTests.cs @@ -0,0 +1,21 @@ +using BookmarkSync.Core.Entities.Config; + +namespace BookmarkSync.Core.Tests.Entities.Config; + +[TestClass] +public class BookmarkingTests +{ + [TestMethod] + public void Bookmarking_HasProperties() + { + // Arrange + Bookmarking obj = new(); + + // Assert + Assert.AreEqual(2, obj.PropertyCount()); + Assert.IsTrue(obj.HasProperty("ApiToken")); + Assert.IsTrue(obj.HasProperty("Service")); + + Assert.IsTrue(obj.HasMethod("IsValid")); + } +} diff --git a/BookmarkSync.Core.Tests/Entities/Config/InstanceTests.cs b/BookmarkSync.Core.Tests/Entities/Config/InstanceTests.cs new file mode 100644 index 0000000..cf82c9d --- /dev/null +++ b/BookmarkSync.Core.Tests/Entities/Config/InstanceTests.cs @@ -0,0 +1,34 @@ +using BookmarkSync.Core.Entities.Config; + +namespace BookmarkSync.Core.Tests.Entities.Config; + +[TestClass] +public class InstanceTests +{ + [TestMethod] + public void Instance_HasProperties() + { + // Arrange + Instance obj = new(); + + // Assert + Assert.AreEqual(3, obj.PropertyCount()); + Assert.IsTrue(obj.HasProperty("AccessToken")); + Assert.IsTrue(obj.HasProperty("DeleteBookmarks")); + Assert.IsTrue(obj.HasProperty("Uri")); + + Assert.IsTrue(obj.HasMethod("IsValid")); + } + [TestMethod] + public void Instance_ToString_Is_Uri() + { + // Arrange + Instance obj = new() + { + Uri = "https://compostintraining.club" + }; + + // Assert + Assert.AreEqual(obj.ToString(), obj.Uri); + } +} diff --git a/BookmarkSync.Core.Tests/Entities/Config/PinboardTests.cs b/BookmarkSync.Core.Tests/Entities/Config/PinboardTests.cs new file mode 100644 index 0000000..725d663 --- /dev/null +++ b/BookmarkSync.Core.Tests/Entities/Config/PinboardTests.cs @@ -0,0 +1,19 @@ +using BookmarkSync.Core.Entities.Config; + +namespace BookmarkSync.Core.Tests.Entities.Config; + +[TestClass] +public class PinboardTests +{ + [TestMethod] + public void Pinboard_HasProperties() + { + // Arrange + Pinboard obj = new(); + + // Assert + Assert.AreEqual(0, obj.PropertyCount()); + + Assert.IsTrue(obj.HasMethod("IsValid")); + } +} diff --git a/BookmarkSync.Core.Tests/Helpers/TestHelpers.cs b/BookmarkSync.Core.Tests/Helpers/TestHelpers.cs new file mode 100644 index 0000000..c947eaf --- /dev/null +++ b/BookmarkSync.Core.Tests/Helpers/TestHelpers.cs @@ -0,0 +1,106 @@ +using System.Reflection; +#pragma warning disable CS8604 +#pragma warning disable CS8600 +#pragma warning disable CS8602 +namespace BookmarkSync.Core.Tests.Helpers; + +public static class EntityTests +{ + public static bool HasMethod(this object obj, string methodName) + { + var type = obj.GetType(); + try + { + return type.GetMethod(methodName) != null; + } + catch (AmbiguousMatchException) + { + return true; + } + } + public static bool HasProperty(this object obj, string propertyName) + { + var type = obj.GetType(); + return type.GetProperty(propertyName) != null; + } + public static int PropertyCount(this object obj) + { + var type = obj.GetType(); + return type.GetProperties().Length; + } + public static T SetProperties(T domainObject, bool recursive = false) + { + var props = domainObject.GetType().GetProperties(); + foreach (var prop in props) + { + var propType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; + try + { + object propObj = null; + object data; + switch (propType.Name.ToLower()) + { + case "string": + data = "test"; + break; + case "int": + case "int32": + data = 2; + break; + case "datetime": + data = DateTime.Now; + break; + case "bool": + case "boolean": + data = true; + break; + case "decimal": + data = decimal.Parse("1.23"); + break; + case "double": + data = double.Parse("3.21"); + break; + default: + if (propType.IsInterface) + { + propObj = null; + } + else if (propType.IsArray) + { + var elementType = propType.GetElementType(); + propObj = Array.CreateInstance(elementType, 1); + } + else + { + var ctr = propType.GetConstructors()[0]; + propObj = ctr.GetParameters().Any() ? null : Activator.CreateInstance(propType); + } + data = propObj; + break; + } + if (data != null && prop.CanWrite) + { + prop.SetValue(domainObject, data); + Assert.AreEqual(data, prop.GetValue(domainObject)); + } + if (recursive && propObj != null) + { + if (propType.IsGenericType) + { + var myListElementType = propType.GetGenericArguments().Single(); + propObj = Activator.CreateInstance(myListElementType); + } + SetProperties(propObj, recursive); + } + } + catch (Exception ex) + { + var msg = string.Format( + "Error creating instance of {0} because: {1}", + propType.Name, ex.Message); + Assert.IsNotNull(prop.GetValue(domainObject), msg); + } + } + return domainObject; + } +} diff --git a/BookmarkSync.Core.Tests/Usings.cs b/BookmarkSync.Core.Tests/Usings.cs new file mode 100644 index 0000000..87c1c76 --- /dev/null +++ b/BookmarkSync.Core.Tests/Usings.cs @@ -0,0 +1,2 @@ +global using BookmarkSync.Core.Tests.Helpers; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/mastodon-bookmark-sync.sln b/mastodon-bookmark-sync.sln index eba9b9e..2964f73 100644 --- a/mastodon-bookmark-sync.sln +++ b/mastodon-bookmark-sync.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Core", "Bookma EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Infrastructure", "BookmarkSync.Infrastructure\BookmarkSync.Infrastructure.csproj", "{D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Core.Tests", "BookmarkSync.Core.Tests\BookmarkSync.Core.Tests.csproj", "{9B28E255-C1D4-43A6-861A-DD1752F31A0E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,5 +33,9 @@ Global {D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {D11BD068-7AD0-4C9D-85A0-BD04A67AA7FC}.Release|Any CPU.Build.0 = Release|Any CPU + {9B28E255-C1D4-43A6-861A-DD1752F31A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B28E255-C1D4-43A6-861A-DD1752F31A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B28E255-C1D4-43A6-861A-DD1752F31A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B28E255-C1D4-43A6-861A-DD1752F31A0E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 3aed8ee0dfcd5fad1ed6694ca9cc7ec710e57ee4 Mon Sep 17 00:00:00 2001 From: prplecake Date: Thu, 2 Mar 2023 18:03:43 -0600 Subject: [PATCH 08/33] Update JetBrains project settings --- .../.idea/codeStyles/Project.xml | 22 +++++++++++++++++++ .../.idea/codeStyles/codeStyleConfig.xml | 5 +++++ mastodon-bookmark-sync.sln.DotSettings | 2 ++ 3 files changed, 29 insertions(+) create mode 100644 .idea/.idea.mastodon-bookmark-sync/.idea/codeStyles/Project.xml create mode 100644 .idea/.idea.mastodon-bookmark-sync/.idea/codeStyles/codeStyleConfig.xml create mode 100644 mastodon-bookmark-sync.sln.DotSettings diff --git a/.idea/.idea.mastodon-bookmark-sync/.idea/codeStyles/Project.xml b/.idea/.idea.mastodon-bookmark-sync/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..0d4f3b0 --- /dev/null +++ b/.idea/.idea.mastodon-bookmark-sync/.idea/codeStyles/Project.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.mastodon-bookmark-sync/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.mastodon-bookmark-sync/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/.idea.mastodon-bookmark-sync/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/mastodon-bookmark-sync.sln.DotSettings b/mastodon-bookmark-sync.sln.DotSettings new file mode 100644 index 0000000..7f259e9 --- /dev/null +++ b/mastodon-bookmark-sync.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file From 708bd4cc2e7519c2a7ef4e8108cbf797df1d87df Mon Sep 17 00:00:00 2001 From: prplecake Date: Thu, 2 Mar 2023 18:12:41 -0600 Subject: [PATCH 09/33] Resolve nullability warnings --- BookmarkSync.CLI/Program.cs | 6 +++--- .../Configuration/ConfigManagerTests.cs | 16 ++++++++-------- BookmarkSync.Core/Configuration/ConfigManager.cs | 16 +++++++--------- BookmarkSync.Core/Entities/Account.cs | 4 ++-- BookmarkSync.Core/Entities/Bookmark.cs | 10 +++++----- BookmarkSync.Core/Entities/Config/App.cs | 2 +- BookmarkSync.Core/Entities/Config/Bookmarking.cs | 4 ++-- BookmarkSync.Core/Entities/Config/Instance.cs | 6 +++--- 8 files changed, 31 insertions(+), 33 deletions(-) diff --git a/BookmarkSync.CLI/Program.cs b/BookmarkSync.CLI/Program.cs index 8d54aaa..82fec66 100644 --- a/BookmarkSync.CLI/Program.cs +++ b/BookmarkSync.CLI/Program.cs @@ -11,11 +11,11 @@ namespace BookmarkSync.CLI; public class Program { - private static IConfiguration Configuration; + private static IConfiguration? _configuration; public async static Task Main(string[] args) { - Configuration = SetupConfiguration(args); - IConfigManager configManager = new ConfigManager(Configuration); + _configuration = SetupConfiguration(args); + IConfigManager configManager = new ConfigManager(_configuration); using var host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => diff --git a/BookmarkSync.Core.Tests/Configuration/ConfigManagerTests.cs b/BookmarkSync.Core.Tests/Configuration/ConfigManagerTests.cs index 5bb81f4..21aabac 100644 --- a/BookmarkSync.Core.Tests/Configuration/ConfigManagerTests.cs +++ b/BookmarkSync.Core.Tests/Configuration/ConfigManagerTests.cs @@ -24,18 +24,18 @@ public void Init() _configManager = new ConfigManager(_config); } - private IConfigManager _configManager; - private IConfiguration _config; + private IConfigManager? _configManager; + private IConfiguration? _config; [TestMethod] public void ConfigManager_HasProperties() { // Assert - Assert.AreEqual(3, _configManager.PropertyCount()); - Assert.IsTrue(_configManager.HasProperty("Configuration")); - Assert.IsTrue(_configManager.HasProperty("Instances")); - Assert.IsTrue(_configManager.HasProperty("App")); + Assert.AreEqual(3, _configManager?.PropertyCount()); + Assert.IsTrue(_configManager?.HasProperty("Configuration")); + Assert.IsTrue(_configManager?.HasProperty("Instances")); + Assert.IsTrue(_configManager?.HasProperty("App")); - Assert.IsTrue(_configManager.HasMethod("GetConfigValue")); + Assert.IsTrue(_configManager?.HasMethod("GetConfigValue")); } [TestMethod] public void GetConfigValue_Success() @@ -44,7 +44,7 @@ public void GetConfigValue_Success() var expected = "Pinboard"; // Act - string actual = _configManager.GetConfigValue("App:Bookmarking:Service"); + string? actual = _configManager?.GetConfigValue("App:Bookmarking:Service"); // Assert Assert.AreEqual(expected, actual); diff --git a/BookmarkSync.Core/Configuration/ConfigManager.cs b/BookmarkSync.Core/Configuration/ConfigManager.cs index 692459e..0bcfe34 100644 --- a/BookmarkSync.Core/Configuration/ConfigManager.cs +++ b/BookmarkSync.Core/Configuration/ConfigManager.cs @@ -10,9 +10,9 @@ namespace BookmarkSync.Core.Configuration; public interface IConfigManager { - App App { get; set; } + App? App { get; set; } IConfiguration Configuration { get; set; } - Instance[] Instances { get; set; } + List? Instances { get; set; } string GetConfigValue(string key); void SaveToFile(); } @@ -22,19 +22,17 @@ public ConfigManager( IConfiguration configuration) { Configuration = configuration; - var instance = Configuration.GetSection("Instances").Get>(); - var app = Configuration.GetSection("App").Get(); - var instances = Configuration.GetSection("Instances").Get(); - App = app; + App = Configuration.GetSection("App").Get(); + Instances = Configuration.GetSection("Instances").Get>(); - if (!App.IsValid()) + if (App is not null && !App.IsValid()) { throw new InvalidConfigurationException(); } } public IConfiguration Configuration { get; set; } - public Instance[] Instances { get; set; } - public App App { get; set; } + public List? Instances { get; set; } + public App? App { get; set; } public string GetConfigValue(string key) { string? value = Configuration[key]; diff --git a/BookmarkSync.Core/Entities/Account.cs b/BookmarkSync.Core/Entities/Account.cs index 09a5515..452ba8e 100644 --- a/BookmarkSync.Core/Entities/Account.cs +++ b/BookmarkSync.Core/Entities/Account.cs @@ -2,7 +2,7 @@ namespace BookmarkSync.Core.Entities; public class Account { - public string Name { get; set; } + public string? Name { get; set; } /// - public override string ToString() => Name; + public override string ToString() => Name ?? GetType().Name; } diff --git a/BookmarkSync.Core/Entities/Bookmark.cs b/BookmarkSync.Core/Entities/Bookmark.cs index 2707cdf..8a444d9 100644 --- a/BookmarkSync.Core/Entities/Bookmark.cs +++ b/BookmarkSync.Core/Entities/Bookmark.cs @@ -2,9 +2,9 @@ namespace BookmarkSync.Core.Entities; public class Bookmark { - public Account Account { get; set; } - public string Content { get; set; } - public string Id { get; set; } - public string Uri { get; set; } - public string Visibility { get; set; } + public Account? Account { get; set; } + public string? Content { get; set; } + public string? Id { get; set; } + public string? Uri { get; set; } + public string? Visibility { get; set; } } diff --git a/BookmarkSync.Core/Entities/Config/App.cs b/BookmarkSync.Core/Entities/Config/App.cs index 669ae68..95ecd3d 100644 --- a/BookmarkSync.Core/Entities/Config/App.cs +++ b/BookmarkSync.Core/Entities/Config/App.cs @@ -5,6 +5,6 @@ namespace BookmarkSync.Core.Entities.Config; public class App : ConfigurationBase { - [ConfigRequired] public Bookmarking Bookmarking { get; set; } + [ConfigRequired] public Bookmarking? Bookmarking { get; set; } public DateTime LastSynced { get; set; } } diff --git a/BookmarkSync.Core/Entities/Config/Bookmarking.cs b/BookmarkSync.Core/Entities/Config/Bookmarking.cs index b028f53..179d638 100644 --- a/BookmarkSync.Core/Entities/Config/Bookmarking.cs +++ b/BookmarkSync.Core/Entities/Config/Bookmarking.cs @@ -2,6 +2,6 @@ namespace BookmarkSync.Core.Entities.Config; public class Bookmarking : ConfigurationBase { - public string ApiToken { get; set; } - public string Service { get; set; } + public string? ApiToken { get; set; } + public string? Service { get; set; } } diff --git a/BookmarkSync.Core/Entities/Config/Instance.cs b/BookmarkSync.Core/Entities/Config/Instance.cs index 0b592cb..9e339d2 100644 --- a/BookmarkSync.Core/Entities/Config/Instance.cs +++ b/BookmarkSync.Core/Entities/Config/Instance.cs @@ -2,9 +2,9 @@ namespace BookmarkSync.Core.Entities.Config; public class Instance : ConfigurationBase { - public string AccessToken { get; set; } + public string? AccessToken { get; set; } public bool DeleteBookmarks { get; set; } - public string Uri { get; set; } + public string? Uri { get; set; } /// - public override string ToString() => Uri; + public override string ToString() => Uri ?? GetType().Name; } From 5c3767c35ee2427094651f48d79ad4d40b93bfd2 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 01:32:14 -0500 Subject: [PATCH 10/33] Stub out BookmarkingService base and Pinboard class --- BookmarkSync.Core/Entities/Config/App.cs | 2 +- .../Interfaces/IBookmarkingService.cs | 12 +++- .../BookmarkSync.Infrastructure.Tests.csproj | 23 ++++++++ .../Services/BookmarkingServiceTests.cs | 59 +++++++++++++++++++ .../Pinboard/PinboardBookmarkTests.cs | 50 ++++++++++++++++ BookmarkSync.Infrastructure.Tests/Usings.cs | 2 + .../BookmarkSync.Infrastructure.csproj | 2 + .../Services/BookmarkSyncService.cs | 39 ++++++++++++ .../Services/BookmarkingService.cs | 44 ++++++++++++++ .../Services/Pinboard/PinboardBookmark.cs | 12 ++++ .../Pinboard/PinboardBookmarkingService.cs | 37 ++++++++++++ .../Services/PinboardBookmarkingService.cs | 5 -- mastodon-bookmark-sync.sln | 6 ++ 13 files changed, 286 insertions(+), 7 deletions(-) create mode 100644 BookmarkSync.Infrastructure.Tests/BookmarkSync.Infrastructure.Tests.csproj create mode 100644 BookmarkSync.Infrastructure.Tests/Services/BookmarkingServiceTests.cs create mode 100644 BookmarkSync.Infrastructure.Tests/Services/Pinboard/PinboardBookmarkTests.cs create mode 100644 BookmarkSync.Infrastructure.Tests/Usings.cs create mode 100644 BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs create mode 100644 BookmarkSync.Infrastructure/Services/BookmarkingService.cs create mode 100644 BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmark.cs create mode 100644 BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs delete mode 100644 BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs diff --git a/BookmarkSync.Core/Entities/Config/App.cs b/BookmarkSync.Core/Entities/Config/App.cs index 95ecd3d..2921eca 100644 --- a/BookmarkSync.Core/Entities/Config/App.cs +++ b/BookmarkSync.Core/Entities/Config/App.cs @@ -5,6 +5,6 @@ namespace BookmarkSync.Core.Entities.Config; public class App : ConfigurationBase { - [ConfigRequired] public Bookmarking? Bookmarking { get; set; } + [ConfigRequired] public Bookmarking Bookmarking { get; set; } = null!; public DateTime LastSynced { get; set; } } diff --git a/BookmarkSync.Core/Interfaces/IBookmarkingService.cs b/BookmarkSync.Core/Interfaces/IBookmarkingService.cs index 0b6bba6..7ed1459 100644 --- a/BookmarkSync.Core/Interfaces/IBookmarkingService.cs +++ b/BookmarkSync.Core/Interfaces/IBookmarkingService.cs @@ -1,3 +1,13 @@ +using System.Threading.Tasks; +using BookmarkSync.Core.Entities; + namespace BookmarkSync.Core.Interfaces; -public interface IBookmarkingService { } +public interface IBookmarkingService +{ + ///

+ /// Saves a bookmark to the bookmarking service. + /// + /// A service's bookmark implementation. + public Task Save(Bookmark bookmark); +} diff --git a/BookmarkSync.Infrastructure.Tests/BookmarkSync.Infrastructure.Tests.csproj b/BookmarkSync.Infrastructure.Tests/BookmarkSync.Infrastructure.Tests.csproj new file mode 100644 index 0000000..127dac4 --- /dev/null +++ b/BookmarkSync.Infrastructure.Tests/BookmarkSync.Infrastructure.Tests.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/BookmarkSync.Infrastructure.Tests/Services/BookmarkingServiceTests.cs b/BookmarkSync.Infrastructure.Tests/Services/BookmarkingServiceTests.cs new file mode 100644 index 0000000..c36b1b1 --- /dev/null +++ b/BookmarkSync.Infrastructure.Tests/Services/BookmarkingServiceTests.cs @@ -0,0 +1,59 @@ +using BookmarkSync.Core.Configuration; +using BookmarkSync.Infrastructure.Services; +using BookmarkSync.Infrastructure.Services.Pinboard; +using Microsoft.Extensions.Configuration; + +namespace BookmarkSync.Infrastructure.Tests.Services; + +[TestClass] +public class BookmarkingServiceTests +{ + [TestMethod] + public void GetBookmarkingService_Pinboard() + { + // Arrange + var config = new Dictionary + { + { + "App:Bookmarking:Service", "Pinboard" + }, + { + "App:Bookmarking:ApiToken", "secret:123456789" + } + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(config) + .Build(); + + IConfigManager configManager = new ConfigManager(configuration); + + // Act + var obj = BookmarkingService.GetBookmarkingService(configManager); + + // Assert + Assert.AreEqual(obj.GetType(), typeof(PinboardBookmarkingService)); + Assert.IsInstanceOfType(obj, typeof(PinboardBookmarkingService)); + } + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void GetBookmarkingService_Exception() + { + // Arrange + var config = new Dictionary + { + { + "App:Bookmarking:Service", "Nonsense" + } + }; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(config) + .Build(); + + IConfigManager configManager = new ConfigManager(configuration); + + // Act + var unused = BookmarkingService.GetBookmarkingService(configManager); + + // Assert - Exception + } +} diff --git a/BookmarkSync.Infrastructure.Tests/Services/Pinboard/PinboardBookmarkTests.cs b/BookmarkSync.Infrastructure.Tests/Services/Pinboard/PinboardBookmarkTests.cs new file mode 100644 index 0000000..d41ba90 --- /dev/null +++ b/BookmarkSync.Infrastructure.Tests/Services/Pinboard/PinboardBookmarkTests.cs @@ -0,0 +1,50 @@ +using BookmarkSync.Infrastructure.Services.Pinboard; + +namespace BookmarkSync.Infrastructure.Tests.Services.Pinboard; + +[TestClass] +public class PinboardBookmarkTests +{ + [TestMethod] + public void PinboardBookmark_HasProperties() + { + // Arrange + PinboardBookmark obj = new(); + + // Assert + Assert.AreEqual(5, obj.PropertyCount()); + Assert.IsTrue(obj.HasProperty("Description")); + Assert.IsTrue(obj.HasProperty("ExtendedDescription")); + Assert.IsTrue(obj.HasProperty("Shared")); + Assert.IsTrue(obj.HasProperty("Tags")); + Assert.IsTrue(obj.HasProperty("Uri")); + + Assert.IsTrue(obj.HasMethod("GetFormattedTags")); + } + + [TestMethod] + public void GetFormattedTags_NullTags() + { + // Arrange + PinboardBookmark obj = new(); + + // Assert + Assert.IsNull(obj.GetFormattedTags()); + } + [TestMethod] + public void GetFormattedTags_WithTags() + { + // Arrange + PinboardBookmark obj = new() + { + Tags = new[] + { + "first-tag", "second-tag" + } + }; + const string expected = "first-tag second-tag"; + + // Assert + Assert.AreEqual(expected, obj.GetFormattedTags()); + } +} diff --git a/BookmarkSync.Infrastructure.Tests/Usings.cs b/BookmarkSync.Infrastructure.Tests/Usings.cs new file mode 100644 index 0000000..87c1c76 --- /dev/null +++ b/BookmarkSync.Infrastructure.Tests/Usings.cs @@ -0,0 +1,2 @@ +global using BookmarkSync.Core.Tests.Helpers; +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj b/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj index 9d63ce0..ca178e5 100644 --- a/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj +++ b/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj @@ -8,6 +8,8 @@ + + diff --git a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs new file mode 100644 index 0000000..96414dc --- /dev/null +++ b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using BookmarkSync.Core.Configuration; +using BookmarkSync.Core.Entities; +using BookmarkSync.Core.Interfaces; +using Microsoft.Extensions.Hosting; +using Serilog; + +namespace BookmarkSync.Infrastructure.Services; + +public class BookmarkSyncService : IHostedService +{ + private readonly IHostApplicationLifetime _host; + private readonly IBookmarkingService _bookmarkingService; + private static readonly ILogger _logger = Log.ForContext(); + public BookmarkSyncService(IHostApplicationLifetime host, IConfigManager configManager) + { + _bookmarkingService = BookmarkingService.GetBookmarkingService(configManager); + _host = host; + } + /// + public Task StartAsync(CancellationToken stoppingToken) + { + _logger.Information("The current time is: {CurrentTime}", DateTimeOffset.UtcNow); + + // Get bookmarks from mastodon account + // TODO + + // Save bookmarks to bookmarking service + _bookmarkingService.Save(new Bookmark()); + + // Finish task + _host.StopApplication(); + return Task.CompletedTask; + } + /// + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} diff --git a/BookmarkSync.Infrastructure/Services/BookmarkingService.cs b/BookmarkSync.Infrastructure/Services/BookmarkingService.cs new file mode 100644 index 0000000..7d6b231 --- /dev/null +++ b/BookmarkSync.Infrastructure/Services/BookmarkingService.cs @@ -0,0 +1,44 @@ +using System; +using System.Diagnostics; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Reflection; +using System.Threading.Tasks; +using BookmarkSync.Core.Configuration; +using BookmarkSync.Core.Entities; +using BookmarkSync.Core.Interfaces; +using BookmarkSync.Infrastructure.Services.Pinboard; + +namespace BookmarkSync.Infrastructure.Services; + +public abstract class BookmarkingService +{ + /// + /// The HttpClient object. + /// + private static readonly HttpClient Client = new(); + protected BookmarkingService() + { + // Setup HttpClient + Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); + Client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("mastodon-bookmark-sync", + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion)); + } + /// + /// The API auth token.. + /// + protected string ApiToken { get; init; } = null!; + /// + /// The Api URL. + /// + protected string ApiUri { get; init; } = null!; + public static IBookmarkingService GetBookmarkingService(IConfigManager configManager) + { + return configManager.App.Bookmarking.Service switch + { + "Pinboard" => new PinboardBookmarkingService(configManager), + _ => throw new InvalidOperationException("Bookmark service either not provided or unknown") + }; + } +} diff --git a/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmark.cs b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmark.cs new file mode 100644 index 0000000..fb10171 --- /dev/null +++ b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmark.cs @@ -0,0 +1,12 @@ +namespace BookmarkSync.Infrastructure.Services.Pinboard; + +public class PinboardBookmark +{ + public string Description { get; set; } + public string ExtendedDescription { get; set; } // TODO: check this name + public bool Shared { get; set; } = false; + public string[]? Tags { get; set; } + public string Uri { get; set; } + public string? GetFormattedTags() + => Tags != null ? string.Join(" ", Tags) : null; +} diff --git a/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs new file mode 100644 index 0000000..263a66e --- /dev/null +++ b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using System.Web; +using BookmarkSync.Core.Configuration; +using BookmarkSync.Core.Entities; +using BookmarkSync.Core.Interfaces; +using Serilog; + +namespace BookmarkSync.Infrastructure.Services.Pinboard; + +public class PinboardBookmarkingService : BookmarkingService, IBookmarkingService +{ + private static readonly ILogger _logger = Log.ForContext(); + public PinboardBookmarkingService(IConfigManager configManager) + { + ApiToken = configManager.App.Bookmarking.ApiToken ?? throw new InvalidOperationException(); + ApiUri = "https://api.pinboard.in/v1/posts/add"; + } + /// + public async Task Save(Bookmark bookmark) + { + var builder = new UriBuilder(ApiUri); + var query = HttpUtility.ParseQueryString(string.Empty); + query["auth_token"] = ApiToken; + // query["description"] = bookmark.Description; + query["url"] = bookmark.Uri; + // query["shared"] = bookmark.Shared ? "yes" : "no"; + // query["extended"] = bookmark.ExtendedDescription; + // query["tags"] = bookmark.GetFormattedTags(); + builder.Query = query.ToString(); + var requestUri = builder.ToString(); + _logger.Debug("Request URI: {RequestUri}", requestUri); + + // var result = await Client.GetAsync(requestUri); + // result.EnsureSuccessStatusCode(); + } +} diff --git a/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs b/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs deleted file mode 100644 index 44894a1..0000000 --- a/BookmarkSync.Infrastructure/Services/PinboardBookmarkingService.cs +++ /dev/null @@ -1,5 +0,0 @@ -using BookmarkSync.Core.Interfaces; - -namespace BookmarkSync.Infrastructure.Services; - -public class PinboardBookmarkingService : IBookmarkingService { } diff --git a/mastodon-bookmark-sync.sln b/mastodon-bookmark-sync.sln index 2964f73..287c739 100644 --- a/mastodon-bookmark-sync.sln +++ b/mastodon-bookmark-sync.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Infrastructure EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Core.Tests", "BookmarkSync.Core.Tests\BookmarkSync.Core.Tests.csproj", "{9B28E255-C1D4-43A6-861A-DD1752F31A0E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookmarkSync.Infrastructure.Tests", "BookmarkSync.Infrastructure.Tests\BookmarkSync.Infrastructure.Tests.csproj", "{DA6C5B23-C780-479D-8D14-20307A5A91D8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,5 +39,9 @@ Global {9B28E255-C1D4-43A6-861A-DD1752F31A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B28E255-C1D4-43A6-861A-DD1752F31A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B28E255-C1D4-43A6-861A-DD1752F31A0E}.Release|Any CPU.Build.0 = Release|Any CPU + {DA6C5B23-C780-479D-8D14-20307A5A91D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA6C5B23-C780-479D-8D14-20307A5A91D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA6C5B23-C780-479D-8D14-20307A5A91D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA6C5B23-C780-479D-8D14-20307A5A91D8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From dab70108ad2de6e8039e1c346bdfa343ce1dccc6 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 01:33:31 -0500 Subject: [PATCH 11/33] Refactor Program.cs - Implement Serilog - Use HostedService --- BookmarkSync.CLI/BookmarkSync.CLI.csproj | 3 ++ BookmarkSync.CLI/Program.cs | 46 ++++++++++++++--------- BookmarkSync.CLI/appsettings.Example.json | 18 +++++++++ 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/BookmarkSync.CLI/BookmarkSync.CLI.csproj b/BookmarkSync.CLI/BookmarkSync.CLI.csproj index b964d88..0bd98c0 100644 --- a/BookmarkSync.CLI/BookmarkSync.CLI.csproj +++ b/BookmarkSync.CLI/BookmarkSync.CLI.csproj @@ -10,6 +10,9 @@ + + + diff --git a/BookmarkSync.CLI/Program.cs b/BookmarkSync.CLI/Program.cs index 82fec66..6085c7f 100644 --- a/BookmarkSync.CLI/Program.cs +++ b/BookmarkSync.CLI/Program.cs @@ -1,42 +1,54 @@ using System; -using System.Threading.Tasks; using BookmarkSync.Core.Configuration; -using BookmarkSync.Core.Interfaces; using BookmarkSync.Infrastructure.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Serilog; namespace BookmarkSync.CLI; public class Program { private static IConfiguration? _configuration; - public async static Task Main(string[] args) + public static int Main(string[] args) { _configuration = SetupConfiguration(args); IConfigManager configManager = new ConfigManager(_configuration); + + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(_configuration) + .Enrich.FromLogContext() + .CreateLogger(); - using var host = Host.CreateDefaultBuilder(args) + try + { + Log.Information("Starting host"); + BuildHost(args, configManager).Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; + } + finally + { + // Shutdown + configManager.SaveToFile(); + Log.CloseAndFlush(); + } + } + public static IHost BuildHost(string[] args, IConfigManager configManager) => + new HostBuilder() .ConfigureServices(services => { services.AddSingleton(configManager); - services.AddTransient(); + services.AddHostedService(); }) + .UseSerilog() .Build(); - await host.StartAsync(); - var lifetime = host.Services.GetRequiredService(); - - // Do work here - Console.WriteLine("Hello, world!"); - - // Shutdown - configManager.SaveToFile(); - - lifetime.StopApplication(); - await host.WaitForShutdownAsync(); - } private static IConfiguration SetupConfiguration(string[] args) => new ConfigurationBuilder() .AddJsonFile("appsettings.json", false, true) .AddEnvironmentVariables() diff --git a/BookmarkSync.CLI/appsettings.Example.json b/BookmarkSync.CLI/appsettings.Example.json index df410ff..8239684 100644 --- a/BookmarkSync.CLI/appsettings.Example.json +++ b/BookmarkSync.CLI/appsettings.Example.json @@ -11,5 +11,23 @@ "Service": "Pinboard", "ApiToken": "" } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Information", + "System": "Error" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] [{SourceContext}] [{EventId}] {Message}{NewLine}{Exception}" + } + } + ] } } \ No newline at end of file From 7ad6e34851f214cf4e8071daed110668711c1d00 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 01:33:46 -0500 Subject: [PATCH 12/33] Refactor ConfigManager.App --- BookmarkSync.Core/Configuration/ConfigManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BookmarkSync.Core/Configuration/ConfigManager.cs b/BookmarkSync.Core/Configuration/ConfigManager.cs index 0bcfe34..760ef56 100644 --- a/BookmarkSync.Core/Configuration/ConfigManager.cs +++ b/BookmarkSync.Core/Configuration/ConfigManager.cs @@ -10,7 +10,7 @@ namespace BookmarkSync.Core.Configuration; public interface IConfigManager { - App? App { get; set; } + App App { get; set; } IConfiguration Configuration { get; set; } List? Instances { get; set; } string GetConfigValue(string key); @@ -22,17 +22,17 @@ public ConfigManager( IConfiguration configuration) { Configuration = configuration; - App = Configuration.GetSection("App").Get(); + App = Configuration.GetSection("App").Get() ?? throw new NullReferenceException(); Instances = Configuration.GetSection("Instances").Get>(); - if (App is not null && !App.IsValid()) + if (!App.IsValid()) { throw new InvalidConfigurationException(); } } public IConfiguration Configuration { get; set; } public List? Instances { get; set; } - public App? App { get; set; } + public App App { get; set; } public string GetConfigValue(string key) { string? value = Configuration[key]; From 9970e824bdf4cfbf0e79cc50b914e41f4740b4e9 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 01:42:52 -0500 Subject: [PATCH 13/33] Add run configuration --- .run/BookmarkSync.CLI.run.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .run/BookmarkSync.CLI.run.xml diff --git a/.run/BookmarkSync.CLI.run.xml b/.run/BookmarkSync.CLI.run.xml new file mode 100644 index 0000000..1e35d15 --- /dev/null +++ b/.run/BookmarkSync.CLI.run.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file From 113340a904eed44fe8666b18e7f18c5e4f61edd0 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 02:33:41 -0500 Subject: [PATCH 14/33] Update PinboardBookmarkingService --- .../Interfaces/IBookmarkingService.cs | 3 ++- .../Services/BookmarkSyncService.cs | 2 +- .../Services/BookmarkingService.cs | 4 +-- .../Pinboard/PinboardBookmarkingService.cs | 27 ++++++++++++++----- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/BookmarkSync.Core/Interfaces/IBookmarkingService.cs b/BookmarkSync.Core/Interfaces/IBookmarkingService.cs index 7ed1459..94a8bf6 100644 --- a/BookmarkSync.Core/Interfaces/IBookmarkingService.cs +++ b/BookmarkSync.Core/Interfaces/IBookmarkingService.cs @@ -1,3 +1,4 @@ +using System.Net.Http; using System.Threading.Tasks; using BookmarkSync.Core.Entities; @@ -9,5 +10,5 @@ public interface IBookmarkingService /// Saves a bookmark to the bookmarking service. /// /// A service's bookmark implementation. - public Task Save(Bookmark bookmark); + public Task Save(Bookmark bookmark); } diff --git a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs index 96414dc..67db13e 100644 --- a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs +++ b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; using BookmarkSync.Core.Configuration; -using BookmarkSync.Core.Entities; +using BookmarkSync.Core.Entities.Config; using BookmarkSync.Core.Interfaces; using Microsoft.Extensions.Hosting; using Serilog; diff --git a/BookmarkSync.Infrastructure/Services/BookmarkingService.cs b/BookmarkSync.Infrastructure/Services/BookmarkingService.cs index 7d6b231..f6788ae 100644 --- a/BookmarkSync.Infrastructure/Services/BookmarkingService.cs +++ b/BookmarkSync.Infrastructure/Services/BookmarkingService.cs @@ -4,9 +4,7 @@ using System.Net.Http.Headers; using System.Net.Mime; using System.Reflection; -using System.Threading.Tasks; using BookmarkSync.Core.Configuration; -using BookmarkSync.Core.Entities; using BookmarkSync.Core.Interfaces; using BookmarkSync.Infrastructure.Services.Pinboard; @@ -17,7 +15,7 @@ public abstract class BookmarkingService /// /// The HttpClient object. /// - private static readonly HttpClient Client = new(); + protected static readonly HttpClient Client = new(); protected BookmarkingService() { // Setup HttpClient diff --git a/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs index 263a66e..10092cb 100644 --- a/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs +++ b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; using System.Threading.Tasks; using System.Web; using BookmarkSync.Core.Configuration; @@ -10,6 +11,7 @@ namespace BookmarkSync.Infrastructure.Services.Pinboard; public class PinboardBookmarkingService : BookmarkingService, IBookmarkingService { + private const int PinboardDescriptionMaxLength = 255; private static readonly ILogger _logger = Log.ForContext(); public PinboardBookmarkingService(IConfigManager configManager) { @@ -17,21 +19,32 @@ public PinboardBookmarkingService(IConfigManager configManager) ApiUri = "https://api.pinboard.in/v1/posts/add"; } /// - public async Task Save(Bookmark bookmark) + public async Task Save(Bookmark bookmark) { + // Prep bookmark + string? extended = null; + if (bookmark.Content!.Length >= PinboardDescriptionMaxLength) + { + string? trimmedDescription = bookmark.Content[..PinboardDescriptionMaxLength]; + extended = bookmark.Content; + bookmark.Content = trimmedDescription; + } + var builder = new UriBuilder(ApiUri); var query = HttpUtility.ParseQueryString(string.Empty); query["auth_token"] = ApiToken; - // query["description"] = bookmark.Description; + query["description"] = bookmark.Content; query["url"] = bookmark.Uri; - // query["shared"] = bookmark.Shared ? "yes" : "no"; - // query["extended"] = bookmark.ExtendedDescription; - // query["tags"] = bookmark.GetFormattedTags(); + query["shared"] = "no"; + if (!string.IsNullOrEmpty(extended)) + { + query["extended"] = extended; + } + query["tags"] = string.Join(" ", $"via:@{bookmark.Account}", "via:mastodon-bookmark-sync"); builder.Query = query.ToString(); var requestUri = builder.ToString(); _logger.Debug("Request URI: {RequestUri}", requestUri); - // var result = await Client.GetAsync(requestUri); - // result.EnsureSuccessStatusCode(); + return await Client.GetAsync(requestUri); } } From bd7299eedd26f9d9b1d9de5f7cbba1f1538648bf Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 02:33:54 -0500 Subject: [PATCH 15/33] Update Account, add constructor --- BookmarkSync.Core/Entities/Account.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BookmarkSync.Core/Entities/Account.cs b/BookmarkSync.Core/Entities/Account.cs index 452ba8e..b45db1c 100644 --- a/BookmarkSync.Core/Entities/Account.cs +++ b/BookmarkSync.Core/Entities/Account.cs @@ -3,6 +3,11 @@ namespace BookmarkSync.Core.Entities; public class Account { public string? Name { get; set; } + public Account(string name) + { + Name = name; + } + public Account() { } /// public override string ToString() => Name ?? GetType().Name; } From 67157f167093ef89d3e2f2a269c7fc78a247f2b9 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 02:39:21 -0500 Subject: [PATCH 16/33] Stub out required Mastodon API methods --- .../Services/BookmarkSyncService.cs | 40 +++++++++++++++---- .../Services/Mastodon/ApiClient.cs | 38 ++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 BookmarkSync.Infrastructure/Services/Mastodon/ApiClient.cs diff --git a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs index 67db13e..19904fd 100644 --- a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs +++ b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using BookmarkSync.Core.Configuration; @@ -12,27 +13,52 @@ namespace BookmarkSync.Infrastructure.Services; public class BookmarkSyncService : IHostedService { private readonly IHostApplicationLifetime _host; + private readonly List? _instances; private readonly IBookmarkingService _bookmarkingService; private static readonly ILogger _logger = Log.ForContext(); public BookmarkSyncService(IHostApplicationLifetime host, IConfigManager configManager) { _bookmarkingService = BookmarkingService.GetBookmarkingService(configManager); _host = host; + _instances = configManager.Instances; } /// - public Task StartAsync(CancellationToken stoppingToken) + public async Task StartAsync(CancellationToken stoppingToken) { _logger.Information("The current time is: {CurrentTime}", DateTimeOffset.UtcNow); - // Get bookmarks from mastodon account - // TODO - - // Save bookmarks to bookmarking service - _bookmarkingService.Save(new Bookmark()); + if (_instances == null || _instances.Count == 0) + { + _logger.Information("No instances configured"); + return; + } + foreach (var instance in _instances) + { + _logger.Information("Processing {Instance}", instance); + _logger.Debug("Setting up Mastodon API client"); + var client = new Mastodon.ApiClient(instance); + // Get bookmarks from mastodon account + var bookmarks = await client.GetBookmarks(); + + if (bookmarks == null || bookmarks.Count == 0) + { + _logger.Information("No bookmarks received"); + return; + } + foreach (var bookmark in bookmarks) + { + // Save bookmarks to bookmarking service + var result = await _bookmarkingService.Save(bookmark); + + if (instance.DeleteBookmarks && result.IsSuccessStatusCode) + { + await client.DeleteBookmark(bookmark); + } + } + } // Finish task _host.StopApplication(); - return Task.CompletedTask; } /// public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; diff --git a/BookmarkSync.Infrastructure/Services/Mastodon/ApiClient.cs b/BookmarkSync.Infrastructure/Services/Mastodon/ApiClient.cs new file mode 100644 index 0000000..c6a6ee7 --- /dev/null +++ b/BookmarkSync.Infrastructure/Services/Mastodon/ApiClient.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Reflection; +using System.Threading.Tasks; +using BookmarkSync.Core.Entities; +using BookmarkSync.Core.Entities.Config; +using Serilog; + +namespace BookmarkSync.Infrastructure.Services.Mastodon; + +public class ApiClient +{ + private readonly HttpClient _client = new(); + private readonly Instance _instance; + private static readonly ILogger _logger = Log.ForContext(); + public ApiClient(Instance instance) + { + _instance = instance; + _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); + _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("mastodon-bookmark-sync", + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion)); + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _instance.AccessToken); + } + public async Task?> GetBookmarks() + { + _logger.Debug("Running {Method}", "GetBookmarks"); + return null; + } + public async Task DeleteBookmark(Bookmark bookmark) + { + _logger.Debug("Running {Method}", "DeleteBookmark"); + throw new NotImplementedException(); + } +} From 6908fd22024a235e665000e298b655dd6a8381c7 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 02:44:42 -0500 Subject: [PATCH 17/33] Remove PinboardBookmark and related tests --- .../Pinboard/PinboardBookmarkTests.cs | 50 ------------------- .../Services/Pinboard/PinboardBookmark.cs | 12 ----- 2 files changed, 62 deletions(-) delete mode 100644 BookmarkSync.Infrastructure.Tests/Services/Pinboard/PinboardBookmarkTests.cs delete mode 100644 BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmark.cs diff --git a/BookmarkSync.Infrastructure.Tests/Services/Pinboard/PinboardBookmarkTests.cs b/BookmarkSync.Infrastructure.Tests/Services/Pinboard/PinboardBookmarkTests.cs deleted file mode 100644 index d41ba90..0000000 --- a/BookmarkSync.Infrastructure.Tests/Services/Pinboard/PinboardBookmarkTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using BookmarkSync.Infrastructure.Services.Pinboard; - -namespace BookmarkSync.Infrastructure.Tests.Services.Pinboard; - -[TestClass] -public class PinboardBookmarkTests -{ - [TestMethod] - public void PinboardBookmark_HasProperties() - { - // Arrange - PinboardBookmark obj = new(); - - // Assert - Assert.AreEqual(5, obj.PropertyCount()); - Assert.IsTrue(obj.HasProperty("Description")); - Assert.IsTrue(obj.HasProperty("ExtendedDescription")); - Assert.IsTrue(obj.HasProperty("Shared")); - Assert.IsTrue(obj.HasProperty("Tags")); - Assert.IsTrue(obj.HasProperty("Uri")); - - Assert.IsTrue(obj.HasMethod("GetFormattedTags")); - } - - [TestMethod] - public void GetFormattedTags_NullTags() - { - // Arrange - PinboardBookmark obj = new(); - - // Assert - Assert.IsNull(obj.GetFormattedTags()); - } - [TestMethod] - public void GetFormattedTags_WithTags() - { - // Arrange - PinboardBookmark obj = new() - { - Tags = new[] - { - "first-tag", "second-tag" - } - }; - const string expected = "first-tag second-tag"; - - // Assert - Assert.AreEqual(expected, obj.GetFormattedTags()); - } -} diff --git a/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmark.cs b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmark.cs deleted file mode 100644 index fb10171..0000000 --- a/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmark.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace BookmarkSync.Infrastructure.Services.Pinboard; - -public class PinboardBookmark -{ - public string Description { get; set; } - public string ExtendedDescription { get; set; } // TODO: check this name - public bool Shared { get; set; } = false; - public string[]? Tags { get; set; } - public string Uri { get; set; } - public string? GetFormattedTags() - => Tags != null ? string.Join(" ", Tags) : null; -} From acbb73cbab89ac5f5d96a1a030c24c109e6d723e Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 04:05:45 -0500 Subject: [PATCH 18/33] Add HtmlUtilities to sanitize HTML --- BookmarkSync.Core/BookmarkSync.Core.csproj | 1 + BookmarkSync.Core/Entities/Bookmark.cs | 9 ++- BookmarkSync.Core/Utilities/HtmlUtilities.cs | 80 ++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 BookmarkSync.Core/Utilities/HtmlUtilities.cs diff --git a/BookmarkSync.Core/BookmarkSync.Core.csproj b/BookmarkSync.Core/BookmarkSync.Core.csproj index e47591a..626afc7 100644 --- a/BookmarkSync.Core/BookmarkSync.Core.csproj +++ b/BookmarkSync.Core/BookmarkSync.Core.csproj @@ -8,6 +8,7 @@ + diff --git a/BookmarkSync.Core/Entities/Bookmark.cs b/BookmarkSync.Core/Entities/Bookmark.cs index 8a444d9..65dc4e6 100644 --- a/BookmarkSync.Core/Entities/Bookmark.cs +++ b/BookmarkSync.Core/Entities/Bookmark.cs @@ -1,9 +1,16 @@ +using BookmarkSync.Core.Utilities; + namespace BookmarkSync.Core.Entities; public class Bookmark { public Account? Account { get; set; } - public string? Content { get; set; } + private string? _content; + public string? Content + { + get => _content != null ? HtmlUtilities.ConvertToPlainText(_content) : ""; + set => _content = value; + } public string? Id { get; set; } public string? Uri { get; set; } public string? Visibility { get; set; } diff --git a/BookmarkSync.Core/Utilities/HtmlUtilities.cs b/BookmarkSync.Core/Utilities/HtmlUtilities.cs new file mode 100644 index 0000000..d931f47 --- /dev/null +++ b/BookmarkSync.Core/Utilities/HtmlUtilities.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using HtmlAgilityPack; + +namespace BookmarkSync.Core.Utilities; + +public static class HtmlUtilities +{ + public static string ConvertToPlainText(string html) + { + var doc = new HtmlDocument(); + doc.LoadHtml(html); + + var sw = new StringWriter(); + ConvertTo(doc.DocumentNode, sw); + + sw.Flush(); + return sw.ToString(); + } + private static void ConvertTo(HtmlNode node, TextWriter outText) + { + string html; + switch (node.NodeType) + { + case HtmlNodeType.Comment: + // don't output comments + break; + case HtmlNodeType.Document: + ConvertContentTo(node, outText); + break; + + case HtmlNodeType.Text: + // script and style must not be output + string parentName = node.ParentNode.Name; + if (parentName is "script" or "style") + break; + + // get text + html = ((HtmlTextNode)node).Text; + + // is it a special closing node output as text? + if (HtmlNode.IsOverlappedClosingElement(html)) + break; + + // check the text is meaningful and not a bunch of whitespace + if (html.Trim().Length > 0) + { + outText.Write(HtmlEntity.DeEntitize(html)); + } + break; + + case HtmlNodeType.Element: + switch (node.Name) + { + case "p": + // treat paragraphs as crlf + outText.Write("\r\n"); + break; + case "br": + outText.Write("\r\n"); + break; + } + + if (node.HasChildNodes) + { + ConvertContentTo(node, outText); + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + private static void ConvertContentTo(HtmlNode node, TextWriter outText) + { + foreach (var subnode in node.ChildNodes) + { + ConvertTo(subnode, outText); + } + } +} From 1feda2f1eef756c4bdb3de322bb2dba39ab49430 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 04:11:55 -0500 Subject: [PATCH 19/33] Finish up PinboardBookmarkingService, Mastodon.ApiClient, and BookmarkSyncService --- BookmarkSync.Core/BookmarkSync.Core.csproj | 1 + BookmarkSync.Core/Entities/Account.cs | 4 ++- .../BookmarkSync.Infrastructure.csproj | 1 + .../Services/BookmarkSyncService.cs | 25 ++++++++++++++++--- .../Services/Mastodon/ApiClient.cs | 15 ++++++++--- .../Pinboard/PinboardBookmarkingService.cs | 5 +++- 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/BookmarkSync.Core/BookmarkSync.Core.csproj b/BookmarkSync.Core/BookmarkSync.Core.csproj index 626afc7..c9220eb 100644 --- a/BookmarkSync.Core/BookmarkSync.Core.csproj +++ b/BookmarkSync.Core/BookmarkSync.Core.csproj @@ -11,6 +11,7 @@ + diff --git a/BookmarkSync.Core/Entities/Account.cs b/BookmarkSync.Core/Entities/Account.cs index b45db1c..2aee3dc 100644 --- a/BookmarkSync.Core/Entities/Account.cs +++ b/BookmarkSync.Core/Entities/Account.cs @@ -1,8 +1,10 @@ +using Newtonsoft.Json; + namespace BookmarkSync.Core.Entities; public class Account { - public string? Name { get; set; } + [JsonProperty("acct")] public string? Name { get; set; } public Account(string name) { Name = name; diff --git a/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj b/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj index ca178e5..ddbb223 100644 --- a/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj +++ b/BookmarkSync.Infrastructure/BookmarkSync.Infrastructure.csproj @@ -9,6 +9,7 @@ + diff --git a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs index 19904fd..5e63bd1 100644 --- a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs +++ b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using BookmarkSync.Core.Configuration; +using BookmarkSync.Core.Entities; using BookmarkSync.Core.Entities.Config; using BookmarkSync.Core.Interfaces; using Microsoft.Extensions.Hosting; @@ -38,17 +40,34 @@ public async Task StartAsync(CancellationToken stoppingToken) _logger.Debug("Setting up Mastodon API client"); var client = new Mastodon.ApiClient(instance); // Get bookmarks from mastodon account - var bookmarks = await client.GetBookmarks(); + List? bookmarks = null; + try + { + bookmarks = await client.GetBookmarks(); + } + catch (HttpRequestException ex) + { + _logger.Error(ex, "Failed to retrieve bookmarks from {Instance}", instance); + } if (bookmarks == null || bookmarks.Count == 0) { _logger.Information("No bookmarks received"); - return; + continue; } foreach (var bookmark in bookmarks) { // Save bookmarks to bookmarking service - var result = await _bookmarkingService.Save(bookmark); + HttpResponseMessage result; + try + { + result = await _bookmarkingService.Save(bookmark); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to save bookmark"); + continue; + } if (instance.DeleteBookmarks && result.IsSuccessStatusCode) { diff --git a/BookmarkSync.Infrastructure/Services/Mastodon/ApiClient.cs b/BookmarkSync.Infrastructure/Services/Mastodon/ApiClient.cs index c6a6ee7..750458d 100644 --- a/BookmarkSync.Infrastructure/Services/Mastodon/ApiClient.cs +++ b/BookmarkSync.Infrastructure/Services/Mastodon/ApiClient.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Diagnostics; using System.Net.Http; @@ -8,6 +7,7 @@ using System.Threading.Tasks; using BookmarkSync.Core.Entities; using BookmarkSync.Core.Entities.Config; +using Newtonsoft.Json; using Serilog; namespace BookmarkSync.Infrastructure.Services.Mastodon; @@ -28,11 +28,20 @@ public ApiClient(Instance instance) public async Task?> GetBookmarks() { _logger.Debug("Running {Method}", "GetBookmarks"); - return null; + var requestUri = $"{_instance.Uri}/api/v1/bookmarks"; + var response = await _client.GetAsync(requestUri); + response.EnsureSuccessStatusCode(); + string responseContent = await response.Content.ReadAsStringAsync(); + var obj = JsonConvert.DeserializeObject>(responseContent); + + return obj; } public async Task DeleteBookmark(Bookmark bookmark) { _logger.Debug("Running {Method}", "DeleteBookmark"); - throw new NotImplementedException(); + var requestUri = $"{_instance.Uri}/api/v1/statuses/{bookmark.Id}/unbookmark"; + var response = await _client.PostAsync(requestUri, null); + response.EnsureSuccessStatusCode(); + _logger.Information("Bookmark {BookmarkId} deleted successfully", bookmark.Id); } } diff --git a/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs index 10092cb..d2df84d 100644 --- a/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs +++ b/BookmarkSync.Infrastructure/Services/Pinboard/PinboardBookmarkingService.cs @@ -45,6 +45,9 @@ public async Task Save(Bookmark bookmark) var requestUri = builder.ToString(); _logger.Debug("Request URI: {RequestUri}", requestUri); - return await Client.GetAsync(requestUri); + var response = await Client.GetAsync(requestUri); + response.EnsureSuccessStatusCode(); + _logger.Debug("Response status: {StatusCode}", response.StatusCode); + return response; } } From 1c7e5ba78ac133597a98e2f2b9531719f4ccc869 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 04:20:31 -0500 Subject: [PATCH 20/33] Ignore private or direct messages --- BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs index 5e63bd1..ca1c3fe 100644 --- a/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs +++ b/BookmarkSync.Infrastructure/Services/BookmarkSyncService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -43,7 +44,9 @@ public async Task StartAsync(CancellationToken stoppingToken) List? bookmarks = null; try { - bookmarks = await client.GetBookmarks(); + bookmarks = (await client.GetBookmarks()) + .Where(b => b.Visibility is not ("private" or "direct")) + .ToList(); } catch (HttpRequestException ex) { From 6ccb2c652a33b55003dba902eabfe0faa0f990d3 Mon Sep 17 00:00:00 2001 From: Matthew Jorgensen Date: Mon, 27 Mar 2023 05:05:47 -0500 Subject: [PATCH 21/33] Change executable name --- .run/BookmarkSync.CLI.run.xml | 2 +- BookmarkSync.CLI/BookmarkSync.CLI.csproj | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.run/BookmarkSync.CLI.run.xml b/.run/BookmarkSync.CLI.run.xml index 1e35d15..c88836a 100644 --- a/.run/BookmarkSync.CLI.run.xml +++ b/.run/BookmarkSync.CLI.run.xml @@ -1,6 +1,6 @@ -