I use go-telegram-bot-api for building a Telegram Bot and deploying it on Heroku.
I need to set Webhooks as I used to do in Python like in this Python case.
Can't understand how to set Webhooks in go-telegram-bot-api without providing certificate files.
The main example contains such lines:
If you need to use webhooks (if you wish to run on Google App Engine), you may use a slightly different method.
package main
import (
"gopkg.in/telegram-bot-api.v4"
"log"
"net/http"
)
func main() {
bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken")
if err != nil {
log.Fatal(err)
}
bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName)
_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem"))
if err != nil {
log.Fatal(err)
}
updates := bot.ListenForWebhook("/" + bot.Token)
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
for update := range updates {
log.Printf("%+v\n", update)
}
}
But using Heroku for deploying how could I listen to Webhooks without providing pem certificate files?
Following this https://devcenter.heroku.com/articles/getting-started-with-go to get the starter code.
initTelegram() and webhookHandler() are functions to study.
Once code is deployed to Heroku, run:
heroku logs --tail -a <YOUR-APP-NAME>
send some messages to the bot, you should see messages logged.
UPDATE 1
Check your app URL is the same as baseURL variable in your code -- run heroku info -a <YOUR-APP-NAME> -- your URL should be equal to what Web URL is.
UPDATE 2
In order to check your Telegram API Webhooks response ping this address in your browser: https://api.telegram.org/bot<YOUR BOT TOKEN GOES HERE>/getWebhookInfo where you should concatenate string bot and your actual Telegram Bot Token in that address string.
Optionally if you run your code not on Heroku, running curl from terminal could be an option according to Official Telegram API Reference:
$ curl -F "url=https://your.domain.or.ip.com" -F "certificate=#/etc/ssl/certs/bot.pem" https://api.telegram.org/bot<YOUR BOT TOKEN GOES HERE>/setWebhook
THE CODE:
package main
import (
"encoding/json"
"io"
"io/ioutil"
"log"
"os"
"github.com/gin-gonic/gin"
"gopkg.in/telegram-bot-api.v4"
_ "github.com/heroku/x/hmetrics/onload"
_ "github.com/lib/pq"
)
var (
bot *tgbotapi.BotAPI
botToken = "<YOUR BOT TOKEN GOES HERE>"
baseURL = "https://<YOUR-APP-NAME>.herokuapp.com/"
)
func initTelegram() {
var err error
bot, err = tgbotapi.NewBotAPI(botToken)
if err != nil {
log.Println(err)
return
}
// this perhaps should be conditional on GetWebhookInfo()
// only set webhook if it is not set properly
url := baseURL + bot.Token
_, err = bot.SetWebhook(tgbotapi.NewWebhook(url))
if err != nil {
log.Println(err)
}
}
func webhookHandler(c *gin.Context) {
defer c.Request.Body.Close()
bytes, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
log.Println(err)
return
}
var update tgbotapi.Update
err = json.Unmarshal(bytes, &update)
if err != nil {
log.Println(err)
return
}
// to monitor changes run: heroku logs --tail
log.Printf("From: %+v Text: %+v\n", update.Message.From, update.Message.Text)
}
func main() {
port := os.Getenv("PORT")
if port == "" {
log.Fatal("$PORT must be set")
}
// gin router
router := gin.New()
router.Use(gin.Logger())
// telegram
initTelegram()
router.POST("/" + bot.Token, webhookHandler)
err := router.Run(":" + port)
if err != nil {
log.Println(err)
}
}
Good luck and have fun!
If you are using Heroku for your bot, You don't need certificates. Heroku provides free SSL certificates. So, You put the URL of your app with https scheme and listen with a HTTP Server on the PORT that heroku sets for your application. See the example below to understand better.
func main() {
// Create bot instance
u := fetchUpdates(bot)
go http.ListenAndServe(":" + PORT, nil)
}
func fetchUpdates(bot *tgbotapi.BotAPI) tgbotapi.UpdatesChannel {
_, err = bot.SetWebhook(tgbotapi.NewWebhook("https://dry-hamlet-60060.herokuapp.com/instagram_profile_bot/" + bot.Token))
if err != nil {
fmt.Fatalln("Problem in setting Webhook", err.Error())
}
updates := bot.ListenForWebhook("/instagram_profile_bot/" + bot.Token)
return updates
}
Related
I managed to create a script that creates an email draft using Gmail in GO. I would like a solution to be able to attach a file to this draft.
p.s.: It needs to be a draft because someone else will review the email before sending it.
The code is here:
package main
import (
"bytes"
"log"
"os"
"time"
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
)
func main() {
log.Println("Connecting to server...")
// Connect to server
c, err := client.DialTLS(os.Getenv("IMAP_SERVER"), nil)
if err != nil {
log.Fatal(err)
}
log.Println("Connected")
// Don't forget to logout
defer c.Logout()
// Login
if err := c.Login(os.Getenv("IMAP_USER"), os.Getenv("IMAP_PASSWORD")); err != nil {
log.Fatal(err)
}
log.Println("Logged in")
// Write the message to a buffer
var b bytes.Buffer
b.WriteString("From: <...#gmail.com>\r\n")
b.WriteString("To: <...#gmail.com>\r\n")
b.WriteString("Subject: Append test\r\n")
b.WriteString("\r\n")
// Message body
b.WriteString("Append test using Gmail IMAP and Draft folder")
// Append it to Drafts
if err := c.Append("[Gmail]/Drafts", nil, time.Now(), &b); err != nil {
log.Fatal(err)
}
My org is new to Google Auth and we have poured countless hours into documentation readings. The mission was simple: view members in our google groups through the Directory API.
Our setup: The cloud function deploys and runs with a service account that has been granted domain-wide access with the proper scopes, and impersonates an admin user detailed here:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation
When I run the function locally and pull the service account key from a file path I get the error: "Error 403: Not Authorized to access this resource/api, forbidden"
I noticed that when deploying the Cloud Function via inline text or an uploaded zip it was unable to read a .json or .text file type when I included it in the package. I know this is bad practice but just to see I put in marshaled the JSON key in the main file.
And still got a "Error 403: Not Authorized to access this resource/api, forbidden"
Where am I going wrong?
import (
"encoding/json"
"fmt"
_"io/ioutil"
"log"
"net/http"
_ "os"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
admin "google.golang.org/api/admin/directory/v1"
"google.golang.org/api/option"
)
var User_email = <user>
func createAdminDirectoryService(serviceAccountFilePath, gsuiteAdminUserEmail string) *admin.Service {
jsonCredentials,_ := json.Marshal(map[string]string{<SERVICE KEY FILE>})
log.Println("Json creds: ", jsonCredentials)
config, err := google.JWTConfigFromJSON(
jsonCredentials,
"https://www.googleapis.com/auth/admin.directory.group.member.readonly",
)
if err != nil {
log.Printf("Json Config error:%v", err.Error())
}
config.Subject = gsuiteAdminUserEmail
fmt.Println(serviceAccountFilePath)//vestigial of previous version reading key from file
fmt.Println(gsuiteAdminUserEmail)
ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(20*time.Second))
ts := config.TokenSource(ctx)
srv, err := admin.NewService(ctx, option.WithTokenSource(ts))
if err != nil {
log.Println("Admin Service error:", err.Error())
}
return srv
}
func listUsersInGroup(srv *admin.Service, groupEmail string) ([]string, error) {
membersEmails := make([]string, 1)
members, err := srv.Members.List(groupEmail).Do()
if err != nil {
log.Fatal("fatalerror list users: ", err)
membersEmails[0] = "Nope"
} else {
membersEmails := make([]string, len(members.Members))
for i, member := range members.Members {
membersEmails[i] = member.Email
}
}
return membersEmails, err
}
func Main(w http.ResponseWriter, r *http.Request) {
groupEmail := <groupemail>
path := "./key.json" //vestigial of previous version reading key from file
fmt.Println("Path:", path)
srv := createAdminDirectoryService(
path,
User_email,
)
members, err := listUsersInGroup(srv, groupEmail)
if err != nil {
log.Println(members)
} else {
log.Println("sorry bud")
}
}
There is a piece of working code that launches a bot to receive webhooks.
package main
import (
"log"
"net/http"
)
func main() {
bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken")
if err != nil {
log.Fatal(err)
}
bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName)
_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert("https://example.com:8443/"+bot.Token, "cert.pem"))
if err != nil {
log.Fatal(err)
}
info, err := bot.GetWebhookInfo()
if err != nil {
log.Fatal(err)
}
if info.LastErrorDate != 0 {
log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
}
updates := bot.ListenForWebhook("/" + bot.Token)
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
for update := range updates {
log.Printf("%+v\n", update)
}
}
Library for golang - tgbotapi
When I try to launch another bot on the same server, with the same code,
but with a different token, of course, the following happens, the bot is successfully authorized but does not respond to commands, or it simply does not receive a webhook, who knows the reason?
I turn off 1 bot, then the second one starts without problems, at the same time they do not want to..
I have a mongodb service up and running. I port-forward to access it locally and in the meantime, I try to check connection with a go app. But I get the error below.
panic: error parsing uri: lookup _mongodb._tcp.localhost on 8.8.8.8:53: no such host
Port-forward:
kubectl port-forward service/mongodb-svc 27017:27017
Go app:
package main
import (
"context"
"fmt"
//"log"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
func main() {
username := "username"
address := "localhost"
password := "password"
// Replace the uri string with your MongoDB deployment's connection string.
uri := "mongodb+srv://" + username + ":" + password + "#" + address + "/admin?w=majority"
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
if err != nil {
panic(err)
}
defer func() {
if err = client.Disconnect(ctx); err != nil {
panic(err)
}
}()
// Ping the primary
if err := client.Ping(ctx, readpref.Primary()); err != nil {
panic(err)
}
fmt.Println("Successfully connected and pinged.")
}
Your client is trying to a DNS service lookup because you specified the +srv connection type in your URI. Stop doing that and use the correct connection string instead. We do support that in-cluster but not via port forward. I suspect you're trying to mix and match tutorials for both in-cluster and out of cluster. You can't do that.
I am trying to work out a golang script that uses my service account to manage my google domain. I get an error when I try to do a simple user list: 400 invalid_grant. It appears that I am using my service account correctly(?), and my service account is a super admin. I am using credentials in java code; so I know that it is valid. Any thoughts?
package main
import (
"fmt"
"io/ioutil"
"log"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
directory "google.golang.org/api/admin/directory/v1"
)
func main() {
serviceAccountFile := "/credentials.json"
serviceAccountJSON, err := ioutil.ReadFile(serviceAccountFile)
if err != nil {
log.Fatalf("Could not read service account credentials file, %s => {%s}", serviceAccountFile, err)
}
config, err := google.JWTConfigFromJSON(serviceAccountJSON,
directory.AdminDirectoryUserScope,
directory.AdminDirectoryUserReadonlyScope,
)
// Add the service account.
config.Email = "serviceaccount#domain.com"
srv, err := directory.New(config.Client(context.Background()))
if err != nil {
log.Fatalf("Could not create directory service client => {%s}", err)
}
// The next step fails with:
//2019/03/25 10:38:43 Unable to retrieve users in domain: Get https://www.googleapis.com/admin/directory/v1/users?alt=json&maxResults=10&orderBy=email&prettyPrint=false: oauth2: cannot fetch token: 400 Bad Request
//Response: {
// "error": "invalid_grant",
// "error_description": "Robot is missing a project number."
//}
//exit status 1
usersReport, err := srv.Users.List().MaxResults(10).OrderBy("email").Do()
if err != nil {
log.Fatalf("Unable to retrieve users in domain: %v", err)
}
if len(usersReport.Users) == 0 {
fmt.Print("No users found.\n")
} else {
fmt.Print("Users:\n")
for _, u := range usersReport.Users {
fmt.Printf("%s (%s)\n", u.PrimaryEmail, u.Name.FullName)
}
}
}
I got this working. It seems like it may be a combination of things. DazWilkin, yes I get the 401 unauthorized error when I switch around how I pass in my service account. I made the following changes.
Used Subject instead of Email in the configuration.
Only used the AdminDirectoryUserScope scope, instead of the AdminDirectoryUserReadonlyScope scope (or the combination of both).
Included the Domain in the request. I get a 400 Bad Request without it.
I verified that Directory APIs were on from this link: https://developers.google.com/admin-sdk/directory/v1/quickstart/go . When I clicked on this link, it said that apis were already working. I am using the same json credentials here that I am using in some production scripts written in other languages. Meaning, I thought that this was already in place. So I don't think I needed to do this step, but I will include it in case it is useful for others.
Here is what my script looks like now:
package main
import (
"fmt"
"io/ioutil"
"log"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
directory "google.golang.org/api/admin/directory/v1"
)
func main() {
serviceAccountFile := "/credentials.json"
serviceAccountJSON, err := ioutil.ReadFile(serviceAccountFile)
if err != nil {
log.Fatalf("Could not read service account credentials file, %s => {%s}", serviceAccountFile, err)
}
// I want to use these options, but these cause a 401 unauthorized error
// config, err := google.JWTConfigFromJSON(serviceAccountJSON,
// directory.AdminDirectoryUserScope,
// directory.AdminDirectoryUserReadonlyScope,
// )
config, err := google.JWTConfigFromJSON(serviceAccountJSON,
directory.AdminDirectoryUserScope,
)
// Add the service account.
//config.Email = "serviceaccount#domain.com" // Don't use Email, use Subject.
config.Subject = "serviceaccount#domain.com"
srv, err := directory.New(config.Client(context.Background()))
if err != nil {
log.Fatalf("Could not create directory service client => {%s}", err)
}
// Get the results.
usersReport, err := srv.Users.List().Domain("domain.com").MaxResults(100).Do()
if err != nil {
log.Fatalf("Unable to retrieve users in domain: %v", err)
}
// Report results.
if len(usersReport.Users) == 0 {
fmt.Print("No users found.\n")
} else {
fmt.Print("Users:\n")
for _, u := range usersReport.Users {
fmt.Printf("%s (%s)\n", u.PrimaryEmail, u.Name.FullName)
}
}
}
Fixed!
Thanks to Sal.
Here's a working Golang example:
https://gist.github.com/DazWilkin/afb0413a25272dc7d855ebec5fcadcb6
NB
Line 24 --config.Subject
Line 31 --You'll need to include the CustomerId (IDPID) using this link
Here's a working Python example:
https://gist.github.com/DazWilkin/dca8c3db8879765632d4c4be8d662074