[backend/federation] Properly handle hashtags in notes & user profiles (ISH-114, ISH-125)
This commit is contained in:
parent
810c21a275
commit
92d229ad63
9 changed files with 6416 additions and 148 deletions
|
@ -461,15 +461,7 @@ public class DatabaseContext(DbContextOptions<DatabaseContext> options)
|
||||||
entity.HasOne(d => d.User).WithMany(p => p.GalleryPosts);
|
entity.HasOne(d => d.User).WithMany(p => p.GalleryPosts);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity<Hashtag>(entity =>
|
modelBuilder.Entity<Hashtag>();
|
||||||
{
|
|
||||||
entity.Property(e => e.AttachedLocalUsersCount).HasDefaultValue(0);
|
|
||||||
entity.Property(e => e.AttachedRemoteUsersCount).HasDefaultValue(0);
|
|
||||||
entity.Property(e => e.AttachedUsersCount).HasDefaultValue(0);
|
|
||||||
entity.Property(e => e.MentionedLocalUsersCount).HasDefaultValue(0);
|
|
||||||
entity.Property(e => e.MentionedRemoteUsersCount).HasDefaultValue(0);
|
|
||||||
entity.Property(e => e.MentionedUsersCount).HasDefaultValue(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity<Instance>(entity =>
|
modelBuilder.Entity<Instance>(entity =>
|
||||||
{
|
{
|
||||||
|
|
6092
Iceshrimp.Backend/Core/Database/Migrations/20240304201225_RemoveHashtagStatisticsColumns.Designer.cs
generated
Normal file
6092
Iceshrimp.Backend/Core/Database/Migrations/20240304201225_RemoveHashtagStatisticsColumns.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,199 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Iceshrimp.Backend.Core.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class RemoveHashtagStatisticsColumns : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_hashtag_attachedLocalUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_hashtag_attachedRemoteUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_hashtag_attachedUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_hashtag_mentionedLocalUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_hashtag_mentionedRemoteUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_hashtag_mentionedUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "attachedLocalUserIds",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "attachedLocalUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "attachedRemoteUserIds",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "attachedRemoteUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "attachedUserIds",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "attachedUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "mentionedLocalUserIds",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "mentionedLocalUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "mentionedRemoteUserIds",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "mentionedRemoteUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "mentionedUserIds",
|
||||||
|
table: "hashtag");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "mentionedUsersCount",
|
||||||
|
table: "hashtag");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<List<string>>(
|
||||||
|
name: "attachedLocalUserIds",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "character varying(32)[]",
|
||||||
|
nullable: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "attachedLocalUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<List<string>>(
|
||||||
|
name: "attachedRemoteUserIds",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "character varying(32)[]",
|
||||||
|
nullable: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "attachedRemoteUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<List<string>>(
|
||||||
|
name: "attachedUserIds",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "character varying(32)[]",
|
||||||
|
nullable: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "attachedUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<List<string>>(
|
||||||
|
name: "mentionedLocalUserIds",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "character varying(32)[]",
|
||||||
|
nullable: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "mentionedLocalUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<List<string>>(
|
||||||
|
name: "mentionedRemoteUserIds",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "character varying(32)[]",
|
||||||
|
nullable: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "mentionedRemoteUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<List<string>>(
|
||||||
|
name: "mentionedUserIds",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "character varying(32)[]",
|
||||||
|
nullable: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "mentionedUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_hashtag_attachedLocalUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
column: "attachedLocalUsersCount");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_hashtag_attachedRemoteUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
column: "attachedRemoteUsersCount");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_hashtag_attachedUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
column: "attachedUsersCount");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_hashtag_mentionedLocalUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
column: "mentionedLocalUsersCount");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_hashtag_mentionedRemoteUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
column: "mentionedRemoteUsersCount");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_hashtag_mentionedUsersCount",
|
||||||
|
table: "hashtag",
|
||||||
|
column: "mentionedUsersCount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1488,72 +1488,6 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
|
||||||
.HasColumnType("character varying(32)")
|
.HasColumnType("character varying(32)")
|
||||||
.HasColumnName("id");
|
.HasColumnName("id");
|
||||||
|
|
||||||
b.Property<List<string>>("AttachedLocalUserIds")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("character varying(32)[]")
|
|
||||||
.HasColumnName("attachedLocalUserIds");
|
|
||||||
|
|
||||||
b.Property<int>("AttachedLocalUsersCount")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("attachedLocalUsersCount");
|
|
||||||
|
|
||||||
b.Property<List<string>>("AttachedRemoteUserIds")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("character varying(32)[]")
|
|
||||||
.HasColumnName("attachedRemoteUserIds");
|
|
||||||
|
|
||||||
b.Property<int>("AttachedRemoteUsersCount")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("attachedRemoteUsersCount");
|
|
||||||
|
|
||||||
b.Property<List<string>>("AttachedUserIds")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("character varying(32)[]")
|
|
||||||
.HasColumnName("attachedUserIds");
|
|
||||||
|
|
||||||
b.Property<int>("AttachedUsersCount")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("attachedUsersCount");
|
|
||||||
|
|
||||||
b.Property<List<string>>("MentionedLocalUserIds")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("character varying(32)[]")
|
|
||||||
.HasColumnName("mentionedLocalUserIds");
|
|
||||||
|
|
||||||
b.Property<int>("MentionedLocalUsersCount")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("mentionedLocalUsersCount");
|
|
||||||
|
|
||||||
b.Property<List<string>>("MentionedRemoteUserIds")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("character varying(32)[]")
|
|
||||||
.HasColumnName("mentionedRemoteUserIds");
|
|
||||||
|
|
||||||
b.Property<int>("MentionedRemoteUsersCount")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("mentionedRemoteUsersCount");
|
|
||||||
|
|
||||||
b.Property<List<string>>("MentionedUserIds")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("character varying(32)[]")
|
|
||||||
.HasColumnName("mentionedUserIds");
|
|
||||||
|
|
||||||
b.Property<int>("MentionedUsersCount")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasDefaultValue(0)
|
|
||||||
.HasColumnName("mentionedUsersCount");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(128)
|
.HasMaxLength(128)
|
||||||
|
@ -1562,18 +1496,6 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("AttachedLocalUsersCount");
|
|
||||||
|
|
||||||
b.HasIndex("AttachedRemoteUsersCount");
|
|
||||||
|
|
||||||
b.HasIndex("AttachedUsersCount");
|
|
||||||
|
|
||||||
b.HasIndex("MentionedLocalUsersCount");
|
|
||||||
|
|
||||||
b.HasIndex("MentionedRemoteUsersCount");
|
|
||||||
|
|
||||||
b.HasIndex("MentionedUsersCount");
|
|
||||||
|
|
||||||
b.HasIndex("Name")
|
b.HasIndex("Name")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,7 @@ using Microsoft.EntityFrameworkCore;
|
||||||
namespace Iceshrimp.Backend.Core.Database.Tables;
|
namespace Iceshrimp.Backend.Core.Database.Tables;
|
||||||
|
|
||||||
[Table("hashtag")]
|
[Table("hashtag")]
|
||||||
[Index("AttachedRemoteUsersCount")]
|
|
||||||
[Index("AttachedLocalUsersCount")]
|
|
||||||
[Index("MentionedLocalUsersCount")]
|
|
||||||
[Index("MentionedUsersCount")]
|
|
||||||
[Index("Name", IsUnique = true)]
|
[Index("Name", IsUnique = true)]
|
||||||
[Index("MentionedRemoteUsersCount")]
|
|
||||||
[Index("AttachedUsersCount")]
|
|
||||||
public class Hashtag : IEntity
|
public class Hashtag : IEntity
|
||||||
{
|
{
|
||||||
[Key]
|
[Key]
|
||||||
|
@ -20,34 +14,4 @@ public class Hashtag : IEntity
|
||||||
public string Id { get; set; } = null!;
|
public string Id { get; set; } = null!;
|
||||||
|
|
||||||
[Column("name")] [StringLength(128)] public string Name { get; set; } = null!;
|
[Column("name")] [StringLength(128)] public string Name { get; set; } = null!;
|
||||||
|
|
||||||
[Column("mentionedUserIds", TypeName = "character varying(32)[]")]
|
|
||||||
public List<string> MentionedUserIds { get; set; } = null!;
|
|
||||||
|
|
||||||
[Column("mentionedUsersCount")] public int MentionedUsersCount { get; set; }
|
|
||||||
|
|
||||||
[Column("mentionedLocalUserIds", TypeName = "character varying(32)[]")]
|
|
||||||
public List<string> MentionedLocalUserIds { get; set; } = null!;
|
|
||||||
|
|
||||||
[Column("mentionedLocalUsersCount")] public int MentionedLocalUsersCount { get; set; }
|
|
||||||
|
|
||||||
[Column("mentionedRemoteUserIds", TypeName = "character varying(32)[]")]
|
|
||||||
public List<string> MentionedRemoteUserIds { get; set; } = null!;
|
|
||||||
|
|
||||||
[Column("mentionedRemoteUsersCount")] public int MentionedRemoteUsersCount { get; set; }
|
|
||||||
|
|
||||||
[Column("attachedUserIds", TypeName = "character varying(32)[]")]
|
|
||||||
public List<string> AttachedUserIds { get; set; } = null!;
|
|
||||||
|
|
||||||
[Column("attachedUsersCount")] public int AttachedUsersCount { get; set; }
|
|
||||||
|
|
||||||
[Column("attachedLocalUserIds", TypeName = "character varying(32)[]")]
|
|
||||||
public List<string> AttachedLocalUserIds { get; set; } = null!;
|
|
||||||
|
|
||||||
[Column("attachedLocalUsersCount")] public int AttachedLocalUsersCount { get; set; }
|
|
||||||
|
|
||||||
[Column("attachedRemoteUserIds", TypeName = "character varying(32)[]")]
|
|
||||||
public List<string> AttachedRemoteUserIds { get; set; } = null!;
|
|
||||||
|
|
||||||
[Column("attachedRemoteUsersCount")] public int AttachedRemoteUsersCount { get; set; }
|
|
||||||
}
|
}
|
|
@ -59,15 +59,19 @@ public class NoteRenderer(IOptions<Config.InstanceSection> config, MfmConverter
|
||||||
_ => []
|
_ => []
|
||||||
};
|
};
|
||||||
|
|
||||||
var tags = mentions
|
var tags = note.Tags.Select(tag => new ASHashtag
|
||||||
.Select(mention => new ASMention
|
{
|
||||||
|
Name = $"#{tag}",
|
||||||
|
Href = new ASObjectBase($"https://{config.Value.WebDomain}/tags/{tag}")
|
||||||
|
})
|
||||||
|
.Concat<ASTag>(mentions.Select(mention => new ASMention
|
||||||
{
|
{
|
||||||
Name = $"@{mention.Username}@{mention.Host}",
|
Name = $"@{mention.Username}@{mention.Host}",
|
||||||
Href = new ASObjectBase(mention.Uri)
|
Href = new ASObjectBase(mention.Uri)
|
||||||
})
|
}))
|
||||||
.Cast<ASTag>()
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
var attachments = note.FileIds.Count > 0
|
var attachments = note.FileIds.Count > 0
|
||||||
? await db.DriveFiles
|
? await db.DriveFiles
|
||||||
.Where(p => note.FileIds.Contains(p.Id) && p.UserHost == null)
|
.Where(p => note.FileIds.Contains(p.Id) && p.UserHost == null)
|
||||||
|
|
|
@ -46,6 +46,15 @@ public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseConte
|
||||||
? ASActor.Types.Service
|
? ASActor.Types.Service
|
||||||
: ASActor.Types.Person;
|
: ASActor.Types.Person;
|
||||||
|
|
||||||
|
var tags = user.Tags
|
||||||
|
.Select(tag => new ASHashtag
|
||||||
|
{
|
||||||
|
Name = $"#{tag}",
|
||||||
|
Href = new ASObjectBase($"https://{config.Value.WebDomain}/tags/{tag}")
|
||||||
|
})
|
||||||
|
.Cast<ASTag>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
return new ASActor
|
return new ASActor
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
|
@ -74,7 +83,8 @@ public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseConte
|
||||||
PublicKey = new ASPublicKey
|
PublicKey = new ASPublicKey
|
||||||
{
|
{
|
||||||
Id = $"{id}#main-key", Owner = new ASObjectBase(id), PublicKey = keypair.PublicKey
|
Id = $"{id}#main-key", Owner = new ASObjectBase(id), PublicKey = keypair.PublicKey
|
||||||
}
|
},
|
||||||
|
Tags = tags
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -77,6 +77,8 @@ public class NoteService(
|
||||||
if ((user.UserSettings?.PrivateMode ?? false) && visibility < Note.NoteVisibility.Followers)
|
if ((user.UserSettings?.PrivateMode ?? false) && visibility < Note.NoteVisibility.Followers)
|
||||||
visibility = Note.NoteVisibility.Followers;
|
visibility = Note.NoteVisibility.Followers;
|
||||||
|
|
||||||
|
var tags = ResolveHashtags(text);
|
||||||
|
|
||||||
var note = new Note
|
var note = new Note
|
||||||
{
|
{
|
||||||
Id = IdHelpers.GenerateSlowflakeId(),
|
Id = IdHelpers.GenerateSlowflakeId(),
|
||||||
|
@ -97,7 +99,8 @@ public class NoteService(
|
||||||
Mentions = mentionedUserIds,
|
Mentions = mentionedUserIds,
|
||||||
VisibleUserIds = visibility == Note.NoteVisibility.Specified ? mentionedUserIds : [],
|
VisibleUserIds = visibility == Note.NoteVisibility.Specified ? mentionedUserIds : [],
|
||||||
MentionedRemoteUsers = remoteMentions,
|
MentionedRemoteUsers = remoteMentions,
|
||||||
ThreadId = reply?.ThreadId ?? reply?.Id
|
ThreadId = reply?.ThreadId ?? reply?.Id,
|
||||||
|
Tags = tags
|
||||||
};
|
};
|
||||||
|
|
||||||
await UpdateNoteCountersAsync(note, true);
|
await UpdateNoteCountersAsync(note, true);
|
||||||
|
@ -213,7 +216,7 @@ public class NoteService(
|
||||||
|
|
||||||
mentionedLocalUserIds = mentionedLocalUserIds.Except(previousMentionedLocalUserIds).ToList();
|
mentionedLocalUserIds = mentionedLocalUserIds.Except(previousMentionedLocalUserIds).ToList();
|
||||||
note.Text = text;
|
note.Text = text;
|
||||||
|
note.Tags = ResolveHashtags(text);
|
||||||
|
|
||||||
if (text is not null)
|
if (text is not null)
|
||||||
{
|
{
|
||||||
|
@ -396,8 +399,6 @@ public class NoteService(
|
||||||
if (actor.IsSuspended)
|
if (actor.IsSuspended)
|
||||||
throw GracefulException.Forbidden("User is suspended");
|
throw GracefulException.Forbidden("User is suspended");
|
||||||
|
|
||||||
//TODO: resolve emoji
|
|
||||||
|
|
||||||
var (mentionedUserIds, mentionedLocalUserIds, mentions, remoteMentions, splitDomainMapping) =
|
var (mentionedUserIds, mentionedLocalUserIds, mentions, remoteMentions, splitDomainMapping) =
|
||||||
await ResolveNoteMentionsAsync(note);
|
await ResolveNoteMentionsAsync(note);
|
||||||
|
|
||||||
|
@ -461,6 +462,7 @@ public class NoteService(
|
||||||
}
|
}
|
||||||
|
|
||||||
dbNote.Text = mentionsResolver.ResolveMentions(dbNote.Text, dbNote.UserHost, mentions, splitDomainMapping);
|
dbNote.Text = mentionsResolver.ResolveMentions(dbNote.Text, dbNote.UserHost, mentions, splitDomainMapping);
|
||||||
|
dbNote.Tags = ResolveHashtags(dbNote.Text, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sensitive = (note.Sensitive ?? false) || dbNote.Cw != null;
|
var sensitive = (note.Sensitive ?? false) || dbNote.Cw != null;
|
||||||
|
@ -544,6 +546,7 @@ public class NoteService(
|
||||||
}
|
}
|
||||||
|
|
||||||
dbNote.Text = mentionsResolver.ResolveMentions(dbNote.Text, dbNote.UserHost, mentions, splitDomainMapping);
|
dbNote.Text = mentionsResolver.ResolveMentions(dbNote.Text, dbNote.UserHost, mentions, splitDomainMapping);
|
||||||
|
dbNote.Tags = ResolveHashtags(dbNote.Text, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: handle updated alt text et al
|
//TODO: handle updated alt text et al
|
||||||
|
@ -608,6 +611,49 @@ public class NoteService(
|
||||||
return ResolveNoteMentions(users.Where(p => p != null).Select(p => p!).ToList());
|
return ResolveNoteMentions(users.Where(p => p != null).Select(p => p!).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<string> ResolveHashtags(string? text, ASNote? note = null)
|
||||||
|
{
|
||||||
|
List<string> tags = [];
|
||||||
|
|
||||||
|
if (text != null)
|
||||||
|
{
|
||||||
|
tags = MfmParser.Parse(text)
|
||||||
|
.SelectMany(p => p.Children.Append(p))
|
||||||
|
.OfType<MfmHashtagNode>()
|
||||||
|
.Select(p => p.Hashtag.ToLowerInvariant())
|
||||||
|
.Select(p => p.Trim('#'))
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var extracted = note?.Tags?.OfType<ASHashtag>()
|
||||||
|
.Select(p => p.Name?.ToLowerInvariant())
|
||||||
|
.Where(p => p != null)
|
||||||
|
.Cast<string>()
|
||||||
|
.Select(p => p.Trim('#'))
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (extracted != null)
|
||||||
|
tags.AddRange(extracted);
|
||||||
|
|
||||||
|
if (tags.Count == 0) return [];
|
||||||
|
|
||||||
|
tags = tags.Distinct().ToList();
|
||||||
|
|
||||||
|
_ = followupTaskSvc.ExecuteTask("UpdateHashtagsTable", async provider =>
|
||||||
|
{
|
||||||
|
var bgDb = provider.GetRequiredService<DatabaseContext>();
|
||||||
|
var existing = await bgDb.Hashtags.Where(p => tags.Contains(p.Name)).Select(p => p.Name).ToListAsync();
|
||||||
|
var dbTags = tags.Except(existing)
|
||||||
|
.Select(p => new Hashtag { Id = IdHelpers.GenerateSlowflakeId(), Name = p });
|
||||||
|
await bgDb.AddRangeAsync(dbTags);
|
||||||
|
await bgDb.SaveChangesAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
private MentionQuintuple ResolveNoteMentions(IReadOnlyCollection<User> users)
|
private MentionQuintuple ResolveNoteMentions(IReadOnlyCollection<User> users)
|
||||||
{
|
{
|
||||||
var userIds = users.Select(p => p.Id).Distinct().ToList();
|
var userIds = users.Select(p => p.Id).Distinct().ToList();
|
||||||
|
|
|
@ -10,6 +10,8 @@ using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||||
using Iceshrimp.Backend.Core.Helpers;
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
||||||
|
using Iceshrimp.Backend.Core.Helpers.LibMfm.Parsing;
|
||||||
|
using Iceshrimp.Backend.Core.Helpers.LibMfm.Types;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
@ -115,11 +117,6 @@ public class UserService(
|
||||||
var host = AcctToTuple(acct).Host ?? throw new Exception("Host must not be null at this stage");
|
var host = AcctToTuple(acct).Host ?? throw new Exception("Host must not be null at this stage");
|
||||||
|
|
||||||
var emoji = await emojiSvc.ProcessEmojiAsync(actor.Tags?.OfType<ASEmoji>().ToList(), host);
|
var emoji = await emojiSvc.ProcessEmojiAsync(actor.Tags?.OfType<ASEmoji>().ToList(), host);
|
||||||
var tags = actor.Tags?.OfType<ASHashtag>()
|
|
||||||
.Select(p => p.Name?.ToLowerInvariant())
|
|
||||||
.Where(p => p != null)
|
|
||||||
.Cast<string>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var fields = actor.Attachments != null
|
var fields = actor.Attachments != null
|
||||||
? await actor.Attachments
|
? await actor.Attachments
|
||||||
|
@ -134,6 +131,8 @@ public class UserService(
|
||||||
|
|
||||||
var bio = actor.MkSummary ?? await MfmConverter.FromHtmlAsync(actor.Summary);
|
var bio = actor.MkSummary ?? await MfmConverter.FromHtmlAsync(actor.Summary);
|
||||||
|
|
||||||
|
var tags = ResolveHashtags(bio, actor);
|
||||||
|
|
||||||
user = new User
|
user = new User
|
||||||
{
|
{
|
||||||
Id = IdHelpers.GenerateSlowflakeId(),
|
Id = IdHelpers.GenerateSlowflakeId(),
|
||||||
|
@ -261,12 +260,6 @@ public class UserService(
|
||||||
user.Host ??
|
user.Host ??
|
||||||
throw new Exception("User host must not be null at this stage"));
|
throw new Exception("User host must not be null at this stage"));
|
||||||
|
|
||||||
var tags = actor.Tags?.OfType<ASHashtag>()
|
|
||||||
.Select(p => p.Name?.ToLowerInvariant())
|
|
||||||
.Where(p => p != null)
|
|
||||||
.Cast<string>()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var fields = actor.Attachments != null
|
var fields = actor.Attachments != null
|
||||||
? await actor.Attachments
|
? await actor.Attachments
|
||||||
.OfType<ASField>()
|
.OfType<ASField>()
|
||||||
|
@ -279,7 +272,6 @@ public class UserService(
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
user.Emojis = emoji.Select(p => p.Id).ToList();
|
user.Emojis = emoji.Select(p => p.Id).ToList();
|
||||||
user.Tags = tags ?? [];
|
|
||||||
//TODO: FollowersCount
|
//TODO: FollowersCount
|
||||||
//TODO: FollowingCount
|
//TODO: FollowingCount
|
||||||
|
|
||||||
|
@ -296,6 +288,8 @@ public class UserService(
|
||||||
|
|
||||||
user.UserProfile.MentionsResolved = false;
|
user.UserProfile.MentionsResolved = false;
|
||||||
|
|
||||||
|
user.Tags = ResolveHashtags(user.UserProfile.Description, actor);
|
||||||
|
|
||||||
db.Update(user);
|
db.Update(user);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
await processPendingDeletes();
|
await processPendingDeletes();
|
||||||
|
@ -309,6 +303,8 @@ public class UserService(
|
||||||
if (user.Host != null) throw new Exception("This method is only valid for local users");
|
if (user.Host != null) throw new Exception("This method is only valid for local users");
|
||||||
if (user.UserProfile == null) throw new Exception("user.UserProfile must not be null at this stage");
|
if (user.UserProfile == null) throw new Exception("user.UserProfile must not be null at this stage");
|
||||||
|
|
||||||
|
user.Tags = ResolveHashtags(user.UserProfile.Description);
|
||||||
|
|
||||||
db.Update(user);
|
db.Update(user);
|
||||||
db.Update(user.UserProfile);
|
db.Update(user.UserProfile);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
@ -786,4 +782,47 @@ public class UserService(
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<string> ResolveHashtags(string? text, ASActor? actor = null)
|
||||||
|
{
|
||||||
|
List<string> tags = [];
|
||||||
|
|
||||||
|
if (text != null)
|
||||||
|
{
|
||||||
|
tags = MfmParser.Parse(text)
|
||||||
|
.SelectMany(p => p.Children.Append(p))
|
||||||
|
.OfType<MfmHashtagNode>()
|
||||||
|
.Select(p => p.Hashtag.ToLowerInvariant())
|
||||||
|
.Select(p => p.Trim('#'))
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var extracted = actor?.Tags?.OfType<ASHashtag>()
|
||||||
|
.Select(p => p.Name?.ToLowerInvariant())
|
||||||
|
.Where(p => p != null)
|
||||||
|
.Cast<string>()
|
||||||
|
.Select(p => p.Trim('#'))
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (extracted != null)
|
||||||
|
tags.AddRange(extracted);
|
||||||
|
|
||||||
|
if (tags.Count == 0) return [];
|
||||||
|
|
||||||
|
tags = tags.Distinct().ToList();
|
||||||
|
|
||||||
|
_ = followupTaskSvc.ExecuteTask("UpdateHashtagsTable", async provider =>
|
||||||
|
{
|
||||||
|
var bgDb = provider.GetRequiredService<DatabaseContext>();
|
||||||
|
var existing = await bgDb.Hashtags.Where(p => tags.Contains(p.Name)).Select(p => p.Name).ToListAsync();
|
||||||
|
var dbTags = tags.Except(existing)
|
||||||
|
.Select(p => new Hashtag { Id = IdHelpers.GenerateSlowflakeId(), Name = p });
|
||||||
|
await bgDb.AddRangeAsync(dbTags);
|
||||||
|
await bgDb.SaveChangesAsync();
|
||||||
|
});
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue