nightsky.gno
8.90 Kb · 300 lines
1package nightsky
2
3import (
4 "chain"
5 "time"
6
7 "gno.land/p/moul/md"
8 "gno.land/p/nt/avl/v0"
9 "gno.land/p/nt/ufmt/v0"
10 nightsky "gno.land/p/nym-vikbez000/nightsky0"
11 "gno.land/r/sys/users"
12)
13
14// maxRecentCaptures is the number of captures kept in the network-wide feed.
15const maxRecentCaptures = 50
16
17var (
18 // Registry of all telescopes in the network, keyed by realm pkgPath.
19 // An avl.Tree is used over a map: the registry grows over time, only the
20 // search path is loaded on access, and iteration is in deterministic
21 // (sorted) key order — important for stable Render output.
22 telescopes = avl.NewTree()
23
24 // Recent captures from all telescopes (bounded, newest-first).
25 recentCaptures = nightsky.NewCaptureFeed(maxRecentCaptures)
26)
27
28// Register registers a telescope realm in the NightSky network.
29// Call from your realm's init() function:
30//
31// nightsky.Register(cross(cur), config)
32func Register(cur realm, cfg nightsky.TelescopeConfig) {
33 submission := cur.Previous()
34 pkgPath := submission.PkgPath()
35
36 if submission.IsUser() {
37 panic("Register must be called from realm code, not a user transaction")
38 }
39
40 if telescopes.Has(pkgPath) {
41 return // already registered, silently ignore duplicate
42 }
43
44 if cfg.Name == "" || cfg.Model == "" || cfg.Owner == "" {
45 panic("telescope name, model, and owner cannot be empty")
46 }
47
48 cfg.RealmPath = pkgPath
49
50 telescopes.Set(pkgPath, cfg)
51
52 chain.Emit("TelescopeRegistered", "pkgPath", pkgPath, "name", cfg.Name, "owner", cfg.Owner)
53}
54
55// UpdateTelescopeStatus allows a registered telescope realm to update its status in the registry.
56// Call from your realm: nightsky.UpdateTelescopeStatus(cross(cur), "online")
57func UpdateTelescopeStatus(cur realm, status string) {
58 submission := cur.Previous()
59 pkgPath := submission.PkgPath()
60
61 if submission.IsUser() {
62 panic("must be called from realm code")
63 }
64
65 config := mustGetTelescope(pkgPath)
66 config.UpdateStatus(status)
67 telescopes.Set(pkgPath, config)
68
69 chain.Emit("StatusUpdate", "pkgPath", pkgPath, "status", status)
70}
71
72// SubmitCapture allows a registered telescope realm to add a capture to the network feed.
73// Call from your realm: nightsky.SubmitCapture(cross(cur), imageURL, ra, dec, exposure, capturedBy)
74func SubmitCapture(cur realm, imageURL string, targetRA, targetDec float64, exposure int, capturedBy string) {
75 submission := cur.Previous()
76 pkgPath := submission.PkgPath()
77
78 if submission.IsUser() {
79 panic("must be called from realm code")
80 }
81
82 config := mustGetTelescope(pkgPath)
83
84 if imageURL == "" {
85 panic("image URL cannot be empty")
86 }
87
88 recentCaptures.Add(nightsky.CaptureResult{
89 ImageURL: imageURL,
90 TargetRA: targetRA,
91 TargetDec: targetDec,
92 Exposure: exposure,
93 CapturedBy: capturedBy,
94 CapturedAt: time.Now(),
95 TelescopeID: config.Name,
96 })
97
98 chain.Emit("NetworkCapture", "pkgPath", pkgPath, "telescope", config.Name, "imageURL", imageURL, "capturedBy", capturedBy)
99}
100
101// UnregisterTelescope removes a telescope realm from the network.
102// Call from your realm: nightsky.UnregisterTelescope(cross(cur))
103func UnregisterTelescope(cur realm) {
104 submission := cur.Previous()
105 pkgPath := submission.PkgPath()
106
107 if submission.IsUser() {
108 panic("must be called from realm code")
109 }
110
111 if _, removed := telescopes.Remove(pkgPath); !removed {
112 panic("telescope not registered")
113 }
114
115 chain.Emit("TelescopeUnregistered", "pkgPath", pkgPath)
116}
117
118// mustGetTelescope returns the config for pkgPath or panics if not registered.
119func mustGetTelescope(pkgPath string) nightsky.TelescopeConfig {
120 value, exists := telescopes.Get(pkgPath)
121 if !exists {
122 panic("telescope not registered")
123 }
124 return value.(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/nightsky0:telescopes") + "\n\n"
183 out += md.Link("Recent Captures", "/r/nym-vikbez000/nightsky0:captures") + "\n\n"
184 out += md.Link("Telescope Map", "/r/nym-vikbez000/nightsky0:map") + "\n\n"
185
186 if latest, ok := recentCaptures.Latest(); ok {
187 out += md.H2("Latest Capture")
188 out += "\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/nightsky0` 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/nightsky0") + "\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", config.RealmPath))
237 out += md.HorizontalRule()
238 return false
239 })
240
241 out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky0") + "\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/nightsky0") + "\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/nightsky0") + "\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/nightsky0") + "\n\n"
298
299 return out
300}