~bayindirh/nudge

e14b27b120af3e8110f25cb8df9367484382a37a — Hakan Bayindir 3 months ago db452b6
feat: Continue implementing recipient validation.

- feat: Continue implementing verify_recipients function.
- feat: Implement JSON unmarshalling to a data structure for verify_recipients function.
- feat: Complete Validation API request function.
- refactor: Add "dryrun mode" line to runtime configuration dump.
2 files changed, 48 insertions(+), 6 deletions(-)

M CHANGELOG.md
M nudge.go
M CHANGELOG.md => CHANGELOG.md +6 -0
@@ 2,6 2,12 @@

**Note:** Please add newest entries on top. Use ISO date format YYYY-MM-DD and markdown formatting.

## 2024-06-02
- feat: Continue implementing `verify_recipients` function.
- feat: Implement JSON unmarshalling to a data structure for `verify_recipients` function.
- feat: Complete Validation API request function.
- refactor: Add "dryrun mode" line to runtime configuration dump.

## 2024-05-27
- feat: Continue implementing `verify_recipients` function.
- feat: Get user, device and license information from Pushover.net.

M nudge.go => nudge.go +42 -6
@@ 107,6 107,20 @@ type ValidConfigurationOptions struct {
	attachableFileTypes         []string // Stores the file types which can be attached to a notification.
}

// Following struct mirrors Pushover.net's validation API (see https://pushover.net/api#validate).
// It's used in the codebase during recipient verification and listing available recipients.
// Note: All the fields in the following struct are exported in order to make struct tags function.
//
//	struct tags cannot function on non-exported names, since other libraries can't peek into the struct.
type pushoverValidationResponse struct {
	Status   int      `json:"status"`   // Tells us how the request went. 1 is OK, 0 is Not OK.
	Group    int      `json:"group"`    // Denotes whether the submitted user key belongs to a group. Groups are comprised of many users and used by commercial customers.
	Devices  []string `json:"devices"`  // Contains the list of devices (or recipients) under that user key.
	Licenses []string `json:"licenses"` // Contains the list of licenses that the user has (e.g. iOS, desktop, etc.)
	Error    []string `json:"errors"`   // Contains any error if the request didn't return 1. Not present if everything went OK.
	Request  string   `json:"request"`  // Contains the request UUID.
}

// This function determines our input method (direct call or pipe).
// The idea is to get features (Stat) of stdin, and look whether it's a "character device"
// (e.g.: TTY or anything we can enter characters on). If not, this means our input is


@@ 537,6 551,7 @@ func printState(runtimeConfiguration *RuntimeConfiguration, notification *Notifi
	logger.Infof("Pushover API key: %s", runtimeConfiguration.apiKey)
	logger.Infof("Pushover user key: %s", runtimeConfiguration.userKey)
	logger.Infof("Current logging level is %s.", runtimeConfiguration.logLevel)
	logger.Infof("Dry run mode: %t", runtimeConfiguration.dryrun)
	logger.Infof("Verify recipients before sending: %t", runtimeConfiguration.verifyRecipients)
	logger.Infof("Called via pipe: %t", isInputFromPipe(logger))
	logger.Infof("") // Leave an empty line.


@@ 593,27 608,48 @@ func printState(runtimeConfiguration *RuntimeConfiguration, notification *Notifi
 */
func getDeviceList(runtimeConfiguration *RuntimeConfiguration, logger *zap.SugaredLogger) (body []byte) {
	logger.Debugf("Getting list of users from Pushover.net")
	

	valuesToSend := url.Values{} // We will build our URL object and send it to the API.
	

	// Let's add required fields.
	valuesToSend.Add("token", runtimeConfiguration.apiKey)
	valuesToSend.Add("user", runtimeConfiguration.userKey)
	

	response, err := http.PostForm("https://api.pushover.net/1/users/validate.json", valuesToSend)

	if err != nil {
		logger.Panicf("Something went wrong, exiting (error is %s).", err)
	}
	

	defer response.Body.Close() // Automatically close response.Body when the function exits.
	body, err = io.ReadAll(response.Body)

	logger.Debugf("Got the following response: %s", body)

	// Need to convert this JSON to an object to be able to interact with it nicely.
	var responseStruct pushoverValidationResponse
    err = json.Unmarshal(body, &responseStruct)

	if err != nil {
		logger.Panicf("Cannot unmarshal JSON to a struct (error is %s).", err)
	}
	

	logger.Infof("Pushover Response for User List")
	logger.Infof("-------------------------------")
	logger.Infof("Status: %d", responseStruct.Status)

	if responseStruct.Status == 1 {
	logger.Infof("Group: %d", responseStruct.Group)
	logger.Infof("Device(s): %s", responseStruct.Devices)
	logger.Infof("License(s): %s", responseStruct.Licenses)
	} else if responseStruct.Status == 0 {
		// This is not the best way to handle this, but good enough for now.
		// TODO: Revisit here and polish if necessary
		logger.Panicf("There's an error during validation (error is %s).", responseStruct.Error[0])
	}
	
	logger.Infof("Request ID: %s", responseStruct.Request)
	logger.Infof("") // Always leave an empty line at the end.

	return body


@@ 627,7 663,7 @@ func getDeviceList(runtimeConfiguration *RuntimeConfiguration, logger *zap.Sugar
 */
func verifyRecipients(notificationToSend *Notification, runtimeConfiguration *RuntimeConfiguration, logger *zap.SugaredLogger) {
	responseFromPushover := getDeviceList(runtimeConfiguration, logger)
	

	logger.Debugf("Device list query returned as follows: %s", responseFromPushover)
}



@@ 740,7 776,7 @@ func main() {
	 * We know that the config is sane, because it's hardcoded. We're using built-in
	 * defaults at that point.
	 */
	 // TODO: Verify given log level is sane before this line. Otherwise things go bang.
	// TODO: Verify given log level is sane before this line. Otherwise things go bang.
	zapDefaultAtomicLevel, err := zap.ParseAtomicLevel(runtimeConfiguration.logLevel)

	if err != nil {