Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

modrender.gno

12.79 Kb · 414 lines
  1package kourt
  2import (
  3	"math/overflow"
  4	"strconv"
  5	"strings"
  6	bptree "gno.land/p/nt/bptree/v0"
  7	sanitize "gno.land/p/nt/markdown/sanitize/v0"
  8)
  9var (
 10	burnIdx    = bptree.NewBPTree32()
 11	createdIdx = bptree.NewBPTree32()
 12	courtSeq   uint64
 13)
 14func beInv(v int64) string {
 15	if v < 0 {
 16		v = 0
 17	}
 18	return beClaimKey(^uint64(v))
 19}
 20func reindexBurn(c *Court, spent int64) {
 21	sum, ok := overflow.Add64(c.burnedGNOT, spent)
 22	if !ok {
 23		return
 24	}
 25	if c.burnedGNOT > 0 || c.burnKey != "" {
 26		burnIdx.Remove(c.burnKey)
 27	}
 28	c.burnedGNOT = sum
 29	c.burnKey = beInv(c.burnedGNOT) + c.id
 30	burnIdx.Set(c.burnKey, c.id)
 31	recordBurn(c)
 32}
 33func indexNewCourt(c *Court) {
 34	courtSeq++
 35	c.createdSeq = courtSeq
 36	createdIdx.Set(beClaimKey(c.createdSeq), c.id)
 37	c.burnKey = beInv(0) + c.id
 38	burnIdx.Set(c.burnKey, c.id)
 39}
 40func listedPage(order string, offset, limit int) []string {
 41	out := []string{}
 42	if limit <= 0 {
 43		return out
 44	}
 45	skipped := 0
 46	take := func(_ string, v any) bool {
 47		slug, ok := v.(string)
 48		if !ok || !Exists(slug) {
 49			return false
 50		}
 51		c := mustCourt(slug)
 52		if c.tier == tierHidden {
 53			return false
 54		}
 55		if skipped < offset {
 56			skipped++
 57			return false
 58		}
 59		out = append(out, slug)
 60		return len(out) >= limit
 61	}
 62	if order == "new" {
 63		createdIdx.ReverseIterate("", "", take)
 64	} else {
 65		burnIdx.Iterate("", "", take)
 66	}
 67	return out
 68}
 69func ListedCourtsBy(order string, offset, limit int) []string {
 70	return listedPage(order, offset, limit)
 71}
 72func CourtBurnedGNOT(courtSlug string) int64 { return mustCourt(courtSlug).burnedGNOT }
 73func claimTitleFor(c *Court, cs *claimState) string {
 74	if c.mod != nil {
 75		if clm := lookupClaimMod(c.mod, cs.id); clm != nil {
 76			if clm.purged {
 77				return "[removed · " + sanitize.InlineText(clm.tombstone) + "]"
 78			}
 79			if clm.global {
 80				return "[text withheld]"
 81			}
 82		}
 83	}
 84	return sanitize.InlineText(cs.title)
 85}
 86func claimBodyVisible(c *Court, cs *claimState) string {
 87	if cs.body == "" {
 88		return ""
 89	}
 90	if c.mod != nil {
 91		if clm := lookupClaimMod(c.mod, cs.id); clm != nil {
 92			if clm.purged || clm.global {
 93				return ""
 94			}
 95		}
 96	}
 97	return cs.body
 98}
 99func claimMediaVisible(c *Court, cs *claimState) []mediaItem {
100	if len(cs.media) == 0 {
101		return nil
102	}
103	if c.mod != nil {
104		if clm := lookupClaimMod(c.mod, cs.id); clm != nil {
105			if clm.purged || clm.global {
106				return nil
107			}
108		}
109	}
110	return cs.media
111}
112func claimBodyFor(c *Court, cs *claimState) string {
113	body := claimBodyVisible(c, cs)
114	if body == "" {
115		return ""
116	}
117	return sanitize.Block(body)
118}
119func claimBodyQuoted(c *Court, cs *claimState) string {
120	body := claimBodyVisible(c, cs)
121	if body == "" {
122		return ""
123	}
124	return sanitize.Blockquote(body)
125}
126const (
127	platformName   = "Kourt"
128	platformTicker = "KOURT"
129)
130func qualifiedSymbol(c *Court) string {
131	return platformTicker + ":" + sanitize.InlineText(c.coin.Symbol())
132}
133func courtNameFor(c *Court) string {
134	if c.mod != nil && c.mod.courtPurged {
135		return "[removed · " + sanitize.InlineText(c.mod.courtTombstone) + "]"
136	}
137	return sanitize.InlineText(c.name)
138}
139func courtDescFor(c *Court) string {
140	if c.desc == "" {
141		return ""
142	}
143	if c.mod != nil && c.mod.courtPurged {
144		return ""
145	}
146	return sanitize.InlineText(c.desc)
147}
148func hideBanner(c *Court, cs *claimState) string {
149	if c.mod == nil {
150		return ""
151	}
152	clm := lookupClaimMod(c.mod, cs.id)
153	if clm == nil {
154		return ""
155	}
156	switch {
157	case clm.purged:
158		return "> **Removed on legal grounds** (" + sanitize.InlineText(clm.tombstone) +
159			"). The claim's lifecycle is unaffected: stakes, verdict and refunds proceed.\n\n"
160	case clm.global:
161		return "> **Text withheld pending review.** The claim's id, its state, its deadlines and every balance are still shown, so you can always act on a position.\n\n"
162	case clm.meta:
163		return "> **Hidden by the review court.** Listed nowhere, reachable here; the lifecycle is unaffected.\n\n"
164	case clm.court:
165		return "> **Hidden by this court's moderators.** Listed nowhere, reachable here; the lifecycle is unaffected.\n\n"
166	}
167	return ""
168}
169const (
170	stripHeading = "Still flaggable"
171	stripBlurb   = "Answered claims that can still be flagged as spam — nearest deadline first. A claim leaves this list when its rewards are opened, not when it settles, so a settled claim stays here while it can still be flagged. This list ignores moderation: a claim hidden from the lists above still appears here."
172)
173func writeStrip(b *strings.Builder, slug string) {
174	c := mustCourt(slug)
175	n := StripSize(slug)
176	if n == 0 {
177		return
178	}
179	b.WriteString("## " + stripHeading + "\n\n" + stripBlurb + "\n\n")
180	ids := StripPage(slug, 0, renderPageSize)
181	for _, id := range ids {
182		cs := mustClaim(c, id)
183		b.WriteString("- [" + claimTitleFor(c, cs) + "](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + slug + "/" +
184			strconv.FormatUint(id, 10) + ") — settles at block " +
185			strconv.FormatInt(cs.answerHeight+settleDelay, 10) + "\n")
186	}
187	if n > len(ids) {
188		b.WriteString("- _…and " + strconv.Itoa(n-len(ids)) + " more_\n")
189	}
190	b.WriteString("\n")
191}
192func writePending(b *strings.Builder, slug string) {
193	c := mustCourt(slug)
194	n := PendingSize(slug)
195	if n == 0 {
196		return
197	}
198	b.WriteString("## Awaiting an answer\n\nSeeded and appeal claims that no one has answered yet.\n\n")
199	ids := PendingPage(slug, 0, renderPageSize)
200	for _, id := range ids {
201		cs := mustClaim(c, id)
202		tag := ""
203		if cs.seeded {
204			tag = " — seeded by a moderator"
205		}
206		b.WriteString("- [" + claimTitleFor(c, cs) + "](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + slug + "/" +
207			strconv.FormatUint(id, 10) + ")" + tag + "\n")
208	}
209	if n > len(ids) {
210		b.WriteString("- _…and " + strconv.Itoa(n-len(ids)) + " more_\n")
211	}
212	b.WriteString("\n")
213}
214func writeAppeal(b *strings.Builder, c *Court, cs *claimState) {
215	if c.id != metaSlug {
216		return
217	}
218	verb, court, target, valid := AppealParse(cs.id)
219	if !valid {
220		b.WriteString("> _Not a valid review request — this is an ordinary claim on the review court._\n\n")
221		return
222	}
223	b.WriteString("## Review request\n\n")
224	b.WriteString("- action: **" + sanitize.InlineText(verb) + "** — " +
225		appealVerbGloss(verb) + "\n")
226	b.WriteString("- court: " + sanitize.InlineText(court) + "\n")
227	if verbIsAggressive(verb) {
228		b.WriteString("- this is an **aggressive** act: it can only carry on a " +
229			"contested vote that real opposition turned out for. Silence " +
230			"defeats it.\n")
231	} else {
232		b.WriteString("- this is a **restorative** act: it undoes a restriction, so " +
233			"it can carry undisputed. Silence lets it through.\n")
234	}
235	if verb == verbSetmods {
236		b.WriteString("- installs candidate set #" + strconv.FormatUint(target, 10) + ":\n")
237		if !Exists(court) {
238			b.WriteString("  - _that court does not exist_\n\n")
239			return
240		}
241		tc := mustCourt(court)
242		if tc.mod == nil || tc.mod.candidates == nil ||
243			tc.mod.candidates.Get(beClaimKey(target)) == nil {
244			b.WriteString("  - **this candidate set does not exist yet — do not approve**\n\n")
245			return
246		}
247		cand := tc.mod.candidates.Get(beClaimKey(target)).(*modCandidate)
248		for _, a := range cand.members {
249			b.WriteString("  - " + sanitize.InlineText(a.String()) + "\n")
250		}
251		b.WriteString("  - threshold: " + strconv.Itoa(cand.m) + " of " +
252			strconv.Itoa(len(cand.members)) + " must agree\n")
253	} else if target != 0 {
254		b.WriteString("- claim: " + strconv.FormatUint(target, 10) + "\n")
255	}
256	b.WriteString("\n")
257}
258func modActLine(a modAct, kind string) string {
259	line := "- " + kind + " " + strconv.FormatUint(a.rowID, 10) +
260		" · block " + strconv.FormatInt(a.height, 10) + " · " +
261		sanitize.InlineText(a.code) + " · by " + sanitize.InlineText(a.actor.String())
262	switch {
263	case a.purged:
264		line += " — _[reason removed on legal grounds]_"
265	case a.reason != "":
266		line += " — " + sanitize.InlineText(a.reason)
267	}
268	return line
269}
270func renderModLog(slug string) string {
271	if !Exists(slug) {
272		return "## Not found\nNo court by that slug."
273	}
274	c := mustCourt(slug)
275	b := strings.Builder{}
276	b.WriteString("# Moderation log — " + courtNameFor(c) + "\n\n")
277	b.WriteString("Every moderation act on this court's CLAIMS and COMMENTS is recorded here: who acted, what they did, and when. Moderation controls what is listed; it never moves a coin or changes a verdict.\n\n")
278	b.WriteString("[Who holds these seats, and how to challenge them](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" +
279		sanitize.InlineText(slug) + "/election)\n\n")
280	b.WriteString("Curation — which claims a moderator filed under which heading — is " +
281		"not in this list. The tree it produced is on the [court page](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + sanitize.InlineText(slug) + "); the acts themselves are in " +
282		"the chain's event stream.\n\n")
283	if c.mod == nil {
284		b.WriteString("## Moderators\n\n1-of-1 — " + c.admin.String() +
285			" (the court's creator, until an election or an appointment changes it)\n\n")
286		b.WriteString("_No moderation acts yet._\n")
287		return b.String()
288	}
289	cm := c.mod
290	if cm.courtPurged {
291		b.WriteString("> **This court was removed on legal grounds** (" +
292			sanitize.InlineText(cm.courtTombstone) + "). No new claims may be opened; existing claims settle and pay out normally.\n\n")
293	}
294	b.WriteString("## Moderators\n\n")
295	if cm.n == 0 {
296		b.WriteString("_None — the set is empty; the court's holders can elect one._\n\n")
297	} else {
298		b.WriteString("- " + strconv.Itoa(cm.m) + " of " + strconv.Itoa(cm.n) +
299			" must agree for a moderation act\n")
300		if sm := speechM(cm); sm != cm.m {
301			b.WriteString("- " + strconv.Itoa(sm) + " of " + strconv.Itoa(cm.n) +
302				" for a **board** act — hiding a comment, slashing standing, " +
303				"freezing an address — because speech takes a wider agreement " +
304				"than the set's own threshold\n")
305		}
306		if cm.suspended {
307			b.WriteString("- **suspended** — they cannot take new moderation acts. " +
308				"The hides and freezes already in place stand until cleared, and " +
309				"any member may still lift their own\n")
310		}
311		if cm.installedByMeta {
312			b.WriteString("- seated by the review court; may not seed claims until a local election\n")
313		}
314		b.WriteString("\n")
315	}
316	pendingRows := []string{}
317	pendingSeen := 0
318	pendingMore := false
319	cm.pending.Iterate("", "", func(key string, v any) bool {
320		pendingSeen++
321		if a, ok := v.(*approval); ok {
322			deadline := a.openedAt + pendingTTLBlocks
323			if !approvalStale(a) {
324				when := " (block " + strconv.FormatInt(deadline, 10) + ")"
325				if at := deadlineTime(a.openedAtTime, blocksToSecs(pendingTTLBlocks)); at != 0 {
326					when = " " + strconv.FormatInt(at, 10) + when
327				}
328				pendingRows = append(pendingRows,
329					"- "+sanitize.InlineCode(key)+" — "+strconv.Itoa(a.approvers.Size())+
330						" of "+strconv.Itoa(cm.m)+" approved, expires at"+when)
331			}
332		}
333		if len(pendingRows) >= renderPageSize || pendingSeen >= renderPageSize {
334			pendingMore = true
335			return true
336		}
337		return false
338	})
339	if len(pendingRows) > 0 {
340		b.WriteString("## Awaiting approval\n\nProposals this set has started and not yet carried. An approval expires if the set does not reach its threshold in time; the reason is withheld until the act carries.\n\n")
341		for _, r := range pendingRows {
342			b.WriteString(r + "\n")
343		}
344		if pendingMore {
345			b.WriteString("- _…more not shown_\n")
346		}
347		b.WriteString("\n")
348	}
349	rows := []string{}
350	visited := 0
351	truncated := false
352	for i := len(cm.log) - 1; i >= 0 && len(rows) < renderPageSize; i-- {
353		rows = append(rows, modActLine(cm.log[i], "court row"))
354	}
355	if len(rows) >= renderPageSize {
356		truncated = true
357	}
358	cm.claims.ReverseIterate("", "", func(_ string, v any) bool {
359		if len(rows) >= renderPageSize {
360			truncated = true
361			return true
362		}
363		clm, ok := v.(*claimMod)
364		if !ok {
365			return false
366		}
367		visited++
368		for i := len(clm.log) - 1; i >= 0; i-- {
369			rows = append(rows, modActLine(clm.log[i], "row"))
370			if len(rows) >= renderPageSize {
371				truncated = true
372				return true
373			}
374		}
375		if visited >= renderPageSize {
376			truncated = true
377			return true
378		}
379		return false
380	})
381	b.WriteString("## Acts\n\n")
382	if len(rows) == 0 {
383		b.WriteString("_No moderation acts yet._\n")
384		return b.String()
385	}
386	for _, r := range rows {
387		b.WriteString(r + "\n")
388	}
389	if truncated {
390		b.WriteString("\n_Showing the " + strconv.Itoa(len(rows)) +
391			" most recent acts. This court has moderated " + strconv.Itoa(cm.claims.Size()) +
392			" claims; older acts are not shown._\n")
393	}
394	return b.String()
395}
396func appealVerbGloss(verb string) string {
397	switch verb {
398	case verbUnhide:
399		return "un-hides a claim its own court's moderators hid"
400	case verbClear:
401		return "clears a hide the review court itself placed"
402	case verbHide:
403		return "hides a claim across the platform, over its court's head"
404	case verbSuspend:
405		return "disarms a court's moderator set — they can take no further act"
406	case verbUnsuspend:
407		return "re-arms a suspended moderator set, and re-applies the hides that " +
408			"were masked while it was disarmed"
409	case verbSetmods:
410		return "installs a moderator set on that court, replacing whoever holds " +
411			"the seats now"
412	}
413	return "an action this page does not recognise"
414}