initial work

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Vasiliy Tolstov 2022-08-12 01:12:24 +03:00
commit 2ef3e1fc8d
12 changed files with 1100 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.DS_Store
/app/chaturbate/chaturbate
/app/bongacams/bongacams
test/test

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 poiuty
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

118
app/cmd.go Normal file
View file

@ -0,0 +1,118 @@
package main
import (
"fmt"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"time"
)
var memInfo runtime.MemStats
type Info struct {
ch chan struct{}
room string
Server string `json:"server"`
Proxy string `json:"proxy"`
Online string `json:"online"`
Rid int64 `json:"rid"`
Start int64 `json:"start"`
Last int64 `json:"last"`
Income int64 `json:"income"`
Dons int64 `json:"dons"`
Tips int64 `json:"tips"`
}
func updateFileRooms() string {
for {
rooms.Json <- ""
s := <-rooms.Json
err := os.WriteFile(conf.Conn["start"], []byte(s), 0644)
if err != nil {
fmt.Println(err)
}
time.Sleep(10 * time.Second)
}
}
func listHandler(w http.ResponseWriter, _ *http.Request) {
dat, err := os.ReadFile(conf.Conn["start"])
if err != nil {
fmt.Println(err)
return
}
fmt.Fprint(w, string(dat))
}
func debugHandler(w http.ResponseWriter, _ *http.Request) {
ws.Count <- 0
l := <-ws.Count
runtime.ReadMemStats(&memInfo)
j, err := json.Marshal(struct {
Goroutines int
WebSocket int
Uptime int64
Alloc uint64
HeapSys uint64
}{
Goroutines: runtime.NumGoroutine(),
Alloc: memInfo.Alloc,
HeapSys: memInfo.HeapSys,
Uptime: uptime,
WebSocket: l,
})
if err == nil {
fmt.Fprint(w, string(j))
}
}
func cmdHandler(w http.ResponseWriter, r *http.Request) {
if !conf.List[r.Header.Get("X-REAL-IP")] {
fmt.Fprint(w, "403")
return
}
params := r.URL.Query()
if len(params["room"]) > 0 && len(params["server"]) > 0 && len(params["proxy"]) > 0 {
now := time.Now().Unix()
workerData := Info{
room: params["room"][0],
Server: params["server"][0],
Proxy: params["proxy"][0],
Online: "0",
Start: now,
Last: now,
Rid: 0,
Income: 0,
Dons: 0,
Tips: 0,
}
startRoom(workerData)
}
if len(params["exit"]) > 0 {
rooms.Stop <- strings.Join(params["exit"], "")
}
fmt.Fprint(w, string("ok"))
}
func startRoom(workerData Info) {
rooms.Check <- workerData.room
testRoom := <-rooms.Check
if testRoom == workerData.room {
fmt.Println("Already track:", workerData.room)
return
}
rid, ok := getRoomInfo(workerData.room)
if !ok {
fmt.Println("No room in MySQL:", workerData.room)
return
}
workerData.Rid = rid
workerData.ch = make(chan struct{})
go xWorker(workerData, url.URL{Scheme: "wss", Host: workerData.Server + ".bcccdn.com", Path: "/websocket"})
}

34
app/conf.go Normal file
View file

@ -0,0 +1,34 @@
package main
type Conf struct {
Conn map[string]string
Proxy map[string]string
List map[string]bool
}
var conf = &Conf{
Conn: make(map[string]string),
Proxy: make(map[string]string),
List: make(map[string]bool),
}
func startConfig() {
// DB
conf.Conn = map[string]string{
"mysql": "user:passwd@unix(/var/run/mysqld/mysqld.sock)/base?interpolateParams=true",
"click": "tcp://127.0.0.1:9000/base?compress=true&debug=false",
"start": "/tmp/bongaStart.txt",
}
// 3proxy
conf.Proxy = map[string]string{
"us": "ip:port",
"fi": "ip:port",
}
// allow ips
conf.List = map[string]bool{
"::1": true,
"127.0.0.1": true,
}
}

49
app/echo.go Normal file
View file

@ -0,0 +1,49 @@
package main
import (
"net/http"
"github.com/gorilla/websocket"
)
var (
wsClients = make(map[*websocket.Conn]struct{})
ws = struct {
Count chan int
Send chan []byte
Add chan *websocket.Conn
}{
Count: make(chan int, 100),
Send: make(chan []byte, 100),
Add: make(chan *websocket.Conn, 100),
}
)
func broadcast() {
for {
select {
case conn := <-ws.Add:
wsClients[conn] = struct{}{}
case <-ws.Count:
ws.Count <- len(wsClients)
case message := <-ws.Send:
for conn := range wsClients {
if err := conn.WriteMessage(1, message); err != nil {
conn.Close()
delete(wsClients, conn)
}
}
}
}
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
if err != nil {
return
}
ws.Add <- conn
}

132
app/main.go Normal file
View file

@ -0,0 +1,132 @@
package main
import (
"fmt"
"log"
"math/rand"
"net"
"net/http"
"os"
"time"
_ "github.com/ClickHouse/clickhouse-go"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
jsoniter "github.com/json-iterator/go"
)
type Rooms struct {
Count chan int
Json chan string
Check chan string
Stop chan string
Del chan string
Add chan Info
}
var (
Mysql, Clickhouse *sqlx.DB
json = jsoniter.ConfigCompatibleWithStandardLibrary
save = make(chan saveData, 100)
slog = make(chan saveLog, 100)
rooms = &Rooms{
Count: make(chan int),
Json: make(chan string),
Check: make(chan string),
Stop: make(chan string),
Del: make(chan string),
Add: make(chan Info),
}
)
func main() {
rand.Seed(time.Now().UnixNano())
startConfig()
initMysql()
initClickhouse()
go mapRooms()
go announceCount()
go saveDB()
go saveLogs()
go broadcast()
http.HandleFunc("/bongacams/ws/", wsHandler)
http.HandleFunc("/bongacams/cmd/", cmdHandler)
http.HandleFunc("/bongacams/list/", listHandler)
http.HandleFunc("/bongacams/debug/", debugHandler)
go fastStart()
const SOCK = "/tmp/bongacams.sock"
os.Remove(SOCK)
unixListener, err := net.Listen("unix", SOCK)
if err != nil {
log.Fatal("Listen (UNIX socket): ", err)
}
defer unixListener.Close()
os.Chmod(SOCK, 0777)
log.Fatal(http.Serve(unixListener, nil))
}
func initMysql() {
db, err := sqlx.Connect("mysql", conf.Conn["mysql"])
if err != nil {
panic(err)
}
Mysql = db
}
func initClickhouse() {
db, err := sqlx.Connect("clickhouse", conf.Conn["click"])
if err != nil {
panic(err)
}
Clickhouse = db
}
func randInt(min int, max int) int {
return min + rand.Intn(max-min)
}
func fastStart() {
defer func() {
go updateFileRooms()
}()
dat, err := os.ReadFile(conf.Conn["start"])
if err != nil {
fmt.Println(err)
return
}
list := make(map[string]Info)
if err := json.Unmarshal(dat, &list); err != nil {
fmt.Println(err.Error())
return
}
now := time.Now().Unix()
for k, v := range list {
if now > v.Last+60*20 {
continue
}
fmt.Println("fastStart:", k, v.Server, v.Proxy)
workerData := Info{
room: k,
Server: v.Server,
Proxy: v.Proxy,
Online: v.Online,
Start: v.Start,
Last: now,
Rid: v.Rid,
Income: v.Income,
Dons: v.Dons,
Tips: v.Tips,
}
startRoom(workerData)
time.Sleep(2 * time.Second)
}
}

193
app/save.go Normal file
View file

@ -0,0 +1,193 @@
package main
import (
"fmt"
"time"
)
type saveData struct {
Room string
From string
Rid int64
Amount int64
Now int64
}
type saveLog struct {
Rid int64
Now int64
Mes string
}
type DonatorCache struct {
Id int64
Last int64
}
func getDonId(name string) int64 {
var id int64
err := Mysql.Get(&id, "SELECT id FROM donator WHERE name=?", name)
if err != nil {
res, _ := Mysql.Exec("INSERT INTO donator (`name`) VALUES (?)", name)
id, _ = res.LastInsertId()
}
return id
}
func getRoomInfo(name string) (int64, bool) {
var id int64
result := true
err := Mysql.Get(&id, "SELECT id FROM room WHERE name=?", name)
if err != nil {
result = false
}
return id, result
}
func getSumTokens() int64 {
r := struct {
Date string
Sum int64
}{}
err := Clickhouse.Get(&r, "SELECT toStartOfHour(toDateTime(`unix`)) as date, SUM(`token`) as sum FROM `stat` WHERE time = today() GROUP BY date ORDER BY date DESC LIMIT 1")
if err == nil && r.Sum > 0 {
return r.Sum
}
return 0
}
func saveDB() {
hours, _, _ := time.Now().Clock()
bulk := make(map[int]saveData)
update := make(map[int64]int64)
data := make(map[string]*DonatorCache)
index := make(map[string]int64)
index = map[string]int64{"hours": int64(hours), "tokens": getSumTokens(), "last": time.Now().Unix()}
for {
select {
case m := <-save:
//fmt.Println("Save channel:", len(save), cap(save))
now := time.Now().Unix()
if _, ok := data[m.From]; ok {
data[m.From].Last = now
} else {
data[m.From] = &DonatorCache{Id: getDonId(m.From), Last: now}
}
num := len(bulk)
bulk[num] = m
update[m.Rid] = m.Now
if num > 512 {
tx, err := Mysql.Begin()
if err == nil {
st, _ := tx.Prepare("INSERT INTO `stat` (`did`, `rid`, `token`, `time`) VALUES (?, ?, ?, ?)")
for _, v := range bulk {
st.Exec(data[v.From].Id, v.Rid, v.Amount, v.Now)
}
tx.Commit()
st.Close()
}
tx, err = Mysql.Begin()
if err == nil {
st, _ := tx.Prepare("UPDATE `room` SET `last` = ? WHERE `id` = ?")
for k, v := range update {
st.Exec(v, k)
}
tx.Commit()
st.Close()
}
tx, err = Clickhouse.Begin()
if err == nil {
st, _ := tx.Prepare("INSERT INTO stat VALUES (?, ?, ?, ?, ?)")
for _, v := range bulk {
st.Exec(uint32(data[v.From].Id), uint32(v.Rid), uint32(v.Amount), time.Unix(v.Now, 0), uint32(v.Now))
}
tx.Commit()
st.Close()
}
bulk = make(map[int]saveData)
update = make(map[int64]int64)
}
if m.Amount > 99 {
msg, err := json.Marshal(struct {
Room string `json:"room"`
Donator string `json:"donator"`
Amount int64 `json:"amount"`
}{
Room: m.Room,
Donator: m.From,
Amount: m.Amount,
})
if err == nil {
ws.Send <- msg
}
}
hours, minutes, seconds := time.Now().Clock()
if int64(hours) == index["hours"] {
index["tokens"] += m.Amount
} else {
index = map[string]int64{"hours": int64(hours), "tokens": 0, "last": 0}
}
if minutes >= 5 && now > index["last"]+30 {
seconds += minutes * 60
msg, err := json.Marshal(struct {
Index int64 `json:"index"`
}{Index: index["tokens"] / int64(seconds) * 3600 / 1000 * 5 / 100})
if err == nil {
ws.Send <- msg
}
index["last"] = now
}
if randInt(0, 10000) == 777 { // 0.001%
l := len(data)
for k, v := range data {
if now > v.Last+60*60*48 {
delete(data, k)
}
}
fmt.Println("Clean map:", l, "=>", len(data))
}
}
}
}
func saveLogs() {
bulk := make(map[int]saveLog)
for {
select {
case m := <-slog:
if len(m.Mes) > 0 {
num := len(bulk)
bulk[num] = m
if num > 2048 {
tx, err := Mysql.Begin()
if err == nil {
st, _ := tx.Prepare("INSERT INTO `logs` (`rid`, `time`, `mes`) VALUES (?, ?, ?)")
for _, v := range bulk {
st.Exec(v.Rid, v.Now, v.Mes)
}
tx.Commit()
st.Close()
}
bulk = make(map[int]saveLog)
}
}
}
}
}

276
app/worker.go Normal file
View file

@ -0,0 +1,276 @@
package main
import (
"fmt"
"github.com/gorilla/websocket"
jsoniter "github.com/json-iterator/go"
"net/http"
"net/url"
"strings"
"time"
)
var uptime = time.Now().Unix()
type AuthResponse struct {
Status string `json:"status"`
LocalData struct {
DataKey string `json:"dataKey"`
} `json:"localData"`
UserData struct {
Username string `json:"username"`
DisplayName string `json:"displayName"`
Location string `json:"location"`
Chathost string `json:"chathost"`
IsRu bool `json:"isRu"`
} `json:"userData"`
}
type ServerResponse struct {
TS int64 `json:"ts"`
Type string `json:"type"`
Body jsoniter.RawMessage `json:"body"`
}
type DonateResponse struct {
F struct {
Username string `json:"username"`
} `json:"f"`
A int64 `json:"a"`
}
func mapRooms() {
data := make(map[string]*Info)
for {
select {
case m := <-rooms.Add:
data[m.room] = &Info{Server: m.Server, Proxy: m.Proxy, Start: m.Start, Last: m.Last, Online: m.Online, Income: m.Income, Dons: m.Dons, Tips: m.Tips, ch: m.ch}
case s := <-rooms.Json:
j, err := json.Marshal(data)
if err == nil {
s = string(j)
}
rooms.Json <- s
case <-rooms.Count:
rooms.Count <- len(data)
case key := <-rooms.Del:
delete(data, key)
case room := <-rooms.Check:
if _, ok := data[room]; !ok {
room = ""
}
rooms.Check <- room
case room := <-rooms.Stop:
if _, ok := data[room]; ok {
close(data[room].ch)
}
}
}
}
func announceCount() {
for {
time.Sleep(30 * time.Second)
rooms.Count <- 0
l := <-rooms.Count
msg, err := json.Marshal(struct {
Count int `json:"count"`
}{Count: l})
if err == nil {
ws.Send <- msg
}
}
}
func getAMF(room string) (bool, *AuthResponse) {
v := &AuthResponse{}
req, err := http.NewRequest(http.MethodPost, "https://rt.bongocams.com/tools/amf.php?res=771840&t=1654437233142", strings.NewReader(`method=getRoomData&args[]=`+room))
if err != nil {
fmt.Println(err.Error())
return false, v
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Add("X-Requested-With", "XMLHttpRequest")
req.Header.Add("Accept", "application/json")
req.Header.Add("Referrer", "https://bongacams.com")
req.Header.Add("User-agent", "curl/7.79.1")
rsp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println(err.Error())
return false, v
}
defer rsp.Body.Close()
if err = json.NewDecoder(rsp.Body).Decode(v); err != nil {
fmt.Println(err.Error())
return false, v
}
return true, v
}
func xWorker(workerData Info, u url.URL) {
fmt.Println("Start", workerData.room, "server", workerData.Server, "proxy", workerData.Proxy)
rooms.Add <- workerData
defer func() {
rooms.Del <- workerData.room
}()
ok, v := getAMF(workerData.room)
if !ok {
fmt.Println("exit: no amf parms")
return
}
Dialer := *websocket.DefaultDialer
if _, ok := conf.Proxy[workerData.Proxy]; ok {
Dialer = websocket.Dialer{
Proxy: http.ProxyURL(&url.URL{
Scheme: "http", // or "https" depending on your proxy
Host: conf.Proxy[workerData.Proxy],
Path: "/",
}),
HandshakeTimeout: 45 * time.Second, // https://pkg.go.dev/github.com/gorilla/websocket
}
}
c, _, err := Dialer.Dial(u.String(), nil)
if err != nil {
fmt.Println(err.Error(), workerData.room)
return
}
defer c.Close()
c.SetReadDeadline(time.Now().Add(60 * time.Second))
if err = c.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf(`{"id":%d,"name":"joinRoom","args":["%s",{"username":"%s","displayName":"%s","location":"%s","chathost":"%s","isRu":%t,"isPerformer":false,"hasStream":false,"isLogged":false,"isPayable":false,"showType":"public"},"%s"]}`, 1, v.UserData.Chathost, v.UserData.Username, v.UserData.DisplayName, v.UserData.Location, v.UserData.Chathost, v.UserData.IsRu, v.LocalData.DataKey))); err != nil {
fmt.Println(err.Error())
return
}
_, message, err := c.ReadMessage()
if err != nil {
fmt.Println(err.Error(), workerData.room)
return
}
slog <- saveLog{workerData.Rid, time.Now().Unix(), string(message)}
if string(message) == `{"id":1,"result":{"audioAvailable":false,"freeShow":false},"error":null}` {
fmt.Println("room offline, exit", workerData.room)
return
}
if err = c.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf(`{"id":%d,"name":"ChatModule.connect","args":["public-chat"]}`, 2))); err != nil {
fmt.Println(err.Error(), workerData.room)
return
}
_, message, err = c.ReadMessage()
if err != nil {
fmt.Println(err.Error(), workerData.room)
return
}
slog <- saveLog{workerData.Rid, time.Now().Unix(), string(message)}
quit := make(chan struct{})
pid := 3
defer func() {
close(quit)
}()
go func() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-quit:
return
case <-ticker.C:
if err = c.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf(`{"id":%d,"name":"ping"}`, pid))); err != nil {
fmt.Println(err.Error(), workerData.room)
rooms.Stop <- workerData.room
return
}
pid++
break
}
}
}()
dons := make(map[string]struct{})
for {
select {
case <-workerData.ch:
fmt.Println("Exit room:", workerData.room)
return
default:
}
c.SetReadDeadline(time.Now().Add(30 * time.Minute))
_, message, err := c.ReadMessage()
if err != nil {
fmt.Println(err.Error())
return
}
now := time.Now().Unix()
slog <- saveLog{workerData.Rid, now, string(message)}
m := &ServerResponse{}
if err = json.Unmarshal(message, m); err != nil {
fmt.Println(err.Error(), workerData.room)
continue
}
workerData.Last = now
rooms.Add <- workerData
if m.Type == "ServerMessageEvent:PERFORMER_STATUS_CHANGE" && string(m.Body) == `"offline"` {
fmt.Println(m.Type, workerData.room)
return
}
if m.Type == "ServerMessageEvent:ROOM_CLOSE" {
fmt.Println(m.Type, workerData.room)
return
}
if m.Type == "ServerMessageEvent:INCOMING_TIP" {
d := &DonateResponse{}
if err = json.Unmarshal(m.Body, d); err == nil {
//fmt.Println(d.F.Username, "send", d.A, "tokens")
workerData.Tips++
if _, ok := dons[d.F.Username]; !ok {
dons[d.F.Username] = struct{}{}
workerData.Dons++
}
save <- saveData{workerData.room, d.F.Username, workerData.Rid, d.A, now}
workerData.Income += d.A
rooms.Add <- workerData
}
}
}
}

17
go.mod Normal file
View file

@ -0,0 +1,17 @@
module stripchat
go 1.19
require (
github.com/ClickHouse/clickhouse-go v1.5.4
github.com/go-sql-driver/mysql v1.6.0
github.com/gorilla/websocket v1.5.0
github.com/jmoiron/sqlx v1.3.5
github.com/json-iterator/go v1.1.12
)
require (
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
)

37
go.sum Normal file
View file

@ -0,0 +1,37 @@
github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

77
test/main.go Normal file
View file

@ -0,0 +1,77 @@
package main
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"regexp"
"strings"
)
func main() {
if len(os.Args) < 4 {
fmt.Println("./test room server proxy")
return
}
room := os.Args[1]
server := os.Args[2]
proxy := os.Args[3]
u, token, id := getToken(room)
startRoom(room, server, proxy, u, token, id)
}
func getToken(room string) (*url.URL, string, string) {
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "https://ru.stripchat.com/"+room, nil)
if err != nil {
panic(err)
}
req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:33.0) Gecko/20100101 Firefox/33.0")
req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
req.Header.Add("Accept-Language", "en-US,en;q=0.5")
req.Header.Add("Connection", "keep-alive")
req.Header.Add("Referer", "https://ru.stripchat.com")
rsp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
} else if rsp.StatusCode != http.StatusOK {
panic("failed to get rsp")
}
defer rsp.Body.Close()
buf, err := ioutil.ReadAll(rsp.Body)
if err != nil {
panic(err)
}
re := regexp.MustCompile(`"websocketUrl":"*(.*?)\s*"`)
m := re.FindSubmatch(buf)
if len(m) != 2 {
panic("not match")
}
r := bytes.ReplaceAll(m[1], []byte(`\u002F`), []byte(`/`))
u, err := url.Parse(string(r))
if err != nil {
panic(err)
}
re = regexp.MustCompile(`"token":"*(.*?)\s*"`)
m = re.FindSubmatch(buf)
if len(m) != 2 {
panic("not match")
}
re = regexp.MustCompile(`img.strpst.com/thumbs/*(.*?)\s*"`)
id := re.FindSubmatch(buf)
if len(id) != 2 {
panic("not match")
}
xid := string(id[1])
return u, string(m[1]), xid[strings.Index(xid, "/")+1:]
}

142
test/worker.go Normal file
View file

@ -0,0 +1,142 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/gorilla/websocket"
)
var messages [][]byte
func init() {
messages = append(messages, []byte(`{"id":"1660248194970-sub-lotteryChanged","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/lotteryChanged"}`))
messages = append(messages, []byte(`{"id":"1660248194970-sub-userBanned:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/userBanned:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194970-sub-goalChanged:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/goalChanged:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194970-sub-modelStatusChanged:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/modelStatusChanged:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-broadcastSettingsChanged:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/broadcastSettingsChanged:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-tipMenuUpdated:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/tipMenuUpdated:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-topicChanged:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/topicChanged:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-userUpdated:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/userUpdated:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-interactiveToyStatusChanged:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/interactiveToyStatusChanged:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-groupShow:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/groupShow:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-deleteChatMessages:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/deleteChatMessages:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-tipLeaderboardSettingsUpdated:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/tipLeaderboardSettingsUpdated:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194971-sub-modelAppUpdated:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/modelAppUpdated:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194972-sub-newKing:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/newKing:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194972-sub-privateMessageSettingsChanged:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/privateMessageSettingsChanged:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194972-sub-newChatMessage:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/newChatMessage:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194972-sub-fanClubUpdated:XID","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/fanClubUpdated:XID"}`))
messages = append(messages, []byte(`{"id":"1660248194972-sub-viewServerChanged:hls-07","method":"PUT","url":"/front/clients/CLIENT_ID/subscriptions/viewServerChanged:hls-07"}`))
}
type ServerResponse struct {
SubscriptionKey string `json:"subscriptionKey,omitempty"`
Params struct {
ClientId string `json:"clientId,omitempty"`
Message struct {
Type string `json:"type,omitempty"`
Userdata struct {
Username string `json:"username,omitempty"`
} `json:"userdata,omitempty"`
Details struct {
Amount float64 `json:"amount,omitempty"`
LovenseDetails struct {
Type string `json:"type,omitempty"`
Detail struct {
Name string `json:"name,omitempty"`
Amount float64 `json:"amount,omitempty"`
} `json:"detail,omitempty"`
} `json:"lovenseDetails"`
} `json:"details,omitempty"`
} `json:"message,omitempty"`
} `json:"params,omitempty"`
}
func startRoom(room, server, proxy string, u *url.URL, token string, id string) {
// curl -vvv -X POST -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "X-Requested-With: XMLHttpRequest" -d "method=getRoomData" -d "args[]=Icehotangel" "https://rt.bongocams.com/tools/amf.php?res=771840&t=1654437233142"
fmt.Printf("token %s id %s\n", token, id)
fmt.Println("Start", room, "server", server, "proxy", proxy)
Dialer := *websocket.DefaultDialer
proxyMap := make(map[string]string)
proxyMap["us"] = "aaa:port"
proxyMap["fi"] = "bbb:port"
if _, ok := proxyMap[proxy]; ok {
Dialer = websocket.Dialer{
Proxy: http.ProxyURL(&url.URL{
Scheme: "http", // or "https" depending on your proxy
Host: proxyMap[proxy],
Path: "/",
}),
HandshakeTimeout: 45 * time.Second, // https://pkg.go.dev/github.com/gorilla/websocket
}
}
c, _, err := Dialer.Dial(u.String(), nil)
if err != nil {
fmt.Println(err.Error())
return
}
defer c.Close()
ws, _, err := Dialer.Dial("wss://websocket.stripchat.com/connection/websocket", nil)
if err != nil {
panic(err)
}
defer ws.Close()
for {
_, message, err := c.ReadMessage()
if err != nil {
fmt.Println("return " + err.Error())
return
}
fmt.Println(string(message))
m := &ServerResponse{}
if err = json.Unmarshal(message, m); err != nil {
fmt.Println(err.Error())
continue
}
if m.SubscriptionKey == "connected" {
fmt.Println("connected")
for _, msg := range messages {
b := bytes.ReplaceAll(msg, []byte(`CLIENT_ID`), []byte(m.Params.ClientId))
b = bytes.ReplaceAll(b, []byte(`XID`), []byte(id))
if err = c.WriteMessage(websocket.TextMessage, b); err != nil {
fmt.Println("return " + err.Error())
return
}
}
if err = ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf(`{"params":{"token":"%s","name":"js"},"id":1}`, token))); err != nil {
panic(err)
}
}
if !strings.Contains(m.SubscriptionKey, "newChatMessage") {
continue
}
fmt.Println("proper msg")
if m.Params.Message.Type == "lovense" {
fmt.Println("donate")
fmt.Println(m.Params.Message.Details.LovenseDetails.Detail.Name, " send ", m.Params.Message.Details.LovenseDetails.Detail.Amount, "tokens")
} else if m.Params.Message.Type == "tip" {
fmt.Println("donate")
fmt.Println(m.Params.Message.Userdata.Username, " send ", m.Params.Message.Details.Amount, "tokens")
}
}
}