Commit 40b40f60 authored by Thomas Bella's avatar Thomas Bella

Add bot with first command

parent 45910bfd
Pipeline #1057 failed with stages
in 23 seconds
# Text files use LF line endings in this repository
* text=auto
# Declare files that will always have CRLF line endings on checkout.
*.bat text eol=crlf
*.cmd text eol=crlf
# Declare files that will always have LF line endings on checkout.
*.sh text eol=lf
*.service eol=lf
*.conf eol=lf
*.desktop eol=lf
# Per se go.sum should be included because it contains the checksums of packages,
# but we redownload everything on compile time
# See https://github.com/golang/go/wiki/Modules#releasing-modules-all-versions
/go.sum
# .vscode config
/.vscode/
# Previous builds
/build/*
# Mac OS X files
.DS_Store
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
image: thomas2500/golang-helper
stages:
- test
- build
- deploy
# Test formatting, suspicious constructs and predefined tests. Creates code coverage statistics and saves a HTML coverage report to coverage/
test:
stage: test
script:
# We have to use env GO111MODULE=on as long as modules aren't stable
- env GO111MODULE=on go fmt $(go list ./...)
- env GO111MODULE=on go vet $(go list ./...)
- env GO111MODULE=on CGO_ENABLED=0 go test -v -cover -coverprofile=profile.out -covermode=atomic $(go list ./...) && mkdir coverage && go tool cover -html=profile.out -o coverage/index.html
artifacts:
expire_in: 1 hour
paths:
- coverage/
build:
stage: build
script:
# Get current Go version
- go version
# Build program
- bash ./tools/build.sh $CI_PROJECT_DIR/build/ cmd/project
artifacts:
expire_in: 2 hour
paths:
- build/
# Deploy GitLab Pages (everything in public/ will be made available)
pages:
stage: deploy
dependencies:
- test
- build
script:
# Copy code coverage report
- mv coverage/ public/
# Copy binary
# FIXME: Use deploy to upload the binary to the correct destination
- cp build/* public/
artifacts:
paths:
- public
expire_in: 30 days
only:
- master
.deploy:
image: thomas2500/utility-image
stage: deploy
script:
# Prepare SSH for upload
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
- chmod 700 ~/.ssh/id_ed25519
- echo -e "Host *\n\tStrictHostKeyChecking no\n\tIdentityFile ~/.ssh/id_ed25519\n\tVerifyHostKeyDNS yes\n\n" > ~/.ssh/config
# Upload builds
#- rsync -vuzrP --delete -e 'ssh -i ~/.ssh/id_ed25519' $CI_PROJECT_DIR/build/* release@10.20.23.252:/var/vhosts/bella.ml/release
# uTeleBot
uTeleBot is a bot written in Go which provides some useful tools for IT people.
## TODO
- Don't depend on mac API
- Download fom http://standards-oui.ieee.org/oui.txt, parse and update periodically
package main
var availableCommands = map[string]string{
"dns": "Perform a DNS lookup",
}
func commandHelp() string {
help := `Hello!
My name is *uTeleBot* (Version ` + programVersion + `+` + commitHash + `) and I'm created by [Thomas2500](http://t.me/Thomas2500).
Available commands:
`
for name, desc := range availableCommands {
help += "*`" + name + "`*: " + desc + "\n"
}
help += `
You can grab my source code from https://git.bella.network/Thomas2500/utelebot
`
return help
}
package main
import (
"encoding/json"
"log"
"net/http"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
var (
commitHash = "00000000"
programVersion = "20190110142100"
buildDate = "2019-01-10"
)
func main() {
bot, err := tgbotapi.NewBotAPI("551335021:AAF2qBhv55pFmd6hYv2u12HEIfDNXyTkqnk")
if err != nil {
log.Fatal(err)
}
bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName)
// Set webhook target on program start
_, err = bot.SetWebhook(tgbotapi.NewWebhook("https://tgbot.bella.ml/utelebot/"))
if err != nil {
log.Fatal(err)
}
// Verify webhook info and check if calls failed
info, err := bot.GetWebhookInfo()
if err != nil {
log.Fatal(err)
}
if info.LastErrorDate != 0 {
log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
//bot.Send(tgbotapi.NewMessage(79785025, info.LastErrorMessage))
}
// Start http server and listen for incoming data
updates := bot.ListenForWebhook("/")
go http.ListenAndServe("0.0.0.0:5505", nil)
// Process incoming messages
for update := range updates {
// Incoming message was nil - something wrong? - Log it
if update.Message == nil {
ms, _ := json.Marshal(update)
log.Println(string(ms))
continue
}
// Async start processing message
go processMessage(bot, update)
}
}
package main
import (
"strings"
"git.bella.network/playground/golang/internal/dnsfetch"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
func processMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update) {
// TODO: Track user statictics
// check if message was adressed to bot
if update.Message.IsCommand() {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "")
msg.ParseMode = "Markdown"
// Get list of arguments split by space
args := strings.Split(strings.TrimSpace(update.Message.CommandArguments()), " ")
switch strings.ToLower(update.Message.Command()) {
case "help", "bot", "man":
// On help disable notification and webpage preview to not annoy users
msg.DisableNotification = true
msg.DisableWebPagePreview = true
msg.Text = commandHelp()
case "dns":
if len(args) == 0 || len(args[0]) == 0 {
msg.Text = dnsfetch.GetHelp()
} else if len(args) == 1 {
msg.Text = dnsfetch.GetStringResponse(string(args[0]), "A")
} else {
// Detect if first place is a record type and reorder arguments if needed
if dnsfetch.IsRecordType(string(args[0])) {
msg.Text = dnsfetch.GetStringResponse(string(args[1]), string(args[0]))
} else {
msg.Text = dnsfetch.GetStringResponse(string(args[0]), string(args[1]))
}
}
case "hello":
msg.Text = "Hello World!"
default:
// We don't know this command - skip it
return
}
bot.Send(msg)
}
}
module git.bella.network/playground/golang
require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
)
package dnsfetch
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
"git.bella.network/playground/golang/pkg/securetls"
)
// DNSrecordValueToName contains a list of supported DNS records
var DNSrecordValueToName = map[int]string{
1: "A",
2: "NS",
5: "CNAME",
6: "SOA",
15: "MX",
16: "TXT",
28: "AAAA",
33: "SRV",
44: "SSHFP",
}
// contains a dynamically built list of supported records for simple comparision
var recordLookup = []string{}
// DNSresponse contains data of a dns response from cloudflare
type DNSresponse struct {
Status int `json:"Status"` // Response status
Truncated bool `json:"TC"` // DNS answer was larder than a single UDP or TCP packet
RecursiveDesired bool `json:"RD"` // Always true
RecursionAvailable bool `json:"RA"` // Always true
AnswerDNSSECVerified bool `json:"AD"` // Every record was verified with DNSSEC
ClientDNSSECDisabled bool `json:"CD"` // Client disabled DNSSEC validation
Question []struct {
Name string `json:"name"`
Type int `json:"type"`
} `json:"Question"`
Answer []struct {
Name string `json:"name"`
Type int `json:"type"`
TTL int `json:"TTL"`
Data string `json:"data"`
} `json:"Answer"`
}
var client *http.Client
func init() {
// Get TLS config
client = securetls.GetClient()
// Build record lookup table
for _, value := range DNSrecordValueToName {
recordLookup = append(recordLookup, value)
}
}
// IsRecordType checks if given string is a record type
func IsRecordType(data string) bool {
return contains(recordLookup, data)
}
// GetStringResponse fetches DNS from CF and returnes it as formatted string
func GetStringResponse(target string, record string) string {
start := time.Now()
record = strings.ToUpper(record)
data, err := fetch(target, record)
if err != nil {
return err.Error()
}
responseText := "Question: *" + data.Question[0].Name + "* IN *" + record + "*:\n"
for _, value := range data.Answer {
// Translate type to clear name
recordValueToName, err := DNSrecordValueToName[value.Type]
if !err {
recordValueToName = strconv.Itoa(value.Type)
}
// Print response
responseText += value.Name + " " + strconv.Itoa(value.TTL) + " IN " + recordValueToName + " " + value.Data + "\n"
}
responseText += "\nQuery time: " + strconv.FormatFloat(float64(time.Since(start)/time.Millisecond), 'f', 2, 64) + "ms"
return responseText
}
// GetHelp returns
func GetHelp() string {
help := "*DNS Query Command*\nThe command `/dns example.com` requests a basic `A` lookup to the given domain. The command can be extended with a record type like `/dns example.com AAAA`\n\n*Allowed records:*\n"
i := 0
for _, value := range DNSrecordValueToName {
if i != 0 {
help += ", "
}
help += value
i++
}
return help
}
// fetch fetches current DNS data from Cloudflare DoH
func fetch(target string, record string) (DNSresponse, error) {
// Chgeck if DNS type exists
if !contains(recordLookup, record) {
return DNSresponse{}, errors.New("Unsupported record type")
}
// Request lookup on cloudflare
req, _ := http.NewRequest("GET", "https://cloudflare-dns.com/dns-query?name="+target+"&type="+record, nil)
req.Header.Set("Accept", "application/dns-json")
response, err := client.Do(req)
if err != nil {
return DNSresponse{}, err
}
defer response.Body.Close()
// Read content of release.json into variable
// Limit file size to 8 MB
content, err := ioutil.ReadAll(io.LimitReader(response.Body, 8<<20))
if err != nil {
return DNSresponse{}, err
}
// Unmarshal response data
responseData := DNSresponse{}
err = json.Unmarshal(content, &responseData)
if err != nil {
return DNSresponse{}, err
}
// return data
return responseData, nil
}
func contains(sourceArray []string, search string) bool {
for _, part := range sourceArray {
if part == search {
return true
}
}
return false
}
package dnsfetch
import "testing"
func TestIsRecordType(t *testing.T) {
if !IsRecordType("A") {
t.Fail()
}
}
package securetls
import (
"crypto/tls"
"net/http"
"time"
)
// GetClient returns a preconfigured HTTP client
func GetClient() *http.Client {
return &http.Client{
Timeout: time.Second * 30,
Transport: &http.Transport{
MaxIdleConnsPerHost: 10,
TLSHandshakeTimeout: time.Second * 5,
TLSClientConfig: &tls.Config{
// Require a minimum of TLSv1.2
MinVersion: tls.VersionTLS12,
// Use strongest curves first
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP521, tls.CurveP384, tls.CurveP256},
// Sensitive data could be transferred
// Only allow very strong ciphers
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
},
},
}
}
#!/usr/bin/env bash
set -e
# Arguments this script was called
# First argument: Destination folder where builds are stored, when empty current folder is used
PRJ_ROOT=$1
if [ -z "${PRJ_ROOT}" ]; then
PRJ_ROOT="./"
fi
# Second argument: Location of main program, can be empty if folder itself or something like "cmd/program"
MAIN_PROG="./${2}"
# Current commit hash with 8 chars
COMMIT_HASH=$(git rev-parse --short=8 HEAD 2>/dev/null || true)
# Use current time as version
VERSION=$(date "+%Y%m%d%H%I%S")
# Alternatively use git tag
#VERSION=$(git describe --tags --dirty)
# Current date
BUILD_DATE=$(date "+%Y-%m-%d")
# Set project root if not already set
if [ "$PRJ_ROOT" == "" ]; then
PRJ_ROOT="$(pwd)"
fi
# Set current commit if not found with git tools
if [ "$COMMIT_HASH" == "" ]; then
COMMIT_HASH="00000000"
fi
# Go build arguments
GO_BUILD_CMD="go build -a -installsuffix cgo"
GO_BUILD_LDFLAGS="-s -w -X main.commitHash=${COMMIT_HASH} -X main.programVersion=${VERSION} -X main.buildDate=${BUILD_DATE}"
# Declare variable if not run from GitLab CI
if [[ -z "${CI_PROJECT_NAME}" ]]; then
CI_PROJECT_NAME="manualbuild"
fi
# List of available platforms and archs is available at https://github.com/golang/go/blob/master/src/go/build/syslist.go
# Most common values are: linux windows
# Multiple values separated with space
BUILD_PLATFORMS="linux"
# Most common values are: amd64 386 arm arm64
BUILD_ARCHS="amd64"
# Attention with ARM: There are multiple versions possible which sould be
# definied as GOARCH=X. X can be 5, 6 or 7.
# armel kernel uses ARMv5, armhf uses ARMv6+
# Examples for Raspberry: https://de.wikipedia.org/wiki/Raspberry_Pi#Raspberry_Pi
# Examples:
# Raspberry Pi Zero - 1 B+: ARMv6
# Raspberry Pi 2 B: ARMv7
# Raspberry Pi 3: ARMv8, but 32bit is used. So ARMv7
# Create target dir
mkdir -p "${PRJ_ROOT}"
for OS in ${BUILD_PLATFORMS[@]}; do
for ARCH in ${BUILD_ARCHS[@]}; do
NAME="${CI_PROJECT_NAME}-${OS}-${ARCH}"
if [[ "${OS}" == "windows" ]]; then
# Skip arm and arm64 if using windows
# TODO: windows/arm is currently in developement for Windows IoT Core: https://github.com/golang/go/issues/26148
if [[ "${ARCH}" == "arm" ]] || [[ "${ARCH}" == "arm64" ]]; then
continue
fi
# Windows has .exe files as executables
NAME="${NAME}.exe"
fi
echo "Building for ${OS}/${ARCH}"
GOARCH=${ARCH} GOOS=${OS} CGO_ENABLED=0 ${GO_BUILD_CMD} -ldflags "${GO_BUILD_LDFLAGS}"\
-o "${PRJ_ROOT}${NAME}" ${MAIN_PROG}
done
done
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment