~kris/9p

disco

1f273b8ee7d44d4aaffa382444ccc1c58a77f8ec — Sean Hinchee 7 years ago 3a3db19
partially fix pm's ;; fix @'s ;; fix emoji printing ;; add :u ;;
6 files changed, 187 insertions(+), 123 deletions(-)

M TODO
M commands.go
M config.go
M helper.go
M main.go
M menu.go
M TODO => TODO +5 -5
@@ 2,7 2,7 @@ Commands:

:r -- register via Register(email)

:u -- change username via UserUpdate
:u [done] -- change username via UserUpdate

:d [n] [file] -- dump backlog of last 'n' msgs to a file 'file'



@@ 20,17 20,17 @@ Features:

* if ``` entered, allow multi-line message (composition)

* replace <:emoji:3lk4j12:> garbage with :emoji:
* [done] replace <:emoji:3lk4j12:> garbage with :emoji:

* fix @'s
* [done] fix @'s

* flag for larger backlog loading

* CAPTCHA solution -- http-proxy is a bad one

* change > to → or whtever desired
* [done] change > to → or whtever desired

* fix pm's
* [done] fix pm's

* aux/statusmsg backgrounds and doesn't steal input


M commands.go => commands.go +16 -11
@@ 41,7 41,17 @@ func ParseForCommands(line string) string {
		PrintMessages(Amount)
		line = ""
	}

	if strings.HasPrefix(line, ":u") {
		session := State.Session
		user := session.User
		newName := strings.TrimPrefix(line, ":u ")
		_, err := State.Session.DiscordGo.UserUpdate(user.Email, session.Password, newName, user.Avatar, "")
		if err != nil {
			Msg(ErrorMsg, "[:u] Argument Error: %s\n", err)
		}
		line = ""
	}
		
	return line
}



@@ 49,10 59,8 @@ func ParseForCommands(line string) string {
func SelectGuild() {
	State.Enabled = false
	SelectGuildMenu()
	/* this causes a segfault, investigate later */
	//if !State.Channel.IsPrivate {
	// Segfaults would happen here
	SelectChannelMenu()
	//}
	State.Enabled = true
	ShowContent()
}


@@ 76,18 84,15 @@ func SelectChannel() {
//SelectPrivate a private channel
func SelectPrivate() {
	State.Enabled = false
	/* FIXME -- PM's broken
	SelectPrivateMenu()*/
	SelectPrivateMenu()
	State.Enabled = true
	ShowContent()
}

//SelectDeletePrivate a private channel
func SelectDeletePrivate() {
	State.Enabled = false
	/* FIXME -- PM's broken
	SelectDeletePrivateMenu()*/
	SelectDeletePrivateMenu()
	State.Enabled = true
	if State.Channel != nil {
		ShowContent()
	}
	ShowContent()
}

M config.go => config.go +14 -11
@@ 7,8 7,7 @@ import (
	"log"
	"os"
	"os/user"

//	"golang.org/x/crypto/ssh/terminal"
	//	"golang.org/x/crypto/ssh/terminal"
)

//Configuration is a struct that contains all configuration fields


@@ 17,6 16,8 @@ type Configuration struct {
	Password       string `json:"password"`
	MessageDefault bool   `json:"messagedefault"`
	Messages       int    `json:"messages"`
	CompletionChar string `json:"completionchar"`
	TimeCompChar   string `json:"timecompchar"`
}

// Config is the global configuration of discord-cli


@@ 24,7 25,7 @@ var Config Configuration

//GetConfig retrieves configuration file from $home/lib/disco.cfg, if it doesn't exist it calls CreateConfig()
func GetConfig() {
//Get User
	//Get User
Start:
	usr, err := user.Current()
	if err != nil {


@@ 65,7 66,7 @@ func CreateConfig() {
	EmptyStruct.Username = scan.Text()
	fmt.Print("Input your password: ")
	//password, err := terminal.ReadPassword(0)
	

	plan9 := true
	/* Plan 9 raw mode for rio */
	consctl, err := Rawon()


@@ 73,13 74,13 @@ func CreateConfig() {
		fmt.Println("Failed to set rawon")
		plan9 = false
	}
	
	password := "";
	

	password := ""

	if plan9 {
		

		password = GetCons()
	

		err = RawOff(consctl)
		if err != nil {
			fmt.Println("\nFailed to set rawoff")


@@ 89,13 90,15 @@ func CreateConfig() {
		/* Maybe put linux terminal raw mode in here one day */
		fmt.Println("Skipping raw input for Plan 9")
	}
	

	EmptyStruct.Password = string(password)
	EmptyStruct.Messages = 10
	EmptyStruct.MessageDefault = true
	EmptyStruct.CompletionChar = ">"
	EmptyStruct.TimeCompChar = ">"

	//Create File
	os.Mkdir(usr.HomeDir + "/lib", 0775)
	os.Mkdir(usr.HomeDir+"/lib", 0775)
	file, err := os.Create(usr.HomeDir + "/lib/disco.cfg")
	if err != nil {
		log.Fatalln(err)

M helper.go => helper.go +43 -32
@@ 1,6 1,9 @@
package main

import (
	"bufio"
	"fmt"
	"github.com/bwmarrin/discordgo"
	"io"
	"log"
	"os"


@@ 8,16 11,13 @@ import (
	"runtime"
	"strings"
	"time"
	"fmt"
	"github.com/bwmarrin/discordgo"
	"bufio"
)

//HexColor is a struct gives RGB values
type HexColor struct {
	R     int
	G     int
	B     int
	R int
	G int
	B int
}

//Msg is a composition of Color.New printf functions


@@ 28,13 28,18 @@ func Msg(MsgType, format string, a ...interface{}) {
//Header simply prints a header containing state/session information
func Header() {
	Msg(InfoMsg, "Welcome, %s!\n\n", State.Session.User.Username)
	/* FIXME -- PM's won't work
	if State.Channel.IsPrivate {
		Msg(InfoMsg, "Channel: %s\n", State.Channel.Recipient.Username)
	} else {
	*/
	switch State.Channel.Type {
	case discordgo.ChannelTypeGuildText:
		Msg(InfoMsg, "Guild: %s, Channel: %s\n", State.Guild.Name, State.Channel.Name)
	/*}*/
	case discordgo.ChannelTypeDM:
		Msg(InfoMsg, "Channel: %s\n", State.Channel.Recipients[0].Username)
	case discordgo.ChannelTypeGroupDM:
		var nicklist string
		for _, user := range State.Channel.Recipients {
			nicklist += user.Username
		}
		Msg(InfoMsg, "Channel: %s\n", nicklist)
	}
}

//ReceivingMessageParser parses receiving message for mentions, images and MultiLine and returns string array


@@ 57,7 62,6 @@ func PrintMessages(Amount int) {
	for Key, m := range State.Messages {
		if Key >= len(State.Messages)-Amount {
			Messages := ReceivingMessageParser(m)

			for _, Msg := range Messages {
				//log.Printf("> %s > %s\n", UserName(m.Author.Username), Msg)
				MessagePrint(string(m.Timestamp), m.Author.Username, Msg)


@@ 72,15 76,21 @@ func Notify(m *discordgo.Message) {
	if *enableNotify == false {
		return
	}
	Channel, err := State.Session.DiscordGo.Channel(m.ChannelID)
	if err != nil {
		Msg(ErrorMsg, "(NOT) Channel Error: %s\n", err)
	}
	Guild, err := State.Session.DiscordGo.Guild(Channel.GuildID)
	if err != nil {
		Msg(ErrorMsg, "(NOT) Guild Error: %s\n", err)
	var Title string
	switch State.Channel.Type {
	case discordgo.ChannelTypeGuildText:
		Channel, err := State.Session.DiscordGo.Channel(m.ChannelID)
		if err != nil {
			Msg(ErrorMsg, "(NOT) Channel Error: %s\n", err)
		}
		Guild, err := State.Session.DiscordGo.Guild(Channel.GuildID)
		if err != nil {
			Msg(ErrorMsg, "(NOT) Guild Error: %s\n", err)
		}
		Title = "@" + m.Author.Username + " : " + Guild.Name + "/" + Channel.Name
	case discordgo.ChannelTypeDM:
		Title = fmt.Sprintf("%s (pm)\n", m.Author.Username)
	}
	Title := "@" + m.Author.Username + " : " + Guild.Name + "/" + Channel.Name
	switch runtime.GOOS {
	case "plan9":
		pr, pw := io.Pipe()


@@ 91,32 101,33 @@ func Notify(m *discordgo.Message) {
			fmt.Fprintf(pw, "%s\n", m.ContentWithMentionsReplaced())
			cmd.Wait()
		}()
		err = cmd.Start()
		err := cmd.Start()
		if err != nil {
			Msg(ErrorMsg, "%s\n", err)
		}
		

	default:
		cmd := exec.Command("notify-send", Title, m.ContentWithMentionsReplaced())
		err = cmd.Start()
		err := cmd.Start()
		if err != nil {
			Msg(ErrorMsg, "(NOT) Check if libnotify is installed, or disable notifications.\n")
		}
	}


}

//MessagePrint prints one correctly formatted Message to stdout
func MessagePrint(Time, Username, Content string) {
	//Clean up emoji
	content := ParseForEmoji(Content)
	//var Color color.Attribute
	log.SetFlags(0)
	if *hideTimeStamp {
		log.Printf("%s > %s\n", Username, Content)
		log.Printf("%s %s %s\n", Username, Config.CompletionChar, content)
	} else {
		TimeStamp, _ := time.Parse(time.RFC3339, Time)
		LocalTime := TimeStamp.Local().Format("2006/01/02 15:04:05")
		log.Printf("%s > %s > %s\n", LocalTime, Username, Content)
		log.Printf("%s %s %s %s %s\n", LocalTime, Config.TimeCompChar, Username, Config.CompletionChar, content)

	}
	log.SetFlags(log.LstdFlags)


@@ 126,21 137,21 @@ func dis(a, b int) float64 {
	return float64((a - b) * (a - b))
}

func Rawon() (*os.File, error){
func Rawon() (*os.File, error) {
	consctl, err := os.OpenFile("/dev/consctl", os.O_WRONLY, 0200)
	if err != nil {
		/* not on Plan 9 */
		fmt.Println("\nNot running on Plan 9")
		return consctl, err
	}
	

	rawon := []byte("rawon")
	_, err = consctl.Write(rawon)
	if err != nil {
		consctl.Close()
		return consctl, err
	}
	

	return consctl, nil
}



@@ 150,14 161,14 @@ func RawOff(consctl *os.File) error {
	//	/* not on Plan 9 */
	//	return err
	//}
	

	rawoff := []byte("rawoff")
	_, err := consctl.Write(rawoff)
	if err != nil {
		consctl.Close()
		return err
	}
	

	consctl.Close()
	return nil
}

M main.go => main.go +45 -21
@@ 3,13 3,14 @@
package main

import (
	"flag"
	"log"
	"regexp"
	"bitbucket.org/henesy/disco/DiscordState"
	"fmt"
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"strings"
)

//Global Message Types


@@ 88,9 89,9 @@ func main() {
		line = ParseForCommands(line)

		line = ParseForMentions(line)
		

		if line != "" {
			_ ,err := State.Session.DiscordGo.ChannelMessageSend(State.Channel.ID, line)
			_, err := State.Session.DiscordGo.ChannelMessageSend(State.Channel.ID, line)
			if err != nil {
				fmt.Print("Error: ", err, "\n")
			}


@@ 110,7 111,7 @@ func InitWindow() {
	ShowContent()
}

//ShowContent shows defaulth Channel content
//ShowContent shows default Channel content
func ShowContent() {
	Header()
	if Config.MessageDefault {


@@ 122,31 123,54 @@ func ShowContent() {
//ShowEmptyContent shows an empty channel
func ShowEmptyContent() {
	Header()

}

//ParseForMentions parses input string for mentions
func ParseForMentions(line string) string {
	r, err := regexp.Compile("\\@\\w+")
	r, err := regexp.Compile("@\\w+")
	if err != nil {
		Msg(ErrorMsg, "Regex Error: ", err)
	}

	lineByte := r.ReplaceAllFunc([]byte(line), ReplaceMentions)
	lineByte := r.ReplaceAllStringFunc(line, ReplaceMentions)

	return string(lineByte[:])
	return lineByte
}

//ReplaceMentions replaces mentions to ID
func ReplaceMentions(input []byte) []byte {
	var OutputString string

	SizeByte := len(input)
	InputString := string(input[1:SizeByte])
//ReplaceMentions replaces mentions to ID 
func ReplaceMentions(input string) string {
	// Check for guild members that match
	channel := State.Guild.Members
	for _, member := range channel {
		if member.Nick == input[1:] {
			return member.User.Mention()
		}
		if strings.HasPrefix(member.User.Username, input[1:]) {
			return member.User.Mention()
		}
	}
	// Walk all PM channels
	userChannels, err := Session.DiscordGo.UserChannels()
	if err != nil {
		return input
	}
	for _, channel := range userChannels {
		for _, recipient := range channel.Recipients {
			if strings.HasPrefix(input[1:], recipient.Username) {
				fmt.Println("usermatch")
				return recipient.Mention()
			}
		}
	}
	return input
}

	if Member, ok := State.Members[InputString]; ok {
		OutputString = "<@" + Member.User.ID + ">"
	} else {
		OutputString = "@" + InputString
//Parse for guild-specific emoji
func ParseForEmoji(line string) string {
	r, err := regexp.Compile("<(:\\w+:)[0-9]+>")
	if err != nil {
		Msg(ErrorMsg, "Regex Error: ", err)
	}
	return []byte(OutputString)
	return r.ReplaceAllString(line, "$1")
}

M menu.go => menu.go +64 -43
@@ 1,20 1,20 @@
package main

import (
	"bufio"
	"fmt"
	"log"
	"strconv"
	"bufio"
	"os"
	"strconv"
)
/* FIXME -- PM's broken

//SelectPrivateMenu is a menu item that changes to a private channel
func SelectPrivateMenu() {

Start:

	Msg(InfoMsg, "Select a Member:\n")

	// List of PMs
	UserChannels, err := Session.DiscordGo.UserChannels()

	if err != nil {


@@ 26,7 26,18 @@ Start:

	for _, user := range UserChannels {
		UserMap[SelectID] = user.ID
		Msg(TextMsg, "[%d] %s\n", SelectID, UserChannels[SelectID].Recipient.Username)
		// We have to loop through all recipients
		recipients := UserChannels[SelectID].Recipients
		for _, recipient := range recipients {
			if recipient.ID == user.ID {
				continue
			}
			if recipient.Username == "" {
				continue
			}
			Msg(TextMsg, "[%d] %s\n", SelectID, recipient.Username)
			break
		}
		SelectID++
	}
	Msg(TextMsg, "[b] Extra Options\n")


@@ 78,9 89,8 @@ Start:
	State.Channel = UserChannels[ResponseInteger]
	ShowContent()
End:
}*/
}

/* FIXME -- PM's broken
//SelectDeletePrivateMenu deletes a user channel
func SelectDeletePrivateMenu() {



@@ 99,7 109,18 @@ Start:

	for _, user := range UserChannels {
		UserMap[SelectID] = user.ID
		Msg(TextMsg, "[%d] %s\n", SelectID, UserChannels[SelectID].Recipient.Username)
		// We have to loop through all recipients
		recipients := UserChannels[SelectID].Recipients
		for _, recipient := range recipients {
			if recipient.ID == user.ID {
				continue
			}
			if recipient.Username == "" {
				continue
			}
			Msg(TextMsg, "[%d] %s\n", SelectID, recipient.Username)
			break
		}
		SelectID++
	}
	var response string


@@ 118,7 139,7 @@ Start:

	Session.DiscordGo.ChannelDelete(UserChannels[ResponseInteger].ID)

} */
}

//SelectGuildMenu is a menu item that creates a new State on basis of Guild selection
func SelectGuildMenu() {


@@ 245,7 266,7 @@ Start:
		response, _ := reader.ReadString('\n')
		response = response[:len(response)-1]
		fmt.Println(response, " ", len(response))
		

		//fmt.Scanf("%s\n", &response)
		if response == "y" {
			Session.DiscordGo.InviteAccept(Invite.Code)


@@ 281,44 302,44 @@ func ExtraPrivateMenuOptions() {

//AddUserChannelMenu takes a user from the current guild and adds them to a private message. WILL RETURN ERROR IF IN USER CHANNEL.
func AddUserChannelMenu() {
	/* FIXME -- PM's broken
	if State.Channel.IsPrivate {

	if State.Channel.GuildID == "" {
		Msg(ErrorMsg, "Currently in a user channel, move to a guild with :g\n")
	} else {*/
		SelectMap := make(map[int]string)
	Start:
		SelectID := 0
		for _, Member := range State.Members {
			SelectMap[SelectID] = Member.User.ID
			Msg(TextMsg, "[%d] %s\n", SelectID, Member.User.Username)
			SelectID++
		}
		var response string
		fmt.Scanf("%s\n", &response)
	} else {
	SelectMap := make(map[int]string)
Start:
	SelectID := 0
	for _, Member := range State.Members {
		SelectMap[SelectID] = Member.User.ID
		Msg(TextMsg, "[%d] %s\n", SelectID, Member.User.Username)
		SelectID++
	}
	var response string
	fmt.Scanf("%s\n", &response)

		if response == "b" {
			return
		}
	if response == "b" {
		return
	}

		ResponseInteger, err := strconv.Atoi(response)
		if err != nil {
			Msg(ErrorMsg, "(CH) Conversion Error: %s\n", err)
			goto Start
		}
	ResponseInteger, err := strconv.Atoi(response)
	if err != nil {
		Msg(ErrorMsg, "(CH) Conversion Error: %s\n", err)
		goto Start
	}

		if ResponseInteger > SelectID-1 || ResponseInteger < 0 {
			Msg(ErrorMsg, "(CH) Error: ID is out of bound\n")
			goto Start
		}
		Chan, err := Session.DiscordGo.UserChannelCreate(SelectMap[ResponseInteger])
	if ResponseInteger > SelectID-1 || ResponseInteger < 0 {
		Msg(ErrorMsg, "(CH) Error: ID is out of bound\n")
		goto Start
	}
	Chan, err := Session.DiscordGo.UserChannelCreate(SelectMap[ResponseInteger])

		if Chan.LastMessageID == "" {
			var firstMessage string
			fmt.Scanf("%s\n", &firstMessage)
			Session.DiscordGo.ChannelMessageSend(Chan.ID, "Test")
		}
		State.Channel = Chan
	/*}*/
	if Chan.LastMessageID == "" {
		var firstMessage string
		fmt.Scanf("%s\n", &firstMessage)
		Session.DiscordGo.ChannelMessageSend(Chan.ID, "Test")
	}
	State.Channel = Chan
	}
}

//LeaveServerMenu is a copy of SelectGuildMenu that leaves instead of selects