⛳ alerts for any ctfd instance via ntfy
1package status 2 3import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/charmbracelet/lipgloss" 9 "github.com/charmbracelet/lipgloss/table" 10 "github.com/spf13/cobra" 11 "github.com/taciturnaxolotl/ctfd-alerts/clients" 12) 13 14var ( 15 // Colors 16 purple = lipgloss.AdaptiveColor{Light: "#9D8EFF", Dark: "#7D56F4"} 17 gray = lipgloss.AdaptiveColor{Light: "#BEBEBE", Dark: "#4A4A4A"} 18 lightGray = lipgloss.AdaptiveColor{Light: "#CCCCCC", Dark: "#3A3A3A"} 19 green = lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"} 20 red = lipgloss.AdaptiveColor{Light: "#F52D4F", Dark: "#FF5F7A"} 21 22 // Styles 23 titleStyle = lipgloss.NewStyle(). 24 Foreground(purple). 25 Bold(true) 26 27 containerStyle = lipgloss.NewStyle(). 28 BorderStyle(lipgloss.RoundedBorder()). 29 BorderForeground(purple). 30 Padding(1) 31 32 headerStyle = lipgloss.NewStyle(). 33 Foreground(purple). 34 Bold(true). 35 Align(lipgloss.Center) 36 37 oddRowStyle = lipgloss.NewStyle() 38 39 evenRowStyle = lipgloss.NewStyle() 40 // Status indicators 41 solvedStyle = lipgloss.NewStyle().Foreground(green).SetString("✓") 42 unsolvedStyle = lipgloss.NewStyle().Foreground(red).SetString("✗") 43) 44 45func truncateString(s string, maxLen int) string { 46 if len(s) <= maxLen { 47 return s 48 } 49 return s[:maxLen-3] + "..." 50} 51 52func createTable(headers []string, rows [][]string) string { 53 54 t := table.New(). 55 Border(lipgloss.ASCIIBorder()). 56 BorderStyle(lipgloss.NewStyle().Foreground(lightGray)). 57 StyleFunc(func(row, col int) lipgloss.Style { 58 switch { 59 case row == table.HeaderRow: 60 return headerStyle 61 case row%2 == 0: 62 return evenRowStyle 63 default: 64 return oddRowStyle 65 } 66 }). 67 Headers(headers...). 68 Rows(rows...) 69 70 return t.Render() 71} 72 73func runDashboard(cmd *cobra.Command, args []string) { 74 // Get CTFd client from root command context 75 ctfdClient := cmd.Context().Value("ctfd_client").(CTFdClient) 76 77 // Get scoreboard data 78 scoreboard, err := ctfdClient.GetScoreboard() 79 if err != nil { 80 log.Fatalf("Error fetching scoreboard: %v", err) 81 } 82 83 // Prepare scoreboard data 84 scoreboardHeaders := []string{"Pos", "Team", "Score", "Members"} 85 scoreboardRows := make([][]string, len(scoreboard.Data)) 86 87 for i, team := range scoreboard.Data { 88 memberNames := make([]string, 0, len(team.Members)) 89 for _, member := range team.Members { 90 memberNames = append(memberNames, member.Name) 91 } 92 scoreboardRows[i] = []string{ 93 fmt.Sprintf("%d", team.Position), 94 truncateString(team.Name, 24), 95 fmt.Sprintf("%d", team.Score), 96 truncateString(strings.Join(memberNames, ", "), 39), 97 } 98 } 99 100 // Get challenge list 101 challenges, err := ctfdClient.GetChallengeList() 102 if err != nil { 103 log.Fatalf("Error fetching challenges: %v", err) 104 } 105 106 // Prepare challenge data 107 challengeHeaders := []string{"ID", "Name", "Category", "Value", "Solves", "Solved"} 108 challengeRows := make([][]string, len(challenges.Data)) 109 110 for i, challenge := range challenges.Data { 111 solvedStatus := unsolvedStyle.String() 112 if challenge.SolvedByMe { 113 solvedStatus = solvedStyle.String() 114 } 115 challengeRows[i] = []string{ 116 fmt.Sprintf("%d", challenge.ID), 117 truncateString(challenge.Name, 24), 118 truncateString(challenge.Category, 14), 119 fmt.Sprintf("%d", challenge.Value), 120 fmt.Sprintf("%d", challenge.Solves), 121 solvedStatus, 122 } 123 } 124 125 // Build and render the complete dashboard 126 var dashboard strings.Builder 127 128 // Scoreboard section 129 dashboard.WriteString(titleStyle.Render(fmt.Sprintf("CTFd Scoreboard [%d]", len(scoreboard.Data)))) 130 dashboard.WriteString("\n") 131 dashboard.WriteString(createTable(scoreboardHeaders, scoreboardRows)) 132 133 // Challenges section 134 dashboard.WriteString("\n\n") 135 dashboard.WriteString(titleStyle.Render(fmt.Sprintf("CTFd Challenges [%d]", len(challenges.Data)))) 136 dashboard.WriteString("\n") 137 dashboard.WriteString(createTable(challengeHeaders, challengeRows)) 138 139 // Render the final output 140 fmt.Print("\n") 141 fmt.Print(dashboard.String()) 142 fmt.Print("\n") 143} 144 145// CTFdClient alias for the client interface from the clients package 146type CTFdClient = clients.CTFdClient