+12
-10
README.md
+12
-10
README.md
···
36
36
37
37
- `com.blonk.vibe` - Community topic feeds
38
38
- `com.blonk.blip` - Content submissions
39
-
- `com.blonk.groove` - Community engagement
39
+
- `com.blonk.groove` - Community engagement on specific blips
40
40
- `com.blonk.tag` - Universal content labels
41
41
- `com.blonk.blipTag` - Content categorization associations
42
42
···
93
93
Post `#vibe-topic_name` to create new community vibes:
94
94
95
95
```
96
-
Check out this cool #vibe-blockchain project! #defi #web3
96
+
Check out this cool #vibe-art project! #design #creative
97
97
```
98
98
99
-
Once enough community members use `#vibe-blockchain`, it becomes an official vibe.
99
+
Once enough community members use `#vibe-art`, it becomes an official vibe.
100
100
101
101
### Submitting Blips
102
102
103
103
Submit content to vibes with relevant tags:
104
104
105
105
```
106
-
Title: "New DeFi Protocol Launch"
107
-
Body: "Exciting developments in yield farming..."
108
-
Vibe: blockchain_vibe
109
-
Tags: #defi #yield #ethereum
106
+
Title: "Amazing New Design Framework"
107
+
Body: "This changes everything for designers..."
108
+
Vibe: design_vibe
109
+
Tags: #design #tools #creative
110
110
```
111
111
112
112
### Community Engagement
113
113
114
-
- **👍 looks_good** - Positive community feedback
115
-
- **💩 shit_rips** - Critical community feedback
114
+
Groove on blips to provide community feedback:
116
115
117
-
Grooves drive content visibility and trending algorithms.
116
+
- **👍 looks_good** - Positive endorsement of the blip
117
+
- **shit_rips** - Critical feedback on the blip
118
+
119
+
Each groove is linked to a specific blip and drives content visibility on the radar.
118
120
119
121
### Discovery
120
122
+54
-1
lib/elixir_blonk/atproto.ex
+54
-1
lib/elixir_blonk/atproto.ex
···
36
36
- `com.blonk.blip` - Content submissions to vibes
37
37
- `com.blonk.tag` - Universal community labels
38
38
- `com.blonk.blipTag` - Content categorization associations
39
+
- `com.blonk.groove` - Community engagement records (looks_good/shit_rips)
39
40
- `com.blonk.vibe` - Topic-based community feeds (future)
40
-
- `com.blonk.groove` - Community engagement records (future)
41
41
42
42
## Error Handling
43
43
···
55
55
# Create Blonk records
56
56
{:ok, %{uri: uri, cid: cid}} = ATProto.create_blip(client, blip)
57
57
{:ok, %{uri: uri, cid: cid}} = ATProto.create_tag(client, tag)
58
+
{:ok, %{uri: uri, cid: cid}} = ATProto.create_groove(client, groove)
58
59
59
60
# Analyze engagement for hot posts
60
61
{:ok, %{reply_count: count}} = ATProto.get_post_engagement(client, post_uri)
···
254
255
|> compact_record()
255
256
256
257
create_record(client, @blip_tag_nsid, record)
258
+
end
259
+
260
+
@doc """
261
+
Create a groove (community engagement) record in ATProto.
262
+
263
+
Grooves are Blonk's community feedback mechanism, enabling users to express
264
+
their reaction to blips through binary engagement (looks_good/shit_rips).
265
+
Each groove is tightly linked to a specific blip.
266
+
267
+
## Blip-Groove Relationship
268
+
269
+
**Every groove references the blip it's responding to:**
270
+
- Groove record includes blip URI/CID for cross-platform reference
271
+
- Database foreign key ensures data integrity
272
+
- Enables discovery of all grooves for a specific blip
273
+
- Powers community-driven content curation algorithms
274
+
275
+
## ATProto Schema (`com.blonk.groove`)
276
+
277
+
- `blip` - Reference to the grooved blip (uri/cid)
278
+
- `grooveType` - Either "looks_good" or "shit_rips"
279
+
- `author` - DID of user creating the groove
280
+
- `createdAt` - When this groove was created
281
+
282
+
## Community Impact
283
+
284
+
Grooves create the engagement signals that drive Blonk's discovery:
285
+
- High groove counts surface popular content on radar
286
+
- Community consensus emerges through groove patterns
287
+
- Cross-vibe content discovery powered by groove activity
288
+
289
+
## Examples
290
+
291
+
# User grooves positively on a blip
292
+
{:ok, %{uri: uri, cid: cid}} = ATProto.create_groove(client, groove)
293
+
# Results in: at://did:plc:user/com.blonk.groove/rkey
294
+
# Links to: at://did:plc:author/com.blonk.blip/tech-post
295
+
"""
296
+
def create_groove(%__MODULE__{} = client, groove) do
297
+
# Get the blip record that this groove is for
298
+
blip = ElixirBlonk.Blips.get_blip!(groove.blip_id)
299
+
300
+
record = %{
301
+
"$type" => @groove_nsid,
302
+
blip: %{uri: blip.uri, cid: blip.cid},
303
+
grooveType: groove.groove_type,
304
+
author: groove.author_did,
305
+
createdAt: DateTime.to_iso8601(DateTime.utc_now())
306
+
}
307
+
|> compact_record()
308
+
309
+
create_record(client, @groove_nsid, record)
257
310
end
258
311
259
312
defp compact_record(record) do
+8
-8
lib/elixir_blonk/blips.ex
+8
-8
lib/elixir_blonk/blips.ex
···
59
59
60
60
## Examples
61
61
62
-
# Create a blip for the crypto vibe
62
+
# Create a blip for a vibe
63
63
{:ok, blip} = Blips.create_blip(%{
64
-
title: "New DeFi Protocol Launch",
65
-
body: "Exciting developments in yield farming...",
66
-
url: "https://protocol.xyz",
67
-
vibe_uri: crypto_vibe.uri,
68
-
tags: ["defi", "yield", "ethereum"],
64
+
title: "Amazing New Framework Released",
65
+
body: "This changes everything for developers...",
66
+
url: "https://example.com/framework",
67
+
vibe_uri: tech_vibe.uri,
68
+
tags: ["framework", "productivity", "tools"],
69
69
author_did: "did:plc:user123"
70
70
})
71
71
72
72
# Find blips by tag across all vibes
73
-
defi_blips = Blips.list_blips_by_tag("defi")
73
+
framework_blips = Blips.list_blips_by_tag("framework")
74
74
75
75
# Search blips by content
76
-
search_results = Blips.search_blips("protocol")
76
+
search_results = Blips.search_blips("framework")
77
77
"""
78
78
79
79
import Ecto.Query, warn: false
+1
-1
lib/elixir_blonk/firehose/consumer.ex
+1
-1
lib/elixir_blonk/firehose/consumer.ex
+38
-12
lib/elixir_blonk/grooves.ex
+38
-12
lib/elixir_blonk/grooves.ex
···
11
11
12
12
**Grooves are binary community feedback on blips:**
13
13
- **looks_good** (👍) - Positive community endorsement
14
-
- **shit_rips** (💩) - Critical community feedback
14
+
- **shit_rips** - Critical community feedback
15
15
- Each user can groove once per blip with either reaction
16
16
- Groove counts drive content visibility and trending algorithms
17
17
- Community-driven curation without complex scoring systems
···
87
87
"""
88
88
89
89
import Ecto.Query, warn: false
90
+
require Logger
90
91
alias ElixirBlonk.Repo
91
92
92
93
alias ElixirBlonk.Grooves.Groove
···
106
107
attrs
107
108
end
108
109
109
-
%Groove{}
110
-
|> Groove.changeset(attrs)
111
-
|> Repo.insert()
112
-
|> case do
113
-
{:ok, groove} ->
114
-
# Update groove counts on the blip
115
-
if groove.blip_id do
116
-
Blips.update_groove_counts(groove.blip_id)
117
-
end
118
-
{:ok, groove}
119
-
error -> error
110
+
# First create in local database
111
+
with {:ok, groove} <- %Groove{}
112
+
|> Groove.changeset(attrs)
113
+
|> Repo.insert() do
114
+
115
+
# Update groove counts on the blip
116
+
if groove.blip_id do
117
+
Blips.update_groove_counts(groove.blip_id)
118
+
end
119
+
120
+
# Then try to create in ATProto if enabled
121
+
if Application.get_env(:elixir_blonk, :atproto_enabled, true) do
122
+
Task.Supervisor.start_child(ElixirBlonk.TaskSupervisor, fn ->
123
+
create_groove_in_atproto(groove)
124
+
end)
125
+
end
126
+
127
+
{:ok, groove}
120
128
end
121
129
end
122
130
···
224
232
|> where([g], g.author_did == ^author_did and g.subject_uri == ^subject_uri)
225
233
|> select([g], g.groove_type)
226
234
|> Repo.one()
235
+
end
236
+
237
+
# Private functions
238
+
239
+
defp create_groove_in_atproto(groove) do
240
+
with {:ok, client} <- ElixirBlonk.ATProto.SimpleSession.get_client(),
241
+
{:ok, %{uri: uri, cid: cid}} <- ElixirBlonk.ATProto.create_groove(client, groove) do
242
+
243
+
# Update local record with ATProto URI and CID
244
+
groove
245
+
|> Groove.changeset(%{uri: uri, cid: cid})
246
+
|> Repo.update()
247
+
248
+
Logger.info("Created groove in ATProto: #{uri}")
249
+
else
250
+
{:error, reason} ->
251
+
Logger.error("Failed to create groove in ATProto: #{inspect(reason)}")
252
+
end
227
253
end
228
254
end
+49
lib/elixir_blonk/grooves/groove.ex
+49
lib/elixir_blonk/grooves/groove.ex
···
1
1
defmodule ElixirBlonk.Grooves.Groove do
2
+
@moduledoc """
3
+
Represents community engagement on blips in the Blonk ecosystem.
4
+
5
+
Grooves are the fundamental feedback mechanism that drives content curation
6
+
and community engagement. Each groove represents a user's reaction to a
7
+
specific blip, creating the social signal that powers Blonk's trending
8
+
algorithms and community-driven content discovery.
9
+
10
+
## Blip-Groove Relationship
11
+
12
+
**Every groove is tightly coupled to a specific blip:**
13
+
- `blip_id` - Database foreign key to the blip being grooved
14
+
- `subject_uri` - ATProto URI of the blip for cross-platform reference
15
+
- `belongs_to :blip` - Ecto association for database queries
16
+
- Cascade deletion when blip is removed
17
+
18
+
## ATProto Integration
19
+
20
+
**Grooves become `com.blonk.groove` ATProto records:**
21
+
- `uri` - ATProto record URI for the groove
22
+
- `cid` - Content identifier for the groove record
23
+
- `blip` reference in ATProto record links to the grooved blip
24
+
- Enables cross-platform groove discovery and portability
25
+
26
+
## Community Engagement Types
27
+
28
+
- **looks_good** (👍) - Positive community endorsement
29
+
- **shit_rips** - Critical community feedback
30
+
31
+
## Schema Fields
32
+
33
+
- `uri` - ATProto record URI (e.g., "at://did:plc:user/com.blonk.groove/rkey")
34
+
- `cid` - Content identifier for ATProto record
35
+
- `author_did` - DID of user who created this groove
36
+
- `subject_uri` - ATProto URI of the blip being grooved
37
+
- `groove_type` - Either "looks_good" or "shit_rips"
38
+
- `blip_id` - Foreign key to the grooved blip
39
+
40
+
## Examples
41
+
42
+
# Groove on a blip
43
+
%Groove{
44
+
author_did: "did:plc:user123",
45
+
groove_type: "looks_good",
46
+
subject_uri: "at://did:plc:author/com.blonk.blip/tech-news",
47
+
blip_id: tech_blip.id
48
+
}
49
+
"""
50
+
2
51
use Ecto.Schema
3
52
import Ecto.Changeset
4
53
+3
-3
lib/elixir_blonk/hot_post_sweeper.ex
+3
-3
lib/elixir_blonk/hot_post_sweeper.ex
···
54
54
55
55
## Examples
56
56
57
-
# Sweeper finds a trending crypto post
57
+
# Sweeper finds a trending tech post
58
58
hot_post = %HotPost{
59
-
text: "New DeFi protocol just launched...",
60
-
external_url: "https://protocol.xyz",
59
+
text: "Amazing new development framework released...",
60
+
external_url: "https://example.com/framework",
61
61
reply_count: 12 # Above threshold!
62
62
}
63
63
+4
-4
lib/elixir_blonk/vibes.ex
+4
-4
lib/elixir_blonk/vibes.ex
···
9
9
## What Are Vibes?
10
10
11
11
**Vibes are community-driven topic feeds:**
12
-
- Interest-based communities (e.g., crypto_vibe, art_vibe, tech_vibe)
12
+
- Interest-based communities (e.g., art_vibe, tech_vibe, music_vibe)
13
13
- Created organically through #vibe-name mentions reaching critical mass
14
14
- Contain blips relevant to the community's focus
15
15
- Enable targeted audience engagement and content discovery
···
71
71
72
72
# Track a potential new vibe
73
73
Vibes.record_vibe_mention(%{
74
-
vibe_name: "defi",
74
+
vibe_name: "art",
75
75
author_did: "did:plc:user123",
76
76
post_uri: "at://did:plc:user123/app.bsky.feed.post/rkey",
77
77
mentioned_at: DateTime.utc_now()
78
78
})
79
79
80
80
# Check if vibe has reached emergence threshold
81
-
case Vibes.check_vibe_emergence("defi") do
81
+
case Vibes.check_vibe_emergence("art") do
82
82
{:emerging, vibe} ->
83
83
# New community has formed!
84
84
{:not_ready, count} ->
···
86
86
end
87
87
88
88
# Get vibe content for radar
89
-
popular_blips = Blips.list_blips_by_vibe(crypto_vibe.uri)
89
+
popular_blips = Blips.list_blips_by_vibe(art_vibe.uri)
90
90
"""
91
91
92
92
import Ecto.Query, warn: false