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.

nightsky.gno

8.95 Kb · 300 lines
  1package nightsky
  2
  3import (
  4	"chain"
  5	"strings"
  6	"time"
  7
  8	"gno.land/p/moul/md/v0"
  9	"gno.land/p/nt/avl/v0"
 10	"gno.land/p/nt/ufmt/v0"
 11	nightsky "gno.land/p/nym-vikbez000/nightsky/v1"
 12	"gno.land/r/sys/users"
 13)
 14
 15// maxRecentCaptures is the number of captures kept in the network-wide feed.
 16const maxRecentCaptures = 50
 17
 18var (
 19	// Registry of all telescopes in the network, keyed by realm pkgPath.
 20	// An avl.Tree is used over a map: the registry grows over time, only the
 21	// search path is loaded on access, and iteration is in deterministic
 22	// (sorted) key order — important for stable Render output.
 23	telescopes = avl.NewTree()
 24
 25	// Recent captures from all telescopes (bounded, newest-first).
 26	recentCaptures = nightsky.NewCaptureFeed(maxRecentCaptures)
 27)
 28
 29// Register registers a telescope realm in the NightSky network.
 30// Call from your realm's init() function:
 31//
 32//	nightsky.Register(cross(cur), config)
 33func Register(cur realm, cfg nightsky.TelescopeConfig) {
 34	submission := cur.Previous()
 35	pkgPath := submission.PkgPath()
 36
 37	if submission.IsUser() {
 38		panic("Register must be called from realm code, not a user transaction")
 39	}
 40
 41	if telescopes.Has(pkgPath) {
 42		return // already registered, silently ignore duplicate
 43	}
 44
 45	if cfg.Name == "" || cfg.Model == "" || cfg.Owner == "" {
 46		panic("telescope name, model, and owner cannot be empty")
 47	}
 48
 49	cfg.RealmPath = pkgPath
 50
 51	telescopes.Set(pkgPath, cfg)
 52
 53	chain.Emit("TelescopeRegistered", "pkgPath", pkgPath, "name", cfg.Name, "owner", cfg.Owner)
 54}
 55
 56// UpdateTelescopeStatus allows a registered telescope realm to update its status in the registry.
 57// Call from your realm: nightsky.UpdateTelescopeStatus(cross(cur), "online")
 58func UpdateTelescopeStatus(cur realm, status string) {
 59	submission := cur.Previous()
 60	pkgPath := submission.PkgPath()
 61
 62	if submission.IsUser() {
 63		panic("must be called from realm code")
 64	}
 65
 66	config := mustGetTelescope(pkgPath)
 67	config.UpdateStatus(status)
 68	telescopes.Set(pkgPath, config)
 69
 70	chain.Emit("StatusUpdate", "pkgPath", pkgPath, "status", status)
 71}
 72
 73// SubmitCapture allows a registered telescope realm to add a capture to the network feed.
 74// Call from your realm: nightsky.SubmitCapture(cross(cur), imageURL, ra, dec, exposure, capturedBy)
 75func SubmitCapture(cur realm, imageURL string, targetRA, targetDec float64, exposure int, capturedBy string) {
 76	submission := cur.Previous()
 77	pkgPath := submission.PkgPath()
 78
 79	if submission.IsUser() {
 80		panic("must be called from realm code")
 81	}
 82
 83	config := mustGetTelescope(pkgPath)
 84
 85	if imageURL == "" {
 86		panic("image URL cannot be empty")
 87	}
 88
 89	recentCaptures.Add(nightsky.CaptureResult{
 90		ImageURL:    imageURL,
 91		TargetRA:    targetRA,
 92		TargetDec:   targetDec,
 93		Exposure:    exposure,
 94		CapturedBy:  capturedBy,
 95		CapturedAt:  time.Now(),
 96		TelescopeID: config.Name,
 97	})
 98
 99	chain.Emit("NetworkCapture", "pkgPath", pkgPath, "telescope", config.Name, "imageURL", imageURL, "capturedBy", capturedBy)
100}
101
102// UnregisterTelescope removes a telescope realm from the network.
103// Call from your realm: nightsky.UnregisterTelescope(cross(cur))
104func UnregisterTelescope(cur realm) {
105	submission := cur.Previous()
106	pkgPath := submission.PkgPath()
107
108	if submission.IsUser() {
109		panic("must be called from realm code")
110	}
111
112	if _, removed := telescopes.Remove(pkgPath); !removed {
113		panic("telescope not registered")
114	}
115
116	chain.Emit("TelescopeUnregistered", "pkgPath", pkgPath)
117}
118
119// mustGetTelescope returns the config for pkgPath or panics if not registered.
120func mustGetTelescope(pkgPath string) nightsky.TelescopeConfig {
121	if !telescopes.Has(pkgPath) {
122		panic("telescope not registered")
123	}
124	return telescopes.Get(pkgPath).(nightsky.TelescopeConfig)
125}
126
127// GetTelescopeCount returns the number of registered telescopes
128func GetTelescopeCount() int {
129	return telescopes.Size()
130}
131
132// GetOnlineTelescopeCount returns the number of online telescopes
133func GetOnlineTelescopeCount() int {
134	count := 0
135	telescopes.Iterate("", "", func(_ string, value any) bool {
136		if value.(nightsky.TelescopeConfig).Status == "online" {
137			count++
138		}
139		return false
140	})
141	return count
142}
143
144func usernameOrAddressLink(addr string) string {
145	user := users.ResolveAddress(address(addr))
146	if user != nil {
147		return user.RenderLink("")
148	}
149	return addr
150}
151
152func Render(path string) string {
153	if path == "telescopes" {
154		return renderTelescopeList()
155	}
156
157	if path == "captures" {
158		return renderRecentCaptures()
159	}
160
161	if path == "map" {
162		return renderTelescopeMap()
163	}
164
165	return renderMainPage()
166}
167
168func renderMainPage() string {
169	out := ""
170	out += md.H1("Gno NightSky 🔭")
171	out += "Decentralized telescope network for stargazing\n\n"
172	out += "Connect your smart telescope to the Gno.land blockchain and share access to the stars!\n\n"
173
174	out += md.H2("Network Status")
175	totalTelescopes := GetTelescopeCount()
176	onlineTelescopes := GetOnlineTelescopeCount()
177	out += ufmt.Sprintf("🔭 Total Telescopes: %d\n\n", totalTelescopes)
178	out += ufmt.Sprintf("🟢 Online: %d\n\n", onlineTelescopes)
179	out += ufmt.Sprintf("🔴 Offline: %d\n\n", totalTelescopes-onlineTelescopes)
180
181	out += md.H2("Quick Links")
182	out += md.Link("View All Telescopes", "/r/nym-vikbez000/nightsky/v1:telescopes") + "\n\n"
183	out += md.Link("Recent Captures", "/r/nym-vikbez000/nightsky/v1:captures") + "\n\n"
184	out += md.Link("Telescope Map", "/r/nym-vikbez000/nightsky/v1:map") + "\n\n"
185
186	if latest, ok := recentCaptures.Latest(); ok {
187		out += md.H2("Latest Capture")
188		out += "![Latest capture](" + latest.ImageURL + ")\n\n"
189		out += ufmt.Sprintf("_RA %.8fh · Dec %.8f° · %ds · %s_\n\n",
190			latest.TargetRA, latest.TargetDec, latest.Exposure, latest.TelescopeID)
191	}
192
193	out += md.HorizontalRule()
194
195	out += md.H2("How to Register Your Telescope")
196	out += "Deploy your own telescope realm using the `/p/nym-vikbez000/nightsky/v1` package,\n"
197	out += "then call `nightsky.Register(cross(cur), ...)` from your `init()` function.\n\n"
198	out += "You can clone " + md.Link("my telescope realm", "/r/nym-vikbez000/telescope") + " as a starting point.\n\n"
199
200	out += md.H2("About")
201	out += "NightSky enables telescope owners to share access to their equipment through a decentralized network.\n\n"
202	out += "Also take a look at " + md.Link("PiaGno", "https://github.com/gnoverse/piagno") + " 🎹\n\n"
203
204	return out
205}
206
207func renderTelescopeList() string {
208	out := ""
209	out += md.H1("NightSky - Telescope Network")
210
211	if telescopes.Size() == 0 {
212		out += "\nNo telescopes registered yet.\n\n"
213		out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
214		return out
215	}
216
217	out += ufmt.Sprintf("\nTotal: %d telescopes\n\n", telescopes.Size())
218
219	telescopes.Iterate("", "", func(_ string, value any) bool {
220		config := value.(nightsky.TelescopeConfig)
221		statusIcon := "🔴"
222		switch config.Status {
223		case "online":
224			statusIcon = "🟢"
225		case "busy":
226			statusIcon = "🟡"
227		case "error":
228			statusIcon = "🔴"
229		}
230
231		out += md.H3(statusIcon + " " + config.Name)
232		out += ufmt.Sprintf("**Model:** %s\n\n", config.Model)
233		out += ufmt.Sprintf("**Owner:** %s\n\n", usernameOrAddressLink(config.Owner))
234		out += ufmt.Sprintf("**Location:** %.4f°, %.4f°\n\n", config.Latitude, config.Longitude)
235		out += ufmt.Sprintf("**Status:** %s\n\n", config.Status)
236		out += ufmt.Sprintf("**Realm:** %s\n\n", md.Link("Visit", strings.TrimPrefix(config.RealmPath, "gno.land")))
237		out += md.HorizontalRule()
238		return false
239	})
240
241	out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
242
243	return out
244}
245
246func renderRecentCaptures() string {
247	out := ""
248	out += md.H1("NightSky - Recent Captures")
249
250	if recentCaptures.Len() == 0 {
251		out += "\nNo captures yet.\n\n"
252		out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
253		return out
254	}
255
256	out += ufmt.Sprintf("\nShowing %d most recent captures\n\n", recentCaptures.Len())
257
258	i := 0
259	recentCaptures.IterateNewest(func(capture nightsky.CaptureResult) bool {
260		i++
261		out += md.H3(ufmt.Sprintf("Capture #%d", i))
262		out += ufmt.Sprintf("**Telescope:** %s\n\n", capture.TelescopeID)
263		out += ufmt.Sprintf("**Captured by:** %s\n\n", usernameOrAddressLink(capture.CapturedBy))
264		out += ufmt.Sprintf("**Target:** RA %.8fh, Dec %.8f°\n\n", capture.TargetRA, capture.TargetDec)
265		out += ufmt.Sprintf("**Exposure:** %d seconds\n\n", capture.Exposure)
266		out += ufmt.Sprintf("**Image:** %s\n\n", md.Link("View", capture.ImageURL))
267		out += md.HorizontalRule()
268		return false
269	})
270
271	out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
272
273	return out
274}
275
276func renderTelescopeMap() string {
277	out := ""
278	out += md.H1("NightSky - Telescope Map")
279	out += "\n📍 Interactive map coming soon!\n\n"
280	out += "For now, here are the telescope locations:\n\n"
281
282	if telescopes.Size() == 0 {
283		out += "No telescopes registered yet.\n\n"
284	} else {
285		telescopes.Iterate("", "", func(_ string, value any) bool {
286			config := value.(nightsky.TelescopeConfig)
287			out += ufmt.Sprintf("- **%s**: %.4f°, %.4f° (%s)\n\n",
288				config.Name,
289				config.Latitude,
290				config.Longitude,
291				config.Status,
292			)
293			return false
294		})
295	}
296
297	out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
298
299	return out
300}