Transaction

7B597BD13A16B4…926AB34F3D66

Block 654,240 · index 0 · RPC

Summary

Hash
7B597BD13A16B413C9A45B0996BFD7106D6197C98884793F3084926AB34F3D66
Block
654,240
Size
26067 bytes
Gas used
38,801,182 / 100,000,000
Fee
10000000 ugnot
Status
success

Messages

#1AddPackagegno.land/p/nym-vikbez000/nightsky/v113 arguments

Arguments · 13

  1. #1nightsky
  2. #2feed.gno
  3. #3package nightsky import ( "gno.land/p/nt/avl/v0" "gno.land/p/nt/ufmt/v0" ) // CaptureFeed is a bounded, newest-first collection of CaptureResult values // backed by an avl.Tree. It is used for both a single telescope's history and // the network-wide recent feed. // // Entries are keyed by a zero-padded, monotonically increasing sequence so the // tree iterates in insertion order. Newest-first reads use ReverseIterate, and // trimming removes the lowest key (the oldest entry). This avoids the O(n) // slice-prepend the previous implementation paid on every insert, and only // loads the search path on access instead of the whole collection. type CaptureFeed struct { tree *avl.Tree seq int max int } // NewCaptureFeed creates a feed that keeps at most max entries. func NewCaptureFeed(max int) *CaptureFeed { if max <= 0 { panic("capture feed size must be positive") } return &CaptureFeed{ tree: avl.NewTree(), seq: 0, max: max, } } // feedKey renders a sequence into a fixed-width key so that string ordering // matches numeric ordering. func feedKey(seq int) string { return ufmt.Sprintf("%020d", seq) } // Add inserts a capture as the newest entry and trims the oldest if the feed is // over capacity. func (f *CaptureFeed) Add(c CaptureResult) { f.seq++ f.tree.Set(feedKey(f.seq), c) for f.tree.Size() > f.max { oldest, _ := f.tree.GetByIndex(0) f.tree.Remove(oldest) } } // Len returns the number of stored captures. func (f *CaptureFeed) Len() int { return f.tree.Size() } // Latest returns the most recent capture, if any. func (f *CaptureFeed) Latest() (CaptureResult, bool) { if f.tree.Size() == 0 { return CaptureResult{}, false } _, value := f.tree.GetByIndex(f.tree.Size() - 1) return value.(CaptureResult), true } // RemoveAt removes the index-th newest capture (0 = most recent). It reports // whether a capture was removed. Used for moderation of individual entries. func (f *CaptureFeed) RemoveAt(index int) bool { size := f.tree.Size() if index < 0 || index >= size { return false } // Newest-first index i maps to ascending-key index (size-1-i). key, _ := f.tree.GetByIndex(size - 1 - index) _, removed := f.tree.Remove(key) return removed } // IterateNewest calls cb for each capture from newest to oldest. Return true // from cb to stop early. func (f *CaptureFeed) IterateNewest(cb func(c CaptureResult) bool) { f.tree.ReverseIterate("", "", func(_ string, value any) bool { return cb(value.(CaptureResult)) }) }
  4. #4gnomod.toml
  5. #5module = "gno.land/p/nym-vikbez000/nightsky/v1" gno = "0.9" private = false
  6. #6nightsky.gno
  7. #7// Package nightsky holds the shared types and single-telescope logic for the // GnoNightSky telescope network. // // Note on panics: although this lives under p/, it is application-specific // logic for the NightSky realms rather than a general-purpose reusable // library. It deliberately panics on invalid input / failed preconditions // (the assert-like style described in effective-gno) so the wrapping realms // don't have to re-check every error. Callers that want softer handling // should validate before calling. package nightsky import ( "time" "gno.land/p/nt/avl/v0" ) // TelescopeConfig contains the settings and configuration for a telescope type TelescopeConfig struct { Name string // Name of the telescope Model string // Telescope model (e.g., "Seestar S50") Latitude float64 // Geographic location Longitude float64 // Geographic location Owner string // Owner's gno address RealmPath string // Path to the telescope's realm (e.g., "/r/username/telescope") Status string // Current status: "online", "offline", "busy", "error" LastUpdate time.Time // Last status update } // AccessRule defines who can access a telescope type AccessRule struct { AllowedAddress string // Gno address allowed to access GrantedBy string // Who granted access GrantedAt time.Time // When access was granted ExpiresAt time.Time // When access expires (zero time = never) } // TelescopeCommand represents a command sent to a telescope type TelescopeCommand struct { CommandType string // "capture" or "stop" TargetRA float64 // Right Ascension (for pointing/tracking) TargetDec float64 // Declination (for pointing/tracking) Exposure int // Exposure time in seconds (for capture) RequestedBy string // Who requested the command RequestedAt time.Time // When the command was requested } // CaptureResult represents the result of an image capture type CaptureResult struct { ImageURL string // URL to the captured image (e.g., Imgur) TargetRA float64 // Right Ascension of target TargetDec float64 // Declination of target Exposure int // Exposure time in seconds CapturedBy string // Who captured the image CapturedAt time.Time // When the image was captured TelescopeID string // Which telescope captured it } // NewTelescopeConfig creates a new telescope configuration func NewTelescopeConfig(name, model string, lat, lon float64, owner, realmPath string) TelescopeConfig { return TelescopeConfig{ Name: name, Model: model, Latitude: lat, Longitude: lon, Owner: owner, RealmPath: realmPath, Status: "offline", LastUpdate: time.Now(), } } // UpdateStatus updates the telescope status func (t *TelescopeConfig) UpdateStatus(status string) { t.Status = status t.LastUpdate = time.Now() } // IsAccessAllowed checks if an address has a current (non-expired) access rule. // rules is an avl.Tree keyed by allowed address holding AccessRule values. func IsAccessAllowed(address string, rules *avl.Tree) bool { if !rules.Has(address) { return false } rule := rules.Get(address).(AccessRule) return rule.ExpiresAt.IsZero() || time.Now().Before(rule.ExpiresAt) } // ValidateCommand validates a telescope command func ValidateCommand(cmd TelescopeCommand, config TelescopeConfig) { switch cmd.CommandType { case "capture", "stop": default: panic("invalid command type: must be capture or stop") } if cmd.CommandType == "capture" { if cmd.TargetRA < 0 || cmd.TargetRA >= 24 { panic("invalid RA: must be between 0 and 24 hours") } if cmd.TargetDec < -90 || cmd.TargetDec > 90 { panic("invalid Dec: must be between -90 and 90 degrees") } if cmd.Exposure < 1 || cmd.Exposure > 300 { panic("invalid exposure time: must be between 1 and 300 seconds") } } } // maxCaptureHistory is the number of captures a single telescope keeps locally. const maxCaptureHistory = 100 // TelescopeRealm manages a single telescope's state and operations type TelescopeRealm struct { Config *TelescopeConfig AccessRules *avl.Tree // key: allowed address -> AccessRule CommandQueue []TelescopeCommand Captures *CaptureFeed OwnerAddress string } // NewTelescopeRealm creates a new telescope realm instance func NewTelescopeRealm(config *TelescopeConfig) *TelescopeRealm { return &TelescopeRealm{ Config: config, AccessRules: avl.NewTree(), CommandQueue: []TelescopeCommand{}, Captures: NewCaptureFeed(maxCaptureHistory), OwnerAddress: config.Owner, } } // GrantAccess allows the owner to grant access to another user. Re-granting to // an existing address overwrites the previous rule. func (r *TelescopeRealm) GrantAccess(caller, address string, durationDays int) { r.checkOwner(caller) if address == "" { panic("address cannot be empty") } expiresAt := time.Time{} // Never expires by default if durationDays > 0 { expiresAt = time.Now().Add(time.Duration(durationDays) * 24 * time.Hour) } rule := AccessRule{ AllowedAddress: address, GrantedBy: r.OwnerAddress, GrantedAt: time.Now(), ExpiresAt: expiresAt, } r.AccessRules.Set(address, rule) } // RevokeAccess removes access for a specific address func (r *TelescopeRealm) RevokeAccess(caller, address string) { r.checkOwner(caller) r.AccessRules.Remove(address) } // SubmitCommand allows authorized users to submit telescope commands func (r *TelescopeRealm) SubmitCommand(caller, commandType string, targetRA, targetDec float64, exposure int) { // Check if caller has access (owner always has access) if caller != r.OwnerAddress { if !IsAccessAllowed(caller, r.AccessRules) { panic("access denied: you don't have permission to use this telescope") } } // Create command cmd := TelescopeCommand{ CommandType: commandType, TargetRA: targetRA, TargetDec: targetDec, Exposure: exposure, RequestedBy: caller, RequestedAt: time.Now(), } // Validate command ValidateCommand(cmd, *r.Config) // Add to queue r.CommandQueue = append(r.CommandQueue, cmd) // Update telescope status to busy r.Config.UpdateStatus("busy") } // GetNextCommand returns the next command in the queue (for telescope controller) func (r *TelescopeRealm) GetNextCommand(caller string) TelescopeCommand { r.checkOwner(caller) if len(r.CommandQueue) == 0 { panic("no commands in queue") } cmd := r.CommandQueue[0] r.CommandQueue = r.CommandQueue[1:] return cmd } // UpdateStatus allows the owner to update telescope status func (r *TelescopeRealm) UpdateStatus(caller string, status string) { r.checkOwner(caller) r.Config.UpdateStatus(status) } // RecordCapture allows the owner to record a capture result func (r *TelescopeRealm) RecordCapture(caller, imageURL string, targetRA, targetDec float64, exposure int, capturedBy string) { r.checkOwner(caller) if imageURL == "" { panic("image URL cannot be empty") } capture := CaptureResult{ ImageURL: imageURL, TargetRA: targetRA, TargetDec: targetDec, Exposure: exposure, CapturedBy: capturedBy, CapturedAt: time.Now(), TelescopeID: r.Config.Name, } r.Captures.Add(capture) // Update status back to online r.Config.UpdateStatus("online") } // ClearCommandQueue clears all pending commands (emergency stop) func (r *TelescopeRealm) ClearCommandQueue(caller string) { r.checkOwner(caller) r.CommandQueue = []TelescopeCommand{} r.Config.UpdateStatus("online") } // GetCommandCount returns the number of pending commands func (r *TelescopeRealm) GetCommandCount() int { return len(r.CommandQueue) } // GetCaptureCount returns the number of stored captures func (r *TelescopeRealm) GetCaptureCount() int { return r.Captures.Len() } // GetAccessRuleCount returns the number of access rules func (r *TelescopeRealm) GetAccessRuleCount() int { return r.AccessRules.Size() } // checkOwner verifies the caller is the owner func (r *TelescopeRealm) checkOwner(caller string) { if caller != r.OwnerAddress { panic("access restricted: owner only") } }
  8. #8nightsky_test.gno
  9. #9package nightsky import ( "testing" "time" "gno.land/p/nt/avl/v0" "gno.land/p/nt/uassert/v0" "gno.land/p/nt/urequire/v0" ) const ( owner = "g1owner" guest = "g1guest" ) func newTestRealm() *TelescopeRealm { cfg := NewTelescopeConfig("Test Scope", "Seestar S50", 49.0, 1.0, owner, "/r/test/telescope") return NewTelescopeRealm(&cfg) } func TestNewTelescopeConfig(t *testing.T) { cfg := NewTelescopeConfig("Scope", "Model", 1.5, 2.5, owner, "/r/x/y") uassert.Equal(t, "Scope", cfg.Name) uassert.Equal(t, "offline", cfg.Status) // starts offline uassert.Equal(t, owner, cfg.Owner) cfg.UpdateStatus("online") uassert.Equal(t, "online", cfg.Status) } func TestValidateCommand(cur realm, t *testing.T) { cfg := NewTelescopeConfig("Scope", "Model", 0, 0, owner, "/r/x/y") // Valid capture and stop should not panic. uassert.NotPanics(t, cur, func() { ValidateCommand(TelescopeCommand{CommandType: "capture", TargetRA: 5.5, TargetDec: 22.5, Exposure: 60}, cfg) }) uassert.NotPanics(t, cur, func() { // stop ignores coordinates entirely. ValidateCommand(TelescopeCommand{CommandType: "stop", TargetRA: 999, TargetDec: 999, Exposure: 0}, cfg) }) cases := []struct { name string cmd TelescopeCommand msg string }{ {"bad type", TelescopeCommand{CommandType: "explode"}, "invalid command type"}, {"RA low", TelescopeCommand{CommandType: "capture", TargetRA: -1, Exposure: 10}, "invalid RA"}, {"RA high", TelescopeCommand{CommandType: "capture", TargetRA: 24, Exposure: 10}, "invalid RA"}, {"Dec low", TelescopeCommand{CommandType: "capture", TargetRA: 1, TargetDec: -91, Exposure: 10}, "invalid Dec"}, {"Dec high", TelescopeCommand{CommandType: "capture", TargetRA: 1, TargetDec: 91, Exposure: 10}, "invalid Dec"}, {"exp low", TelescopeCommand{CommandType: "capture", TargetRA: 1, Exposure: 0}, "invalid exposure"}, {"exp high", TelescopeCommand{CommandType: "capture", TargetRA: 1, Exposure: 301}, "invalid exposure"}, } for _, tc := range cases { cmd := tc.cmd uassert.PanicsContains(t, cur, tc.msg, func() { ValidateCommand(cmd, cfg) }, tc.name) } } func TestIsAccessAllowed(t *testing.T) { rules := avl.NewTree() // Unknown address: denied. uassert.False(t, IsAccessAllowed(guest, rules)) // Never-expires rule (zero time): allowed. rules.Set(guest, AccessRule{AllowedAddress: guest, ExpiresAt: time.Time{}}) uassert.True(t, IsAccessAllowed(guest, rules)) // Future expiry: allowed. rules.Set(guest, AccessRule{AllowedAddress: guest, ExpiresAt: time.Now().Add(time.Hour)}) uassert.True(t, IsAccessAllowed(guest, rules)) // Past expiry: denied. rules.Set(guest, AccessRule{AllowedAddress: guest, ExpiresAt: time.Now().Add(-time.Hour)}) uassert.False(t, IsAccessAllowed(guest, rules)) } func TestAccessFlow(cur realm, t *testing.T) { r := newTestRealm() // Owner can always submit. urequire.NotPanics(t, cur, func() { r.SubmitCommand(owner, "capture", 5, 10, 30) }) // Guest without a grant is denied. uassert.PanicsContains(t, cur, "access denied", func() { r.SubmitCommand(guest, "capture", 5, 10, 30) }) // Grant, then guest can submit. r.GrantAccess(owner, guest, 0) // 0 = never expires uassert.Equal(t, 1, r.GetAccessRuleCount()) urequire.NotPanics(t, cur, func() { r.SubmitCommand(guest, "capture", 5, 10, 30) }) // Re-granting the same address does not duplicate the rule. r.GrantAccess(owner, guest, 5) uassert.Equal(t, 1, r.GetAccessRuleCount()) // Revoke, then guest is denied again. r.RevokeAccess(owner, guest) uassert.Equal(t, 0, r.GetAccessRuleCount()) uassert.PanicsContains(t, cur, "access denied", func() { r.SubmitCommand(guest, "capture", 5, 10, 30) }) // Non-owner cannot grant access. uassert.PanicsContains(t, cur, "owner only", func() { r.GrantAccess(guest, "g1evil", 0) }) } func TestCommandQueueFIFO(cur realm, t *testing.T) { r := newTestRealm() r.SubmitCommand(owner, "capture", 1, 1, 10) r.SubmitCommand(owner, "capture", 2, 2, 20) r.SubmitCommand(owner, "stop", 0, 0, 0) uassert.Equal(t, 3, r.GetCommandCount()) // FIFO order. c1 := r.GetNextCommand(owner) uassert.Equal(t, "capture", c1.CommandType) uassert.Equal(t, int64(10), int64(c1.Exposure)) c2 := r.GetNextCommand(owner) uassert.Equal(t, int64(20), int64(c2.Exposure)) c3 := r.GetNextCommand(owner) uassert.Equal(t, "stop", c3.CommandType) uassert.Equal(t, 0, r.GetCommandCount()) // Draining an empty queue panics. uassert.PanicsContains(t, cur, "no commands", func() { r.GetNextCommand(owner) }) // Only the owner may pull commands. r.SubmitCommand(owner, "stop", 0, 0, 0) uassert.PanicsContains(t, cur, "owner only", func() { r.GetNextCommand(guest) }) // Emergency stop clears everything and sets status online. r.ClearCommandQueue(owner) uassert.Equal(t, 0, r.GetCommandCount()) uassert.Equal(t, "online", r.Config.Status) } func TestRecordCaptureUpdatesStatus(cur realm, t *testing.T) { r := newTestRealm() // Submitting a command flips status to busy. r.SubmitCommand(owner, "capture", 5, 10, 30) uassert.Equal(t, "busy", r.Config.Status) // Recording a capture flips it back to online and stores the capture. r.RecordCapture(owner, "https://img/1.png", 5, 10, 30, owner) uassert.Equal(t, "online", r.Config.Status) uassert.Equal(t, 1, r.GetCaptureCount()) // Empty image URL is rejected. uassert.PanicsContains(t, cur, "image URL", func() { r.RecordCapture(owner, "", 5, 10, 30, owner) }) // Non-owner cannot record. uassert.PanicsContains(t, cur, "owner only", func() { r.RecordCapture(guest, "https://img/2.png", 5, 10, 30, guest) }) } // --- CaptureFeed tests ------------------------------------------------------ func mkCap(url string) CaptureResult { return CaptureResult{ImageURL: url} } func TestCaptureFeedOrderAndLatest(t *testing.T) { f := NewCaptureFeed(10) uassert.Equal(t, 0, f.Len()) _, ok := f.Latest() uassert.False(t, ok) f.Add(mkCap("a")) f.Add(mkCap("b")) f.Add(mkCap("c")) uassert.Equal(t, 3, f.Len()) latest, ok := f.Latest() urequire.True(t, ok) uassert.Equal(t, "c", latest.ImageURL) // Newest-first iteration order. var got []string f.IterateNewest(func(c CaptureResult) bool { got = append(got, c.ImageURL) return false }) urequire.Equal(t, 3, len(got)) uassert.Equal(t, "c", got[0]) uassert.Equal(t, "b", got[1]) uassert.Equal(t, "a", got[2]) // Early stop. count := 0 f.IterateNewest(func(c CaptureResult) bool { count++ return true // stop after first }) uassert.Equal(t, 1, count) } func TestCaptureFeedTrim(t *testing.T) { f := NewCaptureFeed(3) f.Add(mkCap("1")) f.Add(mkCap("2")) f.Add(mkCap("3")) f.Add(mkCap("4")) // evicts "1" f.Add(mkCap("5")) // evicts "2" uassert.Equal(t, 3, f.Len()) var got []string f.IterateNewest(func(c CaptureResult) bool { got = append(got, c.ImageURL) return false }) // Newest-first: 5, 4, 3 — oldest two evicted. uassert.Equal(t, "5", got[0]) uassert.Equal(t, "4", got[1]) uassert.Equal(t, "3", got[2]) } func TestCaptureFeedRemoveAt(t *testing.T) { f := NewCaptureFeed(10) f.Add(mkCap("a")) f.Add(mkCap("b")) f.Add(mkCap("c")) // newest-first: c(0), b(1), a(2) // Out-of-range is a no-op returning false. uassert.False(t, f.RemoveAt(-1)) uassert.False(t, f.RemoveAt(3)) uassert.Equal(t, 3, f.Len()) // Remove the middle (b). uassert.True(t, f.RemoveAt(1)) uassert.Equal(t, 2, f.Len()) var got []string f.IterateNewest(func(c CaptureResult) bool { got = append(got, c.ImageURL) return false }) uassert.Equal(t, "c", got[0]) uassert.Equal(t, "a", got[1]) // Removing newest (c). uassert.True(t, f.RemoveAt(0)) latest, ok := f.Latest() urequire.True(t, ok) uassert.Equal(t, "a", latest.ImageURL) } func TestNewCaptureFeedRejectsNonPositive(cur realm, t *testing.T) { uassert.PanicsContains(t, cur, "must be positive", func() { NewCaptureFeed(0) }) }
  10. #10render.gno
  11. #11package nightsky import ( "strings" "time" "gno.land/p/jeronimoalbi/mdform/v0" "gno.land/p/moul/md/v0" "gno.land/p/nt/ufmt/v0" ) // RenderNightsky generates the markdown output for the telescope realm. // Call this from your realm's Render(path string) string function. func (r *TelescopeRealm) RenderNightsky(path string) string { switch path { case "status": return r.renderStatus() case "commands": return r.renderCommandQueue() case "captures": return r.renderCaptureHistory() case "access": return r.renderAccessRules() case "commandData": return r.renderCommandData() case "control": return r.renderControl() default: return r.renderMainPage() } } func (r *TelescopeRealm) renderMainPage() string { out := "# " + r.Config.Name + " 🔭\n\n" statusIcon := "🔴" switch r.Config.Status { case "online": statusIcon = "🟢" case "busy": statusIcon = "🟡" case "error": statusIcon = "🔴" } out += statusIcon + " **Status:** " + r.Config.Status + "\n\n" out += "**Model:** " + r.Config.Model + "\n\n" out += ufmt.Sprintf("**Position:** %.2f°, %.2f°\n\n", r.Config.Latitude, r.Config.Longitude) out += "## Quick Stats\n\n" out += ufmt.Sprintf("📋 Commands in queue: %d\n\n", len(r.CommandQueue)) out += ufmt.Sprintf("📸 Total captures: %d\n\n", r.Captures.Len()) out += ufmt.Sprintf("👥 Authorized users: %d\n\n", r.AccessRules.Size()) out += "## Quick Links\n\n" out += md.Link("🎮 Control Telescope", r.path(":control")) + " - Submit commands\n\n" out += md.Link("👥 Access Rules", r.path(":access")) + " - View and manage user access\n\n" out += md.Link("📋 Command Queue", r.path(":commands")) + "\n\n" out += md.Link("📸 Capture History", r.path(":captures")) + "\n\n" if latest, ok := r.Captures.Latest(); ok { out += "## Latest Capture\n\n" out += "![Latest capture](" + latest.ImageURL + ")\n\n" out += ufmt.Sprintf("_RA %.8fh · Dec %.8f° · %ds_\n\n", latest.TargetRA, latest.TargetDec, latest.Exposure) } out += "---\n\n" out += "## CLI Usage\n\n" out += "```\n" out += "gnokey maketx call -pkgpath \"gno.land" + r.Config.RealmPath + "\" \\\n" out += " -func \"SubmitCommand\" \\\n" out += " -args \"capture\" -args \"5.5\" -args \"22.5\" -args \"60\" \\\n" out += " -gas-fee 1000000ugnot -gas-wanted 2000000 \\\n" out += " -broadcast YOUR_ADDRESS\n" out += "```\n\n" out += md.Link("← Back to NightSky Network", "/r/nym-vikbez000/nightsky/v1") + "\n\n" return out } func (r *TelescopeRealm) renderControl() string { out := "# 🎮 Control Telescope\n\n" out += "**Note:** You must have access to submit commands.\n\n" out += "---\n\n" form := mdform. New("exec", "SubmitCommand"). Select("commandType", "capture", "description", "Command type"). Select("commandType", "stop"). Input("targetRA", "placeholder", "0.0", "description", "Right Ascension (0–24h) — leave empty for stop"). Input("targetDec", "placeholder", "0.0", "description", "Declination (−90–90°) — leave empty for stop"). Input("exposure", "placeholder", "60", "description", "Exposure time in seconds (1–300) — leave empty for stop") out += form.String() + "\n\n" out += "---\n\n" out += md.Link("← Back", r.path("")) + "\n\n" return out } func (r *TelescopeRealm) renderCommandQueue() string { out := "# Command Queue\n\n" if len(r.CommandQueue) == 0 { out += "No pending commands.\n\n" } else { out += ufmt.Sprintf("%d command(s) pending:\n\n", len(r.CommandQueue)) for i, cmd := range r.CommandQueue { out += ufmt.Sprintf("### Command #%d\n\n", i+1) out += "**Type:** " + cmd.CommandType + "\n\n" if cmd.CommandType == "capture" { out += ufmt.Sprintf("**Target:** RA %.8fh, Dec %.8f°\n\n", cmd.TargetRA, cmd.TargetDec) out += ufmt.Sprintf("**Exposure:** %d seconds\n\n", cmd.Exposure) } out += "**Requested by:** " + cmd.RequestedBy + "\n\n" out += "---\n\n" } } out += md.Link("← Back", r.path("")) + "\n\n" return out } func (r *TelescopeRealm) renderCaptureHistory() string { out := "# Capture History\n\n" if r.Captures.Len() == 0 { out += "No captures yet.\n\n" } else { out += ufmt.Sprintf("Showing %d most recent captures:\n\n", r.Captures.Len()) i := 0 r.Captures.IterateNewest(func(capture CaptureResult) bool { i++ out += ufmt.Sprintf("### Capture #%d\n\n", i) out += ufmt.Sprintf("**Target:** RA %.8fh, Dec %.8f°\n\n", capture.TargetRA, capture.TargetDec) out += ufmt.Sprintf("**Exposure:** %d seconds\n\n", capture.Exposure) out += "**Captured by:** " + capture.CapturedBy + "\n\n" out += "**Image:** " + md.Link("View", capture.ImageURL) + "\n\n" out += "---\n\n" return false }) } out += md.Link("← Back", r.path("")) + "\n\n" return out } func (r *TelescopeRealm) renderAccessRules() string { out := "# 👥 Access Rules\n\n" if r.AccessRules.Size() == 0 { out += "No access rules configured. Only the owner can use the telescope.\n\n" } else { out += ufmt.Sprintf("%d user(s) have access:\n\n", r.AccessRules.Size()) r.AccessRules.Iterate("", "", func(_ string, value any) bool { rule := value.(AccessRule) out += "**User:** " + rule.AllowedAddress if rule.ExpiresAt.IsZero() { out += " (Expires: Never)\n\n" } else { out += " (Expires: " + rule.ExpiresAt.Format(time.RFC1123) + ")\n\n" } out += "---\n\n" return false }) } out += "## Grant Access\n\n" out += mdform. New("exec", "GrantAccess"). Input("address", "placeholder", "g1...", "description", "Gno address to grant access to"). Input("durationDays", "placeholder", "0", "description", "Duration in days (0 = never expires)"). String() + "\n\n" out += "## Revoke Access\n\n" out += mdform. New("exec", "RevokeAccess"). Input("address", "placeholder", "g1...", "description", "Gno address to revoke"). String() + "\n\n" out += "---\n\n" out += md.Link("← Back", r.path("")) + "\n\n" return out } // renderStatus returns a machine-readable status block for the telescope controller. func (r *TelescopeRealm) renderStatus() string { out := ufmt.Sprintf("commands=%d\n", len(r.CommandQueue)) out += "status=" + r.Config.Status return "```\n" + out + "\n```" } // renderCommandData returns the first queued command in a machine-readable block. func (r *TelescopeRealm) renderCommandData() string { if len(r.CommandQueue) == 0 { return "```\nno_commands\n```" } cmd := r.CommandQueue[0] data := "type=" + cmd.CommandType + "\n" data += ufmt.Sprintf("ra=%.8f\n", cmd.TargetRA) data += ufmt.Sprintf("dec=%.8f\n", cmd.TargetDec) data += ufmt.Sprintf("exposure=%d\n", cmd.Exposure) data += "requester=" + cmd.RequestedBy return "```\n" + data + "\n```" } // path builds an absolute URL path for this realm with an optional suffix, // using the pkgPath stored on the config at construction time. This avoids a // runtime realm lookup entirely (no chain/runtime/unsafe needed). func (r *TelescopeRealm) path(addon string) string { return strings.TrimPrefix(r.Config.RealmPath, "gno.land") + addon }
  12. #12/gno.MemPackageType
  13. #13 MPUserAll

Result log

msg:0,success:true,log:,events:[]