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.

sheet.gno

6.60 Kb · 326 lines
  1package arenaview
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	arena "gno.land/r/g10ragdr3cjqkael8rslpw4y6rlelhev4h0fswcx/arena"
  8)
  9
 10const pageSize = 8
 11
 12// Sheet is the stable text board the website reads.
 13// kind selects the page. a, b, and c are cursors or decimal limits.
 14// Gated bodies are left empty. The fund realm still stores them.
 15func Sheet(kind, a, b, c string) string {
 16	var body string
 17	switch kind {
 18	case "feed":
 19		body = sheetPosts(arena.Feed(atoiOpt(a), atoiLimit(b)), false)
 20	case "author":
 21		p, ok := arena.ProfileByHandle(a)
 22		if !ok {
 23			panic("unknown handle")
 24		}
 25		body = sheetPosts(arena.PostsBy(p.Addr, atoiOpt(b), atoiLimit(c)), false)
 26	case "replies":
 27		body = sheetPosts(arena.Replies(atoiReq(a), atoiOpt(b), atoiLimit(c)), true)
 28	case "post":
 29		body = sheetOne(atoiReq(a))
 30	case "people":
 31		body = sheetProfiles(arena.Handles(a, atoiLimit(b)))
 32	case "profile":
 33		body = sheetProfile(a)
 34	case "account":
 35		body = sheetAccount(a)
 36	case "hot":
 37		body = sheetPosts(arena.Hot(atoiLimit(a)), false)
 38	case "followfeed":
 39		body = sheetFollowFeed(a, atoiLimit(b))
 40	case "graph":
 41		body = sheetProfiles(arena.FollowingList(mustAddr(a), optAddr(b), atoiLimit(c)))
 42	case "fans":
 43		body = sheetProfiles(arena.FollowerList(mustAddr(a), optAddr(b), atoiLimit(c)))
 44	case "holdings":
 45		body = sheetHoldings(arena.Holdings(mustAddr(a), optAddr(b), atoiLimit(c)))
 46	case "meta":
 47		body = sheetMeta()
 48	case "quotebuy":
 49		due, curve, cf, pf, rf := arena.QuoteBuy(a, atoiReq(b))
 50		body = join5(due, curve, cf, pf, rf)
 51	case "quotesell":
 52		proceeds, curve, cf, pf, rf := arena.QuoteSell(a, atoiReq(b))
 53		body = join5(proceeds, curve, cf, pf, rf)
 54	case "shares":
 55		body = strconv.Itoa(arena.SharesOf(mustAddr(a), b))
 56	default:
 57		panic("kind")
 58	}
 59	return "v1\n" + body
 60}
 61
 62func sheetPosts(posts []arena.Article, full bool) string {
 63	var b strings.Builder
 64	for i, p := range posts {
 65		if i > 0 {
 66			b.WriteString("\n")
 67		}
 68		if full {
 69			b.WriteString(postLine(p))
 70		} else {
 71			b.WriteString(cardLine(p))
 72		}
 73	}
 74	return b.String()
 75}
 76
 77func sheetOne(id int) string {
 78	p, ok := arena.GetPost(id)
 79	if !ok {
 80		return ""
 81	}
 82	return postLine(p)
 83}
 84
 85func sheetProfile(handle string) string {
 86	p, ok := arena.ProfileByHandle(handle)
 87	if !ok {
 88		return ""
 89	}
 90	return profileLine(p)
 91}
 92
 93func sheetAccount(raw string) string {
 94	who := optAddr(raw)
 95	if !who.IsValid() {
 96		return ""
 97	}
 98	p, ok := arena.ProfileOf(who)
 99	if !ok {
100		return ""
101	}
102	return profileLine(p)
103}
104
105func sheetProfiles(rows []arena.Profile) string {
106	var b strings.Builder
107	for i, p := range rows {
108		if i > 0 {
109			b.WriteString("\n")
110		}
111		b.WriteString(p.Handle + "\t" + esc(p.Name) + "\t" + strconv.Itoa(p.Followers) + "\t" + strconv.Itoa(p.Posts))
112	}
113	return b.String()
114}
115
116func sheetHoldings(rows []arena.Holding) string {
117	var b strings.Builder
118	for i, h := range rows {
119		if i > 0 {
120			b.WriteString("\n")
121		}
122		b.WriteString(h.Creator.String() + "\t" + esc(h.Handle) + "\t" + strconv.Itoa(h.Amount))
123	}
124	return b.String()
125}
126
127func sheetFollowFeed(raw string, limit int) string {
128	who := mustAddr(raw)
129	posts := arena.Feed(0, limit)
130	var b strings.Builder
131	n := 0
132	for _, p := range posts {
133		prof, ok := arena.ProfileOf(p.Author)
134		if !ok || !arena.Following(who, prof.Handle) {
135			continue
136		}
137		if n > 0 {
138			b.WriteString("\n")
139		}
140		b.WriteString(cardLine(p))
141		n++
142	}
143	return b.String()
144}
145
146func sheetMeta() string {
147	paused := "0"
148	if arena.Paused() {
149		paused = "1"
150	}
151	return strings.Join([]string{
152		arena.Version(),
153		arena.Successor(),
154		paused,
155		strconv.FormatInt(arena.TotalReserve(), 10),
156		arena.Admin().String(),
157		arena.Treasury().String(),
158		strconv.Itoa(arena.MemberCount()),
159		strconv.Itoa(arena.FeedCount()),
160		strconv.Itoa(arena.LatestID()),
161	}, "\t")
162}
163
164func profileLine(p arena.Profile) string {
165	supply, reserve, holders := arena.SupplyOf(p.Handle)
166	next := "-1"
167	if supply < arena.MaxSupply {
168		due, _, _, _, _ := arena.QuoteBuy(p.Handle, 1)
169		next = strconv.FormatInt(due, 10)
170	}
171	ref := ""
172	if p.Referrer.IsValid() {
173		ref = p.Referrer.String()
174	}
175	return strings.Join([]string{
176		p.Handle,
177		esc(p.Name),
178		esc(p.Bio),
179		p.Addr.String(),
180		ref,
181		strconv.Itoa(p.Posts),
182		strconv.Itoa(p.Followers),
183		strconv.Itoa(p.Following),
184		strconv.FormatInt(p.TipsIn, 10),
185		strconv.FormatInt(p.TipsOut, 10),
186		strconv.Itoa(supply),
187		strconv.FormatInt(reserve, 10),
188		strconv.Itoa(holders),
189		next,
190	}, "\t")
191}
192
193func cardLine(p arena.Article) string {
194	h, name := namesOf(p.Author)
195	return strings.Join([]string{
196		strconv.Itoa(p.ID),
197		esc(h),
198		esc(name),
199		strconv.Itoa(p.Likes),
200		strconv.Itoa(p.Replies),
201		strconv.FormatInt(p.Tips, 10),
202		bit(p.Gated),
203		esc(openPreview(p)),
204	}, "\t")
205}
206
207func postLine(p arena.Article) string {
208	h, name := namesOf(p.Author)
209	body := ""
210	if !p.Gated && !p.Deleted {
211		body = p.Body
212	}
213	return strings.Join([]string{
214		strconv.Itoa(p.ID),
215		p.Author.String(),
216		esc(h),
217		esc(name),
218		strconv.Itoa(p.ReplyTo),
219		strconv.Itoa(p.Likes),
220		strconv.Itoa(p.Replies),
221		strconv.FormatInt(p.Tips, 10),
222		bit(p.Gated),
223		bit(p.Deleted),
224		strconv.FormatInt(p.Height, 10),
225		esc(body),
226	}, "\t")
227}
228
229func namesOf(a address) (handle, name string) {
230	p, ok := arena.ProfileOf(a)
231	if !ok {
232		return a.String(), a.String()
233	}
234	return p.Handle, p.Name
235}
236
237func openPreview(p arena.Article) string {
238	if p.Gated || p.Deleted {
239		return ""
240	}
241	return preview(strings.ReplaceAll(p.Body, "\n", " "), 160)
242}
243
244func bit(v bool) string {
245	if v {
246		return "1"
247	}
248	return "0"
249}
250
251func esc(s string) string {
252	s = strings.ReplaceAll(s, "\\", "\\\\")
253	s = strings.ReplaceAll(s, "\r", "")
254	s = strings.ReplaceAll(s, "\n", "\\n")
255	s = strings.ReplaceAll(s, "\t", "\\t")
256	return s
257}
258
259func join5(a, b, c, d, e int64) string {
260	return strconv.FormatInt(a, 10) + "\t" + strconv.FormatInt(b, 10) + "\t" + strconv.FormatInt(c, 10) + "\t" + strconv.FormatInt(d, 10) + "\t" + strconv.FormatInt(e, 10)
261}
262
263func atoiOpt(s string) int {
264	s = strings.TrimSpace(s)
265	if s == "" {
266		return 0
267	}
268	n, err := strconv.Atoi(s)
269	if err != nil || n < 0 {
270		panic("cursor")
271	}
272	return n
273}
274
275func atoiReq(s string) int {
276	n := atoiOpt(s)
277	if n < 1 {
278		panic("id")
279	}
280	return n
281}
282
283func atoiLimit(s string) int {
284	s = strings.TrimSpace(s)
285	if s == "" {
286		return pageSize
287	}
288	n, err := strconv.Atoi(s)
289	if err != nil {
290		panic("limit")
291	}
292	if n < 1 || n > 20 {
293		panic("limit")
294	}
295	return n
296}
297
298func mustAddr(s string) address {
299	a := optAddr(s)
300	if !a.IsValid() {
301		panic("bad address")
302	}
303	return a
304}
305
306func optAddr(s string) address {
307	s = strings.TrimSpace(s)
308	if s == "" {
309		return address("")
310	}
311	if len(s) > 60 {
312		panic("bad address")
313	}
314	return address(s)
315}
316
317func preview(s string, n int) string {
318	if len(s) <= n {
319		return s
320	}
321	i := n
322	for i > 0 && s[i]&0xC0 == 0x80 {
323		i--
324	}
325	return s[:i] + "…"
326}