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

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