Skip to content

Commit b38d099

Browse files
authored
Merge Feature/jetstream (#155) & grand rename
* Grand renaming - resolves #149 Add AtProtoRepositoryRecord<T> Added did and service property to authentication events Fixed authentication restoration documentation * Update docs. Change SetProfile to wrap UpdateProfile * Add AtProtoJetstream consuming class * Add the new jetstream class
1 parent 7a87de4 commit b38d099

File tree

116 files changed

+3265
-961
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+3265
-961
lines changed

.github/workflows/ci-build.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ permissions:
1919
checks: write
2020

2121
env:
22-
DOTNET_NOLOGO: true
2322
DOTNET_GENERATE_ASPNET_CERTIFICATE: false
2423
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
2524

@@ -40,6 +39,10 @@ jobs:
4039

4140
- name: 'Setup .NET SDK'
4241
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
42+
with:
43+
dotnet-version: |
44+
8.0.x
45+
9.0.x
4346
4447
- name: 'Restore external dependencies'
4548
run: dotnet restore

Directory.Build.props

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
<PropertyGroup>
2121
<Nullable>enable</Nullable>
22-
<LangVersion>12.0</LangVersion>
2322
<EnableNETAnalyzers>true</EnableNETAnalyzers>
2423
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
2524
</PropertyGroup>

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@
3131
<PackageVersion Include="Duende.IdentityModel.OidcClient.Extensions" Version="6.0.1" />
3232
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="8.0.15" />
3333
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.15" />
34+
<PackageVersion Include="ZstdSharp.Port" Version="0.8.5" />
3435
</ItemGroup>
35-
</Project>
36+
</Project>

THIRD-PARTY-NOTICES.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,28 @@ For the avoidance of doubt, this Public License does not, and shall not be inter
510510
To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
511511
No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
512512
Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
513+
514+
License notice for ZstdSharp
515+
----------------------------
516+
517+
MIT License
518+
519+
Copyright (c) 2021 Oleg Stepanischev
520+
521+
Permission is hereby granted, free of charge, to any person obtaining a copy
522+
of this software and associated documentation files (the "Software"), to deal
523+
in the Software without restriction, including without limitation the rights
524+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
525+
copies of the Software, and to permit persons to whom the Software is
526+
furnished to do so, subject to the following conditions:
527+
528+
The above copyright notice and this permission notice shall be included in all
529+
copies or substantial portions of the Software.
530+
531+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
532+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
533+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
534+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
535+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
536+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
537+
SOFTWARE.

docs/docs/code/createASession.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
using idunno.Bluesky;
22

33
using BlueskyAgent agent = new();
4-
var loginResult = await agent.Login(handle, password);
4+
await agent.Login(handle, password);

docs/docs/conversationsAndMessages.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
To get a list of conversations for the authenticated user use the `ListConversations()` api:
1212

1313
```c#
14-
var listConversations = await agent.ListConversations();
14+
var listConversationsResult = await agent.ListConversations();
1515
```
1616

1717
This returns a [pageable list](cursorsAndPagination.md) of `ConversationView` which supplies the conversation ID, members,
@@ -22,14 +22,14 @@ If you already have a conversation ID you can use `GetConversation` to retrieve
2222
To retrieve the messages in a conversation use `GetMessages`:
2323

2424
```c#
25-
var getMessages = await agent.GetMessages(conversationId, cancellationToken: cancellationToken);
25+
var getMessagesResult = await agent.GetMessages(conversationId, cancellationToken: cancellationToken);
2626
```
2727

2828
The returns a [pageable list](cursorsAndPagination.md) of either `MessageView` or `DeletedMessageView` for each message in the conversation, as well
2929
as the DID of the sender, which you can match up to the `ConversationView` to get the sender information, for example
3030

3131
```c#
32-
foreach (MessageViewBase message in getMessages.Result)
32+
foreach (MessageViewBase message in getMessagesResult.Result)
3333
{
3434
if (message is MessageView view)
3535
{
@@ -53,7 +53,7 @@ optionally the message id of the last message seen.
5353
To send a message to a conversation use `SendMessage()`:
5454

5555
```c#
56-
var sendMessage = await agent.SendMessage(conversationID, "hello"", cancellationToken);
56+
var sendMessageResult = await agent.SendMessage(conversationID, "hello"", cancellationToken);
5757
```
5858

5959
This returns a `MessageView` which includes the message identifier, which you can use to delete a message.
@@ -99,7 +99,6 @@ foreach (MessageViewBase message in getMessages.Result)
9999
To add a reaction to a message call `AddReaction()`. This requires the conversation id and the message id, and the reaction you want to add. A reaction is an single emoji grapheme.
100100
To delete a reaction call `RemoveReaction()` with the same parameters with which you added a reaction.
101101

102-
103102
## <a name="creating">Starting a conversation</a>
104103

105104
To start a conversation you will need the DIDs of the conversation members, which you pass a collection to `GetConversationForMembers()`. If the user has left a conversation with these DIDs
@@ -109,7 +108,7 @@ it will be restored in the direct message list.
109108
var memberDid = await agent.ResolveHandle("example.invalid.handle", cancellationToken);
110109
List<Did> conversationMembers = new() { agent.Did!, bot2Did! };
111110

112-
var startConversation = await agent.GetConversationForMembers(conversationMembers, cancellationToken);
111+
var startConversationResult = await agent.GetConversationForMembers(conversationMembers, cancellationToken);
113112
```
114113

115114
`StartConversation()` returns a `ConversationView` which includes the conversation ID, which you can then use to send messages to the chat.

docs/docs/jetstream.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Using the JetStream
2+
3+
The [Jetstream](https://github.com/bluesky-social/jetstream) is a streaming service that provides information on activity on the ATProto network.
4+
You can consume the Jetstream using the `AtProtoJetstream` class.
5+
6+
## Jetstream events
7+
8+
`AtProtoJetstream` has three events you can subscribe to:
9+
10+
* `ConnectionStateChanged` - fired when the state of the underlying WebSocket changes, typically on open and close.
11+
* `MessageReceived` - fired when a message has been received from the Jetstream, but has not yet been parsed.
12+
* `RecordReceived` - fired when a message has been parsed into a JetStream event.
13+
14+
There are three types of Jetstream event that are passed to `RecordReceived`:
15+
16+
* `AtJetstreamCommitEvent` - an event raised when a change happens to a record in a repo, creation, deletion or changes. For example a post is created, or a user profile is updated.
17+
* `AtJetstreamAccountEvent` - an event that has happened on an actor's account, activation or deactivation, with an optional status indicating if deactivation was performed by moderation.
18+
* `AtJetstreamIdentityEvent` - an event raised when an actor changes their handle
19+
20+
## Consuming the Jetstream
21+
22+
To connect to the Jetstream create a new instance of `AtProtoJetstream` and subscribe to the events you are interesting in reacting to.
23+
24+
Typical use would be subscribing to the `RecordReceived` event, examining the event arguments and reacting accordingly to the wanted jetstream events.
25+
26+
```c#
27+
using (var jetStream = new AtProtoJetstream())
28+
{
29+
jetStream.RecordReceived += (sender, e) =>
30+
{
31+
string timeStamp = e.ParsedEvent.DateTimeOffset.ToLocalTime().ToString("G", CultureInfo.DefaultThreadCurrentUICulture);
32+
33+
switch (e.ParsedEvent)
34+
{
35+
case AtJetstreamAccountEvent accountEvent:
36+
if (accountEvent.Account.Active)
37+
{
38+
Console.WriteLine($"ACCOUNT: {accountEvent.Did} activated at {timeStamp}");
39+
}
40+
else
41+
{
42+
Console.WriteLine($"ACCOUNT: {accountEvent.Did} deactivated at {timeStamp}");
43+
}
44+
break;
45+
46+
case AtJetstreamCommitEvent commitEvent:
47+
Console.WriteLine($"COMMIT: {commitEvent.Did} executed a {commitEvent.Commit.Operation} in {commitEvent.Commit.Collection} at {timeStamp}");
48+
break;
49+
50+
case AtJetstreamIdentityEvent identityEvent:
51+
Console.WriteLine($"IDENTITY: {identityEvent.Did} changed handle to {identityEvent.Identity.Handle} at {timeStamp}");
52+
break;
53+
54+
default:
55+
break;
56+
}
57+
};
58+
}
59+
```
60+
61+
If you want the raw messages from the jetstream subscribe to the `MessageReceived` event.
62+
63+
> [!WARNING]
64+
> The Jetstream covers all ATProto events. The Commit events cover not only Bluesky record commits but any commits from a registered PDS, such as [WhiteWind](https://whtwnd.com)
65+
> blog records or [Tangled](https://blog.tangled.sh/intro) collaboration messages. This is why the `Record` property in `AtJetstreamCommit` is presented as a `JsonDocument`.
66+
> When deserializing this property to, for example, a `BlueskyRecord` you will encounter exceptions if you attempt it on a non-Bluesky defined record
67+
68+
Once you have a configured instance of `AtProtoJetstream` call `ConnectAsync` and processing will begin in the background, raising events as appropriate.
69+
When you are finished with the jetstream call `CloseAsync`
70+
71+
```c#
72+
await jetStream.ConnectAsync();
73+
74+
await jetStream.CloseAsync();
75+
```
76+
77+
The [Jetstream sample](https://github.com/blowdart/idunno.Bluesky/tree/main/samples/Samples.Jetstream) shows subscribing to both raw messages and events,
78+
writing the raw message and a break down of the event to the console.type.
79+
80+
## Filtering commit events
81+
82+
You can limit the commit events you receive by [DID](commonTerms.md#dids) or [Collection](commonTerms.md#records). You can configure the filters
83+
when creating of `AtProtoJetstream`:
84+
85+
```c#
86+
using (var jetStream = new AtProtoJetstream(
87+
collections: ["app.bsky.feed.post"],
88+
dids: ["did:plc:ec72yg6n2sydzjvtovvdlxrk"])
89+
{
90+
}
91+
```
92+
93+
You can also change the `CollectionFilter` and `DidFilter` properties on a running instance.
94+
95+
## Configuring AtProtoJetstream
96+
97+
`AtProtoJetstream` has two configuration options, `options` and `webSocketOptions`.
98+
99+
The `options` parameter on the constructor allows you to configure
100+
101+
* `LoggerFactory` - The `ILoggerFactory` to use for logging
102+
* `UseCompression` - a flag indication whether compression should be used. This defaults to `true`.
103+
* `Dictionary` - the zst compression/decompression dictionary to use if compression is enabled. This defaults to a generated dictionary specific to the jetstream.
104+
* `TaskFactory` - the `TaskFactory` to use when creating new tasks. This allows you to configure `TaskScheduler` settings if needed.
105+
* `MaximumMessageSize` - the maximum size of messages you are willing to accept, in bytes. This defaults to 8096.
106+
107+
The `webSocketOptions` parameter allows you to configure the underlying web socket client,
108+
109+
* `Proxy` - A proxy to use, if supplied.
110+
* `KeepAliveInterval` - Sets the keep alive interval.
111+
112+
The following code snippet demonstrates setting a `LoggerFactory` and a `Proxy`
113+
114+
```c#
115+
using (var jetStream = new AtProtoJetstream(
116+
options: new JetstreamOptions()
117+
{
118+
LoggerFactory = loggerFactory
119+
},
120+
webSocketOptions: new WebSocketOptions()
121+
{
122+
Proxy = new WebProxy(new Uri("http://localhost:8866"))
123+
}))
124+
{
125+
}
126+
```

docs/docs/logging.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ using var agent = new BlueSkyAgent(
2626
LoggerFactory = loggerFactory
2727
}))
2828
{
29-
await agent.Login(handle, password, cancellationToken: cancellationToken);
30-
await agent.Logout(cancellationToken: cancellationToken);
29+
await agent.Login(handle, password);
30+
await agent.Logout();
3131
}
3232
```
3333

docs/docs/posting.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ if (postResult.Succeeded)
1616
```
1717

1818
The result from creating a post contains. amongst other things, a strong reference to the new record. This `StrongReference` consists of an
19-
[AT URI](https://atproto.com/specs/at-uri-scheme) and a Content Identifier ([CID](https://github.com/multiformats/cid)).
19+
[at:// uri](commonTerms.md#uri) and a Content Identifier ([CID](https://github.com/multiformats/cid)).
2020

2121
An AT URI is a way to reference individual records in a specific repository (every Bluesky user has their own repository).
2222

@@ -52,7 +52,7 @@ If you don't provide `createdAt` the current date and time will be used.
5252

5353
## <a name="understandingPostResults">Understanding the results from a post call</a>
5454

55-
The `Post()` method creates a record in your Bluesky repo and returns an`AtProtoHttpResult<CreateRecordResponse>`
55+
The `Post()` method creates a record in your Bluesky repo and returns an`AtProtoHttpResult<CreateRecordResult>`
5656
This encapsulates the HTTP status code returned by the Bluesky API, the result of the operation,
5757
if the operation was successful, any error messages the API returned, and information on the current rate limits applied to you,
5858
which can be useful for making sure you don't flood the servers and get locked by a rate limiter.
@@ -101,20 +101,20 @@ To reply to a post, again, you need a post's `StrongReference`, which you pass i
101101

102102
```c#
103103
// Create a test post we will reply to.
104-
var createPostResponse =
104+
var createPostResult =
105105
await agent.Post("Another test post, this time to check replying.");
106106

107107
// Reply to the post we just created
108-
var replyCreatePostResponse =
109-
await agent.ReplyTo(createPostResponse.Result.StrongReference, "This is a reply.");
108+
var replyCreatePostResult =
109+
await agent.ReplyTo(createPostResult.Result.StrongReference, "This is a reply.");
110110

111111
// Reply to the reply using the reply's StrongReference
112112
var replyToReplyStrongReference =
113-
await agent.ReplyTo(replyCreatePostResponse.StrongReference, "This is a reply to the reply.");
113+
await agent.ReplyTo(replyCreatePostResult.StrongReference, "This is a reply to the reply.");
114114
```
115115

116116
Replying to a post creates a new record, and it may not surprise you to see that the `ReplyTo()`
117-
methods returns an `HttpResult<CreateRecordResponse>` just like creating a post does.
117+
methods returns an `HttpResult<CreateRecordResult>` just like creating a post does.
118118

119119
## <a name="likeRepostQuote">Liking, reposting and quote posting posts</a>
120120

@@ -272,7 +272,7 @@ postBuilder.Append('.');
272272
var hashTag = new HashTag("beans");
273273
postBuilder.Append(hashTag);
274274

275-
var facetedCreatePostResponse =
275+
var facetedCreatePostResult =
276276
await agent.Post(postBuilder, cancellationToken: cancellationToken);
277277
```
278278

@@ -379,6 +379,7 @@ var postResult = await agent.Post("Naughty bean content", labels : labels, cance
379379

380380
var postBuilder = new PostBuilder("Naughty bean content");
381381
postBuilder.SetSelfLabels(labels);
382+
382383
var builderPostResult = await agent.Post(postBuilder, cancellationToken: cancellationToken);
383384
```
384385

docs/docs/profileEditing.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ To get the profile record for the current user call `agent.GetProfileRecord()`.
1111
var profileRecordResult = await agent.GetProfileRecord();
1212
```
1313

14-
If the call is successful, it will return an `AtProtoHttpResult` wrapping an `AtProtoRecord<ProfileRecord>` which, in turn,
14+
If the call is successful, it will return an `AtProtoHttpResult` wrapping an `AtProtoRecord<Profile>` which, in turn,
1515
exposes the profile values in its `Value` property.
1616

1717
```c#
@@ -32,7 +32,7 @@ if (profileRecordResult.Succeeded)
3232
profileRecordResult.Result.Value.Description =
3333
$"Profile updated on {DateTimeOffset.Now:G}";
3434

35-
var updateResult = await agent.UpdateProfileRecord(profileRecordResult.Result);
35+
var updateResult = await agent.UpdateProfile(profileRecordResult.Result);
3636
}
3737
```
3838

@@ -45,7 +45,7 @@ This requires a strong reference to a post, and the post must belong to the curr
4545

4646
To remove the pinned post set `PinnedPost` to null and call `UpdateProfileRecord()`.
4747

48-
## <a name="discouragingUnauthenticatedViewing">Discouraging apps from showing the account to unauthenticated users.</a>
48+
## <a name="discouragingUnauthenticatedViewing">Discouraging apps from showing an account to unauthenticated users.</a>
4949

5050
Bluesky has a setting to *discourage* applications from showing an account to unauthenticated users. The Bluesky application respects this setting,
5151
and other applications *may* respect the setting.
@@ -58,7 +58,7 @@ var profileRecordResult = await agent.GetProfileRecord();
5858
if (profileRecordResult.Succeeded)
5959
{
6060
profileRecordResult.Result.Value.DiscourageShowingToLoggedOutUsers = true;
61-
await agent.UpdateProfileRecord(profileRecordResult.Result);
61+
await agent.UpdateProfile(profileRecordResult.Result);
6262
}
6363
```
6464

0 commit comments

Comments
 (0)