mirror of
https://github.com/muesli/telephant
synced 2024-11-26 21:30:19 +00:00
Use OAuth to authorize with Mastodon API
This commit is contained in:
parent
c4f5332b31
commit
fe0ac8c5eb
6 changed files with 210 additions and 36 deletions
|
@ -20,13 +20,8 @@ const (
|
||||||
|
|
||||||
// Account is a Mastodon account for Chirp.
|
// Account is a Mastodon account for Chirp.
|
||||||
type Account struct {
|
type Account struct {
|
||||||
username string
|
|
||||||
password string
|
|
||||||
instance string
|
|
||||||
clientID string
|
|
||||||
clientSecret string
|
|
||||||
|
|
||||||
client *mastodon.Client
|
client *mastodon.Client
|
||||||
|
config *mastodon.Config
|
||||||
self *mastodon.Account
|
self *mastodon.Account
|
||||||
|
|
||||||
evchan chan interface{}
|
evchan chan interface{}
|
||||||
|
@ -34,30 +29,54 @@ type Account struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccount returns a new Mastodon account.
|
// NewAccount returns a new Mastodon account.
|
||||||
func NewAccount(username, password, instance, clientID, clientSecret string) *Account {
|
func NewAccount(instance, token, clientID, clientSecret string) *Account {
|
||||||
return &Account{
|
mconfig := &mastodon.Config{
|
||||||
username: username,
|
Server: instance,
|
||||||
password: password,
|
AccessToken: token,
|
||||||
instance: instance,
|
ClientID: clientID,
|
||||||
clientID: clientID,
|
ClientSecret: clientSecret,
|
||||||
clientSecret: clientSecret,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &Account{
|
||||||
|
config: mconfig,
|
||||||
|
client: mastodon.NewClient(mconfig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterAccount(instance string) (*Account, string, string, error) {
|
||||||
|
app, err := mastodon.RegisterApp(context.Background(), &mastodon.AppConfig{
|
||||||
|
Server: instance,
|
||||||
|
ClientName: "Telephant",
|
||||||
|
Scopes: "read write follow post",
|
||||||
|
Website: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
a := NewAccount(instance, "", app.ClientID, app.ClientSecret)
|
||||||
|
|
||||||
|
return a, app.AuthURI, app.RedirectURI, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *Account) Authenticate(code string) (string, string, string, string, error) {
|
||||||
|
err := mod.client.AuthenticateToken(context.Background(), code, "urn:ietf:wg:oauth:2.0:oob")
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mod.config.Server, mod.config.AccessToken, mod.config.ClientID, mod.config.ClientSecret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run executes the account's event loop.
|
// Run executes the account's event loop.
|
||||||
func (mod *Account) Run(eventChan chan interface{}) {
|
func (mod *Account) Run(eventChan chan interface{}) {
|
||||||
mod.evchan = eventChan
|
mod.evchan = eventChan
|
||||||
|
|
||||||
mod.client = mastodon.NewClient(&mastodon.Config{
|
if mod.config.AccessToken == "" {
|
||||||
Server: mod.instance,
|
return
|
||||||
ClientID: mod.clientID,
|
|
||||||
ClientSecret: mod.clientSecret,
|
|
||||||
})
|
|
||||||
err := mod.client.Authenticate(context.Background(), mod.username, mod.password)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
mod.self, err = mod.client.GetAccountCurrentUser(context.Background())
|
mod.self, err = mod.client.GetAccountCurrentUser(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -75,6 +94,10 @@ func (mod *Account) Run(eventChan chan interface{}) {
|
||||||
mod.handleStream()
|
mod.handleStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mod *Account) Logo() string {
|
||||||
|
return "mastodon.svg"
|
||||||
|
}
|
||||||
|
|
||||||
// Post posts a new status
|
// Post posts a new status
|
||||||
func (mod *Account) Post(message string) error {
|
func (mod *Account) Post(message string) error {
|
||||||
_, err := mod.client.PostStatus(context.Background(), &mastodon.Toot{
|
_, err := mod.client.PostStatus(context.Background(), &mastodon.Toot{
|
||||||
|
|
|
@ -9,6 +9,9 @@ import (
|
||||||
type UIBridge struct {
|
type UIBridge struct {
|
||||||
core.QObject
|
core.QObject
|
||||||
|
|
||||||
|
_ func(instance string) `slot:"connectButton"`
|
||||||
|
_ func(instance string) `slot:"authButton"`
|
||||||
|
|
||||||
_ func(replyid string, message string) `slot:"postButton"`
|
_ func(replyid string, message string) `slot:"postButton"`
|
||||||
_ func(id string) `slot:"shareButton"`
|
_ func(id string) `slot:"shareButton"`
|
||||||
_ func(id string) `slot:"likeButton"`
|
_ func(id string) `slot:"likeButton"`
|
||||||
|
@ -52,6 +55,8 @@ func setupQmlBridges() {
|
||||||
accountBridge.SetUsername("Chirp!")
|
accountBridge.SetUsername("Chirp!")
|
||||||
|
|
||||||
uiBridge = NewUIBridge(nil)
|
uiBridge = NewUIBridge(nil)
|
||||||
|
uiBridge.ConnectConnectButton(connectToInstance)
|
||||||
|
uiBridge.ConnectAuthButton(authInstance)
|
||||||
uiBridge.ConnectPostButton(reply)
|
uiBridge.ConnectPostButton(reply)
|
||||||
uiBridge.ConnectShareButton(share)
|
uiBridge.ConnectShareButton(share)
|
||||||
uiBridge.ConnectLikeButton(like)
|
uiBridge.ConnectLikeButton(like)
|
||||||
|
|
34
chirp.go
34
chirp.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
@ -12,6 +13,35 @@ import (
|
||||||
"github.com/muesli/chirp/accounts/mastodon"
|
"github.com/muesli/chirp/accounts/mastodon"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
config Config
|
||||||
|
)
|
||||||
|
|
||||||
|
func connectToInstance(instance string) {
|
||||||
|
var authURI string
|
||||||
|
var redirectURI string
|
||||||
|
var err error
|
||||||
|
tc, authURI, redirectURI, err = mastodon.RegisterAccount(instance)
|
||||||
|
|
||||||
|
fmt.Println("auth uri:", authURI)
|
||||||
|
// fmt.Println("redirect uri:", redirectURI)
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authInstance(code string) {
|
||||||
|
instance, token, clientID, clientSecret, err := tc.Authenticate(code)
|
||||||
|
fmt.Println("authenticate:", err)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Account[0].Instance = instance
|
||||||
|
config.Account[0].ClientID = clientID
|
||||||
|
config.Account[0].ClientSecret = clientSecret
|
||||||
|
config.Account[0].Token = token
|
||||||
|
setupMastodon(config.Account[0])
|
||||||
|
}
|
||||||
|
|
||||||
// reply is used to post a new message
|
// reply is used to post a new message
|
||||||
// if replyid is > 0, it's send as a reply
|
// if replyid is > 0, it's send as a reply
|
||||||
func reply(replyid string, message string) {
|
func reply(replyid string, message string) {
|
||||||
|
@ -59,7 +89,7 @@ func runApp(config Config) {
|
||||||
|
|
||||||
// setupMastodon starts a new Mastodon client and sets up event handling & models for it
|
// setupMastodon starts a new Mastodon client and sets up event handling & models for it
|
||||||
func setupMastodon(config Account) {
|
func setupMastodon(config Account) {
|
||||||
tc = mastodon.NewAccount(config.Username, config.Password, config.Instance, config.ClientID, config.ClientSecret)
|
tc = mastodon.NewAccount(config.Instance, config.Token, config.ClientID, config.ClientSecret)
|
||||||
postModel := NewMessageModel(nil)
|
postModel := NewMessageModel(nil)
|
||||||
notificationModel := NewMessageModel(nil)
|
notificationModel := NewMessageModel(nil)
|
||||||
|
|
||||||
|
@ -83,7 +113,7 @@ func main() {
|
||||||
setupQmlBridges()
|
setupQmlBridges()
|
||||||
|
|
||||||
// load config
|
// load config
|
||||||
config := LoadConfig()
|
config = LoadConfig()
|
||||||
if config.Style == "" {
|
if config.Style == "" {
|
||||||
config.Style = "Material"
|
config.Style = "Material"
|
||||||
}
|
}
|
||||||
|
|
18
config.go
18
config.go
|
@ -16,10 +16,10 @@ type Account struct {
|
||||||
AccessTokenSecret string
|
AccessTokenSecret string
|
||||||
*/
|
*/
|
||||||
Instance string
|
Instance string
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
ClientID string
|
ClientID string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
|
Token string
|
||||||
|
RedirectURI string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config holds chirp's config settings
|
// Config holds chirp's config settings
|
||||||
|
@ -37,18 +37,10 @@ func LoadConfig() Config {
|
||||||
_, err := os.Stat(configFile)
|
_, err := os.Stat(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SaveConfig(Config{
|
SaveConfig(Config{
|
||||||
Style: "Material",
|
Style: "Material",
|
||||||
Account: []Account{
|
Account: []Account{Account{}},
|
||||||
{
|
|
||||||
Instance: "your instance",
|
|
||||||
Username: "your username",
|
|
||||||
Password: "your password",
|
|
||||||
ClientID: "your client id",
|
|
||||||
ClientSecret: "your client secret",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
log.Fatal("Config file is missing, but a template was created for you! Please edit ", configFile)
|
//log.Fatal("Config file is missing, but a template was created for you! Please edit ", configFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
var config Config
|
var config Config
|
||||||
|
|
116
qml/ConnectDialog.qml
Normal file
116
qml/ConnectDialog.qml
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import QtQuick 2.4
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Controls.Material 2.1
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: connectDialog
|
||||||
|
property string instance
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
focus: true
|
||||||
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 16
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Add an Account")
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: logo
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
smooth: true
|
||||||
|
source: "images/accounts/mastodon.svg"
|
||||||
|
sourceSize.height: 128
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeView {
|
||||||
|
id: connectSwipeView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Component.onCompleted: contentItem.interactive = false
|
||||||
|
|
||||||
|
currentIndex: 0
|
||||||
|
Item {
|
||||||
|
id: instancePage
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: instanceArea
|
||||||
|
focus: true
|
||||||
|
selectByMouse: true
|
||||||
|
placeholderText: qsTr("Instance, e.g. https://mastodon.social")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: connectButton
|
||||||
|
enabled: instanceArea.text.length > 0
|
||||||
|
Layout.alignment: Qt.AlignBottom | Qt.AlignCenter
|
||||||
|
highlighted: true
|
||||||
|
Material.accent: Material.Blue
|
||||||
|
text: qsTr("Authorize Telephant")
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
var instance = instanceArea.text
|
||||||
|
connectSwipeView.currentIndex = 1
|
||||||
|
uiBridge.connectButton(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: authPage
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: codeArea
|
||||||
|
focus: true
|
||||||
|
selectByMouse: true
|
||||||
|
placeholderText: qsTr("Auth code provided by your instance")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: authButton
|
||||||
|
enabled: codeArea.text.length > 0
|
||||||
|
Layout.alignment: Qt.AlignBottom | Qt.AlignCenter
|
||||||
|
highlighted: true
|
||||||
|
Material.accent: Material.Blue
|
||||||
|
text: qsTr("Login")
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
var code = codeArea.text
|
||||||
|
connectDialog.close()
|
||||||
|
uiBridge.authButton(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PageIndicator {
|
||||||
|
id: indicator
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
count: connectSwipeView.count
|
||||||
|
currentIndex: connectSwipeView.currentIndex
|
||||||
|
|
||||||
|
// anchors.bottom: connectSwipeView.bottom
|
||||||
|
// anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,14 @@ ApplicationWindow {
|
||||||
width: Math.min(mainWindow.width, mainWindow.height) / 3 * 2
|
width: Math.min(mainWindow.width, mainWindow.height) / 3 * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectDialog {
|
||||||
|
id: connectDialog
|
||||||
|
x: mainWindow.width / 2 - width / 2
|
||||||
|
y: mainWindow.height / 2 - height / 2 - mainWindow.header.height / 2
|
||||||
|
width: 340
|
||||||
|
height: 340
|
||||||
|
}
|
||||||
|
|
||||||
SettingsDialog {
|
SettingsDialog {
|
||||||
id: settingsDialog
|
id: settingsDialog
|
||||||
x: (mainWindow.width - width) / 2
|
x: (mainWindow.width - width) / 2
|
||||||
|
|
Loading…
Reference in a new issue