mirror of
https://github.com/muesli/telephant
synced 2024-11-22 19:33:06 +00:00
376 lines
9.5 KiB
Go
376 lines
9.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/therecipe/qt/core"
|
|
"github.com/therecipe/qt/gui"
|
|
"github.com/therecipe/qt/qml"
|
|
"github.com/therecipe/qt/quickcontrols2"
|
|
|
|
gap "github.com/muesli/go-app-paths"
|
|
"github.com/muesli/telephant/accounts/mastodon"
|
|
)
|
|
|
|
var (
|
|
config Config
|
|
configFile string
|
|
|
|
notificationModel = NewMessageModel(nil)
|
|
conversationModel = NewMessageModel(nil)
|
|
accountMessagesModel = NewMessageModel(nil)
|
|
attachmentModel = NewAttachmentModel(nil)
|
|
paneModel = NewPaneModel(nil)
|
|
)
|
|
|
|
// connectToInstance registers an app with the instance and retrieves an
|
|
// authentication URI.
|
|
func connectToInstance(instance string) bool {
|
|
var authURI string
|
|
var redirectURI string
|
|
var err error
|
|
instance = addHTTPPrefixIfNeeded(instance)
|
|
tc, authURI, redirectURI, err = mastodon.RegisterAccount(instance)
|
|
if err != nil {
|
|
fmt.Println("Error registering app:", err)
|
|
accountBridge.SetError(err.Error())
|
|
return false
|
|
}
|
|
|
|
configBridge.SetAuthURL(authURI)
|
|
configBridge.SetRedirectURL(redirectURI)
|
|
|
|
fmt.Println("auth uri:", authURI)
|
|
fmt.Println("redirect uri:", redirectURI)
|
|
return true
|
|
}
|
|
|
|
// addHTTPPrefixIfNeeded adds "https://" to an instance URL where it's missing.
|
|
func addHTTPPrefixIfNeeded(instance string) string {
|
|
if !strings.HasPrefix(instance, "http://") && !strings.HasPrefix(instance, "https://") {
|
|
return "https://" + instance
|
|
}
|
|
|
|
return instance
|
|
}
|
|
|
|
// authInstance authenticates a user via OAuth and retrieves an accesstoken
|
|
// we'll use for future logins.
|
|
func authInstance(code, redirectURI string) bool {
|
|
instance, token, clientID, clientSecret, err := tc.Authenticate(code, redirectURI)
|
|
if err != nil {
|
|
fmt.Println("Error authenticating with instance:", err)
|
|
accountBridge.SetError(err.Error())
|
|
return false
|
|
}
|
|
|
|
config.Account[0].Instance = instance
|
|
config.Account[0].ClientID = clientID
|
|
config.Account[0].ClientSecret = clientSecret
|
|
config.Account[0].Token = token
|
|
config.FirstRun = false
|
|
SaveConfig(configFile, config)
|
|
|
|
setupMastodon(config.Account[0])
|
|
return true
|
|
}
|
|
|
|
func postLimitCount(body string) int {
|
|
return tc.PostLimitCount(body)
|
|
}
|
|
|
|
// reply is used to post a new message
|
|
// if replyid is > 0, it's send as a reply
|
|
func reply(replyid string, message string) {
|
|
var attachments []string
|
|
for _, v := range attachmentModel.Attachments() {
|
|
attachments = append(attachments, v.ID)
|
|
}
|
|
|
|
var err error
|
|
if replyid != "" {
|
|
log.Println("Sending reply to:", replyid, attachments, message)
|
|
err = tc.Reply(replyid, message, attachments)
|
|
} else {
|
|
log.Println("Posting:", attachments, message)
|
|
err = tc.Post(message, attachments)
|
|
}
|
|
if err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error posting:", err)
|
|
}
|
|
}
|
|
|
|
func uploadAttachment(pathurl string) {
|
|
u, _ := url.ParseRequestURI(pathurl)
|
|
log.Println("Uploding:", u.Path)
|
|
tc.UploadAttachment(u.Path)
|
|
}
|
|
|
|
// deletePost deletes a post
|
|
func deletePost(id string) {
|
|
log.Println("Deleting:", id)
|
|
if err := tc.DeletePost(id); err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error deleting:", err)
|
|
}
|
|
}
|
|
|
|
// share a post
|
|
func share(id string) {
|
|
log.Println("Sharing:", id)
|
|
if err := tc.Share(id); err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error sharing:", err)
|
|
}
|
|
}
|
|
|
|
// unshare a post
|
|
func unshare(id string) {
|
|
log.Println("Unsharing:", id)
|
|
if err := tc.Unshare(id); err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error unsharing:", err)
|
|
}
|
|
}
|
|
|
|
// like a post
|
|
func like(id string) {
|
|
log.Println("Liking:", id)
|
|
if err := tc.Like(id); err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error liking:", err)
|
|
}
|
|
}
|
|
|
|
// unlike a post
|
|
func unlike(id string) {
|
|
log.Println("Unliking:", id)
|
|
if err := tc.Unlike(id); err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error unliking:", err)
|
|
}
|
|
}
|
|
|
|
// follow changes the relationship to another user
|
|
func follow(id string, follow bool) {
|
|
if follow {
|
|
log.Println("Following:", id)
|
|
if err := tc.Follow(id); err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error following user:", err)
|
|
return
|
|
}
|
|
} else {
|
|
log.Println("Unfollowing:", id)
|
|
if err := tc.Unfollow(id); err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error unfollowing user:", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
profileBridge.SetFollowing(follow)
|
|
}
|
|
|
|
// loadConversation loads a message thread
|
|
func loadConversation(id string) {
|
|
log.Println("Loading conversation:", id)
|
|
messages, err := tc.LoadConversation(id)
|
|
if err != nil {
|
|
log.Println("Error loading conversation:", err)
|
|
return
|
|
}
|
|
|
|
fmt.Println("Found conversation posts:", len(messages))
|
|
conversationModel.Clear()
|
|
for _, m := range messages {
|
|
p := messageFromEvent(m)
|
|
conversationModel.AppendMessage(p)
|
|
}
|
|
}
|
|
|
|
// loadAccount loads an entire profile
|
|
func loadAccount(id string) {
|
|
log.Println("Loading account:", id)
|
|
profile, messages, err := tc.LoadAccount(id)
|
|
if err != nil {
|
|
log.Println("Error loading account:", err)
|
|
return
|
|
}
|
|
|
|
profileBridge.SetUsername(profile.Username)
|
|
profileBridge.SetName(profile.Name)
|
|
profileBridge.SetAvatar(profile.Avatar)
|
|
profileBridge.SetProfileURL(profile.ProfileURL)
|
|
profileBridge.SetProfileID(profile.ProfileID)
|
|
profileBridge.SetPosts(profile.Posts)
|
|
profileBridge.SetFollowCount(profile.FollowCount)
|
|
profileBridge.SetFollowerCount(profile.FollowerCount)
|
|
profileBridge.SetFollowing(profile.Following)
|
|
profileBridge.SetFollowedBy(profile.FollowedBy)
|
|
|
|
fmt.Println("Found account posts:", len(messages))
|
|
accountMessagesModel.Clear()
|
|
for _, m := range messages {
|
|
p := messageFromEvent(m)
|
|
accountMessagesModel.AppendMessage(p)
|
|
}
|
|
}
|
|
|
|
// tag
|
|
func tag(token string) {
|
|
model := NewMessageModel(nil)
|
|
evchan := make(chan interface{})
|
|
go handleEvents(evchan, model)
|
|
|
|
log.Println("Hashtag:", token)
|
|
if err := tc.Tag(token, evchan); err != nil {
|
|
accountBridge.SetError(err.Error())
|
|
log.Println("Error retrieving hashtag:", err)
|
|
return
|
|
}
|
|
|
|
var pane = NewPane(nil)
|
|
pane.Name = "Tag: #" + token
|
|
pane.Model = model
|
|
paneModel.AddPane(pane)
|
|
}
|
|
|
|
// closePane closes a pane
|
|
func closePane(idx int64) {
|
|
fmt.Println("Closing pane", idx)
|
|
paneModel.RemovePane(int(idx))
|
|
}
|
|
|
|
// runApp loads and executes the QML UI
|
|
func runApp(config Config) {
|
|
var theme string
|
|
switch config.Theme {
|
|
case "System":
|
|
theme = ""
|
|
case "Light":
|
|
theme = "Default"
|
|
default:
|
|
theme = config.Theme
|
|
}
|
|
if theme != "" {
|
|
quickcontrols2.QQuickStyle_SetStyle(theme)
|
|
}
|
|
|
|
app := qml.NewQQmlApplicationEngine(nil)
|
|
app.RootContext().SetContextProperty("uiBridge", uiBridge)
|
|
app.RootContext().SetContextProperty("accountBridge", accountBridge)
|
|
app.RootContext().SetContextProperty("profileBridge", profileBridge)
|
|
app.RootContext().SetContextProperty("settings", configBridge)
|
|
|
|
app.Load(core.NewQUrl3("qrc:/qml/telephant.qml", 0))
|
|
gui.QGuiApplication_Exec()
|
|
}
|
|
|
|
// setupMastodon starts a new Mastodon client and sets up event handling & models for it
|
|
func setupMastodon(config Account) {
|
|
tc = mastodon.NewAccount(config.Instance, config.Token, config.ClientID, config.ClientSecret)
|
|
postModel := NewMessageModel(nil)
|
|
|
|
accountBridge.SetUsername("Not connected...")
|
|
accountBridge.SetNotifications(notificationModel)
|
|
accountBridge.SetAttachments(attachmentModel)
|
|
accountBridge.SetConversation(conversationModel)
|
|
accountBridge.SetAccountMessages(accountMessagesModel)
|
|
accountBridge.SetAvatar("qrc:/qml/images/telephant_logo.png")
|
|
accountBridge.SetPosts(0)
|
|
accountBridge.SetFollowCount(0)
|
|
accountBridge.SetFollowerCount(0)
|
|
accountBridge.SetPostSizeLimit(0)
|
|
|
|
// Notifications model must the first model to be added
|
|
// It will always be displayed right-most
|
|
paneModel.clear()
|
|
{
|
|
var pane = NewPane(nil)
|
|
pane.Name = "Notifications"
|
|
pane.Sticky = true
|
|
pane.Model = notificationModel
|
|
paneModel.AddPane(pane)
|
|
}
|
|
{
|
|
var pane = NewPane(nil)
|
|
pane.Name = "Messages"
|
|
pane.Model = postModel
|
|
paneModel.AddPane(pane)
|
|
}
|
|
|
|
panes := tc.Panes()
|
|
for _, p := range panes {
|
|
model := NewMessageModel(nil)
|
|
evchan := make(chan interface{})
|
|
|
|
go handleEvents(evchan, model)
|
|
p.Stream(evchan)
|
|
|
|
var pane = NewPane(nil)
|
|
pane.Name = p.Title
|
|
pane.Model = model
|
|
paneModel.AddPane(pane)
|
|
}
|
|
accountBridge.SetPanes(paneModel)
|
|
|
|
evchan := make(chan interface{})
|
|
go handleEvents(evchan, postModel)
|
|
go tc.Run(evchan)
|
|
}
|
|
|
|
func main() {
|
|
core.QCoreApplication_SetApplicationName("Telephant")
|
|
core.QCoreApplication_SetOrganizationName("fribbledom.com")
|
|
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true)
|
|
|
|
ga := gui.NewQGuiApplication(len(os.Args), os.Args)
|
|
ga.SetWindowIcon(gui.NewQIcon5(":/qml/images/telephant_logo.png"))
|
|
setupQmlBridges()
|
|
|
|
// load config
|
|
scope := gap.NewScope(gap.User, "fribbledom.com", "telephant")
|
|
configDir, err := scope.ConfigPath("")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
os.MkdirAll(configDir, 0700)
|
|
|
|
configFile, err = scope.ConfigPath("telephant.conf")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
config = LoadConfig(configFile)
|
|
if config.Theme == "" {
|
|
config.Theme = "Material"
|
|
}
|
|
if config.Style == "" {
|
|
config.Style = "Dark"
|
|
}
|
|
configBridge.SetTheme(config.Theme)
|
|
configBridge.SetStyle(config.Style)
|
|
configBridge.SetFirstRun(config.FirstRun)
|
|
configBridge.SetPositionX(config.PositionX)
|
|
configBridge.SetPositionY(config.PositionY)
|
|
configBridge.SetWidth(config.Width)
|
|
configBridge.SetHeight(config.Height)
|
|
|
|
setupMastodon(config.Account[0])
|
|
runApp(config)
|
|
|
|
// save config
|
|
config.Theme = configBridge.Theme()
|
|
config.Style = configBridge.Style()
|
|
config.PositionX = configBridge.PositionX()
|
|
config.PositionY = configBridge.PositionY()
|
|
config.Width = configBridge.Width()
|
|
config.Height = configBridge.Height()
|
|
config.FirstRun = false
|
|
SaveConfig(configFile, config)
|
|
}
|