mirror of
https://github.com/Unkn0wnCat/matrix-veles.git
synced 2025-04-28 17:56:49 +02:00
Create auth API & comment code
This commit is contained in:
parent
1b15b12859
commit
c8d1c33cb4
17 changed files with 475 additions and 101 deletions
|
@ -58,6 +58,9 @@ func init() {
|
|||
viper.SetDefault("bot.mongo.collection.entries", "entries")
|
||||
viper.SetDefault("bot.mongo.collection.lists", "lists")
|
||||
viper.SetDefault("bot.mongo.collection.rooms", "rooms")
|
||||
viper.SetDefault("bot.mongo.collection.users", "users")
|
||||
viper.SetDefault("bot.web.listen", "127.0.0.1:8123")
|
||||
viper.SetDefault("bot.web.secret", "hunter2")
|
||||
|
||||
cobra.OnInitialize(loadConfig)
|
||||
}
|
||||
|
@ -75,4 +78,8 @@ func loadConfig() {
|
|||
if err := viper.ReadInConfig(); err == nil {
|
||||
log.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
|
||||
if viper.GetString("bot.web.secret") == "hunter2" {
|
||||
log.Println("Web secret is not set! YOUR INSTALLATION IS INSECURE!")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package cmd
|
|||
import (
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/bot"
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/db"
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/web"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -33,6 +34,7 @@ var runCmd = &cobra.Command{
|
|||
The bot will log in to the homeserver and start posting updates to subscribed channels.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
db.Connect()
|
||||
go web.StartServer()
|
||||
bot.Run()
|
||||
},
|
||||
}
|
||||
|
|
7
go.mod
7
go.mod
|
@ -3,11 +3,12 @@ module github.com/Unkn0wnCat/matrix-veles
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7
|
||||
github.com/golang-jwt/jwt/v4 v4.3.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/spf13/viper v1.10.1
|
||||
go.mongodb.org/mongo-driver v1.8.3 // indirect
|
||||
go.mongodb.org/mongo-driver v1.8.3
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
golang.org/x/text v0.3.7
|
||||
maunium.net/go/mautrix v0.9.14
|
||||
)
|
||||
|
|
9
go.sum
9
go.sum
|
@ -133,6 +133,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
|||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
|
||||
github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -167,8 +169,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
|||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7 h1:oKYOfNR7Hp6XpZ4JqolL5u642Js5Z0n7psPVl+S5heo=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -182,6 +182,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
|
@ -208,6 +209,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
|
@ -372,6 +374,7 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
|
|||
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
||||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
|
@ -404,7 +407,6 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
|
|||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
@ -671,6 +673,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
|
|
@ -158,19 +158,20 @@ func handleMessageEvent(matrixClient *mautrix.Client, startTs int64) mautrix.Eve
|
|||
}
|
||||
|
||||
if content.URL != "" {
|
||||
handleHashing(content, evt, matrixClient)
|
||||
|
||||
// This has an attachment!
|
||||
handleHashing(content, evt, matrixClient) // -> handleHashing.go
|
||||
return
|
||||
}
|
||||
|
||||
// No attachment, is this a command?
|
||||
if !strings.HasPrefix(content.Body, "!"+username) &&
|
||||
!strings.HasPrefix(content.Body, "@"+username) &&
|
||||
!(strings.HasPrefix(content.Body, username) && strings.HasPrefix(content.FormattedBody, "<a href=\"https://matrix.to/#/"+matrixClient.UserID.String()+"\">")) {
|
||||
return
|
||||
}
|
||||
|
||||
handleCommand(content.Body, evt.Sender, evt.RoomID, matrixClient)
|
||||
|
||||
// It is a command!
|
||||
handleCommand(content.Body, evt.Sender, evt.RoomID, matrixClient) // -> commandParser.go
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,19 +8,19 @@ import (
|
|||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type StateEventPL struct {
|
||||
Type string `json:"type"`
|
||||
Sender string `json:"sender"`
|
||||
RoomID string `json:"room_id"`
|
||||
EventID string `json:"event_id"`
|
||||
OriginServerTS int64 `json:"origin_server_ts"`
|
||||
Content StateEventPLContent `json:"content"`
|
||||
type StateEventPowerLevel struct {
|
||||
Type string `json:"type"`
|
||||
Sender string `json:"sender"`
|
||||
RoomID string `json:"room_id"`
|
||||
EventID string `json:"event_id"`
|
||||
OriginServerTS int64 `json:"origin_server_ts"`
|
||||
Content StateEventPowerLevelContent `json:"content"`
|
||||
Unsigned struct {
|
||||
Age int `json:"age"`
|
||||
} `json:"unsigned"`
|
||||
}
|
||||
|
||||
type StateEventPLContent struct {
|
||||
type StateEventPowerLevelContent struct {
|
||||
Ban int `json:"ban"`
|
||||
Events map[string]int `json:"events"`
|
||||
EventsDefault int `json:"events_default"`
|
||||
|
@ -33,45 +33,51 @@ type StateEventPLContent struct {
|
|||
UsersDefault int `json:"users_default"`
|
||||
}
|
||||
|
||||
func GetRoomState(matrixClient *mautrix.Client, roomId id.RoomID) (*StateEventPLContent, error) {
|
||||
// GetRoomPowerLevelState returns the rooms current power levels from the state
|
||||
func GetRoomPowerLevelState(matrixClient *mautrix.Client, roomId id.RoomID) (*StateEventPowerLevelContent, error) {
|
||||
// https://matrix.example.com/_matrix/client/r0/rooms/<roomId.String()>/state
|
||||
url := matrixClient.BuildURL("rooms", roomId.String(), "state")
|
||||
|
||||
res, err := matrixClient.MakeRequest("GET", url, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ERROR: Could request room state - %v", err)
|
||||
}
|
||||
|
||||
var stateEvents []StateEventPL
|
||||
// res contains an array of state events
|
||||
var stateEvents []StateEventPowerLevel
|
||||
|
||||
err = json.Unmarshal(res, &stateEvents)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ERROR: Could parse room state - %v", err)
|
||||
}
|
||||
|
||||
var plEventContent StateEventPLContent
|
||||
// plEventContent will hold the final event
|
||||
var plEventContent StateEventPowerLevelContent
|
||||
|
||||
found := false
|
||||
|
||||
for _, e2 := range stateEvents {
|
||||
if e2.Type != event.StatePowerLevels.Type {
|
||||
continue
|
||||
continue // If the current event is not of the power level, skip.
|
||||
}
|
||||
|
||||
// This is what we're looking for!
|
||||
found = true
|
||||
plEventContent = e2.Content
|
||||
break
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("ERROR: Could find room power level - %v", err)
|
||||
}
|
||||
|
||||
// The following handle cases in which empty lists may have been parsed as nil
|
||||
if plEventContent.Events == nil {
|
||||
plEventContent.Events = make(map[string]int)
|
||||
}
|
||||
|
||||
if plEventContent.Notifications == nil {
|
||||
plEventContent.Notifications = make(map[string]int)
|
||||
}
|
||||
|
||||
if plEventContent.Users == nil {
|
||||
plEventContent.Users = make(map[string]int)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"maunium.net/go/mautrix/event"
|
||||
)
|
||||
|
||||
// handleHashing hashes and checks a message, taking configured actions on match
|
||||
func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixClient *mautrix.Client) {
|
||||
url, err := content.URL.Parse()
|
||||
if err != nil {
|
||||
|
@ -30,9 +31,7 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
|
|||
return
|
||||
}
|
||||
|
||||
defer func(reader io.ReadCloser) {
|
||||
_ = reader.Close()
|
||||
}(reader)
|
||||
defer func(reader io.ReadCloser) { _ = reader.Close() }(reader)
|
||||
|
||||
hashWriter := sha512.New()
|
||||
if _, err = io.Copy(hashWriter, reader); err != nil {
|
||||
|
@ -42,52 +41,51 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
|
|||
|
||||
sum := hex.EncodeToString(hashWriter.Sum(nil))
|
||||
|
||||
// Fetch room configuration for adjusting behaviour
|
||||
roomConfig := config.GetRoomConfig(evt.RoomID.String())
|
||||
|
||||
hashObj, err := db.GetEntryByHash(sum)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
/*entry := model.DBEntry{
|
||||
ID: primitive.NewObjectID(),
|
||||
HashValue: sum,
|
||||
FileURL: "placeholder",
|
||||
Timestamp: time.Now(),
|
||||
AddedBy: nil,
|
||||
Comments: nil,
|
||||
}
|
||||
|
||||
db.SaveEntry(&entry)*/
|
||||
|
||||
if roomConfig.Debug {
|
||||
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG - This file is not on the hashlist: %s", sum))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if roomConfig.Debug {
|
||||
matrixClient.SendNotice(evt.RoomID, "DEBUG - Failed to check file. See log.")
|
||||
}
|
||||
|
||||
fmt.Printf("Error trying to check database: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if roomConfig.Debug {
|
||||
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG !!! This file is on the hashlist: %s", sum))
|
||||
|
||||
jsonVal, _ := json.Marshal(hashObj)
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
json.Indent(&buf, jsonVal, "", " ")
|
||||
|
||||
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG:\n%s", buf.String()))
|
||||
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG:\n%s", makeFancyJSON(jsonVal)))
|
||||
}
|
||||
|
||||
if !checkSubscription(&roomConfig, hashObj) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Illegal content detected in room %s!", roomConfig.RoomID)
|
||||
|
||||
handleIllegalContent(evt, matrixClient, hashObj, roomConfig)
|
||||
}
|
||||
|
||||
// makeFancyJSON formats / indents a JSON string
|
||||
func makeFancyJSON(input []byte) string {
|
||||
var buf bytes.Buffer
|
||||
json.Indent(&buf, input, "", " ")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// checkSubscription checks if the room is subscribed to one of hashObjs lists
|
||||
func checkSubscription(roomConfig *config.RoomConfig, hashObj *model.DBEntry) bool {
|
||||
if roomConfig.HashChecker.SubscribedLists == nil {
|
||||
log.Printf("Room %s is not subscribed to any lists!", roomConfig.RoomID)
|
||||
return // Not subscribed to any lists
|
||||
return false // Not subscribed to any lists
|
||||
}
|
||||
|
||||
subMap := make(map[string]bool)
|
||||
|
@ -110,14 +108,42 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
|
|||
|
||||
if !found {
|
||||
log.Printf("Room %s is not subscribed to any lists of hashobj %s!", roomConfig.RoomID, hashObj.ID.Hex())
|
||||
return // Not subscribed
|
||||
return false // Not subscribed
|
||||
}
|
||||
|
||||
log.Printf("Illegal content detected in room %s!", roomConfig.RoomID)
|
||||
|
||||
handleIllegalContent(evt, matrixClient, hashObj, roomConfig)
|
||||
return true
|
||||
}
|
||||
|
||||
// handleIllegalContent is called when a hash-match is found to take configured actions
|
||||
func handleIllegalContent(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
|
||||
switch roomConfig.HashChecker.HashCheckMode {
|
||||
case 0:
|
||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||
break
|
||||
case 1:
|
||||
redactMessage(evt, matrixClient, hashObj)
|
||||
if roomConfig.HashChecker.NoticeToChat {
|
||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
muteUser(evt, matrixClient)
|
||||
redactMessage(evt, matrixClient, hashObj)
|
||||
if roomConfig.HashChecker.NoticeToChat {
|
||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||
}
|
||||
break
|
||||
case 3:
|
||||
banUser(evt, matrixClient, hashObj)
|
||||
redactMessage(evt, matrixClient, hashObj)
|
||||
if roomConfig.HashChecker.NoticeToChat {
|
||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// redactMessage deletes the message sent in the given event
|
||||
func redactMessage(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
|
||||
opts := mautrix.ReqRedact{Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex())}
|
||||
_, err := matrixClient.RedactEvent(evt.RoomID, evt.ID, opts)
|
||||
|
@ -126,8 +152,9 @@ func redactMessage(evt *event.Event, matrixClient *mautrix.Client, hashObj *mode
|
|||
}
|
||||
}
|
||||
|
||||
func muteUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
|
||||
plEventContent, err := GetRoomState(matrixClient, evt.RoomID)
|
||||
// muteUser sets a users power-level to -1 to prevent them from sending messages
|
||||
func muteUser(evt *event.Event, matrixClient *mautrix.Client) {
|
||||
plEventContent, err := GetRoomPowerLevelState(matrixClient, evt.RoomID)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Could mute user - %v", err)
|
||||
return
|
||||
|
@ -140,9 +167,9 @@ func muteUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBE
|
|||
log.Printf("ERROR: Could mute user - %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// banUser bans the sender of an event from the room
|
||||
func banUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
|
||||
req := mautrix.ReqBanUser{
|
||||
Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex()),
|
||||
|
@ -152,6 +179,7 @@ func banUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEn
|
|||
matrixClient.BanUser(evt.RoomID, &req)
|
||||
}
|
||||
|
||||
// postNotice posts a notice about the given event into its room
|
||||
func postNotice(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
|
||||
local, server, err := evt.Sender.Parse()
|
||||
if err != nil {
|
||||
|
@ -172,32 +200,3 @@ If you believe this action was an accident, please contact an room administrator
|
|||
SendAlert(matrixClient, evt.RoomID.String(), fmt.Sprintf(
|
||||
`Veles Triggered: The message by %s (on %s) was flagged for containing material used by spammers or trolls! (Reference: %s)`, local, server, hashObj.ID.Hex()))
|
||||
}*/
|
||||
|
||||
func handleIllegalContent(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
|
||||
switch roomConfig.HashChecker.HashCheckMode {
|
||||
case 0:
|
||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||
break
|
||||
case 1:
|
||||
redactMessage(evt, matrixClient, hashObj)
|
||||
if roomConfig.HashChecker.NoticeToChat {
|
||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
muteUser(evt, matrixClient, hashObj)
|
||||
redactMessage(evt, matrixClient, hashObj)
|
||||
if roomConfig.HashChecker.NoticeToChat {
|
||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||
}
|
||||
break
|
||||
case 3:
|
||||
banUser(evt, matrixClient, hashObj)
|
||||
redactMessage(evt, matrixClient, hashObj)
|
||||
if roomConfig.HashChecker.NoticeToChat {
|
||||
postNotice(evt, matrixClient, hashObj, roomConfig)
|
||||
}
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ package bot
|
|||
}
|
||||
|
||||
if roomConfig.AlertChannel == nil {
|
||||
roomPLState, err := GetRoomState(matrixClient, id.RoomID(room))
|
||||
roomPLState, err := GetRoomPowerLevelState(matrixClient, id.RoomID(room))
|
||||
if err != nil {
|
||||
log.Printf("Failed to get room power levels - %v", err)
|
||||
return
|
||||
|
|
|
@ -35,16 +35,21 @@ type RoomConfig struct {
|
|||
// Debug specifies if the bot shall run in dry run mode
|
||||
Debug bool `yaml:"debug" bson:"debug"`
|
||||
|
||||
// AlertChannel is currently unused
|
||||
AlertChannel *string `bson:"alert_channel"`
|
||||
|
||||
// AdminPowerLevel specifies the power-level a user has to have to manage the room
|
||||
AdminPowerLevel int `bson:"admin_power_level"`
|
||||
|
||||
// HashChecker contains configuration specific to the hash-checker
|
||||
HashChecker HashCheckerConfig `bson:"hash_checker"`
|
||||
}
|
||||
|
||||
type HashCheckerConfig struct {
|
||||
// NoticeToChat specifies weather or not to post a public notice to chat
|
||||
NoticeToChat bool `bson:"chat_notice"`
|
||||
|
||||
// NotificationPowerLevel is currently unused
|
||||
NotificationPowerLevel int `yaml:"notification_level" bson:"notification_level"`
|
||||
|
||||
/*
|
||||
|
@ -58,5 +63,6 @@ type HashCheckerConfig struct {
|
|||
*/
|
||||
HashCheckMode uint8 `yaml:"mode" bson:"hash_check_mode"`
|
||||
|
||||
// SubscribedLists contains the lists this room is subscribed to
|
||||
SubscribedLists []*primitive.ObjectID `bson:"subscribed_lists" json:"subscribed_lists"`
|
||||
}
|
||||
|
|
|
@ -108,3 +108,51 @@ func GetListByID(id primitive.ObjectID) (*model.DBHashList, error) {
|
|||
|
||||
return &object, nil
|
||||
}
|
||||
|
||||
func SaveUser(user *model.DBUser) error {
|
||||
db := DbClient.Database(viper.GetString("bot.mongo.database"))
|
||||
|
||||
opts := options.Replace().SetUpsert(true)
|
||||
|
||||
filter := bson.D{{"_id", user.ID}}
|
||||
|
||||
_, err := db.Collection(viper.GetString("bot.mongo.collection.users")).ReplaceOne(context.TODO(), filter, user, opts)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetUserByID(id primitive.ObjectID) (*model.DBUser, error) {
|
||||
db := DbClient.Database(viper.GetString("bot.mongo.database"))
|
||||
|
||||
res := db.Collection(viper.GetString("bot.mongo.collection.users")).FindOne(context.TODO(), bson.D{{"_id", id}})
|
||||
if res.Err() != nil {
|
||||
return nil, res.Err()
|
||||
}
|
||||
|
||||
object := model.DBUser{}
|
||||
|
||||
err := res.Decode(&object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &object, nil
|
||||
}
|
||||
|
||||
func GetUserByUsername(username string) (*model.DBUser, error) {
|
||||
db := DbClient.Database(viper.GetString("bot.mongo.database"))
|
||||
|
||||
res := db.Collection(viper.GetString("bot.mongo.collection.users")).FindOne(context.TODO(), bson.D{{"username", username}})
|
||||
if res.Err() != nil {
|
||||
return nil, res.Err()
|
||||
}
|
||||
|
||||
object := model.DBUser{}
|
||||
|
||||
err := res.Decode(&object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &object, nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@ package model
|
|||
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
type DBComment struct {
|
||||
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"`
|
||||
Content string `bson:"content" json:"content"`
|
||||
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"` // CommentedBy contains a reference to the user who commented
|
||||
Content string `bson:"content" json:"content"` // Content is the body of the comment
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ import (
|
|||
|
||||
type DBEntry struct {
|
||||
ID primitive.ObjectID `bson:"_id" json:"id"`
|
||||
Tags []string `bson:"tags" json:"tags"`
|
||||
PartOf []*primitive.ObjectID `bson:"part_of" json:"part_of"`
|
||||
HashValue string `bson:"hash_value" json:"hash"`
|
||||
FileURL string `bson:"file_url" json:"file_url"`
|
||||
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
|
||||
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"`
|
||||
Comments []*DBComment `bson:"comments" json:"comments"`
|
||||
Tags []string `bson:"tags" json:"tags"` // Tags used for searching entries and ordering
|
||||
PartOf []*primitive.ObjectID `bson:"part_of" json:"part_of"` // PartOf specifies the lists this entry is part of
|
||||
HashValue string `bson:"hash_value" json:"hash"` // HashValue is the SHA512-hash of the file
|
||||
FileURL string `bson:"file_url" json:"file_url"` // FileURL may be set to a file link
|
||||
Timestamp time.Time `bson:"timestamp" json:"timestamp"` // Timestamp of when this entry was added
|
||||
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"` // AddedBy is a reference to the user who added this
|
||||
Comments []*DBComment `bson:"comments" json:"comments"` // Comments regarding this entry
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import "go.mongodb.org/mongo-driver/bson/primitive"
|
|||
|
||||
type DBHashList struct {
|
||||
ID primitive.ObjectID `bson:"_id" json:"id"`
|
||||
Tags []string `bson:"tags" json:"tags"`
|
||||
Comments []*DBComment `bson:"comments" json:"comments"`
|
||||
Maintainers []*primitive.ObjectID `bson:"maintainers" json:"maintainers"`
|
||||
Tags []string `bson:"tags" json:"tags"` // Tags of this list for discovery, and sorting
|
||||
Comments []*DBComment `bson:"comments" json:"comments"` // Comments regarding this list
|
||||
Maintainers []*primitive.ObjectID `bson:"maintainers" json:"maintainers"` // Maintainers contains references to the users who may edit this list
|
||||
}
|
||||
|
|
|
@ -2,14 +2,20 @@ package model
|
|||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type DBUser struct {
|
||||
Username string `bson:"username" json:"username"`
|
||||
HashedPassword string `bson:"password" json:"password"`
|
||||
ID primitive.ObjectID `bson:"_id" json:"id"`
|
||||
|
||||
Password *string `bson:"-" json:"-"`
|
||||
Username string `bson:"username" json:"username"` // Username is the username the user has
|
||||
HashedPassword string `bson:"password" json:"password"` // HashedPassword contains the bcrypt-ed password
|
||||
|
||||
MatrixLinks []*string `bson:"matrix_links" json:"matrix_links"` // MatrixLinks is the matrix-users this user has verified ownership over
|
||||
PendingMatrixLinks []*string `bson:"pending_matrix_links" json:"pending_matrix_links"` // PendingMatrixLinks is the matrix-users pending verification
|
||||
|
||||
Password *string `bson:"-" json:"-"` // Password may never be sent out!
|
||||
}
|
||||
|
||||
func (usr *DBUser) HashPassword() error {
|
||||
|
@ -23,6 +29,7 @@ func (usr *DBUser) HashPassword() error {
|
|||
}
|
||||
|
||||
usr.HashedPassword = base64.StdEncoding.EncodeToString(hash)
|
||||
usr.Password = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
86
internal/web/api/api.go
Normal file
86
internal/web/api/api.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SetupAPI(router *mux.Router) {
|
||||
router.NotFoundHandler = NotFoundHandler{}
|
||||
router.MethodNotAllowedHandler = MethodNotAllowedHandler{}
|
||||
|
||||
router.Path("/auth/login").Methods("POST").HandlerFunc(apiHandleAuthLogin)
|
||||
router.Path("/auth/register").Methods("POST").HandlerFunc(apiHandleAuthRegister)
|
||||
|
||||
bot := router.PathPrefix("/bot").Subrouter()
|
||||
bot.Use(checkAuthMiddleware)
|
||||
|
||||
bot.Path("/test").HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(200)
|
||||
|
||||
claims := request.Context().Value("claims").(jwtClaims)
|
||||
|
||||
writer.Write([]byte(`hello ` + claims.Username))
|
||||
})
|
||||
}
|
||||
|
||||
func checkAuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
token := req.Header.Get("Authorization")
|
||||
tokenSplit := strings.Split(token, " ")
|
||||
|
||||
if token == "" || len(tokenSplit) < 2 {
|
||||
writeJSONError(res, http.StatusUnauthorized, errors.New("bearer token required"))
|
||||
return
|
||||
}
|
||||
|
||||
token = tokenSplit[1]
|
||||
|
||||
claims, _, err := parseToken(token)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid token"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(req.Context(), "claims", *claims)
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
next.ServeHTTP(res, req)
|
||||
})
|
||||
}
|
||||
|
||||
func writeJSONError(res http.ResponseWriter, statusCode int, err error) {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(statusCode)
|
||||
|
||||
enc, _ := json.Marshal(struct {
|
||||
Error string `json:"error"`
|
||||
ErrorCode int `json:"error_code"`
|
||||
}{
|
||||
Error: err.Error(),
|
||||
ErrorCode: statusCode,
|
||||
})
|
||||
|
||||
_, _ = res.Write(enc)
|
||||
}
|
||||
|
||||
type NotFoundHandler struct{}
|
||||
|
||||
func (NotFoundHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusNotFound)
|
||||
_, _ = res.Write([]byte(`{"error": "not_found","error_code":404}`))
|
||||
}
|
||||
|
||||
type MethodNotAllowedHandler struct{}
|
||||
|
||||
func (MethodNotAllowedHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||
_, _ = res.Write([]byte(`{"error": "method_not_allowed","error_code":405}`))
|
||||
}
|
173
internal/web/api/auth.go
Normal file
173
internal/web/api/auth.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/db"
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/db/model"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/spf13/viper"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type apiAuthRequestBody struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type jwtClaims struct {
|
||||
Username string `json:"username"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func parseToken(tokenString string) (*jwtClaims, *jwt.Token, error) {
|
||||
claims := jwtClaims{}
|
||||
jwtSigningKey := []byte(viper.GetString("bot.web.secret"))
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
|
||||
return jwtSigningKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &claims, token, nil
|
||||
}
|
||||
|
||||
func apiHandleAuthLogin(res http.ResponseWriter, req *http.Request) {
|
||||
body := req.Body
|
||||
|
||||
bodyContent := apiAuthRequestBody{}
|
||||
|
||||
err := json.NewDecoder(body).Decode(&bodyContent)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusBadRequest, errors.New("malformed body"))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := db.GetUserByUsername(bodyContent.Username)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid credentials"))
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
|
||||
err = user.CheckPassword(bodyContent.Password)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid credentials"))
|
||||
return
|
||||
}
|
||||
|
||||
jwtSigningKey := []byte(viper.GetString("bot.web.secret"))
|
||||
|
||||
claims := jwtClaims{
|
||||
Username: user.Username,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 365 * 100)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: "veles-api",
|
||||
Subject: user.ID.Hex(),
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
ss, err := token.SignedString(jwtSigningKey)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to create token"))
|
||||
return
|
||||
}
|
||||
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
|
||||
enc, err := json.Marshal(struct {
|
||||
Token string `json:"token"`
|
||||
}{
|
||||
Token: ss,
|
||||
})
|
||||
|
||||
_, _ = res.Write(enc)
|
||||
}
|
||||
|
||||
type apiAuthRegisterBody struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func apiHandleAuthRegister(res http.ResponseWriter, req *http.Request) {
|
||||
body := req.Body
|
||||
|
||||
bodyContent := apiAuthRegisterBody{}
|
||||
|
||||
err := json.NewDecoder(body).Decode(&bodyContent)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusBadRequest, errors.New("malformed body"))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = db.GetUserByUsername(bodyContent.Username)
|
||||
if err == nil {
|
||||
writeJSONError(res, http.StatusBadRequest, errors.New("username taken"))
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, mongo.ErrNoDocuments) {
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
|
||||
user := model.DBUser{
|
||||
ID: primitive.NewObjectID(),
|
||||
Username: bodyContent.Username,
|
||||
Password: &bodyContent.Password,
|
||||
}
|
||||
|
||||
err = user.HashPassword()
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to hash password"))
|
||||
return
|
||||
}
|
||||
|
||||
err = db.SaveUser(&user)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
|
||||
jwtSigningKey := viper.GetString("bot.web.secret")
|
||||
|
||||
claims := jwtClaims{
|
||||
Username: user.Username,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: "veles-api",
|
||||
Subject: user.ID.Hex(),
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
ss, err := token.SignedString(jwtSigningKey)
|
||||
if err != nil {
|
||||
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to create token"))
|
||||
}
|
||||
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
|
||||
enc, err := json.Marshal(struct {
|
||||
Token string `json:"token"`
|
||||
}{
|
||||
Token: ss,
|
||||
})
|
||||
|
||||
_, _ = res.Write(enc)
|
||||
}
|
35
internal/web/web.go
Normal file
35
internal/web/web.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"github.com/Unkn0wnCat/matrix-veles/internal/web/api"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func StartServer() {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", HomeHandler)
|
||||
//r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
|
||||
|
||||
apiRouter := r.PathPrefix("/api").Subrouter()
|
||||
api.SetupAPI(apiRouter)
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: r,
|
||||
Addr: viper.GetString("bot.web.listen"),
|
||||
WriteTimeout: 15 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
log.Printf("Now serving web-interface on http://%s", viper.GetString("bot.web.listen"))
|
||||
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func HomeHandler(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(200)
|
||||
writer.Write([]byte("hello world"))
|
||||
}
|
Loading…
Add table
Reference in a new issue