Search Apps Documentation Source Content File Folder Download Copy Actions Download

public.gno

13.80 Kb · 407 lines
  1package social
  2
  3import (
  4	"strconv"
  5
  6	"gno.land/p/nt/bptree/v0"
  7	"gno.land/p/nt/ufmt/v0"
  8	"gno.land/r/sys/users"
  9)
 10
 11type UserAndPostID struct {
 12	UserPostAddr address
 13	PostID       PostID
 14}
 15
 16// Post a message to the caller's main user posts.
 17// The caller must already be registered with /r/gnoland/users/v1 Register.
 18// Return the "thread ID" of the new post.
 19// (This is similar to boards.CreateThread, but no message title)
 20func PostMessage(cur realm, body string) PostID {
 21	caller := cur.Previous().Address()
 22	userPosts := getOrCreateUserPosts(caller, usernameOf(caller))
 23	thread := userPosts.AddThread(body)
 24	return thread.id
 25}
 26
 27// Post a reply to the user posts of userPostsAddr where threadid is the ID returned by
 28// the original call to PostMessage. If postid == threadid then create another top-level
 29// post for the threadid, otherwise post a reply to the postid "sub reply".
 30// The caller must already be registered with /r/gnoland/users/v1 Register.
 31// Return the new post ID.
 32// (This is similar to boards.CreateReply.)
 33func PostReply(cur realm, userPostsAddr address, threadid, postid PostID, body string) PostID {
 34	caller := cur.Previous().Address()
 35	userPosts := getUserPosts(userPostsAddr)
 36	if userPosts == nil {
 37		panic("posts for userPostsAddr do not exist")
 38	}
 39	thread := userPosts.GetThread(threadid)
 40	if thread == nil {
 41		panic("threadid in user posts does not exist")
 42	}
 43	if postid == threadid {
 44		reply := thread.AddReply(caller, body)
 45		return reply.id
 46	} else {
 47		post := thread.GetReply(postid)
 48		if post == nil {
 49			panic("postid does not exist")
 50		}
 51		reply := post.AddReply(caller, body)
 52		return reply.id
 53	}
 54}
 55
 56// Repost the message from the user posts of userPostsAddr where threadid is the ID returned by
 57// the original call to PostMessage. This must be a top-level thread (not a reply).
 58// Return the new post ID.
 59// (This is similar to boards.CreateRepost.)
 60func RepostThread(cur realm, userPostsAddr address, threadid PostID, comment string) PostID {
 61	caller := cur.Previous().Address()
 62	if userPostsAddr == caller {
 63		panic("Cannot repost a user's own message")
 64	}
 65
 66	dstUserPosts := getOrCreateUserPosts(caller, usernameOf(caller))
 67
 68	userPosts := getUserPosts(userPostsAddr)
 69	if userPosts == nil {
 70		panic("posts for userPostsAddr do not exist")
 71	}
 72	thread := userPosts.GetThread(threadid)
 73	if thread == nil {
 74		panic("threadid in user posts does not exist")
 75	}
 76	repost := thread.AddRepostTo(caller, comment, dstUserPosts)
 77	return repost.id
 78}
 79
 80// For each address/PostID in addrAndIDs, get the thread post. The Post ID must be
 81// for a a top-level thread (not a reply; to get reply posts, use GetThreadPosts).
 82// If the Post ID is not found, set the result for that Post ID to {}.
 83// The response is a JSON string.
 84func GetJsonTopPostsByID(addrAndIDs []UserAndPostID) string {
 85	json := "[ "
 86	for _, addrAndID := range addrAndIDs {
 87		if len(json) > 2 {
 88			json += ",\n  "
 89		}
 90
 91		userPosts := getUserPosts(addrAndID.UserPostAddr)
 92		if userPosts == nil {
 93			json += "{}"
 94			continue
 95		}
 96
 97		post := userPosts.GetThread(PostID(addrAndID.PostID))
 98		if post == nil {
 99			json += "{}"
100			continue
101		}
102
103		postJson, err := post.MarshalJSON()
104		if err != nil {
105			panic("can't get post JSON")
106		}
107		json += string(postJson)
108	}
109	json += "]"
110
111	return json
112}
113
114// Get posts in a thread for a user. A thread is the sequence of posts without replies.
115// While each post has an an arbitrary id, it also has an index within the thread starting from 0.
116// Limit the response to posts from startIndex up to (not including) endIndex within the thread.
117// If you just want the total count, set startIndex and endIndex to 0 and see the response "n_threads".
118// If threadID is 0 then return the user's top-level posts. (Like render args "user".)
119// If threadID is X and replyID is 0, then return the posts (without replies) in that thread. (Like render args "user/2".)
120// If threadID is X and replyID is Y, then return the posts in the thread starting with replyID. (Like render args "user/2/5".)
121// The response includes reposts by this user (only if threadID is 0), but not messages of other
122// users that are being followed. (See GetHomePosts.) The response is a JSON string.
123func GetThreadPosts(userPostsAddr address, threadID int, replyID int, startIndex int, endIndex int) string {
124	userPosts := getUserPosts(userPostsAddr)
125	if userPosts == nil {
126		panic("posts for userPostsAddr do not exist")
127	}
128
129	if threadID == 0 {
130		return getPosts(userPosts.threads, startIndex, endIndex)
131	}
132
133	thread := userPosts.GetThread(PostID(threadID))
134	if thread == nil {
135		panic(ufmt.Sprintf("thread does not exist with id %d", threadID))
136	}
137
138	if replyID == 0 {
139		return getPosts(thread.replies, startIndex, endIndex)
140	} else {
141		reply := thread.GetReply(PostID(replyID))
142		if reply == nil {
143			panic(ufmt.Sprintf("reply does not exist with id %d in thread with id %d", replyID, threadID))
144		}
145
146		return getPosts(reply.replies, startIndex, endIndex)
147	}
148}
149
150// Update the home posts by scanning all posts from all followed users and adding the
151// followed posts since the last call to RefreshHomePosts (or since started following the user).
152// Return the new count of home posts. The result is something like "(12 int)".
153func RefreshHomePosts(_ realm, userPostsAddr address) int {
154	userPosts := getUserPosts(userPostsAddr)
155	if userPosts == nil {
156		panic("posts for userPostsAddr do not exist")
157	}
158	userPosts.refreshHomePosts()
159
160	return userPosts.homePosts.Size()
161}
162
163// Get the number of posts which GetHomePosts or GetJsonHomePosts will return.
164// The result is something like "(12 int)".
165// This returns the current count of the home posts (without need to pay gas). To include the
166// latest followed posts, call RefreshHomePosts.
167func GetHomePostsCount(userPostsAddr address) int {
168	return GetHomePosts(userPostsAddr).Size()
169}
170
171// Get home posts for a user, which are the user's top-level posts plus all posts of all
172// users being followed.
173// The response is a map of postID -> *Post. The bptree.BPTree sorts by the post ID which is
174// unique for every post and increases in time.
175// If you just want the total count, use GetHomePostsCount.
176// This returns the current state of the home posts (without need to pay gas). To include the
177// latest followed posts, call RefreshHomePosts.
178func GetHomePosts(userPostsAddr address) *bptree.BPTree {
179	userPosts := getUserPosts(userPostsAddr)
180	if userPosts == nil {
181		panic("posts for userPostsAddr do not exist")
182	}
183	return &userPosts.homePosts
184}
185
186// Get home posts for a user (using GetHomePosts), which are the user's top-level posts plus all
187// posts of all users being followed.
188// Limit the response to posts from startIndex up to (not including) endIndex within the home posts.
189// If you just want the total count, use GetHomePostsCount.
190// The response is a JSON string.
191// This returns the current state of the home posts (without need to pay gas). To include the
192// latest posts, call RefreshHomePosts.
193func GetJsonHomePosts(userPostsAddr address, startIndex int, endIndex int) string {
194	allPosts := GetHomePosts(userPostsAddr)
195	postsJson := ""
196	for i := startIndex; i < endIndex && i < allPosts.Size(); i++ {
197		_, postI := allPosts.GetByIndex(i)
198		if postsJson != "" {
199			postsJson += ",\n  "
200		}
201
202		postJson, err := postI.(*Post).MarshalJSON()
203		if err != nil {
204			panic("can't get post JSON")
205		}
206		postsJson += ufmt.Sprintf("{\"index\": %d, \"post\": %s}", int(i), string(postJson))
207	}
208
209	return ufmt.Sprintf("{\"n_posts\": %d, \"posts\": [\n  %s]}", allPosts.Size(), postsJson)
210}
211
212// Update the caller to follow the user with followedAddr. See UserPosts.Follow.
213func Follow(cur realm, followedAddr address) PostID {
214	caller := cur.Previous().Address()
215	if followedAddr == caller {
216		panic("you can't follow yourself")
217	}
218
219	// A user can follow someone before doing any posts, so create the UserPosts if needed.
220	userPosts := getOrCreateUserPosts(caller, usernameOf(caller))
221	return userPosts.Follow(followedAddr)
222}
223
224// Update the caller to unfollow the user with followedAddr. See UserPosts.Unfollow.
225func Unfollow(cur realm, followedAddr address) {
226	caller := cur.Previous().Address()
227	userPosts := getUserPosts(caller)
228	if userPosts == nil {
229		// We don't expect this, but just do nothing.
230		return
231	}
232
233	userPosts.Unfollow(followedAddr)
234}
235
236// Add the reaction by the caller to the post of userPostsAddr, where threadid is the ID
237// returned by the original call to PostMessage. If postid == threadid then add the reaction
238// to a top-level post for the threadid, otherwise add the reaction to the postid "sub reply".
239// (This function's arguments are similar to PostReply.)
240// The caller must already be registered with /r/gnoland/users/v1 Register.
241// Return a boolean indicating whether the userAddr was added. See Post.AddReaction.
242func AddReaction(cur realm, userPostsAddr address, threadid, postid PostID, reaction Reaction) bool {
243	caller := cur.Previous().Address()
244	userPosts := getUserPosts(userPostsAddr)
245	if userPosts == nil {
246		panic("posts for userPostsAddr do not exist")
247	}
248	thread := userPosts.GetThread(threadid)
249	if thread == nil {
250		panic("threadid in user posts does not exist")
251	}
252	if postid == threadid {
253		return thread.AddReaction(caller, reaction)
254	} else {
255		post := thread.GetReply(postid)
256		if post == nil {
257			panic("postid does not exist")
258		}
259		return post.AddReaction(caller, reaction)
260	}
261}
262
263// Remove the reaction by the caller to the post of userPostsAddr, where threadid is the ID
264// returned by the original call to PostMessage. If postid == threadid then remove the reaction
265// from a top-level post for the threadid, otherwise remove the reaction from the postid "sub reply".
266// (This function's arguments are similar to PostReply.)
267// The caller must already be registered with /r/gnoland/users/v1 Register.
268// Return a boolean indicating whether the userAddr was removed. See Post.RemoveReaction.
269func RemoveReaction(cur realm, userPostsAddr address, threadid, postid PostID, reaction Reaction) bool {
270	caller := cur.Previous().Address()
271	userPosts := getUserPosts(userPostsAddr)
272	if userPosts == nil {
273		panic("posts for userPostsAddr do not exist")
274	}
275	thread := userPosts.GetThread(threadid)
276	if thread == nil {
277		panic("threadid in user posts does not exist")
278	}
279	if postid == threadid {
280		return thread.RemoveReaction(caller, reaction)
281	} else {
282		post := thread.GetReply(postid)
283		if post == nil {
284			panic("postid does not exist")
285		}
286		return post.RemoveReaction(caller, reaction)
287	}
288}
289
290// Call users.ResolveAddress and return the result as JSON, or "" if not found.
291// (This is a temporary utility until gno.land supports returning structured data directly.)
292func GetJsonUserByAddress(addr address) string {
293	user := users.ResolveAddress(addr)
294	if user == nil {
295		return ""
296	}
297
298	return marshalJsonUser(user)
299}
300
301// Call users.ResolveName and return the result as JSON, or "" if not found.
302// (This is a temporary utility until gno.land supports returning structured data directly.)
303func GetJsonUserByName(name string) string {
304	user, _ := users.ResolveName(name)
305	if user == nil {
306		return ""
307	}
308
309	return marshalJsonUser(user)
310}
311
312// Get the UserPosts info for the user with the given address, including
313// url, n_threads, n_followers and n_following. If the user address is not
314// found, return "". The name of this function has "Info" because it just returns
315// the number of items, not the items themselves. To get the items, see
316// GetJsonFollowers, etc.
317// The response is a JSON string.
318func GetJsonUserPostsInfo(address address) string {
319	userPosts := getUserPosts(address)
320	if userPosts == nil {
321		return ""
322	}
323
324	json, err := userPosts.MarshalJSON()
325	if err != nil {
326		panic("can't get UserPosts JSON")
327	}
328
329	return string(json)
330}
331
332// Get the UserPosts for the user with the given address, and return
333// the list of followers. If the user address is not found, return "".
334// Limit the response to entries from startIndex up to (not including) endIndex.
335// The response is a JSON string.
336func GetJsonFollowers(address address, startIndex int, endIndex int) string {
337	userPosts := getUserPosts(address)
338	if userPosts == nil {
339		return ""
340	}
341
342	json := ufmt.Sprintf("{\"n_followers\": %d, \"followers\": [\n  ", userPosts.followers.Size())
343	for i := startIndex; i < endIndex && i < userPosts.followers.Size(); i++ {
344		addr, _ := userPosts.followers.GetByIndex(i)
345
346		if i > startIndex {
347			json += ",\n  "
348		}
349		json += ufmt.Sprintf(`{"address": "%s"}`, addr)
350	}
351	json += "]}"
352
353	return json
354}
355
356// Get the UserPosts for the user with the given address, and return
357// the list of other users that this user is following.
358// If the user address is not found, return "".
359// Limit the response to entries from startIndex up to (not including) endIndex.
360// The response is a JSON string.
361func GetJsonFollowing(address address, startIndex int, endIndex int) string {
362	userPosts := getUserPosts(address)
363	if userPosts == nil {
364		return ""
365	}
366
367	json := ufmt.Sprintf("{\"n_following\": %d, \"following\": [\n  ", userPosts.following.Size())
368	for i := startIndex; i < endIndex && i < userPosts.following.Size(); i++ {
369		addr, infoI := userPosts.following.GetByIndex(i)
370
371		if i > startIndex {
372			json += ",\n  "
373		}
374		startedAt, err := infoI.(*FollowingInfo).startedFollowingAt.MarshalJSON()
375		if err != nil {
376			panic("can't get startedFollowingAt JSON")
377		}
378		json += ufmt.Sprintf(`{"address": "%s", "started_following_at": %s}`,
379			addr, string(startedAt))
380	}
381	json += "]}"
382
383	return json
384}
385
386// Get a list of user names starting from the given prefix. Limit the
387// number of results to maxResults.
388func ListUsersByPrefix(prefix string, maxResults int) []string {
389	return listByteStringKeysByPrefix(&gUserAddressByName, prefix, maxResults)
390}
391
392// Get a list of user names starting from the given prefix. Limit the
393// number of results to maxResults.
394// The response is a JSON string.
395func ListJsonUsersByPrefix(prefix string, maxResults int) string {
396	names := ListUsersByPrefix(prefix, maxResults)
397
398	json := "["
399	for i, name := range names {
400		if i > 0 {
401			json += ", "
402		}
403		json += strconv.Quote(name)
404	}
405	json += "]"
406	return json
407}