diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml
index d28cb08..ae430cc 100644
--- a/.github/workflows/dotnet-core.yml
+++ b/.github/workflows/dotnet-core.yml
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
- dotnet-version: 3.1.101
+ dotnet-version: 6.0.x
- name: Install dependencies
run: dotnet restore
working-directory: ${{env.working-directory}}
diff --git a/Dockerfile b/Dockerfile
index 11a4422..a59f7f4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,11 +1,11 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
-FROM mcr.microsoft.com/dotnet/aspnet:3.1-buster-slim AS base
+FROM mcr.microsoft.com/dotnet/aspnet:6.0-bullseye-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
-FROM mcr.microsoft.com/dotnet/sdk:3.1-buster AS publish
+FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim AS publish
COPY ./src/ ./src/
RUN dotnet publish "/src/BirdsiteLive/BirdsiteLive.csproj" -c Release -o /app/publish
RUN dotnet publish "/src/BSLManager/BSLManager.csproj" -r linux-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeAllContentForSelfExtract=true -c Release -o /app/publish
diff --git a/INSTALLATION.md b/INSTALLATION.md
index 74d9131..569c2a1 100644
--- a/INSTALLATION.md
+++ b/INSTALLATION.md
@@ -186,6 +186,70 @@ services:
+ command: --interval 300
```
+## IP Whitelisting
+
+If you want to use the IP Whitelisting functionality (see related [variable](https://github.com/NicolasConstant/BirdsiteLive/blob/master/VARIABLES.md)) and you are using the nginx reverse proxy set as before, please add the following:
+
+```
+sudo nano /etc/nginx/sites-enabled/{your-domain-name.com}
+```
+
+``` diff
+server {
+ listen 80;
+ server_name {your-domain-name.com};
+ location / {
+ proxy_pass http://localhost:5000;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection keep-alive;
+ proxy_set_header Host $host;
+ proxy_cache_bypass $http_upgrade;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
++ proxy_set_header X-Real-IP $remote_addr;
+ }
+}
+```
+
+And edit the docker-compose file as follow:
+
+```diff
+version: "3"
+
+networks:
+ birdsitelivenetwork:
+ external: false
+
+services:
+ server:
+ image: nicolasconstant/birdsitelive:latest
+ restart: always
+ container_name: birdsitelive
+ environment:
+ - Instance:Domain=domain.name
+ - Instance:AdminEmail=name@domain.ext
++ - Instance:IpWhiteListing=127.0.0.1;127.0.0.2
++ - Instance:EnableXRealIpHeader=true
+ - Db:Type=postgres
+ - Db:Host=db
+ - Db:Name=birdsitelive
+ - Db:User=birdsitelive
+ - Db:Password=birdsitelive
+ - Twitter:ConsumerKey=twitter.api.key
+ - Twitter:ConsumerSecret=twitter.api.key
+ networks:
+ - birdsitelivenetwork
+ ports:
+ - "5000:80"
+ depends_on:
+ - db
+
+ db:
+ image: postgres:9.6
+ [...]
+```
+
## More options
You can find more options available [here](https://github.com/NicolasConstant/BirdsiteLive/blob/master/VARIABLES.md)
diff --git a/VARIABLES.md b/VARIABLES.md
index 2ef0cdf..bb8bf61 100644
--- a/VARIABLES.md
+++ b/VARIABLES.md
@@ -51,6 +51,9 @@ If both whitelisting and blacklisting are set, only the whitelisting will be act
* `Instance:FailingTwitterUserCleanUpThreshold` (default: 700) set the max allowed errors (due to a banned/deleted/private account) from a Twitter Account retrieval before auto-removal. (by default an account is called every 15 mins)
* `Instance:FailingFollowerCleanUpThreshold` (default: 30000) set the max allowed errors from a Follower (Fediverse) Account before auto-removal. (often due to account suppression, instance issues, etc)
* `Instance:UserCacheCapacity` (default: 10000) set the caching limit of the Twitter User retrieval. Must be higher than the number of synchronized accounts on the instance.
+* `Instance:IpWhiteListing` IP Whitelisting (separated by `;`), prevent usage of the instance from other IPs than those provided (if provided).
+* `Instance:EnableXRealIpHeader` (default: false) Enable support of X-Real-IP Header to get the remote IP (useful when using reverse proxy).
+* `Instance:MaxTweetRetention` (default: 20, min: 1, max: 90) Number of days before synchronized tweets get deleted
# Docker Compose full example
diff --git a/src/BSLManager/BSLManager.csproj b/src/BSLManager/BSLManager.csproj
index 52e5cde..1a725e9 100644
--- a/src/BSLManager/BSLManager.csproj
+++ b/src/BSLManager/BSLManager.csproj
@@ -2,15 +2,15 @@
Exe
- netcoreapp3.1
+ net6.0
-
-
-
-
-
+
+
+
+
+
diff --git a/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj b/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj
index a690b63..f914c67 100644
--- a/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj
+++ b/src/BirdsiteLive.ActivityPub/BirdsiteLive.ActivityPub.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/src/BirdsiteLive.ActivityPub/Models/Tombstone.cs b/src/BirdsiteLive.ActivityPub/Models/Tombstone.cs
new file mode 100644
index 0000000..49dcabe
--- /dev/null
+++ b/src/BirdsiteLive.ActivityPub/Models/Tombstone.cs
@@ -0,0 +1,9 @@
+namespace BirdsiteLive.ActivityPub.Models
+{
+ public class Tombstone
+ {
+ public string id { get; set; }
+ public readonly string type = "Tombstone";
+ public string atomUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Common/Settings/InstanceSettings.cs b/src/BirdsiteLive.Common/Settings/InstanceSettings.cs
index 0f701ff..0ece251 100644
--- a/src/BirdsiteLive.Common/Settings/InstanceSettings.cs
+++ b/src/BirdsiteLive.Common/Settings/InstanceSettings.cs
@@ -16,5 +16,9 @@ public class InstanceSettings
public int FailingFollowerCleanUpThreshold { get; set; } = -1;
public int UserCacheCapacity { get; set; }
+ public string IpWhiteListing { get; set; }
+ public bool EnableXRealIpHeader { get; set; }
+
+ public int MaxTweetRetention { get; set; }
}
}
diff --git a/src/BirdsiteLive.Cryptography/BirdsiteLive.Cryptography.csproj b/src/BirdsiteLive.Cryptography/BirdsiteLive.Cryptography.csproj
index f0d9f4f..4fc9f61 100644
--- a/src/BirdsiteLive.Cryptography/BirdsiteLive.Cryptography.csproj
+++ b/src/BirdsiteLive.Cryptography/BirdsiteLive.Cryptography.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/src/BirdsiteLive.Domain/ActivityPubService.cs b/src/BirdsiteLive.Domain/ActivityPubService.cs
index cab0c58..9a893c7 100644
--- a/src/BirdsiteLive.Domain/ActivityPubService.cs
+++ b/src/BirdsiteLive.Domain/ActivityPubService.cs
@@ -9,6 +9,7 @@
using BirdsiteLive.ActivityPub.Converters;
using BirdsiteLive.ActivityPub.Models;
using BirdsiteLive.Common.Settings;
+using BirdsiteLive.DAL.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -22,6 +23,7 @@ public interface IActivityPubService
Task PostNewNoteActivity(Note note, string username, string noteId, string targetHost,
string targetInbox);
Task DeleteUserAsync(string username, string targetHost, string targetInbox);
+ Task DeleteNoteAsync(SyncTweet tweet);
}
public class WebFinger
@@ -108,6 +110,30 @@ public async Task DeleteUserAsync(string username, string targetHost, string tar
}
}
+ public async Task DeleteNoteAsync(SyncTweet tweet)
+ {
+ var acct = tweet.Acct.ToLowerInvariant().Trim();
+
+ var actor = $"https://{_instanceSettings.Domain}/users/{acct}";
+ var noteId = $"https://{_instanceSettings.Domain}/users/{acct}/statuses/{tweet.TweetId}";
+
+ var delete = new ActivityDelete
+ {
+ context = "https://www.w3.org/ns/activitystreams",
+ id = $"{noteId}#delete",
+ type = "Delete",
+ actor = actor,
+ to = new[] { "https://www.w3.org/ns/activitystreams#Public" },
+ apObject = new Tombstone
+ {
+ id = noteId,
+ atomUrl = noteId
+ }
+ };
+
+ await PostDataAsync(delete, tweet.Host, actor, tweet.Inbox);
+ }
+
public async Task PostNewNoteActivity(Note note, string username, string noteId, string targetHost, string targetInbox)
{
try
diff --git a/src/BirdsiteLive.Domain/BirdsiteLive.Domain.csproj b/src/BirdsiteLive.Domain/BirdsiteLive.Domain.csproj
index 7bc9873..57219d8 100644
--- a/src/BirdsiteLive.Domain/BirdsiteLive.Domain.csproj
+++ b/src/BirdsiteLive.Domain/BirdsiteLive.Domain.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/src/BirdsiteLive.Domain/BusinessUseCases/ProcessDeleteUser.cs b/src/BirdsiteLive.Domain/BusinessUseCases/ProcessDeleteUser.cs
index a35b6c8..a36c963 100644
--- a/src/BirdsiteLive.Domain/BusinessUseCases/ProcessDeleteUser.cs
+++ b/src/BirdsiteLive.Domain/BusinessUseCases/ProcessDeleteUser.cs
@@ -9,6 +9,7 @@ public interface IProcessDeleteUser
{
Task ExecuteAsync(Follower follower);
Task ExecuteAsync(string followerUsername, string followerDomain);
+ Task ExecuteAsync(string actorId);
}
public class ProcessDeleteUser : IProcessDeleteUser
@@ -33,6 +34,15 @@ public async Task ExecuteAsync(string followerUsername, string followerDomain)
await ExecuteAsync(follower);
}
+ public async Task ExecuteAsync(string actorId)
+ {
+ // Get Follower and Twitter Users
+ var follower = await _followersDal.GetFollowerAsync(actorId);
+ if (follower == null) return;
+
+ await ExecuteAsync(follower);
+ }
+
public async Task ExecuteAsync(Follower follower)
{
// Remove twitter users if no more followers
diff --git a/src/BirdsiteLive.Domain/UserService.cs b/src/BirdsiteLive.Domain/UserService.cs
index 6f88543..ed31c62 100644
--- a/src/BirdsiteLive.Domain/UserService.cs
+++ b/src/BirdsiteLive.Domain/UserService.cs
@@ -280,15 +280,21 @@ public async Task UndoFollowRequestedAsync(string signature, string method
public async Task DeleteRequestedAsync(string signature, string method, string path, string queryString, Dictionary requestHeaders,
ActivityDelete activity, string body)
{
- // Validate
- var sigValidation = await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders, body);
- if (!sigValidation.SignatureIsValidated) return false;
+ if (activity.apObject is string apObject)
+ {
+ if (!string.Equals(activity.actor.Trim(), apObject.Trim(), StringComparison.InvariantCultureIgnoreCase)) return true;
- // Remove user and followings
- var followerUserName = SigValidationResultExtractor.GetUserName(sigValidation);
- var followerHost = SigValidationResultExtractor.GetHost(sigValidation);
+ try
+ {
+ // Validate
+ var sigValidation = await ValidateSignature(activity.actor, signature, method, path, queryString, requestHeaders, body);
+ if (!sigValidation.SignatureIsValidated) return false;
+ }
+ catch (FollowerIsGoneException){}
- await _processDeleteUser.ExecuteAsync(followerUserName, followerHost);
+ // Remove user and followings
+ await _processDeleteUser.ExecuteAsync(activity.actor.Trim());
+ }
return true;
}
diff --git a/src/BirdsiteLive.Pipeline/BirdsiteLive.Pipeline.csproj b/src/BirdsiteLive.Pipeline/BirdsiteLive.Pipeline.csproj
index 8601b19..bf20e96 100644
--- a/src/BirdsiteLive.Pipeline/BirdsiteLive.Pipeline.csproj
+++ b/src/BirdsiteLive.Pipeline/BirdsiteLive.Pipeline.csproj
@@ -1,14 +1,14 @@
- netstandard2.0
+ net6.0
latest
-
-
-
+
+
+
@@ -19,6 +19,7 @@
+
diff --git a/src/BirdsiteLive.Pipeline/Contracts/IRefreshTwitterUserStatusProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/Federation/IRefreshTwitterUserStatusProcessor.cs
similarity index 85%
rename from src/BirdsiteLive.Pipeline/Contracts/IRefreshTwitterUserStatusProcessor.cs
rename to src/BirdsiteLive.Pipeline/Contracts/Federation/IRefreshTwitterUserStatusProcessor.cs
index 9f20e59..bf74efd 100644
--- a/src/BirdsiteLive.Pipeline/Contracts/IRefreshTwitterUserStatusProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Contracts/Federation/IRefreshTwitterUserStatusProcessor.cs
@@ -3,7 +3,7 @@
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Pipeline.Models;
-namespace BirdsiteLive.Pipeline.Contracts
+namespace BirdsiteLive.Pipeline.Contracts.Federation
{
public interface IRefreshTwitterUserStatusProcessor
{
diff --git a/src/BirdsiteLive.Pipeline/Contracts/IRetrieveFollowersProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveFollowersProcessor.cs
similarity index 89%
rename from src/BirdsiteLive.Pipeline/Contracts/IRetrieveFollowersProcessor.cs
rename to src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveFollowersProcessor.cs
index a9ef35c..567d41b 100644
--- a/src/BirdsiteLive.Pipeline/Contracts/IRetrieveFollowersProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveFollowersProcessor.cs
@@ -3,7 +3,7 @@
using System.Threading.Tasks;
using BirdsiteLive.Pipeline.Models;
-namespace BirdsiteLive.Pipeline.Contracts
+namespace BirdsiteLive.Pipeline.Contracts.Federation
{
public interface IRetrieveFollowersProcessor
{
diff --git a/src/BirdsiteLive.Pipeline/Contracts/IRetrieveTweetsProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveTweetsProcessor.cs
similarity index 84%
rename from src/BirdsiteLive.Pipeline/Contracts/IRetrieveTweetsProcessor.cs
rename to src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveTweetsProcessor.cs
index 49712c2..0cbf2cb 100644
--- a/src/BirdsiteLive.Pipeline/Contracts/IRetrieveTweetsProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveTweetsProcessor.cs
@@ -3,7 +3,7 @@
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Pipeline.Models;
-namespace BirdsiteLive.Pipeline.Contracts
+namespace BirdsiteLive.Pipeline.Contracts.Federation
{
public interface IRetrieveTweetsProcessor
{
diff --git a/src/BirdsiteLive.Pipeline/Contracts/IRetrieveTwitterUsersProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveTwitterUsersProcessor.cs
similarity index 85%
rename from src/BirdsiteLive.Pipeline/Contracts/IRetrieveTwitterUsersProcessor.cs
rename to src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveTwitterUsersProcessor.cs
index b71ae93..c6d08d0 100644
--- a/src/BirdsiteLive.Pipeline/Contracts/IRetrieveTwitterUsersProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Contracts/Federation/IRetrieveTwitterUsersProcessor.cs
@@ -3,7 +3,7 @@
using System.Threading.Tasks.Dataflow;
using BirdsiteLive.DAL.Models;
-namespace BirdsiteLive.Pipeline.Contracts
+namespace BirdsiteLive.Pipeline.Contracts.Federation
{
public interface IRetrieveTwitterUsersProcessor
{
diff --git a/src/BirdsiteLive.Pipeline/Contracts/ISaveProgressionProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/Federation/ISaveProgressionProcessor.cs
similarity index 82%
rename from src/BirdsiteLive.Pipeline/Contracts/ISaveProgressionProcessor.cs
rename to src/BirdsiteLive.Pipeline/Contracts/Federation/ISaveProgressionProcessor.cs
index 6b1c9ba..f0320cb 100644
--- a/src/BirdsiteLive.Pipeline/Contracts/ISaveProgressionProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Contracts/Federation/ISaveProgressionProcessor.cs
@@ -2,7 +2,7 @@
using System.Threading.Tasks;
using BirdsiteLive.Pipeline.Models;
-namespace BirdsiteLive.Pipeline.Contracts
+namespace BirdsiteLive.Pipeline.Contracts.Federation
{
public interface ISaveProgressionProcessor
{
diff --git a/src/BirdsiteLive.Pipeline/Contracts/ISendTweetsToFollowersProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/Federation/ISendTweetsToFollowersProcessor.cs
similarity index 83%
rename from src/BirdsiteLive.Pipeline/Contracts/ISendTweetsToFollowersProcessor.cs
rename to src/BirdsiteLive.Pipeline/Contracts/Federation/ISendTweetsToFollowersProcessor.cs
index 33db423..49d47f0 100644
--- a/src/BirdsiteLive.Pipeline/Contracts/ISendTweetsToFollowersProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Contracts/Federation/ISendTweetsToFollowersProcessor.cs
@@ -2,7 +2,7 @@
using System.Threading.Tasks;
using BirdsiteLive.Pipeline.Models;
-namespace BirdsiteLive.Pipeline.Contracts
+namespace BirdsiteLive.Pipeline.Contracts.Federation
{
public interface ISendTweetsToFollowersProcessor
{
diff --git a/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/IDeleteTweetsProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/IDeleteTweetsProcessor.cs
new file mode 100644
index 0000000..c74659d
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/IDeleteTweetsProcessor.cs
@@ -0,0 +1,11 @@
+using System.Threading;
+using System.Threading.Tasks;
+using BirdsiteLive.Pipeline.Models;
+
+namespace BirdsiteLive.Pipeline.Contracts.TweetsCleanUp
+{
+ public interface IDeleteTweetsProcessor
+ {
+ Task ProcessAsync(TweetToDelete tweet, CancellationToken ct);
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/IRetrieveTweetsToDeleteProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/IRetrieveTweetsToDeleteProcessor.cs
new file mode 100644
index 0000000..45f9442
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/IRetrieveTweetsToDeleteProcessor.cs
@@ -0,0 +1,12 @@
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using BirdsiteLive.Pipeline.Models;
+
+namespace BirdsiteLive.Pipeline.Contracts.TweetsCleanUp
+{
+ public interface IRetrieveTweetsToDeleteProcessor
+ {
+ Task GetTweetsAsync(BufferBlock tweetsBufferBlock, CancellationToken ct);
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/ISaveDeletedTweetStatusProcessor.cs b/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/ISaveDeletedTweetStatusProcessor.cs
new file mode 100644
index 0000000..0486fb9
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Contracts/TweetsCleanUp/ISaveDeletedTweetStatusProcessor.cs
@@ -0,0 +1,11 @@
+using System.Threading;
+using System.Threading.Tasks;
+using BirdsiteLive.Pipeline.Models;
+
+namespace BirdsiteLive.Pipeline.Contracts.TweetsCleanUp
+{
+ public interface ISaveDeletedTweetStatusProcessor
+ {
+ Task ProcessAsync(TweetToDelete tweetToDelete, CancellationToken ct);
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/Models/TweetToDelete.cs b/src/BirdsiteLive.Pipeline/Models/TweetToDelete.cs
new file mode 100644
index 0000000..dd411f9
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Models/TweetToDelete.cs
@@ -0,0 +1,10 @@
+using BirdsiteLive.DAL.Models;
+
+namespace BirdsiteLive.Pipeline.Models
+{
+ public class TweetToDelete
+ {
+ public SyncTweet Tweet { get; set; }
+ public bool DeleteSuccessful { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/Processors/RefreshTwitterUserStatusProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/RefreshTwitterUserStatusProcessor.cs
similarity index 97%
rename from src/BirdsiteLive.Pipeline/Processors/RefreshTwitterUserStatusProcessor.cs
rename to src/BirdsiteLive.Pipeline/Processors/Federation/RefreshTwitterUserStatusProcessor.cs
index 739d50b..a3e7ab2 100644
--- a/src/BirdsiteLive.Pipeline/Processors/RefreshTwitterUserStatusProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/RefreshTwitterUserStatusProcessor.cs
@@ -6,12 +6,12 @@
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Moderation.Actions;
-using BirdsiteLive.Pipeline.Contracts;
+using BirdsiteLive.Pipeline.Contracts.Federation;
using BirdsiteLive.Pipeline.Models;
using BirdsiteLive.Twitter;
using BirdsiteLive.Twitter.Models;
-namespace BirdsiteLive.Pipeline.Processors
+namespace BirdsiteLive.Pipeline.Processors.Federation
{
public class RefreshTwitterUserStatusProcessor : IRefreshTwitterUserStatusProcessor
{
diff --git a/src/BirdsiteLive.Pipeline/Processors/RetrieveFollowersProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveFollowersProcessor.cs
similarity index 89%
rename from src/BirdsiteLive.Pipeline/Processors/RetrieveFollowersProcessor.cs
rename to src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveFollowersProcessor.cs
index 57e3e49..7f696f7 100644
--- a/src/BirdsiteLive.Pipeline/Processors/RetrieveFollowersProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveFollowersProcessor.cs
@@ -2,10 +2,10 @@
using System.Threading;
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
-using BirdsiteLive.Pipeline.Contracts;
+using BirdsiteLive.Pipeline.Contracts.Federation;
using BirdsiteLive.Pipeline.Models;
-namespace BirdsiteLive.Pipeline.Processors
+namespace BirdsiteLive.Pipeline.Processors.Federation
{
public class RetrieveFollowersProcessor : IRetrieveFollowersProcessor
{
diff --git a/src/BirdsiteLive.Pipeline/Processors/RetrieveTweetsProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveTweetsProcessor.cs
similarity index 96%
rename from src/BirdsiteLive.Pipeline/Processors/RetrieveTweetsProcessor.cs
rename to src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveTweetsProcessor.cs
index 321fbf0..d38feb2 100644
--- a/src/BirdsiteLive.Pipeline/Processors/RetrieveTweetsProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveTweetsProcessor.cs
@@ -5,14 +5,14 @@
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
-using BirdsiteLive.Pipeline.Contracts;
+using BirdsiteLive.Pipeline.Contracts.Federation;
using BirdsiteLive.Pipeline.Models;
using BirdsiteLive.Twitter;
using BirdsiteLive.Twitter.Models;
using Microsoft.Extensions.Logging;
using Tweetinvi.Models;
-namespace BirdsiteLive.Pipeline.Processors
+namespace BirdsiteLive.Pipeline.Processors.Federation
{
public class RetrieveTweetsProcessor : IRetrieveTweetsProcessor
{
@@ -64,7 +64,7 @@ public async Task ProcessAsync(UserWithDataToSync[] syncTw
private ExtractedTweet[] RetrieveNewTweets(SyncTwitterUser user)
{
var tweets = new ExtractedTweet[0];
-
+
try
{
if (user.LastTweetPostedId == -1)
diff --git a/src/BirdsiteLive.Pipeline/Processors/RetrieveTwitterUsersProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveTwitterUsersProcessor.cs
similarity index 93%
rename from src/BirdsiteLive.Pipeline/Processors/RetrieveTwitterUsersProcessor.cs
rename to src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveTwitterUsersProcessor.cs
index d9d0ffb..7d16ac5 100644
--- a/src/BirdsiteLive.Pipeline/Processors/RetrieveTwitterUsersProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/RetrieveTwitterUsersProcessor.cs
@@ -7,18 +7,18 @@
using BirdsiteLive.Common.Settings;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
-using BirdsiteLive.Pipeline.Contracts;
+using BirdsiteLive.Pipeline.Contracts.Federation;
using BirdsiteLive.Pipeline.Tools;
using Microsoft.Extensions.Logging;
-namespace BirdsiteLive.Pipeline.Processors
+namespace BirdsiteLive.Pipeline.Processors.Federation
{
public class RetrieveTwitterUsersProcessor : IRetrieveTwitterUsersProcessor
{
private readonly ITwitterUserDal _twitterUserDal;
private readonly IMaxUsersNumberProvider _maxUsersNumberProvider;
private readonly ILogger _logger;
-
+
public int WaitFactor = 1000 * 60; //1 min
#region Ctor
@@ -42,7 +42,7 @@ public async Task GetTwitterUsersAsync(BufferBlock twitterUse
var users = await _twitterUserDal.GetAllTwitterUsersAsync(maxUsersNumber, false);
var userCount = users.Any() ? users.Length : 1;
- var splitNumber = (int) Math.Ceiling(userCount / 15d);
+ var splitNumber = (int)Math.Ceiling(userCount / 15d);
var splitUsers = users.Split(splitNumber).ToList();
foreach (var u in splitUsers)
diff --git a/src/BirdsiteLive.Pipeline/Processors/SaveProgressionProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/SaveProgressionProcessor.cs
similarity index 94%
rename from src/BirdsiteLive.Pipeline/Processors/SaveProgressionProcessor.cs
rename to src/BirdsiteLive.Pipeline/Processors/Federation/SaveProgressionProcessor.cs
index 1f94871..0b29769 100644
--- a/src/BirdsiteLive.Pipeline/Processors/SaveProgressionProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/SaveProgressionProcessor.cs
@@ -5,11 +5,11 @@
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Moderation.Actions;
-using BirdsiteLive.Pipeline.Contracts;
+using BirdsiteLive.Pipeline.Contracts.Federation;
using BirdsiteLive.Pipeline.Models;
using Microsoft.Extensions.Logging;
-namespace BirdsiteLive.Pipeline.Processors
+namespace BirdsiteLive.Pipeline.Processors.Federation
{
public class SaveProgressionProcessor : ISaveProgressionProcessor
{
@@ -36,13 +36,13 @@ public async Task ProcessAsync(UserWithDataToSync userWithTweetsToSync, Cancella
await UpdateUserSyncDateAsync(userWithTweetsToSync.User);
return;
}
- if(userWithTweetsToSync.Followers.Length == 0)
+ if (userWithTweetsToSync.Followers.Length == 0)
{
_logger.LogInformation("No Followers found for {User}", userWithTweetsToSync.User.Acct);
await _removeTwitterAccountAction.ProcessAsync(userWithTweetsToSync.User);
return;
}
-
+
var userId = userWithTweetsToSync.User.Id;
var followingSyncStatuses = userWithTweetsToSync.Followers.Select(x => x.FollowingsSyncStatus[userId]).ToList();
var lastPostedTweet = userWithTweetsToSync.Tweets.Select(x => x.Id).Max();
diff --git a/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/SendTweetsToFollowersProcessor.cs
similarity index 97%
rename from src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs
rename to src/BirdsiteLive.Pipeline/Processors/Federation/SendTweetsToFollowersProcessor.cs
index e210f39..3a52544 100644
--- a/src/BirdsiteLive.Pipeline/Processors/SendTweetsToFollowersProcessor.cs
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/SendTweetsToFollowersProcessor.cs
@@ -10,7 +10,7 @@
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Domain;
using BirdsiteLive.Moderation.Actions;
-using BirdsiteLive.Pipeline.Contracts;
+using BirdsiteLive.Pipeline.Contracts.Federation;
using BirdsiteLive.Pipeline.Models;
using BirdsiteLive.Pipeline.Processors.SubTasks;
using BirdsiteLive.Twitter;
@@ -18,7 +18,7 @@
using Microsoft.Extensions.Logging;
using Tweetinvi.Models;
-namespace BirdsiteLive.Pipeline.Processors
+namespace BirdsiteLive.Pipeline.Processors.Federation
{
public class SendTweetsToFollowersProcessor : ISendTweetsToFollowersProcessor
{
@@ -83,7 +83,7 @@ private async Task ProcessFollowersWithSharedInboxAsync(ExtractedTweet[] tweets,
}
}
}
-
+
private async Task ProcessFollowersWithInboxAsync(ExtractedTweet[] tweets, List followerWtInbox, SyncTwitterUser user)
{
foreach (var follower in followerWtInbox)
@@ -114,7 +114,7 @@ private async Task ProcessFailingUserAsync(Follower follower)
{
follower.PostingErrorCount++;
- if (follower.PostingErrorCount > _instanceSettings.FailingFollowerCleanUpThreshold
+ if (follower.PostingErrorCount > _instanceSettings.FailingFollowerCleanUpThreshold
&& _instanceSettings.FailingFollowerCleanUpThreshold > 0
|| follower.PostingErrorCount > 2147483600)
{
diff --git a/src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsTaskBase.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsTaskBase.cs
new file mode 100644
index 0000000..1cb9be9
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsTaskBase.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading.Tasks;
+using BirdsiteLive.DAL.Contracts;
+using BirdsiteLive.DAL.Models;
+
+namespace BirdsiteLive.Pipeline.Processors.SubTasks
+{
+ public class SendTweetsTaskBase
+ {
+ private readonly ISyncTweetsPostgresDal _syncTweetsPostgresDal;
+
+ #region Ctor
+ protected SendTweetsTaskBase(ISyncTweetsPostgresDal syncTweetsPostgresDal)
+ {
+ _syncTweetsPostgresDal = syncTweetsPostgresDal;
+ }
+ #endregion
+
+ protected async Task SaveSyncTweetAsync(string acct, long tweetId, string host, string inbox)
+ {
+ var tweet = new SyncTweet
+ {
+ Acct = acct,
+ TweetId = tweetId,
+ PublishedAt = DateTime.UtcNow,
+ Inbox = inbox,
+ Host = host
+ };
+ await _syncTweetsPostgresDal.SaveTweetAsync(tweet);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToInboxTask.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsToInboxTask.cs
similarity index 92%
rename from src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToInboxTask.cs
rename to src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsToInboxTask.cs
index a6f6982..0c91750 100644
--- a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToInboxTask.cs
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsToInboxTask.cs
@@ -17,17 +17,16 @@ public interface ISendTweetsToInboxTask
Task ExecuteAsync(IEnumerable tweets, Follower follower, SyncTwitterUser user);
}
- public class SendTweetsToInboxTask : ISendTweetsToInboxTask
+ public class SendTweetsToInboxTask : SendTweetsTaskBase, ISendTweetsToInboxTask
{
private readonly IActivityPubService _activityPubService;
private readonly IStatusService _statusService;
private readonly IFollowersDal _followersDal;
private readonly InstanceSettings _settings;
private readonly ILogger _logger;
-
-
+
#region Ctor
- public SendTweetsToInboxTask(IActivityPubService activityPubService, IStatusService statusService, IFollowersDal followersDal, InstanceSettings settings, ILogger logger)
+ public SendTweetsToInboxTask(IActivityPubService activityPubService, IStatusService statusService, IFollowersDal followersDal, InstanceSettings settings, ILogger logger, ISyncTweetsPostgresDal syncTweetsPostgresDal): base(syncTweetsPostgresDal)
{
_activityPubService = activityPubService;
_statusService = statusService;
@@ -61,6 +60,7 @@ public async Task ExecuteAsync(IEnumerable tweets, Follower foll
{
var note = _statusService.GetStatus(user.Acct, tweet);
await _activityPubService.PostNewNoteActivity(note, user.Acct, tweet.Id.ToString(), follower.Host, inbox);
+ await SaveSyncTweetAsync(user.Acct, tweet.Id, follower.Host, inbox);
}
}
catch (ArgumentException e)
diff --git a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs b/src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsToSharedInboxTask.cs
similarity index 91%
rename from src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs
rename to src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsToSharedInboxTask.cs
index 1abe183..95f32fd 100644
--- a/src/BirdsiteLive.Pipeline/Processors/SubTasks/SendTweetsToSharedInboxTask.cs
+++ b/src/BirdsiteLive.Pipeline/Processors/Federation/SubTasks/SendTweetsToSharedInboxTask.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Net;
using System.Threading.Tasks;
+using BirdsiteLive.ActivityPub.Models;
using BirdsiteLive.Common.Settings;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
@@ -16,7 +17,7 @@ public interface ISendTweetsToSharedInboxTask
Task ExecuteAsync(ExtractedTweet[] tweets, SyncTwitterUser user, string host, Follower[] followersPerInstance);
}
- public class SendTweetsToSharedInboxTask : ISendTweetsToSharedInboxTask
+ public class SendTweetsToSharedInboxTask : SendTweetsTaskBase, ISendTweetsToSharedInboxTask
{
private readonly IStatusService _statusService;
private readonly IActivityPubService _activityPubService;
@@ -25,7 +26,7 @@ public class SendTweetsToSharedInboxTask : ISendTweetsToSharedInboxTask
private readonly ILogger _logger;
#region Ctor
- public SendTweetsToSharedInboxTask(IActivityPubService activityPubService, IStatusService statusService, IFollowersDal followersDal, InstanceSettings settings, ILogger logger)
+ public SendTweetsToSharedInboxTask(IActivityPubService activityPubService, IStatusService statusService, IFollowersDal followersDal, InstanceSettings settings, ILogger logger, ISyncTweetsPostgresDal syncTweetsPostgresDal): base(syncTweetsPostgresDal)
{
_activityPubService = activityPubService;
_statusService = statusService;
@@ -61,6 +62,7 @@ public async Task ExecuteAsync(ExtractedTweet[] tweets, SyncTwitterUser user, st
{
var note = _statusService.GetStatus(user.Acct, tweet);
await _activityPubService.PostNewNoteActivity(note, user.Acct, tweet.Id.ToString(), host, inbox);
+ await SaveSyncTweetAsync(user.Acct, tweet.Id, host, inbox);
}
}
catch (ArgumentException e)
diff --git a/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/Base/RetentionBase.cs b/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/Base/RetentionBase.cs
new file mode 100644
index 0000000..58942ac
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/Base/RetentionBase.cs
@@ -0,0 +1,16 @@
+using BirdsiteLive.Common.Settings;
+using System;
+
+namespace BirdsiteLive.Pipeline.Processors.TweetsCleanUp.Base
+{
+ public class RetentionBase
+ {
+ protected int GetRetentionTime(InstanceSettings settings)
+ {
+ var retentionTime = Math.Abs(settings.MaxTweetRetention);
+ if (retentionTime < 1) retentionTime = 1;
+ if (retentionTime > 90) retentionTime = 90;
+ return retentionTime;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/DeleteTweetsProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/DeleteTweetsProcessor.cs
new file mode 100644
index 0000000..a9841a9
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/DeleteTweetsProcessor.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using BirdsiteLive.Domain;
+using BirdsiteLive.Pipeline.Contracts.TweetsCleanUp;
+using BirdsiteLive.Pipeline.Models;
+using Microsoft.Extensions.Logging;
+
+namespace BirdsiteLive.Pipeline.Processors.TweetsCleanUp
+{
+ public class DeleteTweetsProcessor : IDeleteTweetsProcessor
+ {
+ private readonly IActivityPubService _activityPubService;
+ private readonly ILogger _logger;
+
+ #region Ctor
+ public DeleteTweetsProcessor(IActivityPubService activityPubService, ILogger logger)
+ {
+ _activityPubService = activityPubService;
+ _logger = logger;
+ }
+ #endregion
+
+ public async Task ProcessAsync(TweetToDelete tweet, CancellationToken ct)
+ {
+ try
+ {
+ await _activityPubService.DeleteNoteAsync(tweet.Tweet);
+ tweet.DeleteSuccessful = true;
+ }
+ catch (HttpRequestException e)
+ {
+ var code = e.StatusCode;
+ if (code is HttpStatusCode.Gone or HttpStatusCode.NotFound)
+ {
+ _logger.LogInformation("Tweet already deleted");
+ tweet.DeleteSuccessful = true;
+ }
+ else
+ {
+ _logger.LogError(e.Message, e);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e.Message, e);
+ }
+
+ return tweet;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/RetrieveTweetsToDeleteProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/RetrieveTweetsToDeleteProcessor.cs
new file mode 100644
index 0000000..daf4729
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/RetrieveTweetsToDeleteProcessor.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using BirdsiteLive.Common.Settings;
+using BirdsiteLive.DAL.Contracts;
+using BirdsiteLive.Pipeline.Contracts.TweetsCleanUp;
+using BirdsiteLive.Pipeline.Models;
+using BirdsiteLive.Pipeline.Processors.TweetsCleanUp.Base;
+
+namespace BirdsiteLive.Pipeline.Processors.TweetsCleanUp
+{
+ public class RetrieveTweetsToDeleteProcessor : RetentionBase, IRetrieveTweetsToDeleteProcessor
+ {
+ private readonly ISyncTweetsPostgresDal _syncTweetsPostgresDal;
+ private readonly InstanceSettings _instanceSettings;
+
+ #region Ctor
+ public RetrieveTweetsToDeleteProcessor(ISyncTweetsPostgresDal syncTweetsPostgresDal, InstanceSettings instanceSettings)
+ {
+ _syncTweetsPostgresDal = syncTweetsPostgresDal;
+ _instanceSettings = instanceSettings;
+ }
+ #endregion
+
+ public async Task GetTweetsAsync(BufferBlock tweetsBufferBlock, CancellationToken ct)
+ {
+ var batchSize = 100;
+
+ for (;;)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ var now = DateTime.UtcNow;
+
+
+ var from = now.AddDays(-GetRetentionTime(_instanceSettings));
+ var dbBrowsingEnded = false;
+ var lastId = -1L;
+
+ do
+ {
+ var tweets = await _syncTweetsPostgresDal.GetTweetsOlderThanAsync(from, lastId, batchSize);
+
+ foreach (var syncTweet in tweets)
+ {
+ var tweet = new TweetToDelete
+ {
+ Tweet = syncTweet
+ };
+ await tweetsBufferBlock.SendAsync(tweet, ct);
+ }
+
+ if (tweets.Any()) lastId = tweets.Last().Id;
+ if (tweets.Count < batchSize) dbBrowsingEnded = true;
+
+ } while (!dbBrowsingEnded);
+
+ await Task.Delay(TimeSpan.FromHours(3), ct);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/SaveDeletedTweetStatusProcessor.cs b/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/SaveDeletedTweetStatusProcessor.cs
new file mode 100644
index 0000000..817d0be
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/Processors/TweetsCleanUp/SaveDeletedTweetStatusProcessor.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using BirdsiteLive.Common.Settings;
+using BirdsiteLive.DAL.Contracts;
+using BirdsiteLive.Pipeline.Contracts.TweetsCleanUp;
+using BirdsiteLive.Pipeline.Models;
+using BirdsiteLive.Pipeline.Processors.TweetsCleanUp.Base;
+
+namespace BirdsiteLive.Pipeline.Processors.TweetsCleanUp
+{
+ public class SaveDeletedTweetStatusProcessor : RetentionBase, ISaveDeletedTweetStatusProcessor
+ {
+ private readonly ISyncTweetsPostgresDal _syncTweetsPostgresDal;
+ private readonly InstanceSettings _instanceSettings;
+
+ #region Ctor
+ public SaveDeletedTweetStatusProcessor(ISyncTweetsPostgresDal syncTweetsPostgresDal, InstanceSettings instanceSettings)
+ {
+ _syncTweetsPostgresDal = syncTweetsPostgresDal;
+ _instanceSettings = instanceSettings;
+ }
+ #endregion
+
+ public async Task ProcessAsync(TweetToDelete tweetToDelete, CancellationToken ct)
+ {
+ var retentionTime = GetRetentionTime(_instanceSettings);
+ retentionTime += 20; // Delay until last retry
+ var highLimitDate = DateTime.UtcNow.AddDays(-retentionTime);
+ if (tweetToDelete.DeleteSuccessful || tweetToDelete.Tweet.PublishedAt < highLimitDate)
+ {
+ await _syncTweetsPostgresDal.DeleteTweetAsync(tweetToDelete.Tweet.Id);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Pipeline/StatusPublicationPipeline.cs b/src/BirdsiteLive.Pipeline/StatusPublicationPipeline.cs
index c6917e7..f0a81e6 100644
--- a/src/BirdsiteLive.Pipeline/StatusPublicationPipeline.cs
+++ b/src/BirdsiteLive.Pipeline/StatusPublicationPipeline.cs
@@ -4,7 +4,7 @@
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using BirdsiteLive.DAL.Models;
-using BirdsiteLive.Pipeline.Contracts;
+using BirdsiteLive.Pipeline.Contracts.Federation;
using BirdsiteLive.Pipeline.Models;
using Microsoft.Extensions.Logging;
diff --git a/src/BirdsiteLive.Pipeline/TweetCleanUpPipeline.cs b/src/BirdsiteLive.Pipeline/TweetCleanUpPipeline.cs
new file mode 100644
index 0000000..10f69c0
--- /dev/null
+++ b/src/BirdsiteLive.Pipeline/TweetCleanUpPipeline.cs
@@ -0,0 +1,76 @@
+using System.Threading.Tasks;
+using System.Threading;
+using System.Threading.Tasks.Dataflow;
+using BirdsiteLive.Pipeline.Contracts.TweetsCleanUp;
+using BirdsiteLive.Pipeline.Models;
+using Microsoft.Extensions.Logging;
+
+namespace BirdsiteLive.Pipeline
+{
+ public interface ITweetCleanUpPipeline
+ {
+ Task ExecuteAsync(CancellationToken ct);
+ }
+
+ public class TweetCleanUpPipeline : ITweetCleanUpPipeline
+ {
+ private readonly IRetrieveTweetsToDeleteProcessor _retrieveTweetsToDeleteProcessor;
+ private readonly IDeleteTweetsProcessor _deleteTweetsProcessor;
+ private readonly ISaveDeletedTweetStatusProcessor _saveDeletedTweetStatusProcessor;
+ private readonly ILogger _logger;
+
+ #region Ctor
+ public TweetCleanUpPipeline(IRetrieveTweetsToDeleteProcessor retrieveTweetsToDeleteProcessor, IDeleteTweetsProcessor deleteTweetsProcessor, ISaveDeletedTweetStatusProcessor saveDeletedTweetStatusProcessor, ILogger logger)
+ {
+ _retrieveTweetsToDeleteProcessor = retrieveTweetsToDeleteProcessor;
+ _deleteTweetsProcessor = deleteTweetsProcessor;
+ _saveDeletedTweetStatusProcessor = saveDeletedTweetStatusProcessor;
+ _logger = logger;
+ }
+ #endregion
+
+ public async Task ExecuteAsync(CancellationToken ct)
+ {
+ // Create blocks
+ var tweetsToDeleteBufferBlock = new BufferBlock(new DataflowBlockOptions
+ {
+ BoundedCapacity = 200,
+ CancellationToken = ct
+ });
+ var deleteTweetsBlock = new TransformBlock(
+ async x => await _deleteTweetsProcessor.ProcessAsync(x, ct),
+ new ExecutionDataflowBlockOptions()
+ {
+ MaxDegreeOfParallelism = 5,
+ CancellationToken = ct
+ });
+ var deletedTweetsBufferBlock = new BufferBlock(new DataflowBlockOptions
+ {
+ BoundedCapacity = 200,
+ CancellationToken = ct
+ });
+ var saveProgressionBlock = new ActionBlock(
+ async x => await _saveDeletedTweetStatusProcessor.ProcessAsync(x, ct),
+ new ExecutionDataflowBlockOptions
+ {
+ MaxDegreeOfParallelism = 5,
+ CancellationToken = ct
+ });
+
+ // Link pipeline
+ var dataflowLinkOptions = new DataflowLinkOptions { PropagateCompletion = true };
+ tweetsToDeleteBufferBlock.LinkTo(deleteTweetsBlock, dataflowLinkOptions);
+ deleteTweetsBlock.LinkTo(deletedTweetsBufferBlock, dataflowLinkOptions);
+ deletedTweetsBufferBlock.LinkTo(saveProgressionBlock, dataflowLinkOptions);
+
+ // Launch tweet retriever
+ var retrieveTweetsToDeleteTask = _retrieveTweetsToDeleteProcessor.GetTweetsAsync(tweetsToDeleteBufferBlock, ct);
+
+ // Wait
+ await Task.WhenAny(new[] { retrieveTweetsToDeleteTask, saveProgressionBlock.Completion });
+
+ var ex = retrieveTweetsToDeleteTask.IsFaulted ? retrieveTweetsToDeleteTask.Exception : saveProgressionBlock.Completion.Exception;
+ _logger.LogCritical(ex, "An error occurred, pipeline stopped");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive.Twitter/BirdsiteLive.Twitter.csproj b/src/BirdsiteLive.Twitter/BirdsiteLive.Twitter.csproj
index 438b4e1..49ddda9 100644
--- a/src/BirdsiteLive.Twitter/BirdsiteLive.Twitter.csproj
+++ b/src/BirdsiteLive.Twitter/BirdsiteLive.Twitter.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/src/BirdsiteLive/BirdsiteLive.csproj b/src/BirdsiteLive/BirdsiteLive.csproj
index 122ff88..acdd0be 100644
--- a/src/BirdsiteLive/BirdsiteLive.csproj
+++ b/src/BirdsiteLive/BirdsiteLive.csproj
@@ -1,17 +1,17 @@
- netcoreapp3.1
+ net6.0
d21486de-a812-47eb-a419-05682bb68856
Linux
- 0.22.0
+ 0.23.0
-
-
-
-
+
+
+
+
diff --git a/src/BirdsiteLive/Controllers/DebugingController.cs b/src/BirdsiteLive/Controllers/DebugingController.cs
index 8f37f22..f3e7d4e 100644
--- a/src/BirdsiteLive/Controllers/DebugingController.cs
+++ b/src/BirdsiteLive/Controllers/DebugingController.cs
@@ -56,6 +56,35 @@ public async Task Follow()
return View("Index");
}
+ private static string _noteId;
+
+ [HttpPost]
+ public async Task DeleteNote()
+ {
+ var username = "twitter";
+ var actor = $"https://{_instanceSettings.Domain}/users/{username}";
+ var targetHost = "ioc.exchange";
+ var target = $"https://{targetHost}/users/test";
+ var inbox = $"/inbox";
+
+ var delete = new ActivityDelete
+ {
+ context = "https://www.w3.org/ns/activitystreams",
+ id = $"{_noteId}#delete",
+ type = "Delete",
+ actor = actor,
+ to = new[] { "https://www.w3.org/ns/activitystreams#Public" },
+ apObject = new Tombstone
+ {
+ id = _noteId,
+ atomUrl = _noteId
+ }
+ };
+
+ await _activityPubService.PostDataAsync(delete, targetHost, actor, inbox);
+ return View("Index");
+ }
+
[HttpPost]
public async Task PostNote()
{
@@ -70,6 +99,8 @@ public async Task PostNote()
var noteId = $"https://{_instanceSettings.Domain}/users/{username}/statuses/{noteGuid}";
var noteUrl = $"https://{_instanceSettings.Domain}/@{username}/{noteGuid}";
+ _noteId = noteId;
+
var to = $"{actor}/followers";
to = target;
diff --git a/src/BirdsiteLive/Controllers/InboxController.cs b/src/BirdsiteLive/Controllers/InboxController.cs
index f92a0a6..57825af 100644
--- a/src/BirdsiteLive/Controllers/InboxController.cs
+++ b/src/BirdsiteLive/Controllers/InboxController.cs
@@ -49,14 +49,15 @@ public async Task Inbox()
case "Delete":
{
var succeeded = await _userService.DeleteRequestedAsync(signature, r.Method, r.Path,
- r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers), activity as ActivityDelete, body);
+ r.QueryString.ToString(), HeaderHandler.RequestHeaders(r.Headers),
+ activity as ActivityDelete, body);
if (succeeded) return Accepted();
else return Unauthorized();
}
}
}
}
- catch (FollowerIsGoneException) { } //TODO: check if user in DB
+ catch (FollowerIsGoneException) { }
return Accepted();
}
diff --git a/src/BirdsiteLive/Controllers/MigrationController.cs b/src/BirdsiteLive/Controllers/MigrationController.cs
index f2cde09..8d4b816 100644
--- a/src/BirdsiteLive/Controllers/MigrationController.cs
+++ b/src/BirdsiteLive/Controllers/MigrationController.cs
@@ -3,7 +3,6 @@
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
-using Npgsql.TypeHandlers;
using BirdsiteLive.Domain;
using BirdsiteLive.Domain.Enum;
using BirdsiteLive.DAL.Contracts;
diff --git a/src/BirdsiteLive/Controllers/UsersController.cs b/src/BirdsiteLive/Controllers/UsersController.cs
index e66dac5..c636c0e 100644
--- a/src/BirdsiteLive/Controllers/UsersController.cs
+++ b/src/BirdsiteLive/Controllers/UsersController.cs
@@ -3,7 +3,6 @@
using System.IO;
using System.Linq;
using System.Net.Mime;
-using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
diff --git a/src/BirdsiteLive/Middlewares/IpWhitelistingMiddleware.cs b/src/BirdsiteLive/Middlewares/IpWhitelistingMiddleware.cs
new file mode 100644
index 0000000..da4c275
--- /dev/null
+++ b/src/BirdsiteLive/Middlewares/IpWhitelistingMiddleware.cs
@@ -0,0 +1,82 @@
+using BirdsiteLive.Common.Settings;
+using BirdsiteLive.Domain.Tools;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace BirdsiteLive.Middlewares
+{
+ public class IpWhitelistingMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+ private readonly InstanceSettings _instanceSettings;
+ private readonly byte[][] _safelist;
+ private readonly bool _ipWhitelistingSet;
+
+ public IpWhitelistingMiddleware(
+ RequestDelegate next,
+ ILogger logger,
+ InstanceSettings instanceSettings)
+ {
+ if (!string.IsNullOrWhiteSpace(instanceSettings.IpWhiteListing))
+ {
+ var ips = PatternsParser.Parse(instanceSettings.IpWhiteListing);
+ _safelist = new byte[ips.Length][];
+ for (var i = 0; i < ips.Length; i++)
+ {
+ _safelist[i] = IPAddress.Parse(ips[i]).GetAddressBytes();
+ }
+ _ipWhitelistingSet = true;
+ }
+
+ _next = next;
+ _logger = logger;
+ _instanceSettings = instanceSettings;
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ if (_ipWhitelistingSet)
+ {
+ var remoteIp = context.Connection.RemoteIpAddress;
+
+ if (_instanceSettings.EnableXRealIpHeader)
+ {
+ var forwardedIp = context.Request.Headers.FirstOrDefault(x => x.Key == "X-Real-IP").Value
+ .ToString();
+ if (!string.IsNullOrWhiteSpace(forwardedIp))
+ {
+ _logger.LogDebug("Redirected IP address detected");
+ remoteIp = IPAddress.Parse(forwardedIp);
+ }
+ }
+
+ _logger.LogDebug("Request from Remote IP address: {RemoteIp}", remoteIp);
+
+ var bytes = remoteIp.GetAddressBytes();
+ var badIp = true;
+ foreach (var address in _safelist)
+ {
+ if (address.SequenceEqual(bytes))
+ {
+ badIp = false;
+ break;
+ }
+ }
+
+ if (badIp)
+ {
+ _logger.LogWarning("Forbidden Request from Remote IP address: {RemoteIp}", remoteIp);
+ context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+ return;
+ }
+ }
+
+ await _next.Invoke(context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive/Program.cs b/src/BirdsiteLive/Program.cs
index d238b02..7f29e76 100644
--- a/src/BirdsiteLive/Program.cs
+++ b/src/BirdsiteLive/Program.cs
@@ -29,6 +29,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
.ConfigureServices(services =>
{
services.AddHostedService();
+ services.AddHostedService();
});
}
}
diff --git a/src/BirdsiteLive/Services/FederationService.cs b/src/BirdsiteLive/Services/FederationService.cs
index 0b0faed..32e8b5e 100644
--- a/src/BirdsiteLive/Services/FederationService.cs
+++ b/src/BirdsiteLive/Services/FederationService.cs
@@ -6,6 +6,7 @@
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.Moderation;
using BirdsiteLive.Pipeline;
+using BirdsiteLive.Tools;
using Microsoft.Extensions.Hosting;
namespace BirdsiteLive.Services
@@ -32,6 +33,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
try
{
await _databaseInitializer.InitAndMigrateDbAsync();
+ InitStateSynchronization.IsDbInitialized = true;
await _moderationPipeline.ApplyModerationSettingsAsync();
await _statusPublicationPipeline.ExecuteAsync(stoppingToken);
}
diff --git a/src/BirdsiteLive/Services/TweetCleanUpService.cs b/src/BirdsiteLive/Services/TweetCleanUpService.cs
new file mode 100644
index 0000000..c2d81bd
--- /dev/null
+++ b/src/BirdsiteLive/Services/TweetCleanUpService.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using BirdsiteLive.Pipeline;
+using BirdsiteLive.Tools;
+using Microsoft.Extensions.Hosting;
+
+namespace BirdsiteLive.Services
+{
+ public class TweetCleanUpService : BackgroundService
+ {
+ private readonly ITweetCleanUpPipeline _cleanUpPipeline;
+ private readonly IHostApplicationLifetime _applicationLifetime;
+
+ #region Ctor
+ public TweetCleanUpService(IHostApplicationLifetime applicationLifetime, ITweetCleanUpPipeline cleanUpPipeline)
+ {
+ _applicationLifetime = applicationLifetime;
+ _cleanUpPipeline = cleanUpPipeline;
+ }
+ #endregion
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ try
+ {
+ // Wait for initialization
+ while (!InitStateSynchronization.IsDbInitialized)
+ {
+ if (stoppingToken.IsCancellationRequested) return;
+ await Task.Delay(250, stoppingToken);
+ }
+
+ await _cleanUpPipeline.ExecuteAsync(stoppingToken);
+ }
+ catch (Exception)
+ {
+ await Task.Delay(1000 * 30);
+ _applicationLifetime.StopApplication();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive/Startup.cs b/src/BirdsiteLive/Startup.cs
index 16c7ee4..e088a56 100644
--- a/src/BirdsiteLive/Startup.cs
+++ b/src/BirdsiteLive/Startup.cs
@@ -8,6 +8,7 @@
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Postgres.DataAccessLayers;
using BirdsiteLive.DAL.Postgres.Settings;
+using BirdsiteLive.Middlewares;
using BirdsiteLive.Models;
using BirdsiteLive.Twitter;
using BirdsiteLive.Twitter.Tools;
@@ -18,6 +19,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
namespace BirdsiteLive
{
@@ -132,6 +134,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAuthorization();
+ var instanceSettings = Configuration.GetSection("Instance").Get();
+ app.UseMiddleware(instanceSettings);
+
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
diff --git a/src/BirdsiteLive/Tools/InitStateSynchronization.cs b/src/BirdsiteLive/Tools/InitStateSynchronization.cs
new file mode 100644
index 0000000..c2a2e42
--- /dev/null
+++ b/src/BirdsiteLive/Tools/InitStateSynchronization.cs
@@ -0,0 +1,7 @@
+namespace BirdsiteLive.Tools
+{
+ public static class InitStateSynchronization
+ {
+ public static bool IsDbInitialized { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/BirdsiteLive/Views/Debuging/Index.cshtml b/src/BirdsiteLive/Views/Debuging/Index.cshtml
index e343cf2..886b88d 100644
--- a/src/BirdsiteLive/Views/Debuging/Index.cshtml
+++ b/src/BirdsiteLive/Views/Debuging/Index.cshtml
@@ -18,6 +18,11 @@
+