<commit_before>package slack

import (
	"context"
	"encoding/json"
	"errors"
	"net/url"
)

const (
	DEFAULT_USER_PHOTO_CROP_X = -1
	DEFAULT_USER_PHOTO_CROP_Y = -1
	DEFAULT_USER_PHOTO_CROP_W = -1
)

// UserProfile contains all the information details of a given user
type UserProfile struct {
	FirstName             string `json:"first_name"`
	LastName              string `json:"last_name"`
	RealName              string `json:"real_name"`
	RealNameNormalized    string `json:"real_name_normalized"`
	DisplayName           string `json:"display_name"`
	DisplayNameNormalized string `json:"display_name_normalized"`
	Email                 string `json:"email"`
	Skype                 string `json:"skype"`
	Phone                 string `json:"phone"`
	Image24               string `json:"image_24"`
	Image32               string `json:"image_32"`
	Image48               string `json:"image_48"`
	Image72               string `json:"image_72"`
	Image192              string `json:"image_192"`
	ImageOriginal         string `json:"image_original"`
	Title                 string `json:"title"`
	BotID                 string `json:"bot_id,omitempty"`
	ApiAppID              string `json:"api_app_id,omitempty"`
	StatusText            string `json:"status_text,omitempty"`
	StatusEmoji           string `json:"status_emoji,omitempty"`
}

// User contains all the information of a user
type User struct {
	ID                string      `json:"id"`
	Name              string      `json:"name"`
	Deleted           bool        `json:"deleted"`
	Color             string      `json:"color"`
	RealName          string      `json:"real_name"`
	TZ                string      `json:"tz,omitempty"`
	TZLabel           string      `json:"tz_label"`
	TZOffset          int         `json:"tz_offset"`
	Profile           UserProfile `json:"profile"`
	IsBot             bool        `json:"is_bot"`
	IsAdmin           bool        `json:"is_admin"`
	IsOwner           bool        `json:"is_owner"`
	IsPrimaryOwner    bool        `json:"is_primary_owner"`
	IsRestricted      bool        `json:"is_restricted"`
	IsUltraRestricted bool        `json:"is_ultra_restricted"`
	Has2FA            bool        `json:"has_2fa"`
	HasFiles          bool        `json:"has_files"`
	Presence          string      `json:"presence"`
}

// UserPresence contains details about a user online status
type UserPresence struct {
	Presence        string   `json:"presence,omitempty"`
	Online          bool     `json:"online,omitempty"`
	AutoAway        bool     `json:"auto_away,omitempty"`
	ManualAway      bool     `json:"manual_away,omitempty"`
	ConnectionCount int      `json:"connection_count,omitempty"`
	LastActivity    JSONTime `json:"last_activity,omitempty"`
}

type UserIdentityResponse struct {
	User UserIdentity `json:"user"`
	Team TeamIdentity `json:"team"`
	SlackResponse
}

type UserIdentity struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Email    string `json:"email"`
	Image24  string `json:"image_24"`
	Image32  string `json:"image_32"`
	Image48  string `json:"image_48"`
	Image72  string `json:"image_72"`
	Image192 string `json:"image_192"`
	Image512 string `json:"image_512"`
}

type TeamIdentity struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Domain        string `json:"domain"`
	Image34       string `json:"image_34"`
	Image44       string `json:"image_44"`
	Image68       string `json:"image_68"`
	Image88       string `json:"image_88"`
	Image102      string `json:"image_102"`
	Image132      string `json:"image_132"`
	Image230      string `json:"image_230"`
	ImageDefault  bool   `json:"image_default"`
	ImageOriginal string `json:"image_original"`
}

type userResponseFull struct {
	Members      []User                  `json:"members,omitempty"` // ListUsers
	User         `json:"user,omitempty"` // GetUserInfo
	UserPresence                         // GetUserPresence
	SlackResponse
}

type UserSetPhotoParams struct {
	CropX int
	CropY int
	CropW int
}

func NewUserSetPhotoParams() UserSetPhotoParams {
	return UserSetPhotoParams{
		CropX: DEFAULT_USER_PHOTO_CROP_X,
		CropY: DEFAULT_USER_PHOTO_CROP_Y,
		CropW: DEFAULT_USER_PHOTO_CROP_W,
	}
}

func userRequest(ctx context.Context, path string, values url.Values, debug bool) (*userResponseFull, error) {
	response := &userResponseFull{}
	err := post(ctx, path, values, response, debug)
	if err != nil {
		return nil, err
	}
	if !response.Ok {
		return nil, errors.New(response.Error)
	}
	return response, nil
}

// GetUserPresence will retrieve the current presence status of given user.
func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
	return api.GetUserPresenceContext(context.Background(), user)
}

// GetUserPresenceContext will retrieve the current presence status of given user with a custom context.
func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*UserPresence, error) {
	values := url.Values{
		"token": {api.config.token},
		"user":  {user},
	}
	response, err := userRequest(ctx, "users.getPresence", values, api.debug)
	if err != nil {
		return nil, err
	}
	return &response.UserPresence, nil
}

// GetUserInfo will retrieve the complete user information
func (api *Client) GetUserInfo(user string) (*User, error) {
	return api.GetUserInfoContext(context.Background(), user)
}

// GetUserInfoContext will retrieve the complete user information with a custom context
func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
	values := url.Values{
		"token": {api.config.token},
		"user":  {user},
	}
	response, err := userRequest(ctx, "users.info", values, api.debug)
	if err != nil {
		return nil, err
	}
	return &response.User, nil
}

// GetUsers returns the list of users (with their detailed information)
func (api *Client) GetUsers() ([]User, error) {
	return api.GetUsersContext(context.Background())
}

// GetUsersContext returns the list of users (with their detailed information) with a custom context
func (api *Client) GetUsersContext(ctx context.Context) ([]User, error) {
	values := url.Values{
		"token":    {api.config.token},
		"presence": {"1"},
	}
	response, err := userRequest(ctx, "users.list", values, api.debug)
	if err != nil {
		return nil, err
	}
	return response.Members, nil
}

// GetUserByEmail will retrieve the complete user information by email
func (api *Client) GetUserByEmail(email string) (*User, error) {
	return api.GetUserByEmailContext(context.Background(), email)
}

// GetUserByEmailContext will retrieve the complete user information by email with a custom context
func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
	values := url.Values{
		"token": {api.config.token},
		"email": {email},
	}
	response, err := userRequest(ctx, "users.lookupByEmail", values, api.debug)
	if err != nil {
		return nil, err
	}
	return &response.User, nil
}

// SetUserAsActive marks the currently authenticated user as active
func (api *Client) SetUserAsActive() error {
	return api.SetUserAsActiveContext(context.Background())
}

// SetUserAsActiveContext marks the currently authenticated user as active with a custom context
func (api *Client) SetUserAsActiveContext(ctx context.Context) error {
	values := url.Values{
		"token": {api.config.token},
	}
	_, err := userRequest(ctx, "users.setActive", values, api.debug)
	return err
}

// SetUserPresence changes the currently authenticated user presence
func (api *Client) SetUserPresence(presence string) error {
	return api.SetUserPresenceContext(context.Background(), presence)
}

// SetUserPresenceContext changes the currently authenticated user presence with a custom context
func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error {
	values := url.Values{
		"token":    {api.config.token},
		"presence": {presence},
	}
	_, err := userRequest(ctx, "users.setPresence", values, api.debug)
	if err != nil {
		return err
	}
	return nil

}

// GetUserIdentity will retrieve user info available per identity scopes
func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
	return api.GetUserIdentityContext(context.Background())
}

// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context
func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityResponse, error) {
	values := url.Values{
		"token": {api.config.token},
	}
	response := &UserIdentityResponse{}
	err := post(ctx, "users.identity", values, response, api.debug)
	if err != nil {
		return nil, err
	}
	if !response.Ok {
		return nil, errors.New(response.Error)
	}
	return response, nil
}

// SetUserPhoto changes the currently authenticated user's profile image
func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
	return api.SetUserPhotoContext(context.Background(), image, params)
}

// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error {
	response := &SlackResponse{}
	values := url.Values{
		"token": {api.config.token},
	}
	if params.CropX != DEFAULT_USER_PHOTO_CROP_X {
		values.Add("crop_x", string(params.CropX))
	}
	if params.CropY != DEFAULT_USER_PHOTO_CROP_Y {
		values.Add("crop_y", string(params.CropY))
	}
	if params.CropW != DEFAULT_USER_PHOTO_CROP_W {
		values.Add("crop_w", string(params.CropW))
	}
	err := postLocalWithMultipartResponse(ctx, "users.setPhoto", image, "image", values, response, api.debug)
	if err != nil {
		return err
	}
	if !response.Ok {
		return errors.New(response.Error)
	}
	return nil
}

// DeleteUserPhoto deletes the current authenticated user's profile image
func (api *Client) DeleteUserPhoto() error {
	return api.DeleteUserPhotoContext(context.Background())
}

// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context
func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
	response := &SlackResponse{}
	values := url.Values{
		"token": {api.config.token},
	}
	err := post(ctx, "users.deletePhoto", values, response, api.debug)
	if err != nil {
		return err
	}
	if !response.Ok {
		return errors.New(response.Error)
	}
	return nil
}

// SetUserCustomStatus will set a custom status and emoji for the currently
// authenticated user. If statusEmoji is "" and statusText is not, the Slack API
// will automatically set it to ":speech_balloon:". Otherwise, if both are ""
// the Slack API will unset the custom status/emoji.
func (api *Client) SetUserCustomStatus(statusText, statusEmoji string) error {
	return api.SetUserCustomStatusContext(context.Background(), statusText, statusEmoji)
}

// SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context
//
// For more information see SetUserCustomStatus
func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string) error {
	// XXX(theckman): this anonymous struct is for making requests to the Slack
	// API for setting and unsetting a User's Custom Status/Emoji. To change
	// these values we must provide a JSON document as the profile POST field.
	//
	// We use an anonymous struct over UserProfile because to unset the values
	// on the User's profile we cannot use the `json:"omitempty"` tag. This is
	// because an empty string ("") is what's used to unset the values. Check
	// out the API docs for more details:
	//
	// - https://api.slack.com/docs/presence-and-status#custom_status
	profile, err := json.Marshal(
		&struct {
			StatusText  string `json:"status_text"`
			StatusEmoji string `json:"status_emoji"`
		}{
			StatusText:  statusText,
			StatusEmoji: statusEmoji,
		},
	)

	if err != nil {
		return err
	}

	values := url.Values{
		"token":   {api.config.token},
		"profile": {string(profile)},
	}

	response := &userResponseFull{}

	if err = post(ctx, "users.profile.set", values, response, api.debug); err != nil {
		return err
	}

	if !response.Ok {
		return errors.New(response.Error)
	}

	return nil
}

// UnsetUserCustomStatus removes the custom status message for the currently
// authenticated user. This is a convenience method that wraps (*Client).SetUserCustomStatus().
func (api *Client) UnsetUserCustomStatus() error {
	return api.UnsetUserCustomStatusContext(context.Background())
}

// UnsetUserCustomStatusContext removes the custom status message for the currently authenticated user
// with a custom context. This is a convenience method that wraps (*Client).SetUserCustomStatus().
func (api *Client) UnsetUserCustomStatusContext(ctx context.Context) error {
	return api.SetUserCustomStatusContext(ctx, "", "")
}
<commit_msg>Add fields related to user object (#242)<commit_after>package slack

import (
	"context"
	"encoding/json"
	"errors"
	"net/url"
)

const (
	DEFAULT_USER_PHOTO_CROP_X = -1
	DEFAULT_USER_PHOTO_CROP_Y = -1
	DEFAULT_USER_PHOTO_CROP_W = -1
)

// UserProfile contains all the information details of a given user
type UserProfile struct {
	FirstName             string `json:"first_name"`
	LastName              string `json:"last_name"`
	RealName              string `json:"real_name"`
	RealNameNormalized    string `json:"real_name_normalized"`
	DisplayName           string `json:"display_name"`
	DisplayNameNormalized string `json:"display_name_normalized"`
	Email                 string `json:"email"`
	Skype                 string `json:"skype"`
	Phone                 string `json:"phone"`
	Image24               string `json:"image_24"`
	Image32               string `json:"image_32"`
	Image48               string `json:"image_48"`
	Image72               string `json:"image_72"`
	Image192              string `json:"image_192"`
	ImageOriginal         string `json:"image_original"`
	Title                 string `json:"title"`
	BotID                 string `json:"bot_id,omitempty"`
	ApiAppID              string `json:"api_app_id,omitempty"`
	StatusText            string `json:"status_text,omitempty"`
	StatusEmoji           string `json:"status_emoji,omitempty"`
	Team                  string `json:"team"`
}

// User contains all the information of a user
type User struct {
	ID                string      `json:"id"`
	TeamID            string      `json:"team_id"`
	Name              string      `json:"name"`
	Deleted           bool        `json:"deleted"`
	Color             string      `json:"color"`
	RealName          string      `json:"real_name"`
	TZ                string      `json:"tz,omitempty"`
	TZLabel           string      `json:"tz_label"`
	TZOffset          int         `json:"tz_offset"`
	Profile           UserProfile `json:"profile"`
	IsBot             bool        `json:"is_bot"`
	IsAdmin           bool        `json:"is_admin"`
	IsOwner           bool        `json:"is_owner"`
	IsPrimaryOwner    bool        `json:"is_primary_owner"`
	IsRestricted      bool        `json:"is_restricted"`
	IsUltraRestricted bool        `json:"is_ultra_restricted"`
	IsStranger        bool        `json:"is_stranger"`
	IsAppUser         bool        `json:"is_app_user"`
	Has2FA            bool        `json:"has_2fa"`
	HasFiles          bool        `json:"has_files"`
	Presence          string      `json:"presence"`
	Locale            string      `json:"locale"`
}

// UserPresence contains details about a user online status
type UserPresence struct {
	Presence        string   `json:"presence,omitempty"`
	Online          bool     `json:"online,omitempty"`
	AutoAway        bool     `json:"auto_away,omitempty"`
	ManualAway      bool     `json:"manual_away,omitempty"`
	ConnectionCount int      `json:"connection_count,omitempty"`
	LastActivity    JSONTime `json:"last_activity,omitempty"`
}

type UserIdentityResponse struct {
	User UserIdentity `json:"user"`
	Team TeamIdentity `json:"team"`
	SlackResponse
}

type UserIdentity struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Email    string `json:"email"`
	Image24  string `json:"image_24"`
	Image32  string `json:"image_32"`
	Image48  string `json:"image_48"`
	Image72  string `json:"image_72"`
	Image192 string `json:"image_192"`
	Image512 string `json:"image_512"`
}

type TeamIdentity struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Domain        string `json:"domain"`
	Image34       string `json:"image_34"`
	Image44       string `json:"image_44"`
	Image68       string `json:"image_68"`
	Image88       string `json:"image_88"`
	Image102      string `json:"image_102"`
	Image132      string `json:"image_132"`
	Image230      string `json:"image_230"`
	ImageDefault  bool   `json:"image_default"`
	ImageOriginal string `json:"image_original"`
}

type userResponseFull struct {
	Members      []User                  `json:"members,omitempty"` // ListUsers
	User         `json:"user,omitempty"` // GetUserInfo
	UserPresence                         // GetUserPresence
	SlackResponse
}

type UserSetPhotoParams struct {
	CropX int
	CropY int
	CropW int
}

func NewUserSetPhotoParams() UserSetPhotoParams {
	return UserSetPhotoParams{
		CropX: DEFAULT_USER_PHOTO_CROP_X,
		CropY: DEFAULT_USER_PHOTO_CROP_Y,
		CropW: DEFAULT_USER_PHOTO_CROP_W,
	}
}

func userRequest(ctx context.Context, path string, values url.Values, debug bool) (*userResponseFull, error) {
	response := &userResponseFull{}
	err := post(ctx, path, values, response, debug)
	if err != nil {
		return nil, err
	}
	if !response.Ok {
		return nil, errors.New(response.Error)
	}
	return response, nil
}

// GetUserPresence will retrieve the current presence status of given user.
func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
	return api.GetUserPresenceContext(context.Background(), user)
}

// GetUserPresenceContext will retrieve the current presence status of given user with a custom context.
func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*UserPresence, error) {
	values := url.Values{
		"token": {api.config.token},
		"user":  {user},
	}
	response, err := userRequest(ctx, "users.getPresence", values, api.debug)
	if err != nil {
		return nil, err
	}
	return &response.UserPresence, nil
}

// GetUserInfo will retrieve the complete user information
func (api *Client) GetUserInfo(user string) (*User, error) {
	return api.GetUserInfoContext(context.Background(), user)
}

// GetUserInfoContext will retrieve the complete user information with a custom context
func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
	values := url.Values{
		"token": {api.config.token},
		"user":  {user},
	}
	response, err := userRequest(ctx, "users.info", values, api.debug)
	if err != nil {
		return nil, err
	}
	return &response.User, nil
}

// GetUsers returns the list of users (with their detailed information)
func (api *Client) GetUsers() ([]User, error) {
	return api.GetUsersContext(context.Background())
}

// GetUsersContext returns the list of users (with their detailed information) with a custom context
func (api *Client) GetUsersContext(ctx context.Context) ([]User, error) {
	values := url.Values{
		"token":    {api.config.token},
		"presence": {"1"},
	}
	response, err := userRequest(ctx, "users.list", values, api.debug)
	if err != nil {
		return nil, err
	}
	return response.Members, nil
}

// GetUserByEmail will retrieve the complete user information by email
func (api *Client) GetUserByEmail(email string) (*User, error) {
	return api.GetUserByEmailContext(context.Background(), email)
}

// GetUserByEmailContext will retrieve the complete user information by email with a custom context
func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
	values := url.Values{
		"token": {api.config.token},
		"email": {email},
	}
	response, err := userRequest(ctx, "users.lookupByEmail", values, api.debug)
	if err != nil {
		return nil, err
	}
	return &response.User, nil
}

// SetUserAsActive marks the currently authenticated user as active
func (api *Client) SetUserAsActive() error {
	return api.SetUserAsActiveContext(context.Background())
}

// SetUserAsActiveContext marks the currently authenticated user as active with a custom context
func (api *Client) SetUserAsActiveContext(ctx context.Context) error {
	values := url.Values{
		"token": {api.config.token},
	}
	_, err := userRequest(ctx, "users.setActive", values, api.debug)
	return err
}

// SetUserPresence changes the currently authenticated user presence
func (api *Client) SetUserPresence(presence string) error {
	return api.SetUserPresenceContext(context.Background(), presence)
}

// SetUserPresenceContext changes the currently authenticated user presence with a custom context
func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error {
	values := url.Values{
		"token":    {api.config.token},
		"presence": {presence},
	}
	_, err := userRequest(ctx, "users.setPresence", values, api.debug)
	if err != nil {
		return err
	}
	return nil

}

// GetUserIdentity will retrieve user info available per identity scopes
func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
	return api.GetUserIdentityContext(context.Background())
}

// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context
func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityResponse, error) {
	values := url.Values{
		"token": {api.config.token},
	}
	response := &UserIdentityResponse{}
	err := post(ctx, "users.identity", values, response, api.debug)
	if err != nil {
		return nil, err
	}
	if !response.Ok {
		return nil, errors.New(response.Error)
	}
	return response, nil
}

// SetUserPhoto changes the currently authenticated user's profile image
func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
	return api.SetUserPhotoContext(context.Background(), image, params)
}

// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error {
	response := &SlackResponse{}
	values := url.Values{
		"token": {api.config.token},
	}
	if params.CropX != DEFAULT_USER_PHOTO_CROP_X {
		values.Add("crop_x", string(params.CropX))
	}
	if params.CropY != DEFAULT_USER_PHOTO_CROP_Y {
		values.Add("crop_y", string(params.CropY))
	}
	if params.CropW != DEFAULT_USER_PHOTO_CROP_W {
		values.Add("crop_w", string(params.CropW))
	}
	err := postLocalWithMultipartResponse(ctx, "users.setPhoto", image, "image", values, response, api.debug)
	if err != nil {
		return err
	}
	if !response.Ok {
		return errors.New(response.Error)
	}
	return nil
}

// DeleteUserPhoto deletes the current authenticated user's profile image
func (api *Client) DeleteUserPhoto() error {
	return api.DeleteUserPhotoContext(context.Background())
}

// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context
func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
	response := &SlackResponse{}
	values := url.Values{
		"token": {api.config.token},
	}
	err := post(ctx, "users.deletePhoto", values, response, api.debug)
	if err != nil {
		return err
	}
	if !response.Ok {
		return errors.New(response.Error)
	}
	return nil
}

// SetUserCustomStatus will set a custom status and emoji for the currently
// authenticated user. If statusEmoji is "" and statusText is not, the Slack API
// will automatically set it to ":speech_balloon:". Otherwise, if both are ""
// the Slack API will unset the custom status/emoji.
func (api *Client) SetUserCustomStatus(statusText, statusEmoji string) error {
	return api.SetUserCustomStatusContext(context.Background(), statusText, statusEmoji)
}

// SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context
//
// For more information see SetUserCustomStatus
func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string) error {
	// XXX(theckman): this anonymous struct is for making requests to the Slack
	// API for setting and unsetting a User's Custom Status/Emoji. To change
	// these values we must provide a JSON document as the profile POST field.
	//
	// We use an anonymous struct over UserProfile because to unset the values
	// on the User's profile we cannot use the `json:"omitempty"` tag. This is
	// because an empty string ("") is what's used to unset the values. Check
	// out the API docs for more details:
	//
	// - https://api.slack.com/docs/presence-and-status#custom_status
	profile, err := json.Marshal(
		&struct {
			StatusText  string `json:"status_text"`
			StatusEmoji string `json:"status_emoji"`
		}{
			StatusText:  statusText,
			StatusEmoji: statusEmoji,
		},
	)

	if err != nil {
		return err
	}

	values := url.Values{
		"token":   {api.config.token},
		"profile": {string(profile)},
	}

	response := &userResponseFull{}

	if err = post(ctx, "users.profile.set", values, response, api.debug); err != nil {
		return err
	}

	if !response.Ok {
		return errors.New(response.Error)
	}

	return nil
}

// UnsetUserCustomStatus removes the custom status message for the currently
// authenticated user. This is a convenience method that wraps (*Client).SetUserCustomStatus().
func (api *Client) UnsetUserCustomStatus() error {
	return api.UnsetUserCustomStatusContext(context.Background())
}

// UnsetUserCustomStatusContext removes the custom status message for the currently authenticated user
// with a custom context. This is a convenience method that wraps (*Client).SetUserCustomStatus().
func (api *Client) UnsetUserCustomStatusContext(ctx context.Context) error {
	return api.SetUserCustomStatusContext(ctx, "", "")
}
<|endoftext|>


<commit_before>// Package util - backup module
// Copyright © 2017 Alexander Sosna <alexander@xxor.de>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package util

import (
	"bytes"
	"errors"
	"io"
	"io/ioutil"
	"os/exec"
	"regexp"
	"time"

	log "github.com/Sirupsen/logrus"
	wal "github.com/xxorde/pgglaskugel/wal"
)

// some constants, neccessary for the backupprocess
const (
	BackupTimeFormat  = time.RFC3339
	saneBackupMinSize = 2 * 1000000 // ~ 4MB

	// Larger files are most likely no backup label
	maxBackupLabelSize = 2048
)

var (
	// Regex to identify a backup label file
	regBackupLabelFile = regexp.MustCompile(wal.RegBackupLabel)
)

// Backup stores information about a backup
type Backup struct {
	Name             string
	Extension        string
	Path             string
	Bucket           string
	Size             int64
	Created          time.Time
	LabelFile        string
	BackupLabel      string
	StartWalLocation string
	Backups          *Backups
}

// IsSane returns true if the backup seams sane
func (b *Backup) IsSane() (sane bool) {
	if b.Size < saneBackupMinSize {
		return false
	}

	if b.StorageType() == "" {
		return false
	}

	return true
}

// StorageType returns the type of storage the backup is on
func (b *Backup) StorageType() (storageType string) {
	if b.Path > "" {
		return "file"
	}

	if b.Bucket > "" {
		return "s3"
	}

	// Not defined
	return ""
}

// GetStartWalLocation returns the oldest needed WAL file
// Every older WAL file is not required to use this backup
func (b *Backup) GetStartWalLocation() (startWalLocation string, err error) {
	switch b.StorageType() {
	case "file":
		return b.GetStartWalLocationFromFile()
	case "s3":
		return b.GetStartWalLocationFromS3()
	}
	return "", errors.New("Not supported StorageType: " + b.StorageType())
}

// GetStartWalLocationFromFile returns the oldest needed WAL file
// Every older WAL file is not required to use this backup
func (b *Backup) GetStartWalLocationFromFile() (startWalLocation string, err error) {
	// Escape the name so we can use it in a regular expression
	searchName := regexp.QuoteMeta(b.Name)
	// Regex to identify the right file
	regLabel := regexp.MustCompile(`.*LABEL: ` + searchName)
	log.Debug("regLabel: ", regLabel)

	files, _ := ioutil.ReadDir(b.Backups.WalDir)
	// find all backup labels
	for _, f := range files {
		if f.Size() > maxBackupLabelSize {
			// size is to big for backup label
			continue
		}
		if regBackupLabelFile.MatchString(f.Name()) {
			log.Debug(f.Name(), " => seems to be a backup Label, by size and name")

			labelFile := b.Backups.WalDir + "/" + f.Name()
			catCmd := exec.Command("/usr/bin/zstdcat", labelFile)
			catCmdStdout, err := catCmd.StdoutPipe()
			if err != nil {
				// if we can not open the file we continue with next
				log.Warn("catCmd.StdoutPipe(), ", err)
				continue
			}

			err = catCmd.Start()
			if err != nil {
				log.Warn("catCmd.Start(), ", err)
				continue
			}

			buf, err := ioutil.ReadAll(catCmdStdout)
			if err != nil {
				log.Warn("Reading from command: ", err)
				continue
			}

			err = catCmd.Wait()
			if err != nil {
				log.Warn("catCmd.Wait(), ", err)
				continue
			}

			if len(regLabel.Find(buf)) > 1 {
				log.Debug("Found matching backup label file: ", f.Name())
				err = b.parseBackupLabel(buf)
				if err == nil {
					b.LabelFile = labelFile
				}
				return b.StartWalLocation, err
			}
		}
	}
	return "", errors.New("START WAL LOCATION not found")
}

// GetStartWalLocationFromS3 returns the oldest needed WAL file
// Every older WAL file is not required to use this backup
func (b *Backup) GetStartWalLocationFromS3() (startWalLocation string, err error) {
	// Escape the name so we can use it in a regular expression
	searchName := regexp.QuoteMeta(b.Name)
	// Regex to identify the right file
	regLabel := regexp.MustCompile(`.*LABEL: ` + searchName)
	log.Debug("regLabel: ", regLabel)

	log.Debug("Looking for the backup label that contains: ", searchName)

	// Create a done channel to control 'ListObjects' go routine.
	doneCh := make(chan struct{})
	defer close(doneCh)

	isRecursive := true
	objectCh := b.Backups.MinioClient.ListObjects(b.Backups.WalBucket, "", isRecursive, doneCh)
	for object := range objectCh {
		if object.Err != nil {
			log.Error(object.Err)
		}

		// log.Debug("Looking at potential backup label: ", object.Key)

		if object.Size > maxBackupLabelSize {
			// size is to big for backup label
			// log.Debug("Object is to big to be a backup label, size: ", object.Size)
			continue
		}

		if regBackupLabelFile.MatchString(object.Key) {
			log.Debug(object.Key, " => seems to be a backup Label, by size and name")

			backupLabelFile, err := b.Backups.MinioClient.GetObject(b.Backups.WalBucket, object.Key)
			if err != nil {
				log.Warn("Can not get backupLabel, ", err)
				continue
			}

			bufCompressed := make([]byte, maxBackupLabelSize)
			readCount, err := backupLabelFile.Read(bufCompressed)
			if err != nil && err != io.EOF {
				log.Warn("Can not read backupLabel, ", err)
				continue
			}
			log.Debug("Read ", readCount, " from backupLabel")

			// Command to decompress the backuplabel
			catCmd := exec.Command("zstd", "-d", "--stdout")
			catCmdStdout, err := catCmd.StdoutPipe()
			if err != nil {
				// if we can not open the file we continue with next
				log.Warn("catCmd.StdoutPipe(), ", err)
				continue
			}

			// Use backupLabel as input for catCmd
			catDone := make(chan struct{}) // Channel to wait for WatchOutput
			catCmd.Stdin = bytes.NewReader(bufCompressed)
			catCmdStderror, err := catCmd.StderrPipe()
			go WatchOutput(catCmdStderror, log.Debug, catDone)

			err = catCmd.Start()
			if err != nil {
				log.Warn("catCmd.Start(), ", err)
				continue
			}

			bufPlain, err := ioutil.ReadAll(catCmdStdout)
			if err != nil {
				log.Warn("Reading from command: ", err)
				continue
			}

			// Wait for output watchers to finish
			// If the Cmd.Wait() is called while another process is reading
			// from Stdout / Stderr this is a race condition.
			// So we are waiting for the watchers first
			<-catDone

			// Wait for the command to finish
			err = catCmd.Wait()
			if err != nil {
				// We ignore errors here, zstd returns 1 even if everything is fine here
				log.Debug("catCmd.Wait(), ", err)
			}
			log.Debug("Backuplabel:\n", string(bufPlain))

			if len(regLabel.Find(bufPlain)) > 1 {
				log.Debug("Found matching backup label")
				err = b.parseBackupLabel(bufPlain)
				if err != nil {
					log.Error(err)
				}
				b.LabelFile = object.Key
				return b.StartWalLocation, err
			}
		}
	}
	return "", errors.New("START WAL LOCATION not found")
}

func (b *Backup) parseBackupLabel(backupLabel []byte) (err error) {
	regStartWalLine := regexp.MustCompile(`^START WAL LOCATION: .*\/.* \(file [0-9A-Fa-f]{24}\)`)
	regStartWal := regexp.MustCompile(`[0-9A-Fa-f]{24}`)

	startWalLine := regStartWalLine.Find(backupLabel)
	if len(startWalLine) < 1 {
		log.Debug(string(backupLabel))
		return errors.New("Can not find line with START WAL LOCATION")
	}

	startWal := regStartWal.Find(startWalLine)
	if len(startWal) < 1 {
		return errors.New("Can not find START WAL")
	}

	b.StartWalLocation = string(startWal)
	return nil
}
<commit_msg>Fix another misspell<commit_after>// Package util - backup module
// Copyright © 2017 Alexander Sosna <alexander@xxor.de>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package util

import (
	"bytes"
	"errors"
	"io"
	"io/ioutil"
	"os/exec"
	"regexp"
	"time"

	log "github.com/Sirupsen/logrus"
	wal "github.com/xxorde/pgglaskugel/wal"
)

// some constants, necessary for the backupprocess
const (
	BackupTimeFormat  = time.RFC3339
	saneBackupMinSize = 2 * 1000000 // ~ 4MB

	// Larger files are most likely no backup label
	maxBackupLabelSize = 2048
)

var (
	// Regex to identify a backup label file
	regBackupLabelFile = regexp.MustCompile(wal.RegBackupLabel)
)

// Backup stores information about a backup
type Backup struct {
	Name             string
	Extension        string
	Path             string
	Bucket           string
	Size             int64
	Created          time.Time
	LabelFile        string
	BackupLabel      string
	StartWalLocation string
	Backups          *Backups
}

// IsSane returns true if the backup seams sane
func (b *Backup) IsSane() (sane bool) {
	if b.Size < saneBackupMinSize {
		return false
	}

	if b.StorageType() == "" {
		return false
	}

	return true
}

// StorageType returns the type of storage the backup is on
func (b *Backup) StorageType() (storageType string) {
	if b.Path > "" {
		return "file"
	}

	if b.Bucket > "" {
		return "s3"
	}

	// Not defined
	return ""
}

// GetStartWalLocation returns the oldest needed WAL file
// Every older WAL file is not required to use this backup
func (b *Backup) GetStartWalLocation() (startWalLocation string, err error) {
	switch b.StorageType() {
	case "file":
		return b.GetStartWalLocationFromFile()
	case "s3":
		return b.GetStartWalLocationFromS3()
	}
	return "", errors.New("Not supported StorageType: " + b.StorageType())
}

// GetStartWalLocationFromFile returns the oldest needed WAL file
// Every older WAL file is not required to use this backup
func (b *Backup) GetStartWalLocationFromFile() (startWalLocation string, err error) {
	// Escape the name so we can use it in a regular expression
	searchName := regexp.QuoteMeta(b.Name)
	// Regex to identify the right file
	regLabel := regexp.MustCompile(`.*LABEL: ` + searchName)
	log.Debug("regLabel: ", regLabel)

	files, _ := ioutil.ReadDir(b.Backups.WalDir)
	// find all backup labels
	for _, f := range files {
		if f.Size() > maxBackupLabelSize {
			// size is to big for backup label
			continue
		}
		if regBackupLabelFile.MatchString(f.Name()) {
			log.Debug(f.Name(), " => seems to be a backup Label, by size and name")

			labelFile := b.Backups.WalDir + "/" + f.Name()
			catCmd := exec.Command("/usr/bin/zstdcat", labelFile)
			catCmdStdout, err := catCmd.StdoutPipe()
			if err != nil {
				// if we can not open the file we continue with next
				log.Warn("catCmd.StdoutPipe(), ", err)
				continue
			}

			err = catCmd.Start()
			if err != nil {
				log.Warn("catCmd.Start(), ", err)
				continue
			}

			buf, err := ioutil.ReadAll(catCmdStdout)
			if err != nil {
				log.Warn("Reading from command: ", err)
				continue
			}

			err = catCmd.Wait()
			if err != nil {
				log.Warn("catCmd.Wait(), ", err)
				continue
			}

			if len(regLabel.Find(buf)) > 1 {
				log.Debug("Found matching backup label file: ", f.Name())
				err = b.parseBackupLabel(buf)
				if err == nil {
					b.LabelFile = labelFile
				}
				return b.StartWalLocation, err
			}
		}
	}
	return "", errors.New("START WAL LOCATION not found")
}

// GetStartWalLocationFromS3 returns the oldest needed WAL file
// Every older WAL file is not required to use this backup
func (b *Backup) GetStartWalLocationFromS3() (startWalLocation string, err error) {
	// Escape the name so we can use it in a regular expression
	searchName := regexp.QuoteMeta(b.Name)
	// Regex to identify the right file
	regLabel := regexp.MustCompile(`.*LABEL: ` + searchName)
	log.Debug("regLabel: ", regLabel)

	log.Debug("Looking for the backup label that contains: ", searchName)

	// Create a done channel to control 'ListObjects' go routine.
	doneCh := make(chan struct{})
	defer close(doneCh)

	isRecursive := true
	objectCh := b.Backups.MinioClient.ListObjects(b.Backups.WalBucket, "", isRecursive, doneCh)
	for object := range objectCh {
		if object.Err != nil {
			log.Error(object.Err)
		}

		// log.Debug("Looking at potential backup label: ", object.Key)

		if object.Size > maxBackupLabelSize {
			// size is to big for backup label
			// log.Debug("Object is to big to be a backup label, size: ", object.Size)
			continue
		}

		if regBackupLabelFile.MatchString(object.Key) {
			log.Debug(object.Key, " => seems to be a backup Label, by size and name")

			backupLabelFile, err := b.Backups.MinioClient.GetObject(b.Backups.WalBucket, object.Key)
			if err != nil {
				log.Warn("Can not get backupLabel, ", err)
				continue
			}

			bufCompressed := make([]byte, maxBackupLabelSize)
			readCount, err := backupLabelFile.Read(bufCompressed)
			if err != nil && err != io.EOF {
				log.Warn("Can not read backupLabel, ", err)
				continue
			}
			log.Debug("Read ", readCount, " from backupLabel")

			// Command to decompress the backuplabel
			catCmd := exec.Command("zstd", "-d", "--stdout")
			catCmdStdout, err := catCmd.StdoutPipe()
			if err != nil {
				// if we can not open the file we continue with next
				log.Warn("catCmd.StdoutPipe(), ", err)
				continue
			}

			// Use backupLabel as input for catCmd
			catDone := make(chan struct{}) // Channel to wait for WatchOutput
			catCmd.Stdin = bytes.NewReader(bufCompressed)
			catCmdStderror, err := catCmd.StderrPipe()
			go WatchOutput(catCmdStderror, log.Debug, catDone)

			err = catCmd.Start()
			if err != nil {
				log.Warn("catCmd.Start(), ", err)
				continue
			}

			bufPlain, err := ioutil.ReadAll(catCmdStdout)
			if err != nil {
				log.Warn("Reading from command: ", err)
				continue
			}

			// Wait for output watchers to finish
			// If the Cmd.Wait() is called while another process is reading
			// from Stdout / Stderr this is a race condition.
			// So we are waiting for the watchers first
			<-catDone

			// Wait for the command to finish
			err = catCmd.Wait()
			if err != nil {
				// We ignore errors here, zstd returns 1 even if everything is fine here
				log.Debug("catCmd.Wait(), ", err)
			}
			log.Debug("Backuplabel:\n", string(bufPlain))

			if len(regLabel.Find(bufPlain)) > 1 {
				log.Debug("Found matching backup label")
				err = b.parseBackupLabel(bufPlain)
				if err != nil {
					log.Error(err)
				}
				b.LabelFile = object.Key
				return b.StartWalLocation, err
			}
		}
	}
	return "", errors.New("START WAL LOCATION not found")
}

func (b *Backup) parseBackupLabel(backupLabel []byte) (err error) {
	regStartWalLine := regexp.MustCompile(`^START WAL LOCATION: .*\/.* \(file [0-9A-Fa-f]{24}\)`)
	regStartWal := regexp.MustCompile(`[0-9A-Fa-f]{24}`)

	startWalLine := regStartWalLine.Find(backupLabel)
	if len(startWalLine) < 1 {
		log.Debug(string(backupLabel))
		return errors.New("Can not find line with START WAL LOCATION")
	}

	startWal := regStartWal.Find(startWalLine)
	if len(startWal) < 1 {
		return errors.New("Can not find START WAL")
	}

	b.StartWalLocation = string(startWal)
	return nil
}
<|endoftext|>


<commit_before>package godns

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strings"

	dnsResolver "github.com/TimothyYe/godns/resolver"

	"github.com/miekg/dns"
	"golang.org/x/net/proxy"
	gomail "gopkg.in/gomail.v2"
)

var (
	// Logo for GoDNS
	Logo = `

 ██████╗  ██████╗ ██████╗ ███╗   ██╗███████╗
██╔════╝ ██╔═══██╗██╔══██╗████╗  ██║██╔════╝
██║  ███╗██║   ██║██║  ██║██╔██╗ ██║███████╗
██║   ██║██║   ██║██║  ██║██║╚██╗██║╚════██║
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
 ╚═════╝  ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝

GoDNS V%s
https://github.com/TimothyYe/godns

`
)

const (
	// PanicMax is the max allowed panic times
	PanicMax = 5
	// DNSPOD for dnspod.cn
	DNSPOD = "DNSPod"
	// HE for he.net
	HE = "HE"
	// CLOUDFLARE for cloudflare.com
	CLOUDFLARE = "Cloudflare"
	// ALIDNS for AliDNS
	ALIDNS = "AliDNS"
	// GOOGLE for Google Domains
	GOOGLE = "Google"
	// DUCK for Duck DNS
	DUCK = "DuckDNS"
	// DREAMHOST for Dreamhost
	DREAMHOST = "Dreamhost"
	// IPV4 for IPV4 mode
	IPV4 = "IPV4"
	// IPV6 for IPV6 mode
	IPV6 = "IPV6"
)

//GetIPFromInterface gets IP address from the specific interface
func GetIPFromInterface(configuration *Settings) (string, error) {
	ifaces, err := net.InterfaceByName(configuration.IPInterface)
	if err != nil {
		log.Println("can't get network device "+configuration.IPInterface+":", err)
		return "", err
	}

	addrs, err := ifaces.Addrs()
	if err != nil {
		log.Println("can't get address from "+configuration.IPInterface+":", err)
		return "", err
	}

	for _, addr := range addrs {
		var ip net.IP
		switch v := addr.(type) {
		case *net.IPNet:
			ip = v.IP
		case *net.IPAddr:
			ip = v.IP
		}
		if ip == nil {
			continue
		}

		if !(ip.IsGlobalUnicast() &&
			!(ip.IsUnspecified() ||
				ip.IsMulticast() ||
				ip.IsLoopback() ||
				ip.IsLinkLocalUnicast() ||
				ip.IsLinkLocalMulticast() ||
				ip.IsInterfaceLocalMulticast())) {
			continue
		}

		if isIPv4(ip.String()) {
			if strings.ToUpper(configuration.IPType) != IPV4 {
				continue
			}
		} else {
			if strings.ToUpper(configuration.IPType) != IPV6 {
				continue
			}
		}

		if ip.String() != "" {
			return ip.String(), nil
		}
	}
	return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
}

func isIPv4(ip string) bool {
	return strings.Count(ip, ":") < 2
}

// GetHttpClient creates the HTTP client and return it
func GetHttpClient(configuration *Settings, use_proxy bool) *http.Client {
	client := &http.Client{}

	if use_proxy && configuration.Socks5Proxy != "" {
		log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
		dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
		if err != nil {
			log.Println("can't connect to the proxy:", err)
			return nil
		}

		httpTransport := &http.Transport{}
		client.Transport = httpTransport
		httpTransport.Dial = dialer.Dial
	}

	return client
}

//GetCurrentIP gets an IP from either internet or specific interface, depending on configuration
func GetCurrentIP(configuration *Settings) (string, error) {
	var err error

	if configuration.IPUrl != "" || configuration.IPV6Url != "" {
		ip, err := GetIPOnline(configuration)
		if err != nil {
			log.Println("get ip online failed. Fallback to get ip from interface if possible.")
		} else {
			return ip, nil
		}
	}

	if configuration.IPInterface != "" {
		ip, err := GetIPFromInterface(configuration)
		if err != nil {
			log.Println("get ip from interface failed. There is no more ways to try.")
		} else {
			return ip, nil
		}
	}

	return "", err
}

// GetIPOnline gets public IP from internet
func GetIPOnline(configuration *Settings) (string, error) {
	client := &http.Client{}

	var response *http.Response
	var err error

	if configuration.IPType == "" || strings.ToUpper(configuration.IPType) == IPV4 {
		response, err = client.Get(configuration.IPUrl)
	} else {
		response, err = client.Get(configuration.IPV6Url)
	}

	if err != nil {
		log.Println("Cannot get IP...")
		return "", err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	return strings.Trim(string(body), "\n"), nil
}

// CheckSettings check the format of settings
func CheckSettings(config *Settings) error {
	switch config.Provider {
	case DNSPOD:
		if config.Password == "" && config.LoginToken == "" {
			return errors.New("password or login token cannot be empty")
		}
	case HE:
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	case CLOUDFLARE:
		if config.LoginToken == "" {
			if config.Email == "" {
				return errors.New("email cannot be empty")
			}
			if config.Password == "" {
				return errors.New("password cannot be empty")
			}
		}
	case ALIDNS:
		if config.Email == "" {
			return errors.New("email cannot be empty")
		}
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	case DUCK:
		if config.LoginToken == "" {
			return errors.New("login token cannot be empty")
		}
	case GOOGLE:
		if config.Email == "" {
			return errors.New("email cannot be empty")
		}
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	case DREAMHOST:
		if config.LoginToken == "" {
			return errors.New("login token cannot be empty")
		}

	default:
		return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS/Dreamhost")

	}

	return nil
}

// SendTelegramNotify sends notify if IP is changed
func SendTelegramNotify(configuration *Settings, domain, currentIP string) error {
	if !configuration.Notify.Telegram.Enabled {
		return nil
	}

	if configuration.Notify.Telegram.BotApiKey == "" {
		return errors.New("bot api key cannot be empty")
	}

	if configuration.Notify.Telegram.ChatId == "" {
		return errors.New("chat id cannot be empty")
	}

	client := GetHttpClient(configuration, configuration.Notify.Telegram.UseProxy)
	tpl := configuration.Notify.Telegram.MsgTemplate
	if tpl == "" {
		tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated"
	}

	msg := buildTemplate(currentIP, domain, tpl)
	url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s",
		configuration.Notify.Telegram.BotApiKey,
		configuration.Notify.Telegram.ChatId,
		msg)
	var response *http.Response
	var err error

	response, err = client.Get(url)

	if err != nil {
		return err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	type ResponseParameters struct {
		MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
		RetryAfter      int   `json:"retry_after"`        // optional
	}
	type APIResponse struct {
		Ok          bool                `json:"ok"`
		Result      json.RawMessage     `json:"result"`
		ErrorCode   int                 `json:"error_code"`
		Description string              `json:"description"`
		Parameters  *ResponseParameters `json:"parameters"`
	}
	var resp APIResponse
	err = json.Unmarshal([]byte(body), &resp)
	if err != nil {
		fmt.Println("error:", err)
		return errors.New("Failed to parse response")
	}
	if !resp.Ok {
		return errors.New(resp.Description)
	}

	return nil
}

// SendMailNotify sends mail notify if IP is changed
func SendMailNotify(configuration *Settings, domain, currentIP string) error {
	if !configuration.Notify.Mail.Enabled {
		return nil
	}
	log.Print("Sending notification to:", configuration.Notify.Mail.SendTo)
	m := gomail.NewMessage()

	m.SetHeader("From", configuration.Notify.Mail.SMTPUsername)
	m.SetHeader("To", configuration.Notify.Mail.SendTo)
	m.SetHeader("Subject", "GoDNS Notification")
	log.Println("currentIP:", currentIP)
	log.Println("domain:", domain)
	m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate))

	d := gomail.NewPlainDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword)

	// Send the email config by sendlist	.
	if err := d.DialAndSend(m); err != nil {
		return err
	}
	return nil
}

// SendSlack sends slack if IP is changed
func SendSlackNotify(configuration *Settings, domain, currentIP string) error {
	if !configuration.Notify.Slack.Enabled {
		return nil
	}

	if configuration.Notify.Slack.BotApiToken == "" {
		return errors.New("bot api token cannot be empty")
	}

	if configuration.Notify.Slack.Channel == "" {
		return errors.New("channel cannot be empty")
	}
	client := GetHttpClient(configuration, configuration.Notify.Slack.UseProxy)
	tpl := configuration.Notify.Slack.MsgTemplate
	if tpl == "" {
		tpl = "_Your IP address is changed to_\n\n*{{ .CurrentIP }}*\n\nDomain *{{ .Domain }}* is updated"
	}

	msg := buildTemplate(currentIP, domain, tpl)

	var response *http.Response
	var err error

	formData := url.Values{
		"token": {configuration.Notify.Slack.BotApiToken},
		"channel": {configuration.Notify.Slack.Channel},
		"text": {msg},
	}

	response, err = client.PostForm("https://slack.com/api/chat.postMessage", formData)

	if err != nil {
		return err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	type ResponseParameters struct {
		MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
		RetryAfter      int   `json:"retry_after"`        // optional
	}
	type APIResponse struct {
		Ok          bool                `json:"ok"`
		Result      json.RawMessage     `json:"result"`
		ErrorCode   int                 `json:"error_code"`
		Description string              `json:"description"`
		Parameters  *ResponseParameters `json:"parameters"`
	}
	var resp APIResponse
	err = json.Unmarshal([]byte(body), &resp)
	if err != nil {
		fmt.Println("error:", err)
		return errors.New("Failed to parse response")
	}
	if !resp.Ok {
		return errors.New(resp.Description)
	}

 	return nil
}

// SendNotify sends notify if IP is changed
func SendNotify(configuration *Settings, domain, currentIP string) error {
	err := SendTelegramNotify(configuration, domain, currentIP)
	if err != nil {
		log.Println("Send telegram notification with error:", err.Error())
	}
	err = SendMailNotify(configuration, domain, currentIP)
	if err != nil {
		log.Println("Send email notification with error:", err.Error())
	}
	err = SendSlackNotify(configuration, domain, currentIP)
	if err != nil {
		log.Println("Send slack notification with error:", err.Error())
	}
	return nil
}

func buildTemplate(currentIP, domain string, tplsrc string) string {
	t := template.New("notification template")
	if _, err := t.Parse(tplsrc); err != nil {
		log.Println("Failed to parse template")
		return ""
	}

	data := struct {
		CurrentIP string
		Domain    string
	}{
		currentIP,
		domain,
	}

	var tpl bytes.Buffer
	if err := t.Execute(&tpl, data); err != nil {
		log.Println(err.Error())
		return ""
	}

	return tpl.String()
}

// ResolveDNS will query DNS for a given hostname.
func ResolveDNS(hostname, resolver, ipType string) string {
	var dnsType uint16
	if ipType == "" || strings.ToUpper(ipType) == IPV4 {
		dnsType = dns.TypeA
	} else {
		dnsType = dns.TypeAAAA
	}

	// If no DNS server is set in config file, falls back to default resolver.
	if resolver == "" {
		dnsAdress, err := net.LookupHost(hostname)
		if err != nil {
			if strings.HasSuffix(err.Error(), "no such host") {
				return "<nil>"
			}
			log.Fatalf(err.Error())
		}
		return dnsAdress[0]
	}
	res := dnsResolver.New([]string{resolver})
	// In case of i/o timeout
	res.RetryTimes = 5

	ip, err := res.LookupHost(hostname, dnsType)
	if err != nil {
		if strings.HasSuffix(err.Error(), "NXDOMAIN") {
			return "<nil>"
		}
		log.Fatalf(err.Error())
	}
	return ip[0].String()

}
<commit_msg>refactor: update error messages with lowercase<commit_after>package godns

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strings"

	dnsResolver "github.com/TimothyYe/godns/resolver"

	"github.com/miekg/dns"
	"golang.org/x/net/proxy"
	gomail "gopkg.in/gomail.v2"
)

var (
	// Logo for GoDNS
	Logo = `

 ██████╗  ██████╗ ██████╗ ███╗   ██╗███████╗
██╔════╝ ██╔═══██╗██╔══██╗████╗  ██║██╔════╝
██║  ███╗██║   ██║██║  ██║██╔██╗ ██║███████╗
██║   ██║██║   ██║██║  ██║██║╚██╗██║╚════██║
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
 ╚═════╝  ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝

GoDNS V%s
https://github.com/TimothyYe/godns

`
)

const (
	// PanicMax is the max allowed panic times
	PanicMax = 5
	// DNSPOD for dnspod.cn
	DNSPOD = "DNSPod"
	// HE for he.net
	HE = "HE"
	// CLOUDFLARE for cloudflare.com
	CLOUDFLARE = "Cloudflare"
	// ALIDNS for AliDNS
	ALIDNS = "AliDNS"
	// GOOGLE for Google Domains
	GOOGLE = "Google"
	// DUCK for Duck DNS
	DUCK = "DuckDNS"
	// DREAMHOST for Dreamhost
	DREAMHOST = "Dreamhost"
	// IPV4 for IPV4 mode
	IPV4 = "IPV4"
	// IPV6 for IPV6 mode
	IPV6 = "IPV6"
)

//GetIPFromInterface gets IP address from the specific interface
func GetIPFromInterface(configuration *Settings) (string, error) {
	ifaces, err := net.InterfaceByName(configuration.IPInterface)
	if err != nil {
		log.Println("can't get network device "+configuration.IPInterface+":", err)
		return "", err
	}

	addrs, err := ifaces.Addrs()
	if err != nil {
		log.Println("can't get address from "+configuration.IPInterface+":", err)
		return "", err
	}

	for _, addr := range addrs {
		var ip net.IP
		switch v := addr.(type) {
		case *net.IPNet:
			ip = v.IP
		case *net.IPAddr:
			ip = v.IP
		}
		if ip == nil {
			continue
		}

		if !(ip.IsGlobalUnicast() &&
			!(ip.IsUnspecified() ||
				ip.IsMulticast() ||
				ip.IsLoopback() ||
				ip.IsLinkLocalUnicast() ||
				ip.IsLinkLocalMulticast() ||
				ip.IsInterfaceLocalMulticast())) {
			continue
		}

		if isIPv4(ip.String()) {
			if strings.ToUpper(configuration.IPType) != IPV4 {
				continue
			}
		} else {
			if strings.ToUpper(configuration.IPType) != IPV6 {
				continue
			}
		}

		if ip.String() != "" {
			return ip.String(), nil
		}
	}
	return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
}

func isIPv4(ip string) bool {
	return strings.Count(ip, ":") < 2
}

// GetHttpClient creates the HTTP client and return it
func GetHttpClient(configuration *Settings, use_proxy bool) *http.Client {
	client := &http.Client{}

	if use_proxy && configuration.Socks5Proxy != "" {
		log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
		dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
		if err != nil {
			log.Println("can't connect to the proxy:", err)
			return nil
		}

		httpTransport := &http.Transport{}
		client.Transport = httpTransport
		httpTransport.Dial = dialer.Dial
	}

	return client
}

//GetCurrentIP gets an IP from either internet or specific interface, depending on configuration
func GetCurrentIP(configuration *Settings) (string, error) {
	var err error

	if configuration.IPUrl != "" || configuration.IPV6Url != "" {
		ip, err := GetIPOnline(configuration)
		if err != nil {
			log.Println("get ip online failed. Fallback to get ip from interface if possible.")
		} else {
			return ip, nil
		}
	}

	if configuration.IPInterface != "" {
		ip, err := GetIPFromInterface(configuration)
		if err != nil {
			log.Println("get ip from interface failed. There is no more ways to try.")
		} else {
			return ip, nil
		}
	}

	return "", err
}

// GetIPOnline gets public IP from internet
func GetIPOnline(configuration *Settings) (string, error) {
	client := &http.Client{}

	var response *http.Response
	var err error

	if configuration.IPType == "" || strings.ToUpper(configuration.IPType) == IPV4 {
		response, err = client.Get(configuration.IPUrl)
	} else {
		response, err = client.Get(configuration.IPV6Url)
	}

	if err != nil {
		log.Println("Cannot get IP...")
		return "", err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	return strings.Trim(string(body), "\n"), nil
}

// CheckSettings check the format of settings
func CheckSettings(config *Settings) error {
	switch config.Provider {
	case DNSPOD:
		if config.Password == "" && config.LoginToken == "" {
			return errors.New("password or login token cannot be empty")
		}
	case HE:
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	case CLOUDFLARE:
		if config.LoginToken == "" {
			if config.Email == "" {
				return errors.New("email cannot be empty")
			}
			if config.Password == "" {
				return errors.New("password cannot be empty")
			}
		}
	case ALIDNS:
		if config.Email == "" {
			return errors.New("email cannot be empty")
		}
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	case DUCK:
		if config.LoginToken == "" {
			return errors.New("login token cannot be empty")
		}
	case GOOGLE:
		if config.Email == "" {
			return errors.New("email cannot be empty")
		}
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	case DREAMHOST:
		if config.LoginToken == "" {
			return errors.New("login token cannot be empty")
		}

	default:
		return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS/Dreamhost")

	}

	return nil
}

// SendTelegramNotify sends notify if IP is changed
func SendTelegramNotify(configuration *Settings, domain, currentIP string) error {
	if !configuration.Notify.Telegram.Enabled {
		return nil
	}

	if configuration.Notify.Telegram.BotApiKey == "" {
		return errors.New("bot api key cannot be empty")
	}

	if configuration.Notify.Telegram.ChatId == "" {
		return errors.New("chat id cannot be empty")
	}

	client := GetHttpClient(configuration, configuration.Notify.Telegram.UseProxy)
	tpl := configuration.Notify.Telegram.MsgTemplate
	if tpl == "" {
		tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated"
	}

	msg := buildTemplate(currentIP, domain, tpl)
	url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s",
		configuration.Notify.Telegram.BotApiKey,
		configuration.Notify.Telegram.ChatId,
		msg)
	var response *http.Response
	var err error

	response, err = client.Get(url)

	if err != nil {
		return err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	type ResponseParameters struct {
		MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
		RetryAfter      int   `json:"retry_after"`        // optional
	}
	type APIResponse struct {
		Ok          bool                `json:"ok"`
		Result      json.RawMessage     `json:"result"`
		ErrorCode   int                 `json:"error_code"`
		Description string              `json:"description"`
		Parameters  *ResponseParameters `json:"parameters"`
	}
	var resp APIResponse
	err = json.Unmarshal([]byte(body), &resp)
	if err != nil {
		fmt.Println("error:", err)
		return errors.New("failed to parse response")
	}
	if !resp.Ok {
		return errors.New(resp.Description)
	}

	return nil
}

// SendMailNotify sends mail notify if IP is changed
func SendMailNotify(configuration *Settings, domain, currentIP string) error {
	if !configuration.Notify.Mail.Enabled {
		return nil
	}
	log.Print("Sending notification to:", configuration.Notify.Mail.SendTo)
	m := gomail.NewMessage()

	m.SetHeader("From", configuration.Notify.Mail.SMTPUsername)
	m.SetHeader("To", configuration.Notify.Mail.SendTo)
	m.SetHeader("Subject", "GoDNS Notification")
	log.Println("currentIP:", currentIP)
	log.Println("domain:", domain)
	m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate))

	d := gomail.NewDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword)

	// Send the email config by sendlist	.
	if err := d.DialAndSend(m); err != nil {
		return err
	}
	return nil
}

// SendSlack sends slack if IP is changed
func SendSlackNotify(configuration *Settings, domain, currentIP string) error {
	if !configuration.Notify.Slack.Enabled {
		return nil
	}

	if configuration.Notify.Slack.BotApiToken == "" {
		return errors.New("bot api token cannot be empty")
	}

	if configuration.Notify.Slack.Channel == "" {
		return errors.New("channel cannot be empty")
	}
	client := GetHttpClient(configuration, configuration.Notify.Slack.UseProxy)
	tpl := configuration.Notify.Slack.MsgTemplate
	if tpl == "" {
		tpl = "_Your IP address is changed to_\n\n*{{ .CurrentIP }}*\n\nDomain *{{ .Domain }}* is updated"
	}

	msg := buildTemplate(currentIP, domain, tpl)

	var response *http.Response
	var err error

	formData := url.Values{
		"token":   {configuration.Notify.Slack.BotApiToken},
		"channel": {configuration.Notify.Slack.Channel},
		"text":    {msg},
	}

	response, err = client.PostForm("https://slack.com/api/chat.postMessage", formData)

	if err != nil {
		return err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	type ResponseParameters struct {
		MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
		RetryAfter      int   `json:"retry_after"`        // optional
	}
	type APIResponse struct {
		Ok          bool                `json:"ok"`
		Result      json.RawMessage     `json:"result"`
		ErrorCode   int                 `json:"error_code"`
		Description string              `json:"description"`
		Parameters  *ResponseParameters `json:"parameters"`
	}
	var resp APIResponse
	err = json.Unmarshal([]byte(body), &resp)
	if err != nil {
		fmt.Println("error:", err)
		return errors.New("failed to parse response")
	}
	if !resp.Ok {
		return errors.New(resp.Description)
	}

	return nil
}

// SendNotify sends notify if IP is changed
func SendNotify(configuration *Settings, domain, currentIP string) error {
	err := SendTelegramNotify(configuration, domain, currentIP)
	if err != nil {
		log.Println("Send telegram notification with error:", err.Error())
	}
	err = SendMailNotify(configuration, domain, currentIP)
	if err != nil {
		log.Println("Send email notification with error:", err.Error())
	}
	err = SendSlackNotify(configuration, domain, currentIP)
	if err != nil {
		log.Println("Send slack notification with error:", err.Error())
	}
	return nil
}

func buildTemplate(currentIP, domain string, tplsrc string) string {
	t := template.New("notification template")
	if _, err := t.Parse(tplsrc); err != nil {
		log.Println("Failed to parse template")
		return ""
	}

	data := struct {
		CurrentIP string
		Domain    string
	}{
		currentIP,
		domain,
	}

	var tpl bytes.Buffer
	if err := t.Execute(&tpl, data); err != nil {
		log.Println(err.Error())
		return ""
	}

	return tpl.String()
}

// ResolveDNS will query DNS for a given hostname.
func ResolveDNS(hostname, resolver, ipType string) string {
	var dnsType uint16
	if ipType == "" || strings.ToUpper(ipType) == IPV4 {
		dnsType = dns.TypeA
	} else {
		dnsType = dns.TypeAAAA
	}

	// If no DNS server is set in config file, falls back to default resolver.
	if resolver == "" {
		dnsAdress, err := net.LookupHost(hostname)
		if err != nil {
			if strings.HasSuffix(err.Error(), "no such host") {
				return "<nil>"
			}
			log.Fatalf(err.Error())
		}
		return dnsAdress[0]
	}
	res := dnsResolver.New([]string{resolver})
	// In case of i/o timeout
	res.RetryTimes = 5

	ip, err := res.LookupHost(hostname, dnsType)
	if err != nil {
		if strings.HasSuffix(err.Error(), "NXDOMAIN") {
			return "<nil>"
		}
		log.Fatalf(err.Error())
	}
	return ip[0].String()

}
<|endoftext|>


<commit_before>// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.

package termui

import (
	"fmt"
	"math"
	"reflect"

	rw "github.com/mattn/go-runewidth"
	wordwrap "github.com/mitchellh/go-wordwrap"
)

// InterfaceSlice takes an []interface{} represented as an interface{} and converts it
// https://stackoverflow.com/questions/12753805/type-converting-slices-of-interfaces-in-go
func InterfaceSlice(slice interface{}) []interface{} {
	s := reflect.ValueOf(slice)
	if s.Kind() != reflect.Slice {
		panic("InterfaceSlice() given a non-slice type")
	}

	ret := make([]interface{}, s.Len())

	for i := 0; i < s.Len(); i++ {
		ret[i] = s.Index(i).Interface()
	}

	return ret
}

// TrimString trims a string to a max length and adds '…' to the end if it was trimmed.
func TrimString(s string, w int) string {
	if w <= 0 {
		return ""
	}
	if rw.StringWidth(s) > w {
		return rw.Truncate(s, w, string(ELLIPSES))
	}
	return s
}

func SelectColor(colors []Color, index int) Color {
	return colors[index%len(colors)]
}

func SelectStyle(styles []Style, index int) Style {
	return styles[index%len(styles)]
}

// Math ------------------------------------------------------------------------

func SumIntSlice(slice []int) int {
	sum := 0
	for _, val := range slice {
		sum += val
	}
	return sum
}

func SumFloat64Slice(data []float64) float64 {
	sum := 0.0
	for _, v := range data {
		sum += v
	}
	return sum
}

func GetMaxIntFromSlice(slice []int) (int, error) {
	if len(slice) == 0 {
		return 0, fmt.Errorf("cannot get max value from empty slice")
	}
	var max int
	for _, val := range slice {
		if val > max {
			max = val
		}
	}
	return max, nil
}

func GetMaxFloat64FromSlice(slice []float64) (float64, error) {
	if len(slice) == 0 {
		return 0, fmt.Errorf("cannot get max value from empty slice")
	}
	var max float64
	for _, val := range slice {
		if val > max {
			max = val
		}
	}
	return max, nil
}

func GetMaxFloat64From2dSlice(slices [][]float64) (float64, error) {
	if len(slices) == 0 {
		return 0, fmt.Errorf("cannot get max value from empty slice")
	}
	var max float64
	for _, slice := range slices {
		for _, val := range slice {
			if val > max {
				max = val
			}
		}
	}
	return max, nil
}

func RoundFloat64(x float64) float64 {
	return math.Floor(x + 0.5)
}

func AbsInt(x int) int {
	if x >= 0 {
		return x
	}
	return -x
}

func MinFloat64(x, y float64) float64 {
	if x < y {
		return x
	}
	return y
}

func MaxFloat64(x, y float64) float64 {
	if x > y {
		return x
	}
	return y
}

func MaxInt(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func MinInt(x, y int) int {
	if x < y {
		return x
	}
	return y
}

// []Cell ----------------------------------------------------------------------

// WrapCells takes []Cell and inserts Cells containing '\n' wherever a linebreak should go.
func WrapCells(cells []Cell, width uint) []Cell {
	str := CellsToString(cells)
	wrapped := wordwrap.WrapString(str, width)
	wrappedCells := []Cell{}
	i := 0
	for _, _rune := range wrapped {
		if _rune == '\n' {
			wrappedCells = append(wrappedCells, Cell{_rune, StyleClear})
		} else {
			wrappedCells = append(wrappedCells, Cell{_rune, cells[i].Style})
		}
		i++
	}
	return wrappedCells
}

func RunesToStyledCells(runes []rune, style Style) []Cell {
	cells := []Cell{}
	for _, _rune := range runes {
		cells = append(cells, Cell{_rune, style})
	}
	return cells
}

func CellsToString(cells []Cell) string {
	runes := make([]rune, len(cells))
	for i, cell := range cells {
		runes[i] = cell.Rune
	}
	return string(runes)
}

func TrimCells(cells []Cell, w int) []Cell {
	s := CellsToString(cells)
	s = TrimString(s, w)
	newCells := []Cell{}
	for i, r := range s {
		newCells = append(newCells, Cell{r, cells[i].Style})
	}
	return newCells
}

func SplitCells(cells []Cell, r rune) [][]Cell {
	splitCells := [][]Cell{}
	temp := []Cell{}
	for _, cell := range cells {
		if cell.Rune == r {
			splitCells = append(splitCells, temp)
			temp = []Cell{}
		} else {
			temp = append(temp, cell)
		}
	}
	if len(temp) > 0 {
		splitCells = append(splitCells, temp)
	}
	return splitCells
}
<commit_msg>Added a floor function for scrolling.<commit_after>// Copyright 2017 Zack Guo <zack.y.guo@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT license that can
// be found in the LICENSE file.

package termui

import (
	"fmt"
	"math"
	"reflect"

	rw "github.com/mattn/go-runewidth"
	wordwrap "github.com/mitchellh/go-wordwrap"
)

// InterfaceSlice takes an []interface{} represented as an interface{} and converts it
// https://stackoverflow.com/questions/12753805/type-converting-slices-of-interfaces-in-go
func InterfaceSlice(slice interface{}) []interface{} {
	s := reflect.ValueOf(slice)
	if s.Kind() != reflect.Slice {
		panic("InterfaceSlice() given a non-slice type")
	}

	ret := make([]interface{}, s.Len())

	for i := 0; i < s.Len(); i++ {
		ret[i] = s.Index(i).Interface()
	}

	return ret
}

// TrimString trims a string to a max length and adds '…' to the end if it was trimmed.
func TrimString(s string, w int) string {
	if w <= 0 {
		return ""
	}
	if rw.StringWidth(s) > w {
		return rw.Truncate(s, w, string(ELLIPSES))
	}
	return s
}

func SelectColor(colors []Color, index int) Color {
	return colors[index%len(colors)]
}

func SelectStyle(styles []Style, index int) Style {
	return styles[index%len(styles)]
}

// Math ------------------------------------------------------------------------

func SumIntSlice(slice []int) int {
	sum := 0
	for _, val := range slice {
		sum += val
	}
	return sum
}

func SumFloat64Slice(data []float64) float64 {
	sum := 0.0
	for _, v := range data {
		sum += v
	}
	return sum
}

func GetMaxIntFromSlice(slice []int) (int, error) {
	if len(slice) == 0 {
		return 0, fmt.Errorf("cannot get max value from empty slice")
	}
	var max int
	for _, val := range slice {
		if val > max {
			max = val
		}
	}
	return max, nil
}

func GetMaxFloat64FromSlice(slice []float64) (float64, error) {
	if len(slice) == 0 {
		return 0, fmt.Errorf("cannot get max value from empty slice")
	}
	var max float64
	for _, val := range slice {
		if val > max {
			max = val
		}
	}
	return max, nil
}

func GetMaxFloat64From2dSlice(slices [][]float64) (float64, error) {
	if len(slices) == 0 {
		return 0, fmt.Errorf("cannot get max value from empty slice")
	}
	var max float64
	for _, slice := range slices {
		for _, val := range slice {
			if val > max {
				max = val
			}
		}
	}
	return max, nil
}

func RoundFloat64(x float64) float64 {
	return math.Floor(x + 0.5)
}

func FloorFloat64(x float64) float64 {
	return math.Floor(x)
}

func AbsInt(x int) int {
	if x >= 0 {
		return x
	}
	return -x
}

func MinFloat64(x, y float64) float64 {
	if x < y {
		return x
	}
	return y
}

func MaxFloat64(x, y float64) float64 {
	if x > y {
		return x
	}
	return y
}

func MaxInt(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func MinInt(x, y int) int {
	if x < y {
		return x
	}
	return y
}

// []Cell ----------------------------------------------------------------------

// WrapCells takes []Cell and inserts Cells containing '\n' wherever a linebreak should go.
func WrapCells(cells []Cell, width uint) []Cell {
	str := CellsToString(cells)
	wrapped := wordwrap.WrapString(str, width)
	wrappedCells := []Cell{}
	i := 0
	for _, _rune := range wrapped {
		if _rune == '\n' {
			wrappedCells = append(wrappedCells, Cell{_rune, StyleClear})
		} else {
			wrappedCells = append(wrappedCells, Cell{_rune, cells[i].Style})
		}
		i++
	}
	return wrappedCells
}

func RunesToStyledCells(runes []rune, style Style) []Cell {
	cells := []Cell{}
	for _, _rune := range runes {
		cells = append(cells, Cell{_rune, style})
	}
	return cells
}

func CellsToString(cells []Cell) string {
	runes := make([]rune, len(cells))
	for i, cell := range cells {
		runes[i] = cell.Rune
	}
	return string(runes)
}

func TrimCells(cells []Cell, w int) []Cell {
	s := CellsToString(cells)
	s = TrimString(s, w)
	newCells := []Cell{}
	for i, r := range s {
		newCells = append(newCells, Cell{r, cells[i].Style})
	}
	return newCells
}

func SplitCells(cells []Cell, r rune) [][]Cell {
	splitCells := [][]Cell{}
	temp := []Cell{}
	for _, cell := range cells {
		if cell.Rune == r {
			splitCells = append(splitCells, temp)
			temp = []Cell{}
		} else {
			temp = append(temp, cell)
		}
	}
	if len(temp) > 0 {
		splitCells = append(splitCells, temp)
	}
	return splitCells
}
<|endoftext|>


<commit_before>package htm

import (
	//"fmt"
	"fmt"
	"math"
	"math/big"
	"math/rand"
	"time"
)

type TupleInt struct {
	A int
	B int
}

//Euclidean modulous
func Mod(a, b int) int {
	ab := big.NewInt(int64(a))
	bb := big.NewInt(int64(b))
	return int(ab.Mod(ab, bb).Int64())
}

//Dot product
func DotInt(a, b []int) int {
	if len(a) != len(b) {
		panic("Params have differing lengths")
	}
	result := 0
	for i := range a {
		result += a[i] * b[i]
	}
	return result
}

//Populates integer slice with index values
func FillSliceWithIdxInt(values []int) {
	for i := range values {
		values[i] = i
	}
}

//Populates float64 slice with specified value
func FillSliceInt(values []int, value int) {
	for i := range values {
		values[i] = value
	}
}

//Populates float64 slice with specified value
func FillSliceFloat64(values []float64, value float64) {
	for i := range values {
		values[i] = value
	}
}

//Populates bool slice with specified value
func FillSliceBool(values []bool, value bool) {
	for i := range values {
		values[i] = value
	}
}

//Populates bool slice with specified value
func FillSliceRangeBool(values []bool, value bool, start, length int) {
	for i := 0; i < length; i++ {
		values[start+i] = value
	}
}

//Returns the subset of values specified by indices
func SubsetSliceInt(values, indices []int) []int {
	result := make([]int, len(indices))
	for i, val := range indices {
		result[i] = values[val]
	}
	return result
}

//Returns the subset of values specified by indices
func SubsetSliceFloat64(values []float64, indices []int) []float64 {
	result := make([]float64, len(indices))
	for i, val := range indices {
		result[i] = values[val]
	}
	return result
}

//Creates an integer slice with indices containing
// the specified initial value
func MakeSliceInt(size, initialValue int) []int {
	result := make([]int, size)
	if initialValue != 0 {
		for i, _ := range result {
			result[i] = initialValue
		}
	}
	return result
}

func MakeSliceFloat64(size int, initialValue float64) []float64 {
	result := make([]float64, size)
	if initialValue != 0 {
		for i, _ := range result {
			result[i] = initialValue
		}
	}
	return result
}

//Returns cartesian product of specified
//2d arrayb
func CartProductInt(values [][]int) [][]int {
	pos := make([]int, len(values))
	var result [][]int

	for pos[0] < len(values[0]) {
		temp := make([]int, len(values))
		for j := 0; j < len(values); j++ {
			temp[j] = values[j][pos[j]]
		}
		result = append(result, temp)
		pos[len(values)-1]++
		for k := len(values) - 1; k >= 1; k-- {
			if pos[k] >= len(values[k]) {
				pos[k] = 0
				pos[k-1]++
			} else {
				break
			}
		}
	}
	return result
}

//Searches int slice for specified integer
func ContainsInt(q int, vals []int) bool {
	for _, val := range vals {
		if val == q {
			return true
		}
	}
	return false
}

func ContainsFloat64(q float64, vals []float64) bool {
	for _, val := range vals {
		if val == q {
			return true
		}
	}
	return false
}

// type CompareInt func(int) bool

// func CountInt(q CompareInt, vals []int) int {
// 	count := 0
// 	for i := range vals {
// 		if q(i) {
// 			count++
// 		}
// 	}
// 	return count
// }

func randFloatRange(min, max float64) float64 {
	return rand.Float64()*(max-min) + min
}

//returns max index wise comparison
func MaxInt(a, b []int) []int {
	result := make([]int, len(a))
	for i := 0; i < len(a); i++ {
		if a[i] > b[i] {
			result[i] = a[i]
		} else {
			result[i] = b[i]
		}
	}

	return result
}

//Returns max value from specified int slice
func MaxSliceInt(values []int) int {
	max := 0
	for i := 0; i < len(values); i++ {
		if values[i] > max {
			max = values[i]
		}
	}
	return max
}

//Returns max value from specified float slice
func MaxSliceFloat64(values []float64) float64 {
	max := 0.0
	for i := 0; i < len(values); i++ {
		if values[i] > max {
			max = values[i]
		}
	}
	return max
}

//Returns product of set of integers
func ProdInt(vals []int) int {
	sum := 1
	for x := 0; x < len(vals); x++ {
		sum *= vals[x]
	}

	if sum == 1 {
		return 0
	} else {
		return sum
	}
}

//Returns cumulative product
func CumProdInt(vals []int) []int {
	if len(vals) < 2 {
		return vals
	}
	result := make([]int, len(vals))
	result[0] = vals[0]
	for x := 1; x < len(vals); x++ {
		result[x] = vals[x] * result[x-1]
	}

	return result
}

//Returns cumulative product starting from end
func RevCumProdInt(vals []int) []int {
	if len(vals) < 2 {
		return vals
	}
	result := make([]int, len(vals))
	result[len(vals)-1] = vals[len(vals)-1]
	for x := len(vals) - 2; x >= 0; x-- {
		result[x] = vals[x] * result[x+1]
	}

	return result
}

func RoundPrec(x float64, prec int) float64 {
	if math.IsNaN(x) || math.IsInf(x, 0) {
		return x
	}

	sign := 1.0
	if x < 0 {
		sign = -1
		x *= -1
	}

	var rounder float64
	pow := math.Pow(10, float64(prec))
	intermed := x * pow
	_, frac := math.Modf(intermed)

	if frac >= 0.5 {
		rounder = math.Ceil(intermed)
	} else {
		rounder = math.Floor(intermed)
	}

	return rounder / pow * sign
}

//Helper for unit tests where int literals are easier
// to read
func Make2DBool(values [][]int) [][]bool {
	result := make([][]bool, len(values))

	for i, val := range values {
		result[i] = make([]bool, len(val))
		for j, col := range val {
			result[i][j] = col == 1
		}
	}

	return result
}

func Make1DBool(values []int) []bool {
	result := make([]bool, len(values))
	for i, val := range values {
		result[i] = val == 1
	}
	return result
}

//Returns number of on bits
func CountInt(values []int, value int) int {
	count := 0
	for _, val := range values {
		if val == value {
			count++
		}
	}
	return count
}

//Returns number of on bits
func CountFloat64(values []float64, value float64) int {
	count := 0
	for _, val := range values {
		if val == value {
			count++
		}
	}
	return count
}

//Returns number of on bits
func CountTrue(values []bool) int {
	count := 0
	for _, val := range values {
		if val {
			count++
		}
	}
	return count
}

//Or's 2 bool slices
func OrBool(a, b []bool) []bool {
	result := make([]bool, len(a))
	for i, val := range a {
		result[i] = val || b[i]
	}
	return result
}

//Returns random slice of floats of specified length
func RandomSample(length int) []float64 {
	result := make([]float64, length)

	for i, _ := range result {
		result[i] = rand.Float64()
	}

	return result
}

//Returns a slice of random integers
func RandomInts(length int, max int) []int {
	if max < length {
		panic("Max cannot be less than length")
	}
	result := make([]int, length)
	for i, _ := range result {
		n := rand.Intn(max)
		//no dupes
		for ContainsInt(n, result) {
			n = rand.Intn(max)
		}
		result[i] = n
	}
	return result
}

func timeTrack(start time.Time, name string) {
	elapsed := time.Since(start)
	fmt.Printf("%s took %s \n", name, elapsed)
}

func SumSliceFloat64(values []float64) float64 {
	result := 0.0
	for _, val := range values {
		result += val
	}
	return result
}
<commit_msg>implemented onindices helper function<commit_after>package htm

import (
	//"fmt"
	"fmt"
	"math"
	"math/big"
	"math/rand"
	"time"
)

type TupleInt struct {
	A int
	B int
}

//Euclidean modulous
func Mod(a, b int) int {
	ab := big.NewInt(int64(a))
	bb := big.NewInt(int64(b))
	return int(ab.Mod(ab, bb).Int64())
}

//Dot product
func DotInt(a, b []int) int {
	if len(a) != len(b) {
		panic("Params have differing lengths")
	}
	result := 0
	for i := range a {
		result += a[i] * b[i]
	}
	return result
}

//Populates integer slice with index values
func FillSliceWithIdxInt(values []int) {
	for i := range values {
		values[i] = i
	}
}

//Populates float64 slice with specified value
func FillSliceInt(values []int, value int) {
	for i := range values {
		values[i] = value
	}
}

//Populates float64 slice with specified value
func FillSliceFloat64(values []float64, value float64) {
	for i := range values {
		values[i] = value
	}
}

//Populates bool slice with specified value
func FillSliceBool(values []bool, value bool) {
	for i := range values {
		values[i] = value
	}
}

//Populates bool slice with specified value
func FillSliceRangeBool(values []bool, value bool, start, length int) {
	for i := 0; i < length; i++ {
		values[start+i] = value
	}
}

//Returns the subset of values specified by indices
func SubsetSliceInt(values, indices []int) []int {
	result := make([]int, len(indices))
	for i, val := range indices {
		result[i] = values[val]
	}
	return result
}

//Returns the subset of values specified by indices
func SubsetSliceFloat64(values []float64, indices []int) []float64 {
	result := make([]float64, len(indices))
	for i, val := range indices {
		result[i] = values[val]
	}
	return result
}

//Creates an integer slice with indices containing
// the specified initial value
func MakeSliceInt(size, initialValue int) []int {
	result := make([]int, size)
	if initialValue != 0 {
		for i, _ := range result {
			result[i] = initialValue
		}
	}
	return result
}

func MakeSliceFloat64(size int, initialValue float64) []float64 {
	result := make([]float64, size)
	if initialValue != 0 {
		for i, _ := range result {
			result[i] = initialValue
		}
	}
	return result
}

//Returns cartesian product of specified
//2d arrayb
func CartProductInt(values [][]int) [][]int {
	pos := make([]int, len(values))
	var result [][]int

	for pos[0] < len(values[0]) {
		temp := make([]int, len(values))
		for j := 0; j < len(values); j++ {
			temp[j] = values[j][pos[j]]
		}
		result = append(result, temp)
		pos[len(values)-1]++
		for k := len(values) - 1; k >= 1; k-- {
			if pos[k] >= len(values[k]) {
				pos[k] = 0
				pos[k-1]++
			} else {
				break
			}
		}
	}
	return result
}

//Searches int slice for specified integer
func ContainsInt(q int, vals []int) bool {
	for _, val := range vals {
		if val == q {
			return true
		}
	}
	return false
}

func ContainsFloat64(q float64, vals []float64) bool {
	for _, val := range vals {
		if val == q {
			return true
		}
	}
	return false
}

// type CompareInt func(int) bool

// func CountInt(q CompareInt, vals []int) int {
// 	count := 0
// 	for i := range vals {
// 		if q(i) {
// 			count++
// 		}
// 	}
// 	return count
// }

func randFloatRange(min, max float64) float64 {
	return rand.Float64()*(max-min) + min
}

//returns max index wise comparison
func MaxInt(a, b []int) []int {
	result := make([]int, len(a))
	for i := 0; i < len(a); i++ {
		if a[i] > b[i] {
			result[i] = a[i]
		} else {
			result[i] = b[i]
		}
	}

	return result
}

//Returns max value from specified int slice
func MaxSliceInt(values []int) int {
	max := 0
	for i := 0; i < len(values); i++ {
		if values[i] > max {
			max = values[i]
		}
	}
	return max
}

//Returns max value from specified float slice
func MaxSliceFloat64(values []float64) float64 {
	max := 0.0
	for i := 0; i < len(values); i++ {
		if values[i] > max {
			max = values[i]
		}
	}
	return max
}

//Returns product of set of integers
func ProdInt(vals []int) int {
	sum := 1
	for x := 0; x < len(vals); x++ {
		sum *= vals[x]
	}

	if sum == 1 {
		return 0
	} else {
		return sum
	}
}

//Returns cumulative product
func CumProdInt(vals []int) []int {
	if len(vals) < 2 {
		return vals
	}
	result := make([]int, len(vals))
	result[0] = vals[0]
	for x := 1; x < len(vals); x++ {
		result[x] = vals[x] * result[x-1]
	}

	return result
}

//Returns cumulative product starting from end
func RevCumProdInt(vals []int) []int {
	if len(vals) < 2 {
		return vals
	}
	result := make([]int, len(vals))
	result[len(vals)-1] = vals[len(vals)-1]
	for x := len(vals) - 2; x >= 0; x-- {
		result[x] = vals[x] * result[x+1]
	}

	return result
}

func RoundPrec(x float64, prec int) float64 {
	if math.IsNaN(x) || math.IsInf(x, 0) {
		return x
	}

	sign := 1.0
	if x < 0 {
		sign = -1
		x *= -1
	}

	var rounder float64
	pow := math.Pow(10, float64(prec))
	intermed := x * pow
	_, frac := math.Modf(intermed)

	if frac >= 0.5 {
		rounder = math.Ceil(intermed)
	} else {
		rounder = math.Floor(intermed)
	}

	return rounder / pow * sign
}

//Helper for unit tests where int literals are easier
// to read
func Make2DBool(values [][]int) [][]bool {
	result := make([][]bool, len(values))

	for i, val := range values {
		result[i] = make([]bool, len(val))
		for j, col := range val {
			result[i][j] = col == 1
		}
	}

	return result
}

func Make1DBool(values []int) []bool {
	result := make([]bool, len(values))
	for i, val := range values {
		result[i] = val == 1
	}
	return result
}

//Returns number of on bits
func CountInt(values []int, value int) int {
	count := 0
	for _, val := range values {
		if val == value {
			count++
		}
	}
	return count
}

//Returns number of on bits
func CountFloat64(values []float64, value float64) int {
	count := 0
	for _, val := range values {
		if val == value {
			count++
		}
	}
	return count
}

//Returns number of on bits
func CountTrue(values []bool) int {
	count := 0
	for _, val := range values {
		if val {
			count++
		}
	}
	return count
}

//Or's 2 bool slices
func OrBool(a, b []bool) []bool {
	result := make([]bool, len(a))
	for i, val := range a {
		result[i] = val || b[i]
	}
	return result
}

//Returns random slice of floats of specified length
func RandomSample(length int) []float64 {
	result := make([]float64, length)

	for i, _ := range result {
		result[i] = rand.Float64()
	}

	return result
}

//Returns a slice of random integers
func RandomInts(length int, max int) []int {
	if max < length {
		panic("Max cannot be less than length")
	}
	result := make([]int, length)
	for i, _ := range result {
		n := rand.Intn(max)
		//no dupes
		for ContainsInt(n, result) {
			n = rand.Intn(max)
		}
		result[i] = n
	}
	return result
}

func timeTrack(start time.Time, name string) {
	elapsed := time.Since(start)
	fmt.Printf("%s took %s \n", name, elapsed)
}

func SumSliceFloat64(values []float64) float64 {
	result := 0.0
	for _, val := range values {
		result += val
	}
	return result
}

//Returns "on" indices
func OnIndices(s []bool) []int {
	var result []int
	for idx, val := range s {
		if val {
			result = append(result, idx)
		}
	}
	return result
}
<|endoftext|>


<commit_before>package requests

import (
	"net/http"
	"net/url"
)

const (
	Version = "1.0"
)

var (
	sessionWithoutCookies *session
)

func init() {
	client := &http.Client{}
	s := &session{Client: client}
	s.cookies = make(map[string]string)
	sessionWithoutCookies = s
}

// Parse url params or body params. Usually the callers intend to get application/x-www-form-urlencoded format of the params
func parseParams(params map[string][]string) url.Values {
	v := url.Values{}
	for key, values := range params {
		for _, value := range values {
			v.Add(key, value)
		}
	}
	return v
}

// Parse the headers, with some default values added
func parseHeaders(headers map[string][]string) http.Header {
	h := http.Header{}
	for key, values := range headers {
		for _, value := range values {
			h.Add(key, value)
		}
	}
	_, hasAccept := h["Accept"]
	if !hasAccept {
		h.Add("Accept", "*/*")
	}
	_, hasAgent := h["User-Agent"]
	if !hasAgent {
		h.Add("User-Agent", "go-requests/"+Version)
	}
	return h
}

// Thread-safe version implementations of the seven HTTP methods, but also do not have a cookiejar
func Method(method string, urlPath string) (Request, error) {
	if method != "GET" && method != "POST" && method != "PUT" && method != "DELETE" &&
		method != "HEAD" && method != "OPTIONS" && method != "TRACE" {
		return nil, errors.New("method not supported")
	}
	return newRequest(method, urlPath, sessionWithoutCookies)
}

func Get(urlPath string) (Request, error) {
	return newRequest("GET", urlPath, sessionWithoutCookies)
}

func Post(urlPath string) (Request, error) {
	return newRequest("POST", urlPath, sessionWithoutCookies)
}

func Put(urlPath string) (Request, error) {
	return newRequest("PUT", urlPath, sessionWithoutCookies)
}

func Delete(urlPath string) (Request, error) {
	return newRequest("DELETE", urlPath, sessionWithoutCookies)
}

func Head(urlPath string) (Request, error) {
	return newRequest("HEAD", urlPath, sessionWithoutCookies)
}

func Options(urlPath string) (Request, error) {
	return newRequest("OPTIONS", urlPath, sessionWithoutCookies)
}

func Trace(urlPath string) (Request, error) {
	return newRequest("TRACE", urlPath, sessionWithoutCookies)
}
<commit_msg>fix Method<commit_after>package requests

import (
	"errors"
	"net/http"
	"net/url"
)

const (
	Version = "1.0"
)

var (
	sessionWithoutCookies *session
)

func init() {
	client := &http.Client{}
	s := &session{Client: client}
	s.cookies = make(map[string]string)
	sessionWithoutCookies = s
}

// Parse url params or body params. Usually the callers intend to get application/x-www-form-urlencoded format of the params
func parseParams(params map[string][]string) url.Values {
	v := url.Values{}
	for key, values := range params {
		for _, value := range values {
			v.Add(key, value)
		}
	}
	return v
}

// Parse the headers, with some default values added
func parseHeaders(headers map[string][]string) http.Header {
	h := http.Header{}
	for key, values := range headers {
		for _, value := range values {
			h.Add(key, value)
		}
	}
	_, hasAccept := h["Accept"]
	if !hasAccept {
		h.Add("Accept", "*/*")
	}
	_, hasAgent := h["User-Agent"]
	if !hasAgent {
		h.Add("User-Agent", "go-requests/"+Version)
	}
	return h
}

// Thread-safe version implementations of the seven HTTP methods, but also do not have a cookiejar
func Method(method string, urlPath string) (Request, error) {
	if method != "GET" && method != "POST" && method != "PUT" && method != "DELETE" &&
		method != "HEAD" && method != "OPTIONS" && method != "TRACE" {
		return nil, errors.New("method not supported")
	}
	return newRequest(method, urlPath, sessionWithoutCookies)
}

func Get(urlPath string) (Request, error) {
	return newRequest("GET", urlPath, sessionWithoutCookies)
}

func Post(urlPath string) (Request, error) {
	return newRequest("POST", urlPath, sessionWithoutCookies)
}

func Put(urlPath string) (Request, error) {
	return newRequest("PUT", urlPath, sessionWithoutCookies)
}

func Delete(urlPath string) (Request, error) {
	return newRequest("DELETE", urlPath, sessionWithoutCookies)
}

func Head(urlPath string) (Request, error) {
	return newRequest("HEAD", urlPath, sessionWithoutCookies)
}

func Options(urlPath string) (Request, error) {
	return newRequest("OPTIONS", urlPath, sessionWithoutCookies)
}

func Trace(urlPath string) (Request, error) {
	return newRequest("TRACE", urlPath, sessionWithoutCookies)
}
<|endoftext|>


<commit_before>package ics

func escape(s string) []byte {
	p := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '\\':
			p = append(p, '\\', '\\')
		case ';':
			p = append(p, '\\', ';')
		case ',':
			p = append(p, '\\', ',')
		case '\n':
			p = append(p, '\\', 'n')
		default:
			p = append(p, s[i])
		}
	}
	return p
}

func unescape(p string) []byte {
	u := p[:0]
	for i := 0; i < len(p); i++ {
		if p[i] == '\\' && i+1 < len(p) {
			i++
			switch p[i] {
			case '\\':
				u = append(u, '\\')
			case ';':
				u = append(u, ';')
			case ',':
				u = append(u, ',')
			case 'N', 'n':
				u = append(u, '\n')
			default:
				u = append(u, p[i])
			}
		} else {
			u = append(u, p[i])
		}
	}
	return toRet
}

func escape6868(s string) []byte {
	p := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '\n':
			p = append(p, '^', 'n')
		case '^':
			p = append(p, '^', '^')
		case '"':
			p = append(p, '^', '\'')
		default:
			p = append(p, s[i])
		}
	}
	return p
}

func unescape6868(p []byte) []byte {
	u := p[:0]
	for i := 0; i < len(p); i++ {
		if p[i] == '^' && i+1 < len(p) {
			i++
			switch p[i] {
			case 'n':
				u = append(u, '\n') //crlf on windows?
			case '^':
				u = append(u, '^')
			case '\'':
				u = append(u, '"')
			default:
				u = append(u, '^', p[i])
			}
		} else {
			u = append(u, p[i])
		}
	}
	return u
}

func textSplit(s string, delim byte) []string {
	toRet := make([]string, 0, 1)
	lastPos := 0
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '\\':
			i++
		case delim:
			toRet = append(toRet, unescape(s[lastPos:i]))
			lastPos = i + 2
		}
	}
	if lastPos <= len(s) {
		toRet = append(toRet, unescape(s[lastPos:len(s)]))
	}
	return toRet
}
<commit_msg>added util parsers<commit_after>package ics

import (
	"errors"
	"strconv"
	"time"

	"github.com/MJKWoolnough/strparse"
)

func escape(s string) []byte {
	p := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '\\':
			p = append(p, '\\', '\\')
		case ';':
			p = append(p, '\\', ';')
		case ',':
			p = append(p, '\\', ',')
		case '\n':
			p = append(p, '\\', 'n')
		default:
			p = append(p, s[i])
		}
	}
	return p
}

func unescape(p string) []byte {
	u := p[:0]
	for i := 0; i < len(p); i++ {
		if p[i] == '\\' && i+1 < len(p) {
			i++
			switch p[i] {
			case '\\':
				u = append(u, '\\')
			case ';':
				u = append(u, ';')
			case ',':
				u = append(u, ',')
			case 'N', 'n':
				u = append(u, '\n')
			default:
				u = append(u, p[i])
			}
		} else {
			u = append(u, p[i])
		}
	}
	return toRet
}

func escape6868(s string) []byte {
	p := make([]byte, 0, len(s))
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '\n':
			p = append(p, '^', 'n')
		case '^':
			p = append(p, '^', '^')
		case '"':
			p = append(p, '^', '\'')
		default:
			p = append(p, s[i])
		}
	}
	return p
}

func unescape6868(p []byte) []byte {
	u := p[:0]
	for i := 0; i < len(p); i++ {
		if p[i] == '^' && i+1 < len(p) {
			i++
			switch p[i] {
			case 'n':
				u = append(u, '\n') //crlf on windows?
			case '^':
				u = append(u, '^')
			case '\'':
				u = append(u, '"')
			default:
				u = append(u, '^', p[i])
			}
		} else {
			u = append(u, p[i])
		}
	}
	return u
}

func textSplit(s string, delim byte) []string {
	toRet := make([]string, 0, 1)
	lastPos := 0
	for i := 0; i < len(s); i++ {
		switch s[i] {
		case '\\':
			i++
		case delim:
			toRet = append(toRet, unescape(s[lastPos:i]))
			lastPos = i + 2
		}
	}
	if lastPos <= len(s) {
		toRet = append(toRet, unescape(s[lastPos:len(s)]))
	}
	return toRet
}

func parseDate(s string) (time.Time, error) {
	return time.Parse("20060102", s)
}

func parseDateTime(s string, l *time.Location) (time.Time, error) {
	if l == nil {
		if s[len(s)-1] == 'Z' {
			return time.Parse("20060102T150405Z", s)
		} else {
			return time.ParseInLocation("20060102T150405Z", s, time.Local)
		}
	}
	return time.ParseInLocation("20060102T150405", v, l)
}

func parseTime(s string, l *time.Location) (time.Time, error) {
	if l == nil {
		if s[len(s)-1] == 'Z' {
			return time.Parse("150405Z", s)
		} else {
			return time.ParseInLocation("150405Z", s, time.Local)
		}
	}
	return time.ParseInLocation("150405", v, l)
}

const nums = "0123456789"

func parseDuration(s string) (time.Duration, error) {
	p := strparse.Parser{Str: s}
	var (
		dur time.Duration
		neg bool
	)
	if p.Accept("-") {
		neg = true
	} else {
		p.Accept("+")
	}
	if !p.Accept("P") {
		return ErrInvalidDuration
	}
	p.Get()
	if !p.Accept("T") {
		p.AcceptRun(nums)
		num := p.Get()
		if len(num) == 0 {
			return 0, ErrInvalidDuration
		}
		n, _ := strconv.Atoi(num)
		p.Accept("DW")
		switch p.Get() {
		case "D":
			dur = time.Duration(n) * time.Hour * 24
		case "W":
			return time.Duration(n) * time.Hour * 24 * 7
		default:
			return 0, ErrInvalidDuration
		}
		p.Except("")
		switch p.Get() {
		case "":
			if neg {
				return -dur, nil
			}
			return dur, nil
		case "T":
		default:
			return 0, ErrInvalidDuration
		}
	} else {
		p.Get()
	}
	toRead := "HMS"
	for len(toRead) > 0 {
		p.AcceptRun(nums)
		num := p.Get()
		if len(num) == 0 {
			return 0, ErrInvalidDuration
		}
		n, _ := strconv.Atoi(num)
		p.Accept(toRead)
		switch p.Get() {
		case "H":
			dur += n * time.Hour
			toRead = "MS"
		case "M":
			dur += n * time.Minute
			toRead = "S"
		case "S":
			dur += n * time.Second
			toRead = ""
		default:
			return 0, ErrInvalidDuration
		}
	}
	if neg {
		return -dur, nil
	}
	return dur, nil
}

// Errors

var (
	ErrInvalidDuration = errors.New("invalid duration string")
)
<|endoftext|>