mirror of
https://github.com/Xevion/go-ha.git
synced 2025-12-06 09:15:12 -06:00
checkin before rename
This commit is contained in:
147
app.go
Normal file
147
app.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
type App struct {
|
||||
Name string
|
||||
Schedules []Schedule
|
||||
Listeners []Listener
|
||||
}
|
||||
|
||||
type Schedule struct {
|
||||
/*
|
||||
RunEvery is a time.Duration representing how often you want to run your function.
|
||||
|
||||
Some examples:
|
||||
time.Second * 5 // runs every 5 seconds at 00:00:00, 00:00:05, etc.
|
||||
time.Hour * 12 // runs at BaseTime, +12 hours, +24 hours, etc.
|
||||
gomeassistant.Daily // runs at BaseTime, +24 hours, +48 hours, etc. Daily is a const helper for time.Hour * 24
|
||||
// Helpers include Daily, Hourly, Minutely
|
||||
*/
|
||||
RunEvery time.Duration
|
||||
/* Callback is the function you want to run. Takes zero arguments. */
|
||||
Callback func()
|
||||
/*
|
||||
BaseTime is 4 character string representing hours and minutes
|
||||
in a 24-hr format.
|
||||
It is the base that your RunEvery will be added to.
|
||||
Defaults to "0000" (which is probably fine for most cases).
|
||||
|
||||
Example: Run in the 3rd minute of every hour.
|
||||
Schedule{
|
||||
RunEvery: gomeassistant.Hourly // helper const for time.Hour
|
||||
BaseTime: "0003"
|
||||
}
|
||||
*/
|
||||
BaseTime string
|
||||
}
|
||||
|
||||
const (
|
||||
RunEveryMissing time.Duration = 0
|
||||
|
||||
Daily time.Duration = time.Hour * 24
|
||||
Hourly time.Duration = time.Hour
|
||||
Minutely time.Duration = time.Minute
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
}
|
||||
|
||||
const (
|
||||
_0000 string = "0000"
|
||||
_0015 string = "0015"
|
||||
_0030 string = "0030"
|
||||
_0045 string = "0045"
|
||||
_0100 string = "0100"
|
||||
_0115 string = "0115"
|
||||
_0130 string = "0130"
|
||||
_0145 string = "0145"
|
||||
_0200 string = "0200"
|
||||
_0215 string = "0215"
|
||||
_0230 string = "0230"
|
||||
_0245 string = "0245"
|
||||
_0300 string = "0300"
|
||||
_0315 string = "0315"
|
||||
_0330 string = "0330"
|
||||
_0345 string = "0345"
|
||||
_0400 string = "0400"
|
||||
_0415 string = "0415"
|
||||
_0430 string = "0430"
|
||||
_0445 string = "0445"
|
||||
_0500 string = "0500"
|
||||
_0515 string = "0515"
|
||||
_0530 string = "0530"
|
||||
_0545 string = "0545"
|
||||
_0600 string = "0600"
|
||||
_0615 string = "0615"
|
||||
_0630 string = "0630"
|
||||
_0645 string = "0645"
|
||||
_0700 string = "0700"
|
||||
_0715 string = "0715"
|
||||
_0730 string = "0730"
|
||||
_0745 string = "0745"
|
||||
_0800 string = "0800"
|
||||
_0815 string = "0815"
|
||||
_0830 string = "0830"
|
||||
_0845 string = "0845"
|
||||
_0900 string = "0900"
|
||||
_0915 string = "0915"
|
||||
_0930 string = "0930"
|
||||
_0945 string = "0945"
|
||||
_1000 string = "1000"
|
||||
_1015 string = "1015"
|
||||
_1030 string = "1030"
|
||||
_1045 string = "1045"
|
||||
_1100 string = "1100"
|
||||
_1115 string = "1115"
|
||||
_1130 string = "1130"
|
||||
_1145 string = "1145"
|
||||
_1200 string = "1200"
|
||||
_1215 string = "1215"
|
||||
_1230 string = "1230"
|
||||
_1245 string = "1245"
|
||||
_1300 string = "1300"
|
||||
_1315 string = "1315"
|
||||
_1330 string = "1330"
|
||||
_1345 string = "1345"
|
||||
_1400 string = "1400"
|
||||
_1415 string = "1415"
|
||||
_1430 string = "1430"
|
||||
_1445 string = "1445"
|
||||
_1500 string = "1500"
|
||||
_1515 string = "1515"
|
||||
_1530 string = "1530"
|
||||
_1545 string = "1545"
|
||||
_1600 string = "1600"
|
||||
_1615 string = "1615"
|
||||
_1630 string = "1630"
|
||||
_1645 string = "1645"
|
||||
_1700 string = "1700"
|
||||
_1715 string = "1715"
|
||||
_1730 string = "1730"
|
||||
_1745 string = "1745"
|
||||
_1800 string = "1800"
|
||||
_1815 string = "1815"
|
||||
_1830 string = "1830"
|
||||
_1845 string = "1845"
|
||||
_1900 string = "1900"
|
||||
_1915 string = "1915"
|
||||
_1930 string = "1930"
|
||||
_1945 string = "1945"
|
||||
_2000 string = "2000"
|
||||
_2015 string = "2015"
|
||||
_2030 string = "2030"
|
||||
_2045 string = "2045"
|
||||
_2100 string = "2100"
|
||||
_2115 string = "2115"
|
||||
_2130 string = "2130"
|
||||
_2145 string = "2145"
|
||||
_2200 string = "2200"
|
||||
_2215 string = "2215"
|
||||
_2230 string = "2230"
|
||||
_2245 string = "2245"
|
||||
_2300 string = "2300"
|
||||
_2315 string = "2315"
|
||||
_2330 string = "2330"
|
||||
_2345 string = "2345"
|
||||
)
|
||||
9
go.mod
Normal file
9
go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module github.com/saml-dev/gome-assistant
|
||||
|
||||
go 1.19
|
||||
|
||||
require nhooyr.io/websocket v1.8.7
|
||||
|
||||
require (
|
||||
github.com/klauspost/compress v1.10.3 // indirect
|
||||
)
|
||||
72
go.sum
Normal file
72
go.sum
Normal file
@@ -0,0 +1,72 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
|
||||
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b h1:NVD8gBK33xpdqCaZVVtd6OFJp+3dxkXuz7+U7KaVN6s=
|
||||
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
88
gomeassistant.go
Normal file
88
gomeassistant.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/saml-dev/gome-assistant/internal/network"
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
var c = context.Background()
|
||||
var b, x = context.WithTimeout(c, time.Second)
|
||||
|
||||
const a = time.April
|
||||
|
||||
var ctx, ctxCancel = context.WithTimeout(context.Background(), time.Second*5)
|
||||
var conn, _, err = websocket.Dial(ctx, "ws://192.168.86.67:8123/api/websocket", nil)
|
||||
|
||||
type Light struct {
|
||||
EntityId string
|
||||
}
|
||||
|
||||
type LightOnRequest struct {
|
||||
Id int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Domain string `json:"domain"`
|
||||
Service string `json:"service"`
|
||||
Target struct {
|
||||
EntityId string `json:"entity_id"`
|
||||
} `json:"target"`
|
||||
}
|
||||
|
||||
func NewLightOnRequest(entity_id string) LightOnRequest {
|
||||
req := LightOnRequest{
|
||||
Id: 5,
|
||||
Type: "call_service",
|
||||
Domain: "light",
|
||||
Service: "turn_on",
|
||||
}
|
||||
req.Target.EntityId = entity_id
|
||||
return req
|
||||
}
|
||||
|
||||
func (l *Light) TurnOn() error {
|
||||
// req := json.Marshal()
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
sched := Schedule{
|
||||
RunEvery: Daily,
|
||||
}
|
||||
defer ctxCancel()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close(websocket.StatusInternalError, "the sky is falling")
|
||||
// _, _, err = c.Reader(ctx)
|
||||
// if err != nil {
|
||||
// fmt.Println("err1")
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
msg, err := network.ReadMessage()
|
||||
if err != nil {
|
||||
fmt.Println("err2")
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(string(msg))
|
||||
|
||||
err = network.SendAuthMessage()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
msg, err = network.ReadMessage()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(string(msg))
|
||||
err = network.WriteMessage(NewLightOnRequest("group.living_room_lamps"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
conn.Close(websocket.StatusNormalClosure, "")
|
||||
}
|
||||
4
internal/network/light/light.go
Normal file
4
internal/network/light/light.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package light
|
||||
|
||||
type Light struct {
|
||||
}
|
||||
1
internal/network/service.go
Normal file
1
internal/network/service.go
Normal file
@@ -0,0 +1 @@
|
||||
package network
|
||||
55
internal/network/setup.go
Normal file
55
internal/network/setup.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
var ctx, ctxCancel = context.WithTimeout(context.Background(), time.Second*5)
|
||||
var conn, _, err = websocket.Dial(ctx, "ws://192.168.86.67:8123/api/websocket", nil)
|
||||
|
||||
type AuthMessage struct {
|
||||
MsgType string `json:"type"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
func SendAuthMessage() error {
|
||||
token := os.Getenv("AUTH_TOKEN")
|
||||
msgJson, err := json.Marshal(AuthMessage{MsgType: "auth", AccessToken: token})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conn.Write(ctx, websocket.MessageText, msgJson)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WriteMessage[T any](msg T) error {
|
||||
msgJson, err := json.Marshal(msg)
|
||||
fmt.Println(string(msgJson))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.Write(ctx, websocket.MessageText, msgJson)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadMessage() (string, error) {
|
||||
_, msg, err := conn.Read(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(msg), nil
|
||||
}
|
||||
Reference in New Issue
Block a user