With a token for a persona, a Mastodon client can now:
- accounts: verify/update credentials (display name, note, fields, locked,
bot, discoverable, indexable, hide collections, posting defaults; the
change federates as Update{Person}), get, lookup, statuses (paged, by
visibility to the viewer), relationships, search, follow and unfollow,
follow requests (authorize and reject answer remote followers with
Accept or Reject), remove from followers. Followers and following lists
are shown to their owner only.
- statuses: post (plain text, mentions, hashtags, replies, content
warnings, the four visibilities, Idempotency-Key), get, edit (PUT),
delete returning the source for redrafting, context, history, source,
favourite, reblog and their undos, favourited_by and reblogged_by.
- timelines: home, public (local or remote), tag; favourites;
conversations; markers.
- notifications: list with types and exclude_types, get, dismiss, clear,
unread count.
- /api/v2/search, resolving a handle or a URL to an account or a post.
Media, polls, pins, bookmarks, mutes, blocks, lists, filters, trends and
push answer empty lists or a 422 saying they are not supported yet, so
clients degrade instead of failing. Public reads work without a token.
Persona settings (locked, bot, indexable, discoverable) now also shape
the ActivityPub actor.
Fixed on the way: three conditional expressions whose `default` was the
value type's, so a missing limit became 1, a missing flag became false and
an attachment without dimensions became 0x0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
56 lines
2.1 KiB
C#
56 lines
2.1 KiB
C#
using PrivaPub.Api.Mastodon.Entities;
|
|
using PrivaPub.Federation.Actors;
|
|
using PrivaPub.StaticServices;
|
|
|
|
namespace PrivaPub.Api.Mastodon.Mappers
|
|
{
|
|
public class AccountSearch
|
|
{
|
|
readonly MastodonMapper _mapper;
|
|
readonly DbEntities _dbEntities;
|
|
readonly ILocalActorService _localActors;
|
|
readonly IRemoteActorService _remoteActors;
|
|
|
|
public AccountSearch(MastodonMapper mapper, DbEntities dbEntities, ILocalActorService localActors, IRemoteActorService remoteActors)
|
|
{
|
|
_mapper = mapper;
|
|
_dbEntities = dbEntities;
|
|
_localActors = localActors;
|
|
_remoteActors = remoteActors;
|
|
}
|
|
|
|
public async Task<List<Account>> Find(string q, bool resolve, int limit, CancellationToken token)
|
|
{
|
|
q = q?.Trim().TrimStart('@');
|
|
if (string.IsNullOrEmpty(q))
|
|
return new List<Account>();
|
|
var results = new List<Account>();
|
|
var parts = q.Split('@');
|
|
var localDomain = new Uri(_localActors.BaseAddress).Authority;
|
|
if (parts.Length == 2 && !parts[1].Equals(localDomain, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(parts[0]))
|
|
{
|
|
var userName = parts[0];
|
|
var domain = parts[1].ToLowerInvariant();
|
|
var known = await _dbEntities.ForeignAvatars.Match(f => f.UserName == userName && f.Domain == domain).ExecuteFirstAsync(token);
|
|
if (known == default && resolve)
|
|
{
|
|
var actorUri = await _remoteActors.ResolveHandle(q, token);
|
|
known = actorUri == default ? default : await _remoteActors.GetActor(actorUri, refresh: false, token);
|
|
}
|
|
if (known != default)
|
|
results.Add(_mapper.Foreign(known));
|
|
return results;
|
|
}
|
|
|
|
var prefix = parts[0].ToLowerInvariant();
|
|
foreach (var avatar in await _dbEntities.Avatars.Match(a => a.UserName.StartsWith(prefix) && !a.DeletionAt.HasValue && a.Settings.IsDiscoverable != false)
|
|
.Limit(limit).ExecuteAsync(token))
|
|
results.Add(await _mapper.Local(_localActors.FromAvatar(avatar), false, token));
|
|
foreach (var foreign in await _dbEntities.ForeignAvatars.Match(f => f.UserName.StartsWith(parts[0]) && f.IsDiscoverable)
|
|
.Limit(Math.Max(0, limit - results.Count)).ExecuteAsync(token))
|
|
results.Add(_mapper.Foreign(foreign));
|
|
return results;
|
|
}
|
|
}
|
|
}
|