This commit is contained in:
2026-07-21 03:49:47 +03:00
commit e8a0b2ba42
65 changed files with 40163 additions and 0 deletions

1
1xbet Executable file

File diff suppressed because one or more lines are too long

2136
TEST_ODDS Normal file

File diff suppressed because it is too large Load Diff

1103
api.go Executable file

File diff suppressed because one or more lines are too long

76
cleaner/main.go Executable file
View File

@@ -0,0 +1,76 @@
package main
/*
Чистит названия команд в БД.
Убирает приписки к названиям команд в виде трехбуквенного кода страны в скобках (Aut).
Важно не удалить вместо кода страны приписки типа (Am), (Woman)
*/
import (
"gordenko.dev/dima/flashscore/model"
"regexp"
"strings"
"gordenko.dev/dima/qx"
_ "github.com/go-sql-driver/mysql"
)
var (
reCountryCode = regexp.MustCompile(`\([a-zA-Z]{3}\)`)
)
func main() {
db, err := qx.Open("mysql", "user:password@/tipper")
if err != nil {
panic(err)
}
defer db.Close()
/*
m := model.New(db)
teams, err := m.ListFootballTeams()
if err != nil {
panic(err)
}
for _, team := range teams {
if reCountryCode.MatchString(team.CanonicalName) {
//newCanonicalName := reCountryCode.ReplaceAllString(team.CanonicalName, "")
// Внутри метода будет удален старый alias и добавлен новый
err = m.UpdateFootballTeamCanonicalName(model.UpdateFootballTeamCanonicalNameIn{
TeamID: team.TeamID,
CanonicalName: team.CanonicalName,
NewCanonicalName: team.CanonicalName,
})
if err != nil {
panic(err)
}
}
}
*/
// Capitalize names
var teams []model.Team
db.List(&teams)
tx, err := db.Tx()
if err != nil {
panic(err)
}
for _, team := range teams {
name := strings.Title(team.CanonicalName)
err = tx.Exec(`UPDATE footballTeams SET canonicalName=? WHERE teamID=?`, name, team.TeamID)
if err != nil {
panic(err)
}
}
tx.Commit()
}

581
cmd/main.go Normal file
View File

@@ -0,0 +1,581 @@
package main
/*
Отписка от матча:
- метод Unwatch
- когда матч исчезнет из лайва
*/
import (
"os/signal"
"syscall"
"gordenko.dev/dima/flashscore"
"gordenko.dev/dima/qx"
"gordenko.dev/dima/flashscore/daemon"
"log"
"os"
"gordenko.dev/dima/web"
"gordenko.dev/dima/web/api"
"gordenko.dev/dima/web/router"
_ "github.com/go-sql-driver/mysql"
)
const (
port = 7767
dsn = "user:password@/tipper"
baseDir = "/home/ubuntu/hfs"
)
func main() {
var err error
logger := log.New(os.Stdout, "", log.LstdFlags)
db, err := qx.Open("mysql", dsn)
if err != nil {
log.Fatalf("qx.Open: %s\n", err)
}
defer db.Close()
//fs := flashscore.New(db)
d, err := daemon.New(daemon.Options{
BaseDir: baseDir,
Logger: logger,
CheckInterval: 60 * 60 * 6,
Db: db,
SportIDs: []int{
flashscore.Tennis,
flashscore.Handball,
flashscore.Football,
},
})
if err != nil {
log.Fatalf("daemon.New: %s\n", err)
}
defer d.Close()
publicAPI, err := api.NewAPI(api.Options{
Logger: logger,
})
if err != nil {
log.Fatalf("api.NewAPI: %s\n", err)
}
publicAPI.PublicFunc("listUpcomingMatchReports", d.ListUpcomingMatchReports)
r := router.New()
r.HandleFunc("/reports", d.ShowReports)
r.PrefixHandle("/api", publicAPI)
app, err := web.NewApp(web.AppOptions{
Port: port,
Router: r,
})
if err != nil {
log.Fatalf("web.NewApp: %s\n", err)
}
go app.Run()
go d.Run()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
logger.Println("daemon stopped.")
/*
//err = flashscore.ListFootballChampFixtures(fixturesURL)
_, err = flashscore.GetUpcomingFootballMatchReport("riQ962dS")
//err = testParseTr(trsrc)
//err = loadOdds()
//_, err = loadFootballMatchSummary("Sv4qwtWD")
if err != nil {
panic(err)
}
*/
}
/*
FootballChampIDs: []string{
"/england/premier-league",
"/england/championship",
"/england/league-one",
//"/england/league-two",
//"/england/national-league",
//"/england/national-league-north",
//"/england/national-league-south",
//"/england/npl-premier-division",
//"/england/southern-league-central-division",
//"/england/southern-league-south-division",
//"/england/isthmian-league-premier-division",
//"/england/professional-development-league",
//"/england/efl-cup",
//"/england/efl-trophy",
//"/england/fa-cup",
//"/england/fa-trophy",
//"/england/premier-league-cup",
//"/england/premier-league-international-cup",
//"/england/premier-league-2",
//"/england/premier-league-u18",
//"/england/fa-youth-cup",
//"/england/women-s-super-league", 06.09
//"/england/women-s-championship",
//"/england/women-s-fa-cup", 26.09
//"/england/women-s-league-cup",
//"/germany/bundesliga",
//"/germany/2-bundesliga",
"/germany/3-liga",
//"/germany/regionalliga-north",
"/germany/regionalliga-nordost",
//"/germany/regionalliga-west", // 04.09
//"/germany/regionalliga-sudwest", // 4.09
//"/germany/regionalliga-bayern",
//"/germany/dfb-pokal", // 12.09
"/germany/oberliga-nofv-nord",
"/germany/oberliga-nofv-sud",
//"/germany/bundesliga-women", // 4.09
//"/germany/2-bundesliga-women",
"/austria/tipico-bundesliga",
"/austria/2-liga",
//"/regionalliga-west-promotion-group", // WTF ?
"/austria/regionalliga-east",
"/austria/regionalliga-central",
"/austria/regionalliga-west-tyrol",
"/austria/regionalliga-west-salzburg",
"/austria/regionalliga-west-vorarlberg",
"/austria/regionalliga-west",
"/austria/ofb-cup",
"/austria/bundesliga-women",
//"/netherlands/eredivisie", // 12.09
"/netherlands/eerste-divisie", // 28.08
//"/netherlands/tweede-divisie", // 5.09
//"/netherlands/derde-divisie", 05.09
//"/netherlands/reserve-league",
"/netherlands/knvb-beker",
//"/netherlands/eredivisie-u19",
"/france/ligue-1",
"/france/ligue-2",
"/france/national",
"/france/national-2-group-a",
"/france/national-2-group-b",
"/france/national-2-group-c",
"/france/national-2-group-d",
//"/france/coupe-de-la-ligue",
//"/france/coupe-de-france",
//"/france/division-1-women", 09
//"/france/division-2-women", 09
//"/france/coupe-de-france-women",
//"/netherlands/eredivisie-women", 06.09
//"/netherlands/knvb-beker-women",
//"/netherlands/eredivisie-cup-women",
"/italy/serie-a",
"/italy/serie-b",
"/italy/serie-a-women",
//"/italy/serie-b-women",
//"/italy/coppa-italia-women",
"/spain/laliga",
"/spain/laliga2",
//"/spain/primera-division-women",
//"/spain/copa-de-la-reina-women",
"/portugal/primeira-liga",
//"/portugal/ligapro",
//"/portugal/campeonato-de-portugal",
//"/portugal/taca-de-portugal",
//"/portugal/league-cup",
//"/portugal/liga-revelacao-u23",
//"/portugal/liga-bpi-women",
//"/portugal/taca-de-portugal-women",
"/belgium/jupiler-league", // 08.08
//"/belgium/proximus-league", // 22.08
//"/belgium/first-amateur-division",
//"/belgium/belgian-cup",
//"/belgium/pro-league-u21",
//"/belgium/super-league-women",
//"/belgium/1st-national-women",
//"/belgium/belgian-cup-women",
"/russia/premier-league",
"/russia/fnl",
"/russia/russian-cup",
"/russia/fnl-cup",
"/russia/pfl-group-1",
"/russia/pfl-group-2",
"/russia/pfl-group-3",
"/russia/pfl-group-4",
"/russia/youth-league",
"/russia/supreme-division-women",
"/switzerland/super-league",
"/switzerland/challenge-league",
//"/switzerland/promotion-league", 15.08 ??? (FRO after date)
//"/switzerland/1-liga-classic-group-1",
//"/switzerland/1-liga-classic-group-2",
//"/switzerland/1-liga-classic-group-3",
//"/switzerland/1-liga-classic-play-offs",
//"/switzerland/swiss-cup",
"/switzerland/nationalliga-a-women",
"/ukraine/premier-league",
"/ukraine/persha-liga",
"/ukraine/druha-liga",
"/belarus/vysshaya-liga",
"/belarus/pershaya-liga",
"/belarus/vysshaya-liga-women",
"/estonia/meistriliiga",
"/estonia/esiliiga",
"/estonia/estonian-cup",
"/latvia/optibet-virsliga",
"/latvia/1-liga",
"/latvia/latvian-cup",
"/lithuania/a-lyga",
"/lithuania/i-lyga",
"/lithuania/lithuanian-cup",
"/moldova/divizia-nationala",
"/moldova/moldovan-cup",
"/greece/super-league",
"/greece/super-league-2",
"/greece/football-league",
"/north-macedonia/1-mfl",
//"/north-macedonia/2-mfl",
//"/north-macedonia/macedonian-cup",
"/turkey/super-lig",
"/turkey/1-lig",
//"/turkey/turkish-cup",
"/bulgaria/parva-liga",
"/bulgaria/vtora-liga",
//"/cyprus/first-division", // 22.08
//"/cyprus/division-2", // ??
"/israel/ligat-ha-al",
//"/israel/leumit-league",
"/israel/state-cup",
"/bahrain/premier-league",
//"/bahrain/king-s-cup",
//"/bahrain/bahrain-cup",
"/faroe-islands/premier-league",
"/faroe-islands/faroe-islands-cup",
"/hungary/otp-bank-liga",
"/hungary/merkantil-bank-liga",
"/hungary/nb-iii-east",
"/hungary/nb-iii-centre",
"/hungary/nb-iii-west",
"/hungary/hungarian-cup",
//"/hungary/nb-i-women",
"/montenegro/prva-crnogorska-liga",
"/bosnia-and-herzegovina/premier-league",
"/luxembourg/national-division", // 23.08
"/luxembourg/luxembourg-cup",
"/romania/liga-1",
"/romania/liga-2",
//"/romania/romanian-cup",
//"/romania/superliga-women",
//"/romania/romanian-cup-women",
"/croatia/1-hnl",
"/croatia/2-hnl",
//"/croatia/croatian-cup",
"/czech-republic/1-liga",
"/czech-republic/division-2",
"/czech-republic/cfl-group-a",
"/czech-republic/cfl-group-b",
"/czech-republic/msfl",
"/czech-republic/mol-cup",
"/czech-republic/u19-league",
"/czech-republic/division-a",
"/czech-republic/division-b",
"/czech-republic/division-c",
"/czech-republic/division-d",
"/czech-republic/division-e",
"/czech-republic/division-f",
"/czech-republic/1-liga-women",
//"/czech-republic/2-liga-women", // 6.09
"/czech-republic/czech-cup-women",
"/poland/ekstraklasa",
"/poland/division-1",
"/poland/division-2",
"/poland/iii-liga-group-i",
"/poland/iii-liga-group-ii",
"/poland/iii-liga-group-iii",
"/poland/iii-liga-group-iv",
"/poland/polish-cup",
//"/poland/central-youth-league",
"/poland/ekstraliga-women",
"/poland/polish-cup-women",
"/georgia/crystalbet-erovnuli-liga",
"/georgia/crystalbet-erovnuli-liga-2",
//"/georgia/georgian-cup",
"/armenia/premier-league",
"/armenia/first-league",
"/denmark/superliga",
"/denmark/1st-division",
"/denmark/2nd-division-group-1",
"/denmark/2nd-division-group-2",
"/denmark/2nd-division-group-3",
//"/denmark/2nd-division-promotion-group", // WTF?
//"/denmark/2nd-division-relegation-group", // WTF
//"/denmark/landspokal-cup",
"/denmark/elitedivisionen-women",
"/finland/veikkausliiga",
"/finland/ykkonen", // странное расписание
"/finland/kakkonen-group-a",
"/finland/kakkonen-group-b",
"/finland/kakkonen-group-c",
"/norway/eliteserien",
"/norway/obos-ligaen",
"/norway/division-2-group-1",
"/norway/division-2-group-2",
//"/norway/division-3-group-1",
//"/norway/division-3-group-2",
//"/norway/division-3-group-3",
//"/norway/division-3-group-4",
//"/norway/division-3-group-5",
//"/norway/division-3-group-6",
//"/norway/junior-u19",
//"/norway/nm-cup",
"/norway/toppserien-women",
"/sweden/allsvenskan",
"/sweden/superettan",
"/sweden/division-1-norra",
"/sweden/division-1-sodra",
//"/sweden/division-1-relegation",
//"/sweden/division-2-norrland", // FRO
//"/sweden/division-2-norra-svealand",
//"/sweden/division-2-vastra-gotaland",
//"/sweden/division-2-sodra-svealand",
//"/sweden/division-2-norra-gotaland",
"/sweden/svenska-cupen",
"/sweden/allsvenskan-women",
"/sweden/elitettan-women",
"/sweden/svenska-cupen-women",
"/iceland/pepsideild",
"/iceland/inkasso-deildin",
"/iceland/division-2",
//"/iceland/league-cup",
"/iceland/icelandic-cup",
"/iceland/urvalsdeild-women",
"/iceland/icelandic-cup-women",
"/ireland/premier-division",
"/ireland/division-1",
"/ireland/fai-cup",
"/ireland/league-cup",
//"/northern-ireland/nifl-premiership",
//"/northern-ireland/nifl-championship",
"/scotland/premiership",
//"/scotland/championship", // 17.10
//"/scotland/league-one", // 17.10
//"/scotland/league-two", // 17.10
//"/wales/cymru-premier",
"/serbia/super-liga",
"/serbia/prva-liga",
//"/serbia/serbian-cup",
"/kosovo/superliga",
"/slovakia/fortuna-liga",
"/slovakia/2-liga",
"/slovakia/divison-c-west",
"/slovakia/divison-c-east",
"/slovakia/divison-c-central",
"/slovakia/divison-c-bratislava",
"/slovakia/slovak-cup",
"/slovakia/1-liga-women",
"/slovenia/prva-liga",
"/slovenia/2-snl",
"/malta/premier-league",
"/malta/division-1",
//"/malta/fa-trophy",
"/malta/summer-cup",
"/kazakhstan/premier-league",
"/kazakhstan/first-division",
//"/kyrgyzstan/premier-liga", // 8.08 FRO
"/egypt/premier-league",
"/tunisia/ligue-professionnelle-1",
//"/tunisia/ligue-2",
"/qatar/qsl",
"/qatar/qsl-cup",
"/saudi-arabia/saudi-professional-league",
"/saudi-arabia/division-1",
//"/saudi-arabia/king-cup",
"/jordan/premier-league",
//"/jordan/jordan-cup",
//"/jordan/shield-cup",
//"/oman/professional-league",
//"/oman/sultan-cup",
//"/iran/persian-gulf-pro-league",
"/iran/division-1",
"/iran/hazfi-cup",
//"/iraq/super-league",
"/kuwait/premier-league",
//"/kuwait/crown-prince-cup",
"/kuwait/emir-cup",
//"/pakistan/premier-league",
// AMERICA
"/uruguay/primera-division",
"/uruguay/segunda-division",
"/usa/mls",
"/usa/usl-championship",
"/usa/usl-league-one",
//"/usa/usl-league-two",
"/usa/nisa",
"/usa/nwsl-women",
//"/usa/nwsl-challenge-cup-women",
"/canada/canadian-premier-league",
"/canada/championship",
"/brazil/serie-a",
"/brazil/serie-b",
"/brazil/serie-c",
//"/brazil/serie-d", // 06.09
//"/brazil/primeira-liga",
//"/brazil/campeonato-acreano",
"/brazil/campeonato-alagoano",
//"/brazil/campeonato-amapaense",
//"/brazil/campeonato-amazonense",
"/brazil/campeonato-baiano",
//"/brazil/campeonato-brasiliense",
//"/brazil/campeonato-capixaba",
//"/brazil/campeonato-carioca",
"/brazil/campeonato-catarinense",
//"/brazil/campeonato-cearense",
"/brazil/campeonato-gaucho",
//"/brazil/campeonato-gaucho-2",
//"/brazil/campeonato-goiano",
"/brazil/campeonato-maranhense",
//"/brazil/campeonato-matogrossense",
"/brazil/campeonato-mineiro",
//"/brazil/campeonato-mineiro-2",
"/brazil/campeonato-paraense",
"/brazil/campeonato-paraibano",
"/brazil/campeonato-paranaense",
"/brazil/campeonato-paulista",
//"/brazil/campeonato-paulista-a2",
"/brazil/campeonato-pernambucano",
//"/brazil/campeonato-piauiense",
"/brazil/campeonato-potiguar",
//"/brazil/campeonato-rondoniense",
"/brazil/campeonato-roraimense",
"/brazil/campeonato-sergipano",
//"/brazil/campeonato-sul-matogrossense",
"/brazil/campeonato-tocantinense",
"/brazil/copa-do-brasil",
//"/brazil/copa-do-brasil-u20",
//"/brazil/copa-verde",
"/brazil/copa-do-nordeste",
//"/brazil/brasileiro-u20",
//"/brazil/brasileiro-women",
//"/brazil/brasileiro-a2-women",
//"/argentina/superliga",
//"/argentina/primera-nacional",
//"/argentina/torneo-federal",
"/mexico/liga-mx",
//"/mexico/ascenso-mx",
"/mexico/liga-mx-women",
//"/bolivia/division-profesional",
//"/bolivia/nacional-b",
//"/chile/primera-division",
//"/chile/primera-b",
//"/chile/chilean-cup",
//"/colombia/primera-a", // 06.09
//"/colombia/primera-b", // 05.09
"/ecuador/liga-pro",
"/ecuador/serie-b",
//"/ecuador/copa-ecuador",
"/costa-rica/primera-division",
//"/costa-rica/liga-de-ascenso", end
"/paraguay/primera-division",
//"/paraguay/division-intermedia",
//"/paraguay/copa-paraguay",
"/peru/liga-1",
//"/peru/liga-2",
"/panama/lpf",
"/costa-rica/primera-division",
//"/costa-rica/liga-de-ascenso",
"/nicaragua/liga-primera",
//"/jamaica/premier-league",
// OCEANIA
"/australia/a-league",
"/australia/npl-act",
"/australia/npl-northern-nsw",
"/australia/npl-nsw",
"/australia/npl-queensland",
"/australia/npl-south-australian",
"/australia/npl-tasmania",
"/australia/npl-victoria",
"/australia/npl-western-australia",
"/australia/brisbane-premier-league",
"/australia/ffa-cup",
//"/australia/y-league",
//"/australia/w-league",
"/new-zealand/football-championship",
//"/new-zealand/national-league-women",
//"/philippines/pfl",
"/china/super-league",
//"/china/jia-league",
//"/china/yi-league",
//"/china/fa-cup",
"/mongolia/premier-league",
"/south-korea/k-league-1",
"/south-korea/k-league-2",
"/south-korea/k3-league",
"/south-korea/korean-cup",
"/south-korea/league-cup",
"/south-korea/wk-league-women",
"/japan/j1-league",
"/japan/j2-league",
"/japan/j3-league",
"/japan/ybc-levain-cup",
//"/japan/emperors-cup",
"/japan/nadeshiko-league-women",
"/japan/empress-s-cup-women",
"/vietnam/v-league-1",
"/vietnam/v-league-2",
//"/vietnam/vietnamese-cup",
"/vietnam/national-league-women",
"/vietnam/vietnamese-cup-women",
"/singapore/premier-league",
//"/singapore/singapore-cup",
"/taiwan/premier-league",
"/taiwan/mulan-football-league-women",
"/thailand/thai-league-1",
"/thailand/thai-league-2",
//"/thailand/thai-fa-cup",
"/cambodia/c-league",
"/laos/lao-league",
"/indonesia/liga-1",
"/indonesia/liga-2",
"/indonesia/president-cup",
"/malaysia/super-league",
"/malaysia/premier-league",
//"/malaysia/malaysia-cup",
//"/malaysia/fa-cup",
//"/sri-lanka/champions-league", end
// EUROPA
"/europe/europa-league",
"/europe/champions-league",
// AFRICA
//"/nigeria/npfl",
//"/nigeria/nnl",
//"/nigeria/aiteo-cup",
//"/senegal/ligue-1",
"/south-africa/premier-league",
"/south-africa/gladafrica-championship",
"/south-africa/nedbank-cup",
//"/sudan/premier-league",
//"/zimbabwe/premier-soccer-league",
"/zambia/super-league",
//"/cameroon/elite-one",
//"/cameroon/elite-two",
//"/ivory-coast/ligue-1",
//"/kenya/premier-league",
//"/lesotho/premier-league",
"/morocco/botola-pro",
"/morocco/botola-2",
//"/morocco/coupe-du-trone",
},
*/

159
common.go Normal file
View File

@@ -0,0 +1,159 @@
package flashscore
import (
"bytes"
"fmt"
"github.com/PuerkitoBio/goquery"
"gordenko.dev/dima/flashscore/model"
webspider "gordenko.dev/dima/spider"
)
func loadHomeAndAwayTeams(spider *webspider.Spider, matchID string, sportID int) (home, away model.Team, err error) {
// Загружаем Landing
landingURL := fmt.Sprintf(h2hLandingURLPattern, matchID)
var (
buf []byte
doc *goquery.Document
)
buf, err = spider.Get(landingURL, nil)
if err != nil {
err = fmt.Errorf("load h2hLanding: %s", err)
return
}
//ioutil.WriteFile("h2h.html", buf, 0644)
doc, err = goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
if err != nil {
err = fmt.Errorf("goquery.NewDocumentFromReader: %s", err)
return
}
//var home, away model.Team
switch sportID {
// БОЛЬШЕ НЕ АКТУАЛЬНО!
// case Football:
// // Данные футбольных команд закодированы в HTML на странице
// // Home team
// home, err = parseTeam(doc.Find(".tname-home"))
// if err != nil {
// err = fmt.Errorf("Parse home team: %s", err)
// return
// }
// // Away team
// away, err = parseTeam(doc.Find(".tname-away"))
// if err != nil {
// err = fmt.Errorf("Parse away team: %s", err)
// return
// }
case Football, Handball, Tennis:
// Данные гандбольных команд закодированы в JSON переменной 'environment'
// в теге <script> на странице
home, away, err = parseTeamsOnMatchPageInJsVar(doc)
if err != nil {
err = fmt.Errorf("parseTeamsOnMatchPageInJsVar: %s", err)
return
}
//case Tennis:
}
return
}
// storedHome, storedAway *model.Team
func listOfScheduledMatches(sportID int) (result []ScheduledMatch, err error) {
spider, err := webspider.NewSpider(nil)
if err != nil {
panic(err)
}
var (
referer string
urls []string
)
switch sportID {
case Football:
urls = []string{
footballAllGamesTodayURL,
footballAllGamesTomorrowURL,
}
referer = "https://www.flashscore.com/football/"
case Handball:
urls = []string{
handballAllGamesTodayURL,
handballAllGamesTomorrowURL,
}
referer = "https://www.flashscore.com/handball/"
case Tennis:
urls = []string{
tennisAllGamesTodayURL,
tennisAllGamesTomorrowURL,
}
referer = "https://www.flashscore.com/tennis/"
}
// Загружаем AJAX страницы со списками матчей
for _, allGamesURL := range urls {
var (
buf []byte
list []ScheduledMatch
)
buf, err = spider.Get(allGamesURL, map[string]string{
"referer": "https://d.flashscore.com/x/feed/proxy-local",
"x-requested-with": "XMLHttpRequest",
// Важное поле! Код можно найти в исходниках либо подсмотреть. Без кода
// сервер отдает 401 ошибку
"x-fsign": feedSign,
"x-referer": referer,
"x-geoip": "1",
})
if err != nil {
err = fmt.Errorf("Load all Games feed: %s", err)
return
}
//fmt.Printf("%s\n\n", buf)
list, err = parseFlashscoreEncodedAllGames(string(buf), sportID)
if err != nil {
err = fmt.Errorf("parseListOfScheduledMatches: %s", err)
return
}
result = append(result, list...)
}
return
}
func loadH2H(spider *webspider.Spider, matchID string, sportID int, homeCanonicalName, awayCanonicalName string) (homeTeamMatches, awayTeamMatches, h2hMatches []model.TeamMatch, err error) {
switch sportID {
case Football:
//return loadFootballH2H(spider, matchID)
return loadHandballH2H(spider, matchID, homeCanonicalName, awayCanonicalName)
case Handball:
return loadHandballH2H(spider, matchID, homeCanonicalName, awayCanonicalName)
case Tennis:
// pass
default:
panic(fmt.Sprintf("Bug: h2h loader for the sportID %d is undefined", sportID))
}
return
}

80
common_test.go Normal file
View File

@@ -0,0 +1,80 @@
package flashscore
import (
"encoding/json"
"fmt"
"testing"
webspider "gordenko.dev/dima/spider"
)
func TestTennisLoadHomeAndAwayTeams(t *testing.T) {
spider, _ := webspider.NewSpider(nil)
home, away, err := loadHomeAndAwayTeams(spider, "dUf6AMy8", Tennis)
if err != nil {
t.Fatal(err)
}
fmt.Printf("home: %#v\n", home)
fmt.Printf("away: %#v\n", away)
}
func TestFootballLoadHomeAndAwayTeams(t *testing.T) {
spider, _ := webspider.NewSpider(nil)
home, away, err := loadHomeAndAwayTeams(spider, "rLXrnfEM", Football)
if err != nil {
t.Fatal(err)
}
fmt.Printf("home: %#v\n", home)
fmt.Printf("away: %#v\n", away)
}
func TestFootballLoadH2H(t *testing.T) {
spider, _ := webspider.NewSpider(nil)
homeTeamMatches, awayTeamMatches, h2hMatches, err := loadH2H(spider, "rLXrnfEM", Football, "Gent B", "Saint Eloois")
if err != nil {
t.Fatal(err)
}
var buf []byte
buf, _ = json.MarshalIndent(homeTeamMatches, "", " ")
fmt.Printf("home: %s\n", buf)
buf, _ = json.MarshalIndent(awayTeamMatches, "", " ")
fmt.Printf("away: %s\n", buf)
buf, _ = json.MarshalIndent(h2hMatches, "", " ")
fmt.Printf("h2h: %s\n", buf)
}
func TestLoadOdds(t *testing.T) {
spider, _ := webspider.NewSpider(nil)
matchID := "OtpuFKWi"
url := fmt.Sprintf(handballOddsURLPattern, matchID)
buf, err := spider.Get(url, map[string]string{
"x-requested-with": "XMLHttpRequest",
"x-fsign": feedSign,
"x-geoip": "1",
"referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
})
if err != nil {
err = fmt.Errorf("Load handball odds: %s; url=%s", err, url)
return
}
odds, err := parseFlashscoreEncodedOdds(string(buf))
if err != nil {
return
}
buf, _ = json.MarshalIndent(odds, "", " ")
fmt.Printf("TestLoadOdds: %s\n", buf)
}

6452
daemon/Bundesliga.data Executable file

File diff suppressed because it is too large Load Diff

1462
daemon/LaLiga.data Executable file

File diff suppressed because it is too large Load Diff

1452
daemon/Premier League.data Executable file

File diff suppressed because it is too large Load Diff

1477
daemon/Serie A.data Executable file

File diff suppressed because it is too large Load Diff

BIN
daemon/daemon Executable file

Binary file not shown.

273
daemon/daemon.go Executable file
View File

@@ -0,0 +1,273 @@
package daemon
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"sync"
"time"
"gordenko.dev/dima/flashscore"
"gordenko.dev/dima/flashscore/model"
"gordenko.dev/dima/qx"
"gordenko.dev/dima/web"
)
const (
defaultCheckInterval = 1 * time.Hour
)
type Options struct {
BaseDir string
CheckInterval int
Logger *log.Logger
Db *qx.Db
SportIDs []int
}
type Daemon struct {
mutex sync.Mutex
checkInterval time.Duration
fs *flashscore.Flashscore
baseDir string
dumpFilepath string
logger *log.Logger
port int
model *model.Model
// map[sportID]map[matchID]
reports map[int]map[string]flashscore.UpcomingMatchReport
}
func New(opt Options) (*Daemon, error) {
if opt.Logger == nil {
return nil, errors.New("Logger option is required")
}
if opt.Db == nil {
return nil, errors.New("Db option is required")
}
if opt.BaseDir == "" {
return nil, errors.New("BaseDir option is required")
}
s := &Daemon{
logger: opt.Logger,
baseDir: opt.BaseDir,
dumpFilepath: filepath.Join(opt.BaseDir, "reports.dump"),
fs: flashscore.New(opt.Db, opt.Logger),
model: model.New(opt.Db),
reports: make(map[int]map[string]flashscore.UpcomingMatchReport),
}
if opt.CheckInterval <= 0 {
s.checkInterval = defaultCheckInterval
} else {
s.checkInterval = time.Duration(opt.CheckInterval) * time.Second
}
for _, sportID := range opt.SportIDs {
s.reports[sportID] = make(map[string]flashscore.UpcomingMatchReport)
}
err := s.tryRestoreReportsFromDump()
if err != nil {
// только логгируем, ибо если файла нет или он поврежден - это не проблема
s.logger.Printf("tryRestoreReportsFromDump: %s\n", err)
}
return s, nil
}
func (s *Daemon) Close() {
s.mutex.Lock()
defer s.mutex.Unlock()
buf, err := json.MarshalIndent(s.reports, "", " ")
if err != nil {
return
}
err = os.WriteFile(s.dumpFilepath, buf, 0666)
if err != nil {
return
}
}
func (s *Daemon) tryRestoreReportsFromDump() (err error) {
buf, err := os.ReadFile(s.dumpFilepath)
if err != nil {
return
}
return json.Unmarshal(buf, &s.reports)
}
func (s *Daemon) ShowReports(state *web.State, reply *web.Reply) (err error) {
buf, _ := json.MarshalIndent(s.reports, "", " ")
str := fmt.Sprintf("%s", buf)
reply.WriteString(str)
return nil
}
func (s *Daemon) Run() {
ticker := time.NewTicker(s.checkInterval)
s.logger.Println("tick")
for sportID := range s.reports {
s.checkUpcomingMatches(sportID)
}
s.logger.Println("tick processed")
for {
select {
case <-ticker.C:
s.logger.Println("tick")
for sportID := range s.reports {
s.checkUpcomingMatches(sportID)
}
s.logger.Println("tick processed")
}
}
}
func (s *Daemon) ListUpcomingMatchReports(sportID int) (list []flashscore.UpcomingMatchReport, err error) {
s.mutex.Lock()
defer s.mutex.Unlock()
now := time.Now().Unix() - 3600
reports, ok := s.reports[sportID]
if !ok {
return
}
fmt.Printf("sportID: %d, reports size: %d\n", sportID, len(reports))
var started []string
for matchID, report := range reports {
if report.StartTime < now {
// Матч уже начался
//fmt.Printf("matchStarted: startTime %d < now %d\n", report.StartTime, now)
started = append(started, matchID)
} else {
if report.IsTeamsLoaded && report.IsHistoryLoaded {
list = append(list, report)
}
}
}
// Удаляем матчи, которые начались
for _, matchID := range started {
delete(reports, matchID)
}
// Сортируем матчи
sort.Slice(list, func(i, j int) bool {
a := list[i]
b := list[j]
if a.StartTime == b.StartTime {
// Время начала равно - сравниваем MatchID
return a.MatchID < b.MatchID
}
return a.StartTime < b.StartTime
})
return
}
func (s *Daemon) checkUpcomingMatches(sportID int) {
scheduledMatches, err := s.fs.ListOfScheduledMatches(sportID)
if err != nil {
s.logger.Printf("ListOfScheduledMatches: %s\n", err)
return
}
s.logger.Printf("Sport: %d, scheduled matches found: %d\n", sportID, len(scheduledMatches))
reports := s.reports[sportID]
for idx, match := range scheduledMatches {
if sportID == flashscore.Tennis {
// Пары игнорим
if flashscore.IsTennisDoublesChamp(match.FullChampName) {
continue
}
}
// Здесь лок не нужен, ибо нет race conditions, пишем в мапу в этом же потоке
report, ok := reports[match.MatchID]
if !ok {
report = flashscore.UpcomingMatchReport{
MatchID: match.MatchID,
SportID: match.SportID,
StartTime: match.StartTime.Unix(),
Zone: model.TeamZone{
ZoneID: match.Zone.ZoneID,
Name: match.Zone.Name,
SportID: match.SportID,
},
Champ: model.TeamChamp{
ChampID: match.ChampID,
},
FullChampName: match.FullChampName,
}
// Ниже по тексту report будет перезаписан в коллекции reports
}
// Даже если отчет полный - синкаем, чтобы обновить odds
err = s.fs.SyncUpcomingMatchReport(&report)
if err != nil {
s.logger.Printf("SyncUpcomingMatchReport: %s; matchID=%s\n", err, report.MatchID)
} else {
report.ParseTime = time.Now()
//buf, _ := json.MarshalIndent(report, "", " ")
//fmt.Printf("REPORT:\n\n%s\n\n", buf)
s.mutex.Lock()
reports[report.MatchID] = report
s.mutex.Unlock()
}
//fmt.Printf("REPORT:\n\n%s\n\n", buf)
if (idx % 10) == 0 {
s.logger.Printf("%d matches scanned\n", idx+1)
}
}
s.logger.Printf("Sport: %d, all matches scanned\n", sportID)
}
/*
func (s *Flashscore) ListUpcomingMatchReports(sportID int, reportCh chan *UpcomingMatchReport) (err error) {
matches, err := s.listOfScheduledMatches(sportID)
if err != nil {
err = fmt.Errorf("listOfScheduledMatches: %s", err)
return
}
//matches = DiscardScheduledMatchesFurtherThan(matches, 24*time.Hour)
reports := s.reports
for _, match := range matches {
s.Li
report = &UpcomingMatchReport{
IsComplete: true,
MatchID: match.MatchID,
SportID: match.SportID,
StartTime: match.StartTime.Unix(),
}
report, reportErr := s.getUpcomingMatchReport(match)
if reportErr != nil {
s.logger.Printf("getUpcomingMatchReport: %s; matchID=%s", reportErr, match.MatchID)
} else {
reportCh <- &report
}
}
close(reportCh)
return
}
*/

36
final-round.data Executable file

File diff suppressed because one or more lines are too long

23
flashscore.go Executable file
View File

@@ -0,0 +1,23 @@
package flashscore
import (
"log"
"gordenko.dev/dima/flashscore/model"
"gordenko.dev/dima/qx"
webspider "gordenko.dev/dima/spider"
)
func New(qxdb *qx.Db, logger *log.Logger) *Flashscore {
s := new(Flashscore)
s.model = model.New(qxdb)
s.logger = logger
var err error
s.spider, err = webspider.NewSpider(nil)
if err != nil {
panic(err)
}
return s
}

109
football.go Executable file
View File

@@ -0,0 +1,109 @@
package flashscore
import (
"fmt"
"regexp"
"strconv"
"github.com/PuerkitoBio/goquery"
)
func parseOdds1x2(tableSelection *goquery.Selection) (offers []Winner3Way, err error) {
tdList := tableSelection.Find("td.bookmaker")
for idx := 0; idx < tdList.Length(); idx++ {
//tdList.Each(func(idx int, tdSelection *goquery.Selection) {
var offer Winner3Way
//.Get(0).Data //.Html()
//fmt.Printf("%s\n", txt)
//fmt.Printf("%v\n", tdSelection)
//tdSelection.Get(0)
//tdSelection.Next()
tdSelection := tdList.Eq(idx)
offer.Bookmaker, err = getBookmaker(tdSelection)
if err != nil {
return
}
priceTd := tdSelection.Next()
offer.Home, err = getPrice(priceTd)
if err != nil {
err = fmt.Errorf("get home price: %s", err)
return
}
priceTd = priceTd.Next()
offer.Draw, err = getPrice(priceTd)
if err != nil {
err = fmt.Errorf("get draw price: %s", err)
return
}
priceTd = priceTd.Next()
offer.Away, err = getPrice(priceTd)
if err != nil {
err = fmt.Errorf("get away price: %s", err)
return
}
offers = append(offers, offer)
}
return
}
// /bookmaker/17/
var reBookmakerID = regexp.MustCompile(`/bookmaker/(\d+)`)
func getBookmaker(tdSelection *goquery.Selection) (bookmakerID int, err error) {
aSelection := tdSelection.Find("a")
if aSelection.Length() == 0 {
err = fmt.Errorf("<a> tag not found in bookmaker's <td>")
return
}
//a := aSelection.Get(0)
href, exists := aSelection.Eq(0).Attr("href")
if !exists {
err = fmt.Errorf("href attr not found in <a> tag")
return
}
// /fmt.Printf("href: %s\n", href)
groups := reBookmakerID.FindStringSubmatch(href)
if len(groups) != 2 {
err = fmt.Errorf("BookmakerID in href not found. Regexp result: %q", groups)
return
}
bookmakerID, _ = strconv.Atoi(groups[1])
return
}
func getPrice(tdSelection *goquery.Selection) (price float64, err error) {
if tdSelection == nil {
err = fmt.Errorf("<td> tag selection is nil")
return
}
spanSelection := tdSelection.Find("span")
if spanSelection.Length() == 0 {
err = fmt.Errorf("<span> not found in <td>")
return
}
span := spanSelection.Get(0)
priceStr := span.FirstChild.Data // fix - if not text node
price, err = strconv.ParseFloat(priceStr, 64)
if err != nil {
err = fmt.Errorf("Parse price: %s; priceStr=%q", err, priceStr)
return
}
return
}

45
football_test.go Executable file
View File

@@ -0,0 +1,45 @@
package flashscore
import (
"encoding/json"
"fmt"
"testing"
_ "github.com/go-sql-driver/mysql"
)
/*
func TestFixtures(t *testing.T) {
var err error
//'req, err := http.NewRequest("GET", url, nil)
logger := log.New(os.Stdout, "", log.LstdFlags)
db, err := qx.Open("mysql", "root:zkx75fcy@/tipper")
//db, err := qx.Open("mysql", "root:lklnmf6@/tipper")
if err != nil {
panic(err)
}
defer db.Close()
fs := New(db, logger)
round, err := fs.ListFootballChampNextRoundFixtures("/germany/3-liga")
if err != nil {
fmt.Printf("ListFootballChampFixtures: %s\n", err)
return
}
buf, _ := json.MarshalIndent(round, "", " ")
fmt.Printf("%s\n", buf)
}
*/
func TestFootballListOfScheduledMatches(t *testing.T) {
result, err := listOfScheduledMatches(Football)
if err != nil {
t.Fatal(err)
}
buf, _ := json.MarshalIndent(result, "", " ")
fmt.Printf("ListOfScheduledMatches: %s\n", buf)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
SE÷Match¬~SF÷Service¬~SG÷Aces¬SH÷3¬SI÷2¬~SG÷Double Faults¬SH÷0¬SI÷0¬~SG÷1st Serve Percentage¬SH÷71%¬SI÷76%¬~SG÷1st Serve Points Won¬SH÷63% (22/35)¬SI÷79% (23/29)¬~SG÷2nd Serve Points Won¬SH÷29% (4/14)¬SI÷56% (5/9)¬~SG÷Break Points Saved¬SH÷63% (5/8)¬SI÷100% (2/2)¬~SF÷Return¬~SG÷1st Return Points Won¬SH÷21% (6/29)¬SI÷37% (13/35)¬~SG÷2nd Return Points Won¬SH÷44% (4/9)¬SI÷71% (10/14)¬~SG÷Break Points Converted¬SH÷0% (0/2)¬SI÷38% (3/8)¬~SF÷Points¬~SG÷Max Points In Row¬SH÷5¬SI÷7¬~SG÷Service Points Won¬SH÷53% (26/49)¬SI÷74% (28/38)¬~SG÷Return Points Won¬SH÷26% (10/38)¬SI÷47% (23/49)¬~SG÷Total Points Won¬SH÷41% (36/87)¬SI÷59% (51/87)¬~SF÷Games¬~SG÷Max Games In Row¬SH÷1¬SI÷3¬~SG÷Service Games Won¬SH÷57% (4/7)¬SI÷100% (6/6)¬~SG÷Return Games Won¬SH÷0% (0/6)¬SI÷43% (3/7)¬~SG÷Total Games Won¬SH÷31% (4/13)¬SI÷69% (9/13)¬~SE÷Set 1¬~SF÷Service¬~SG÷Aces¬SH÷3¬SI÷2¬~SG÷Double Faults¬SH÷0¬SI÷0¬~SG÷1st Serve Percentage¬SH÷67%¬SI÷81%¬~SG÷1st Serve Points Won¬SH÷75% (12/16)¬SI÷82% (14/17)¬~SG÷2nd Serve Points Won¬SH÷13% (1/8)¬SI÷75% (3/4)¬~SG÷Break Points Saved¬SH÷33% (1/3)¬SI÷100% (2/2)¬~SF÷Return¬~SG÷1st Return Points Won¬SH÷18% (3/17)¬SI÷25% (4/16)¬~SG÷2nd Return Points Won¬SH÷25% (1/4)¬SI÷88% (7/8)¬~SG÷Break Points Converted¬SH÷0% (0/2)¬SI÷67% (2/3)¬~SF÷Points¬~SG÷Service Points Won¬SH÷54% (13/24)¬SI÷81% (17/21)¬~SG÷Return Points Won¬SH÷19% (4/21)¬SI÷46% (11/24)¬~SG÷Total Points Won¬SH÷38% (17/45)¬SI÷62% (28/45)¬~SF÷Games¬~SG÷Service Games Won¬SH÷50% (2/4)¬SI÷100% (4/4)¬~SG÷Return Games Won¬SH÷0% (0/4)¬SI÷50% (2/4)¬~SG÷Total Games Won¬SH÷25% (2/8)¬SI÷75% (6/8)¬~SE÷Set 2¬~SF÷Service¬~SG÷Aces¬SH÷0¬SI÷0¬~SG÷Double Faults¬SH÷0¬SI÷0¬~SG÷1st Serve Percentage¬SH÷76%¬SI÷71%¬~SG÷1st Serve Points Won¬SH÷53% (10/19)¬SI÷75% (9/12)¬~SG÷2nd Serve Points Won¬SH÷50% (3/6)¬SI÷40% (2/5)¬~SG÷Break Points Saved¬SH÷80% (4/5)¬SI÷0% (0/0)¬~SF÷Return¬~SG÷1st Return Points Won¬SH÷25% (3/12)¬SI÷47% (9/19)¬~SG÷2nd Return Points Won¬SH÷60% (3/5)¬SI÷50% (3/6)¬~SG÷Break Points Converted¬SH÷0% (0/0)¬SI÷20% (1/5)¬~SF÷Points¬~SG÷Service Points Won¬SH÷52% (13/25)¬SI÷65% (11/17)¬~SG÷Return Points Won¬SH÷35% (6/17)¬SI÷48% (12/25)¬~SG÷Total Points Won¬SH÷45% (19/42)¬SI÷55% (23/42)¬~SF÷Games¬~SG÷Service Games Won¬SH÷67% (2/3)¬SI÷100% (2/2)¬~SG÷Return Games Won¬SH÷0% (0/2)¬SI÷33% (1/3)¬~SG÷Total Games Won¬SH÷40% (2/5)¬SI÷60% (3/5)¬~A1÷9bec4594788454c378180830ab0eaa63¬~

1
for_parse/tennisOdds.fsf Normal file

File diff suppressed because one or more lines are too long

23
go.mod Normal file
View File

@@ -0,0 +1,23 @@
module gordenko.dev/dima/flashscore
go 1.25.0
require (
github.com/PuerkitoBio/goquery v1.12.0
github.com/go-sql-driver/mysql v1.10.0
golang.org/x/net v0.57.0
gordenko.dev/dima/fixme v0.0.0-20230801160335-c5c6b3b00ea2
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69
gordenko.dev/dima/qx v0.0.0-20260720012756-9323ec898f91
gordenko.dev/dima/spider v0.0.0-20260721003948-776f3225b709
gordenko.dev/dima/textutil v0.0.0-20260718203502-62db7f60f8f7
gordenko.dev/dima/web v0.0.0-20260719063304-9b080f685b42
)
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/text v0.40.0 // indirect
gordenko.dev/dima/httpreq v1.0.1-0.20230801160925-0916b3afaf24 // indirect
)

93
go.sum Normal file
View File

@@ -0,0 +1,93 @@
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gordenko.dev/dima/fixme v0.0.0-20230801160335-c5c6b3b00ea2 h1:gKOjARkSrD8ARC29Wp0IOjnmCujtL4ju8andsNqH2BQ=
gordenko.dev/dima/fixme v0.0.0-20230801160335-c5c6b3b00ea2/go.mod h1:7tMDvA2ej8e7VwXIlcIZNtUe/Tk5jFAK498WqC+aMYE=
gordenko.dev/dima/httpreq v1.0.1-0.20230801160925-0916b3afaf24 h1:InEx24mURTm8oJstSxUPcBgbPyr/KtxBUaJzSGrOe1I=
gordenko.dev/dima/httpreq v1.0.1-0.20230801160925-0916b3afaf24/go.mod h1:N7UdxORFT8FsC6laerBR3Zf+ur0Zgpv/zuLD2n7SNTQ=
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69 h1:nyJ3mzTQ46yUeMZCdLyYcs7B5JCS54c67v84miyhq2E=
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69/go.mod h1:AxgKDktpqBVyIOhIcP+nlCpK+EsJyjN5kPdqyd8euVU=
gordenko.dev/dima/qx v0.0.0-20260720012756-9323ec898f91 h1:J+C9mqwH5XR1idAi/PcdTMFPRrySyo3EVpDWd4m0FFs=
gordenko.dev/dima/qx v0.0.0-20260720012756-9323ec898f91/go.mod h1:CR5GISpjHFHZ9HeSIo3fYRyp1+kVx0R9exzBYsIuB3c=
gordenko.dev/dima/spider v0.0.0-20260721003948-776f3225b709 h1:zhooBnyXJUCrViDncwUN7Kh6JMobo6vaiAtobHitxG4=
gordenko.dev/dima/spider v0.0.0-20260721003948-776f3225b709/go.mod h1:ZVNbQYItuH9vADWOc9d1Alam646Y29q6JIA6UNFj9N8=
gordenko.dev/dima/textutil v0.0.0-20260718203502-62db7f60f8f7 h1:KzwamqlIEgAjQdTVNVfjGnS1f3bC+PzkTdZfuQhbVfk=
gordenko.dev/dima/textutil v0.0.0-20260718203502-62db7f60f8f7/go.mod h1:M3e8S3N1USCMYmL6Q5uDQJaxfzBlWHnTX7rxCJQ6iJs=
gordenko.dev/dima/web v0.0.0-20260719063304-9b080f685b42 h1:dSMZyLvRuCg24UgKWr68kmgQ1SNcFHtd0jD0nhvnQLU=
gordenko.dev/dima/web v0.0.0-20260719063304-9b080f685b42/go.mod h1:m6fQP/HjoYlcz9gw2pXZNkvRzb6I/9rsMYESpnFzG8Q=

193
h2h.html Normal file

File diff suppressed because one or more lines are too long

2652
h2h.json Normal file

File diff suppressed because it is too large Load Diff

203
handball.sgo Normal file
View File

@@ -0,0 +1,203 @@
package flashscore
import (
"bytes"
"fmt"
webspider "spider"
"time"
"github.com/PuerkitoBio/goquery"
)
func listScheduledHandballMatches() (result []ScheduledMatch, err error) {
spider, err := webspider.NewSpider(nil)
if err != nil {
panic(err)
}
// Загружаем AJAX страницу с таблицами матчей
//url := fmt.Sprintf(h2hTablesURLPattern, matchID)
buf, err := spider.Get(handballAllGamesFeedURL, map[string]string{
"referer": "https://d.flashscore.com/x/feed/proxy-local",
"x-requested-with": "XMLHttpRequest",
// Важное поле! Код можно найти в исходниках либо подсмотреть. Без кода
// сервер отдает 401 ошибку
"x-fsign": feedSign,
"x-referer": "https://www.flashscore.com/handball/",
"x-geoip": "1",
})
if err != nil {
err = fmt.Errorf("Load handball All Games feed: %s", err)
return
}
result, err = parseListOfScheduledMatches(string(buf))
if err != nil {
err = fmt.Errorf("parseListOfScheduledMatches: %s", err)
return
}
return
}
func Check() (err error) {
matches, err := listScheduledHandballMatches()
if err != nil {
return
}
matches = DiscardScheduledMatchesFurtherThan(matches, 24*time.Hour)
for _, m := range matches {
report, err := GetUpcomingMatchReport(m.MatchID)
if err != nil {
// log
}
}
return
}
func GetUpcomingMatchReport(matchID string) (report HandballUpcomingMatchReport, err error) {
report.IsComplete = true
spider, err := webspider.NewSpider(nil)
if err != nil {
panic(err)
}
// Загружаем Landing
landingURL := fmt.Sprintf(h2hLandingURLPattern, matchID)
buf, err := spider.Get(landingURL, nil)
if err != nil {
err = fmt.Errorf("load h2hLanding: %s", err)
return
}
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
if err != nil {
err = fmt.Errorf("goquery.NewDocumentFromReader: %s", err)
return
}
// FIX - добавить чемпионат и парсинг инфы
//_, _, report.Home, report.Away, err = s.parseFootballMatchInfo(doc)
//if err != nil {
// return
//}
// Загружаем AJAX страницу с таблицами матчей
url := fmt.Sprintf(h2hTablesURLPattern, matchID)
buf, err = spider.Get(url, map[string]string{
"referer": "https://d.flashscore.com/x/feed/proxy-local",
"x-requested-with": "XMLHttpRequest",
// Важное поле! Код можно найти в исходниках либо подсмотреть. Без кода
// сервер отдает 401 ошибку
"x-fsign": feedSign,
"x-referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
"x-geoip": "1",
})
if err != nil {
err = fmt.Errorf("load h2hTables: %s", err)
return
}
doc, err = goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
if err != nil {
err = fmt.Errorf("goquery.NewDocumentFromReader: %s", err)
return
}
// var (dirtyTeamID string
// OVERALL - последние матчи команд (хозяев и гостей)
// Внутри блока <div id="tab-h2h-overall" будут 2 таблицы:
// <table class="head_to_head h2h_home"
// <table class="head_to_head h2h_away"
//
// Строки (tr) внутри таблицы имеют вид:
// <tr class="odd highlight"
// <tr class="even highlight"
overall := doc.Find("#tab-h2h-overall")
if overall.Length() == 0 {
err = fmt.Errorf("Div #tab-h2h-overall not found")
return
}
overall = overall.First()
// Последние матчи хозяев (во всех турнирах, на выезде и в гостях)
home := overall.Find(".h2h_home")
if home.Length() == 0 {
err = fmt.Errorf("Table .h2h_home not found")
return
}
report.HomeTeamMatches, err = parseMatchesResults(home.First().Find(".highlight"))
if err != nil {
return
}
// Последние матчи AWAY команды (во всех турнирах, на выезде и в гостях)
away := overall.Find(".h2h_away")
if away.Length() == 0 {
err = fmt.Errorf("Table .h2h_away not found")
return
}
report.AwayTeamMatches, err = parseMatchesResults(away.First().Find(".highlight"))
if err != nil {
return
}
// Последние личные встречи (во всех турнирах, на выезде и в гостях)
h2h := overall.Find(".h2h_mutual")
if home.Length() == 0 {
err = fmt.Errorf("Table .h2h_mutual not found")
return
}
report.H2H, err = parseMatchesResults(h2h.First().Find(".highlight"))
if err != nil {
return
}
/*
var hasLostMatch bool
hasLostMatch, report.HomeTeamMatches, err = s.addSummaryToFootballMatches(report.HomeTeamMatches)
if err != nil {
return
}
if hasLostMatch {
report.IsComplete = false
}
hasLostMatch, report.AwayTeamMatches, err = s.addSummaryToFootballMatches(report.AwayTeamMatches)
if err != nil {
return
}
if hasLostMatch {
report.IsComplete = false
}
hasLostMatch, report.H2H, err = s.addSummaryToFootballMatches(report.H2H)
if err != nil {
return
}
if hasLostMatch {
report.IsComplete = false
}
*/
// load odds +
report.Odds, err = loadFootballOdds(spider, matchID)
if err != nil {
err = fmt.Errorf("loadOdds: %s", err)
return
}
return
}

14
helpers.go Normal file
View File

@@ -0,0 +1,14 @@
package flashscore
import "time"
func DiscardScheduledMatchesFurtherThan(matches []ScheduledMatch, furtherThan time.Duration) (result []ScheduledMatch) {
now := time.Now()
for _, m := range matches {
if now.Add(furtherThan).After(m.StartTime) {
result = append(result, m)
}
}
return
}

1310
html/england.html Executable file

File diff suppressed because one or more lines are too long

1
html/fixtures-data-string Executable file

File diff suppressed because one or more lines are too long

17
html/fixtures-div.html Executable file

File diff suppressed because one or more lines are too long

1
html/fixtures-unescaped Executable file

File diff suppressed because one or more lines are too long

BIN
html/flashscore Executable file

Binary file not shown.

View File

@@ -0,0 +1,85 @@
<div class="detailMS">
<div class="detailMS__incidentsHeader stage-12">
<div class="detailMS__headerText">1st Half</div>
<div class="detailMS__headerScore">
<span class="p1_home" title="">1</span> - <span class="p1_away" title="">0</span>
</div>
</div>
<div class="detailMS__incidentRow incidentRow--home odd">
<div class="time-box">40'</div>
<div title="Harvey Barnes (Leicester) runs on to a clever&lt;br /&gt;pass from Marc Albrighton and finds himself&lt;br /&gt;one-on-one with the keeper. He still has&lt;br /&gt;work to do but finishes with aplomb into&lt;br /&gt;the back of the net." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box soccer-ball">
<span class="icon soccer-ball">&nbsp;</span>
</div>
<span class="participant-name">
<a href="#" onclick="window.open('/player/barnes-harvey/Wjj9WTTi/'); return false;">Barnes H.</a>
</span>
<span class="assist note-name">(<a href="#" onclick="window.open('/player/albrighton-marc/fLz0nwDI/'); return false;">Albrighton M.</a>)</span>
</div>
<div class="detailMS__incidentRow incidentRow--away even">
<div class="time-box">43'</div>
<div title="A yellow card for a tackle by Conor Hourihane&lt;br /&gt;(Aston Villa). Michael Oliver doesn't hesitate&lt;br /&gt;at all to make this decision." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box y-card">
<span class="icon y-card">&nbsp;</span>
</div>
<span class="subincident-penalty subincident-name">(Holding)</span>
<span class="participant-name">
<a href="#" onclick="window.open('/player/hourihane-conor/ruzYi0fn/'); return false;">Hourihane C.</a>
</span>
</div>
<div class="detailMS__incidentsHeader stage-13">
<div class="detailMS__headerText">2nd Half</div>
<div class="detailMS__headerScore">
<span class="p2_home" title="">3</span> -<span class="p2_away" title="">0</span>
</div>
</div>
<div class="detailMS__incidentRow incidentRow--home odd">
<div class="time-box">59'</div>
<div title="Substitution. Kelechi Iheanacho makes his&lt;br /&gt;way off the pitch and Jamie Vardy (Leicester)&lt;br /&gt;comes on as his replacement." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box substitution-in">
<span class="icon substitution-in">&nbsp;</span>
</div>
<span class="substitution-in-name">
<a href="#" onclick="window.open('/player/vardy-jamie/fP3OohgD/'); return false;">Vardy J.</a>
</span>
<span class="substitution-out-name">
<span class="icon substitution-out">&nbsp;</span>
<a href="#" onclick="window.open('/player/iheanacho-kelechi/dIaES012/'); return false;">Iheanacho K.</a>
</span>
</div>
<div class="detailMS__incidentRow incidentRow--home even">
<div class="time-box">63'</div>
<div title="Jamie Vardy (Leicester) makes no mistake&lt;br /&gt;from the penalty spot after waiting for&lt;br /&gt;Pepe Reina to move before slotting the ball&lt;br /&gt;low into the middle of the goal!" onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box soccer-ball">
<span class="icon soccer-ball">&nbsp;</span>
</div>
<span class="participant-name">
<a href="#" onclick="window.open('/player/vardy-jamie/fP3OohgD/'); return false;">Vardy J.</a>
</span>
<span class=" subincident-name">(Penalty)</span>
</div>
<div class="detailMS__incidentRow incidentRow--away odd">
<div class="time-box">63'</div>
<div title="Ahmed Elmohamady goes off the pitch and&lt;br /&gt;Anwar El-Ghazi (Aston Villa) comes on." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box substitution-in">
<span class="icon substitution-in">&nbsp;</span>
</div>
<span class="substitution-in-name">
<a href="#" onclick="window.open('/player/el-ghazi-anwar/fsRCsnjP/'); return false;">El-Ghazi A.</a>
</span>
<span class="substitution-out-name">
<a href="#" onclick="window.open('/player/elmohamady-ahmed/CONYFX94/'); return false;">Elmohamady A.</a>
<span class="icon substitution-out">&nbsp;</span>
</span>
</div>
<div class="detailMS__incidentRow incidentRow--away even">
<div class="time-box">67'</div>
<div title="Dean Smith has decided to make a change.&lt;br /&gt;Keinan Davis (Aston Villa) replaces Conor&lt;br /&gt;Hourihane." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box substitution-in"><span class="icon substitution-in">&nbsp;</span></div><span class="substitution-in-name"><a href="#" onclick="window.open('/player/davis-keinan/SKxz4XFS/'); return false;">Davis K.</a></span><span class="substitution-out-name"><a href="#" onclick="window.open('/player/hourihane-conor/ruzYi0fn/'); return false;">Hourihane C.</a><span class="icon substitution-out">&nbsp;</span></span></div><div class="detailMS__incidentRow incidentRow--home odd"><div class="time-box">71'</div><div title="Dennis Praet (Leicester) sees a yellow card&lt;br /&gt;from Michael Oliver after he sent one of&lt;br /&gt;the opposing players to the ground." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box y-card"><span class="icon y-card">&nbsp;</span></div><span class="participant-name"><a href="#" onclick="window.open('/player/praet-dennis/dUXdn4vC/'); return false;">Praet D.</a></span><span class="subincident-penalty subincident-name">(Tripping)</span></div><div class="detailMS__incidentRow incidentRow--home even"><div class="time-box">76'</div><div title="Michael Oliver shows the yellow card to&lt;br /&gt;Jonny Evans (Leicester) for a heavy challenge." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box y-card"><span class="icon y-card">&nbsp;</span></div><span class="participant-name"><a href="#" onclick="window.open('/player/evans-jonny/vo08q1o2/'); return false;">Evans J.</a></span><span class="subincident-penalty subincident-name">(Foul)</span></div><div class="detailMS__incidentRow incidentRow--home odd"><div class="time-box">76'</div><div title="Brendan Rodgers prepares a substitution.&lt;br /&gt;Dennis Praet is replaced by Youri Tielemans&lt;br /&gt;(Leicester)." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box substitution-in"><span class="icon substitution-in">&nbsp;</span></div><span class="substitution-in-name"><a href="#" onclick="window.open('/player/tielemans-youri/GtkTQyib/'); return false;">Tielemans Y.</a></span><span class="substitution-out-name"><span class="icon substitution-out">&nbsp;</span><a href="#" onclick="window.open('/player/praet-dennis/dUXdn4vC/'); return false;">Praet D.</a></span></div><div class="detailMS__incidentRow incidentRow--home even"><div class="time-box">79'</div><div title="Jamie Vardy (Leicester) takes advantage&lt;br /&gt;of a goalmouth scramble by calmly slotting&lt;br /&gt;the ball into the bottom left corner of&lt;br /&gt;the net!" onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box soccer-ball"><span class="icon soccer-ball">&nbsp;</span></div><span class="participant-name"><a href="#" onclick="window.open('/player/vardy-jamie/fP3OohgD/'); return false;">Vardy J.</a></span></div><div class="detailMS__incidentRow incidentRow--home odd"><div class="time-box">83'</div><div title="The manager has decided to make a substitution&lt;br /&gt;and Wilfred Ndidi walks off the pitch to&lt;br /&gt;be replaced by Nampalys Mendy (Leicester)." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box substitution-in"><span class="icon substitution-in">&nbsp;</span></div><span class="substitution-in-name"><a href="#" onclick="window.open('/player/mendy-nampalys/SAjbGhS3/'); return false;">Mendy N.</a></span><span class="substitution-out-name"><span class="icon substitution-out">&nbsp;</span><a href="#" onclick="window.open('/player/ndidi-wilfred/E71XyFGi/'); return false;">Ndidi W.</a></span></div><div class="detailMS__incidentRow incidentRow--home even"><div class="time-box">85'</div><div title="Harvey Barnes (Leicester) finds himself&lt;br /&gt;in a very promising position after getting&lt;br /&gt;on the end of a brilliant pass and fires&lt;br /&gt;the ball inside the right post!" onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box soccer-ball"><span class="icon soccer-ball">&nbsp;</span></div><span class="participant-name"><a href="#" onclick="window.open('/player/barnes-harvey/Wjj9WTTi/'); return false;">Barnes H.</a></span><span class="assist note-name">(<a href="#" onclick="window.open('/player/albrighton-marc/fLz0nwDI/'); return false;">Albrighton M.</a>)</span></div></div><div class="match-information-wrapper">
<div class="parts match-information">
<div>
<div class="stage-header">
<div class="h-part">Match Information</div>
</div>
<div class="match-information-data">
<div class="content">Referee: Oliver M. (Eng),&nbsp;</div>
<div class="content">Attendance: 32 125,&nbsp;</div>
<div class="content">Venue: King Power Stadium (Leicester)</div>
</div></div></div></div><div id="secret_hash"></div>

480
html/football.odds.html Executable file

File diff suppressed because one or more lines are too long

20
html/football_h2h_tr.html Executable file
View File

@@ -0,0 +1,20 @@
<tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_z7bKeorA', null, false); });">
<td>
<span class="date">1583784000</span>
</td>
<td class="flag_td " title="Premier League (England)">
<span class="flag fl_198"></span>PL]
</td>
<td class="name">
<span><strong>Leicester</strong></span>
</td>
<td class="name highTeam">
<span>Aston Villa</span>
</td>
<td class="">
<span class="score"><strong>4 : 0</strong></span>
</td>
<td class="winLose">
<a title="Loss" class="wld wld--l">L</a>
</td>
</tr>

204
html/h2h.data.england.html Executable file
View File

@@ -0,0 +1,204 @@
<div class="color-px-spacer">&nbsp;</div><div class="h2h-submenu"><ul class="ifmenu"><li class="divider"><li id="h2h-overall" class="li0"><span><a onclick="detail_tab(['head-2-head', 'all_h2h']);">Overall</a></span></li><li class="divider"><li id="h2h-home" class="li1"><span><a onclick="detail_tab(['head-2-head', '1_h2h']);">Aston Villa - Home</a></span></li><li class="divider"><li id="h2h-away" class="li2"><span><a onclick="detail_tab(['head-2-head', '2_h2h']);">Sheffield Utd - Away</a></span></li><li class="divider"></ul></div><div class="color-spacer lineup-spacer"></div><div id="tab-h2h-overall" style="display: block;"><div class="h2h-wrapper"><table class="head_to_head h2h_home"><colgroup><col width="1%"/><col width="1%"/><col width="48%"/><col width="48%"/><col width="1%"/><col width="1%"/></colgroup><thead><tr><td colspan="6" class="h-part last">Last matches: ASTON VILLA</td></tr></thead><tbody><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_z7bKeorA', null, false); });"><td><span class="date">1583784000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Leicester</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>4 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_OK2NPb5H', null, false); });"><td><span class="date">1583080200</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Manchester City</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_GtjJSnEN', null, false); });"><td><span class="date">1582383600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Southampton</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Sv4qwtWD', null, false); });"><td><span class="date">1581861600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Tottenham</strong></span></td><td class=""><span class="score"><strong>2 : 3</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_fLKR1J97', null, false); });"><td><span class="date">1580569200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Bournemouth</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_EDD0cOI3', null, false); });"><td><span class="date">1580240700</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Leicester</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_fXQp1nJd', null, false); });"><td><span class="date">1579635000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Watford</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_dKZ3Hnnt', null, false); });"><td><span class="date">1579359600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Brighton</span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_nVe6899P', null, false); });"><td><span class="date">1578846600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Manchester City</strong></span></td><td class=""><span class="score"><strong>1 : 6</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_WlOeb43c', null, false); });"><td><span class="date">1578513600</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name"><span>Leicester</span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lfjtUlPM', null, false); });"><td><span class="date">1578150060</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span><strong>Fulham</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_j3tSfjuQ', null, false); });"><td><span class="date">1577881800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Burnley</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_M7MJ3uue', null, false); });"><td><span class="date">1577545200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Watford</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>3 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_bR8sqgtE', null, false); });"><td><span class="date">1577372400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Norwich</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_EuEcq2zc', null, false); });"><td><span class="date">1576940400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Southampton</strong></span></td><td class=""><span class="score"><strong>1 : 3</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_A7aQqcfp', null, false); });"><td><span class="date">1576611900</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Liverpool</span></td><td class=""><span class="score"><strong>5 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_vRNnRKKS', null, false); });"><td><span class="date">1576335600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_thatKo7o', null, false); });"><td><span class="date">1575813600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Leicester</strong></span></td><td class=""><span class="score"><strong>1 : 4</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_MmWJ2pxB', null, false); });"><td><span class="date">1575487800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Chelsea</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_v5ETiTNh', null, false); });"><td><span class="date">1575217800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Manchester Utd</span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>2 : 2</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_l2dMcbfE', null, false); });"><td><span class="date">1574712000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Newcastle</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_hzTXjSL1', null, false); });"><td><span class="date">1573394400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Wolves</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_OIHalZa3', null, false); });"><td><span class="date">1572706800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Liverpool</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_CAtTaYiG', null, false); });"><td><span class="date">1572464700</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Wolves</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_KOZXZZpA', null, false); });"><td><span class="date">1572089400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Manchester City</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>3 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Q3kEto2P', null, false); });"><td><span class="date">1571493600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Brighton</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_UH9FLqQa', null, false); });"><td><span class="date">1570284000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Norwich</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>1 : 5</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_x02kHUYD', null, false); });"><td><span class="date">1569679200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span>Burnley</span></td><td class=""><span class="score"><strong>2 : 2</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_48g7pGOs', null, false); });"><td><span class="date">1569437100</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name"><span>Brighton</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>1 : 3</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_4tJXu8C7', null, false); });"><td><span class="date">1569166200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Arsenal</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>3 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_GhCW40qi', null, false); });"><td><span class="date">1568660400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span>West Ham</span></td><td class=""><span class="score"><strong>0 : 0</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_2kncj1Uc', null, false); });"><td><span class="date">1567260000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Crystal Palace</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_zmbIsbmI', null, false); });"><td><span class="date">1566931500</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name"><span>Crewe</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>1 : 6</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_8hhFupGu', null, false); });"><td><span class="date">1566586800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Everton</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_EogafSFP', null, false); });"><td><span class="date">1566050400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Bournemouth</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_jXHoudAL', null, false); });"><td><span class="date">1565454600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Tottenham</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>3 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_ABTAIwRe', null, false); });"><td><span class="date">1564839000</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>RB Leipzig</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>1 : 3</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_MLwhJZlD', null, false); });"><td><span class="date">1564236000</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Charlton</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>1 : 4</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lAx8pCmC', null, false); });"><td><span class="date">1563993900</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Walsall</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>1 : 5</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_tQWmkzZt', null, false); });"><td><span class="date">1563714000</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Shrewsbury</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_pQ7hEwo0', null, false); });"><td><span class="date">1563408000</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Minnesota United</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>0 : 3</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_KWJnxf6a', null, false); });"><td><span class="date">1558965600</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Derby</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_ARXKdR4H', null, false); });"><td><span class="date">1557860400</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span><strong>West Brom</strong></span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>1 : 1</strong><br/>(1 : 0)</span></td><td class="winLose"><a title="Loss/Draw" class="wld wld--dl">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_EavCbmz5', null, false); });"><td><span class="date">1557574200</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>West Brom</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_MospggQp', null, false); });"><td><span class="date">1557055800</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Norwich</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_SO5l4JYo', null, false); });"><td><span class="date">1556449200</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Leeds</span></td><td class="name highTeam"><span>Aston Villa</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lCal2xn1', null, false); });"><td><span class="date">1555934400</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Millwall</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_GpM7ka2e', null, false); });"><td><span class="date">1555682400</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Bolton</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>0 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_n9f1rreS', null, false); });"><td><span class="date">1555164000</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Bristol City</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_8IecqOuM', null, false); });"><td><span class="date">1554921900</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Rotherham</span></td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="hid"><td colspan="6" class="last lastR"><a class="show_more" onclick="glib_show_hidden('tab-h2h-overall','h2h_home',5); return false" href="#">Show more matches <span class="arrow"></span></a></td></tr></tbody></table></div><div class="h2h-wrapper"><table class="head_to_head h2h_away"><colgroup><col width="1%"/><col width="1%"/><col width="48%"/><col width="48%"/><col width="1%"/><col width="1%"/></colgroup><thead><tr><td colspan="6" class="h-part last">Last matches: SHEFFIELD UTD</td></tr></thead><tbody><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_OSjXhqSS', null, false); });"><td><span class="date">1583593200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Norwich</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_vZNmafet', null, false); });"><td><span class="date">1583265600</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span>Reading</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 2</strong><br/>(1 : 1)</span></td><td class="winLose"><a title="Draw/Win" class="wld wld--wo">W</a></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_IeXFTSbH', null, false); });"><td><span class="date">1582383600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span>Brighton</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_G4xXTyg0', null, false); });"><td><span class="date">1581256800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Bournemouth</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_KEv6PM1E', null, false); });"><td><span class="date">1580569200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Crystal Palace</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_SOiNQMcH', null, false); });"><td><span class="date">1579964400</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span>Millwall</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_KvOdbp3F', null, false); });"><td><span class="date">1579635000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span><strong>Manchester City</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_pngbd3GU', null, false); });"><td><span class="date">1579359600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Arsenal</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_AT0oa5WB', null, false); });"><td><span class="date">1578686400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>West Ham</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_tQUREIJI', null, false); });"><td><span class="date">1578232860</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>AFC Fylde</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_WdOfjAms', null, false); });"><td><span class="date">1577995200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Liverpool</strong></span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_fcxuBNnL', null, false); });"><td><span class="date">1577642400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Manchester City</strong></span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Oj1TZl30', null, false); });"><td><span class="date">1577372400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span>Watford</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_SrI1rMk3', null, false); });"><td><span class="date">1576940400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Brighton</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_vRNnRKKS', null, false); });"><td><span class="date">1576335600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Aston Villa</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lOk1FNLG', null, false); });"><td><span class="date">1575813600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Norwich</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_nVw75nhh', null, false); });"><td><span class="date">1575574200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span><strong>Newcastle</strong></span></td><td class=""><span class="score"><strong>0 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_xKAlm5xO', null, false); });"><td><span class="date">1575208800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Wolves</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_86u5kz2f', null, false); });"><td><span class="date">1574613000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span>Manchester Utd</span></td><td class=""><span class="score"><strong>3 : 3</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_bDTTi86e', null, false); });"><td><span class="date">1573311600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Tottenham</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_UXFstB7q', null, false); });"><td><span class="date">1572706800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Burnley</span></td><td class=""><span class="score"><strong>3 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_UJz9RBhj', null, false); });"><td><span class="date">1572098400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>West Ham</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_QRX0XtPB', null, false); });"><td><span class="date">1571684400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Arsenal</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_jXcOJNfC', null, false); });"><td><span class="date">1570284000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Watford</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>0 : 0</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_xOpx8Qe0', null, false); });"><td><span class="date">1569670200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span><strong>Liverpool</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_jPHlcg8m', null, false); });"><td><span class="date">1569437100</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span><strong>Sunderland</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_xpER1kZQ', null, false); });"><td><span class="date">1569074400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Everton</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_z1RGqjDr', null, false); });"><td><span class="date">1568469600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span><strong>Southampton</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_KQdhisFi', null, false); });"><td><span class="date">1567260000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Chelsea</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 2</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_nRNW0Lmt', null, false); });"><td><span class="date">1566931500</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Blackburn</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_0MPRzMUA', null, false); });"><td><span class="date">1566655200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span><strong>Leicester</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_0rRnoOxC', null, false); });"><td><span class="date">1566133200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Crystal Palace</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_OKxmKfYe', null, false); });"><td><span class="date">1565445600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Bournemouth</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_6Z6ljPZD', null, false); });"><td><span class="date">1564846200</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span><strong>Reims</strong></span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>3 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_veRqu6Vd', null, false); });"><td><span class="date">1564225200</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Barnsley</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>1 : 4</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Qiw2GMhd', null, false); });"><td><span class="date">1563906600</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Chesterfield</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 5</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_ClZcH2wj', null, false); });"><td><span class="date">1563631200</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Northampton</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_8K2MMVTR', null, false); });"><td><span class="date">1563302700</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span><strong>Burton</strong></span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_0zXQarPT', null, false); });"><td><span class="date">1562958000</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Betis</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_tQrF6Cfp', null, false); });"><td><span class="date">1557055800</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Stoke</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 2</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_210DbF2N', null, false); });"><td><span class="date">1556381700</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Ipswich</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_AJ987XNt', null, false); });"><td><span class="date">1555941600</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Hull</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 3</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Gb1t4bHl', null, false); });"><td><span class="date">1555673400</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Nottingham</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_n9GWcNfF', null, false); });"><td><span class="date">1555164000</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span>Millwall</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_4Qbtm6tc', null, false); });"><td><span class="date">1554921900</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Birmingham</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_bXiIRbEJ', null, false); });"><td><span class="date">1554559200</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Preston</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_GljliIx7', null, false); });"><td><span class="date">1553958000</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span>Sheffield Utd</span></td><td class="name"><span><strong>Bristol City</strong></span></td><td class=""><span class="score"><strong>2 : 3</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_2JBQb9vG', null, false); });"><td><span class="date">1552739400</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Leeds</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_nDUl33E4', null, false); });"><td><span class="date">1552419900</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Brentford</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_v77NnrUN', null, false); });"><td><span class="date">1552133700</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Rotherham</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="hid"><td colspan="6" class="last lastR"><a class="show_more" onclick="glib_show_hidden('tab-h2h-overall','h2h_away',5); return false" href="#">Show more matches <span class="arrow"></span></a></td></tr></tbody></table></div><div class="h2h-wrapper">
<table class="head_to_head h2h_mutual"><colgroup><col width="1%"/><col width="1%"/><col width="48%"/><col width="48%"/><col width="2%"/></colgroup><thead>
<tr><td colspan="6" class="h-part last">Head-to-head matches: ASTON VILLA - SHEFFIELD UTD</td></tr></thead><tbody><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_vRNnRKKS', null, false); });"><td><span class="date">1576335600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Aston Villa</span></td><td class="last"><span class="score"><strong>2 : 0</strong></span></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_G8pqgPk4', null, false); });"><td><span class="date">1549655100</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 3</strong></span></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_vqecO3iq', null, false); });"><td><span class="date">1535810400</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Aston Villa</span></td><td class="last"><span class="score"><strong>4 : 1</strong></span></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_06POS74c', null, false); });"><td><span class="date">1517341500</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Sheffield Utd</span></td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="last"><span class="score"><strong>0 : 1</strong></span></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Kvptsq7H', null, false); });"><td><span class="date">1514050200</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>2 : 2</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_z7vempF1', null, false); });"><td><span class="date">1388847600</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span>Aston Villa</span></td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="last"><span class="score"><strong>1 : 2</strong></span></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_6Tz7GGmR', null, false); });"><td><span class="date">1294498800</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span>Sheffield Utd</span></td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="last"><span class="score"><strong>1 : 3</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_dCEqGp39', null, false); });"><td><span class="date">1178381700</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 0</strong></span></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_ngN3tziG', null, false); });"><td><span class="date">1165867200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Sheffield Utd</span></td><td class="name"><span>Aston Villa</span></td><td class="last"><span class="score"><strong>2 : 2</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_hpfLrUu2', null, false); });"><td><span class="date">1105187400</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Aston Villa</span></td><td class="last"><span class="score"><strong>3 : 1</strong></span></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Ozck43w1', null, false); });"><td><span class="date">766504800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Sheffield Utd</span></td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="last"><span class="score"><strong>1 : 2</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lUA7cUpC', null, false); });"><td><span class="date">753807600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>1 : 0</strong></span></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_tEGW7kYb', null, false); });"><td><span class="date">728146800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 1</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_6m0LQW8L', null, false); });"><td><span class="date">715096800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Sheffield Utd</span></td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="last"><span class="score"><strong>0 : 2</strong></span></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_4n8isfBD', null, false); });"><td><span class="date">702050400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>1 : 1</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_4hPJg0u8', null, false); });"><td><span class="date">692722800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Aston Villa</span></td><td class="last"><span class="score"><strong>2 : 0</strong></span></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_08AJAZ3n', null, false); });"><td><span class="date">667926000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="name"><span>Aston Villa</span></td><td class="last"><span class="score"><strong>2 : 1</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_CEEUjQfN', null, false); });"><td><span class="date">660063600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>2 : 1</strong></span></td></tr><tr class="hid"><td colspan="6" class="last"><a class="show_more" onclick="glib_show_hidden('tab-h2h-overall','h2h_mutual',0); return false" href="#">Show more matches <span class="arrow"></span></a></td></tr></tbody></table></div></div>
<div id="tab-h2h-home" style="display: none;">
<div class="h2h-wrapper">
<table class="head_to_head h2h_home"><colgroup><col width="1%"/><col width="1%"/>
<col width="48%"/><col width="48%"/><col width="1%"/><col width="1%"/></colgroup>
<thead><tr><td colspan="6" class="h-part last">Last matches: ASTON VILLA</td></tr>
</thead><tbody>
<tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_OK2NPb5H', null, false); });">
<td><span class="date">1583080200</span></td>
<td class="flag_td " title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td>
<td class="name highTeam"><span>Aston Villa</span></td>
<td class="name"><span><strong>Manchester City</strong></span></td>
<td class=""><span class="score"><strong>1 : 2</strong></span></td>
<td class="winLose"><a title="Loss" class="wld wld--l">L</a></td>
</tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Sv4qwtWD', null, false); });"><td><span class="date">1581861600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Tottenham</strong></span></td><td class=""><span class="score"><strong>2 : 3</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_EDD0cOI3', null, false); });"><td><span class="date">1580240700</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Leicester</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_fXQp1nJd', null, false); });"><td><span class="date">1579635000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Watford</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_nVe6899P', null, false); });"><td><span class="date">1578846600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Manchester City</strong></span></td><td class=""><span class="score"><strong>1 : 6</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_bR8sqgtE', null, false); });"><td><span class="date">1577372400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Norwich</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_EuEcq2zc', null, false); });"><td><span class="date">1576940400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Southampton</strong></span></td><td class=""><span class="score"><strong>1 : 3</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_A7aQqcfp', null, false); });"><td><span class="date">1576611900</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Liverpool</span></td><td class=""><span class="score"><strong>5 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_thatKo7o', null, false); });"><td><span class="date">1575813600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Leicester</strong></span></td><td class=""><span class="score"><strong>1 : 4</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_l2dMcbfE', null, false); });"><td><span class="date">1574712000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Newcastle</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_OIHalZa3', null, false); });"><td><span class="date">1572706800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Liverpool</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_CAtTaYiG', null, false); });"><td><span class="date">1572464700</span></td><td class="flag_td "
title="EFL Cup (England)"><span class="flag fl_198"></span>EFL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Wolves</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Q3kEto2P', null, false); });"><td><span class="date">1571493600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Brighton</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_x02kHUYD', null, false); });"><td><span class="date">1569679200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span>Burnley</span></td><td class=""><span class="score"><strong>2 : 2</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_GhCW40qi', null, false); });"><td><span class="date">1568660400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span>West Ham</span></td><td class=""><span class="score"><strong>0 : 0</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_8hhFupGu', null, false); });"><td><span class="date">1566586800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Everton</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_EogafSFP', null, false); });"><td><span class="date">1566050400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Bournemouth</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_KWJnxf6a', null, false); });"><td><span class="date">1558965600</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Derby</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_EavCbmz5', null, false); });"><td><span class="date">1557574200</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>West Brom</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_MospggQp', null, false); });"><td><span class="date">1557055800</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span>Aston Villa</span></td><td class="name"><span><strong>Norwich</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lCal2xn1', null, false); });"><td><span class="date">1555934400</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Millwall</span></td><td class=""><span class="score"><strong>1 : 0</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_n9f1rreS', null, false); });"><td><span class="date">1555164000</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name highTeam"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Bristol City</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="hid"><td colspan="6" class="last lastR"><a class="show_more" onclick="glib_show_hidden('tab-h2h-home','h2h_home',5); return false" href="#">Show more matches <span class="arrow"></span></a></td></tr></tbody></table></div><div class="h2h-wrapper">
<table class="head_to_head h2h_mutual"><colgroup><col width="1%"/><col width="1%"/><col width="48%"/><col width="48%"/><col width="2%"/></colgroup><thead><tr><td colspan="6" class="h-part last">Head-to-head matches: ASTON VILLA - SHEFFIELD UTD</td></tr></thead><tbody><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_G8pqgPk4', null, false); });"><td><span class="date">1549655100</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 3</strong></span></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Kvptsq7H', null, false); });"><td><span class="date">1514050200</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>2 : 2</strong></span></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_z7vempF1', null, false); });"><td><span class="date">1388847600</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span>Aston Villa</span></td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="last"><span class="score"><strong>1 : 2</strong></span></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_dCEqGp39', null, false); });"><td><span class="date">1178381700</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 0</strong></span></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lUA7cUpC', null, false); });"><td><span class="date">753807600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>1 : 0</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_tEGW7kYb', null, false); });"><td><span class="date">728146800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 1</strong></span></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_4n8isfBD', null, false); });"><td><span class="date">702050400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>1 : 1</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_CEEUjQfN', null, false); });"><td><span class="date">660063600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>2 : 1</strong></span></td></tr><tr class="hid"><td colspan="6" class="last"><a class="show_more" onclick="glib_show_hidden('tab-h2h-home','h2h_mutual',0); return false" href="#">Show more matches <span class="arrow"></span></a></td></tr></tbody></table></div></div><div id="tab-h2h-away" style="display: none;"><div class="h2h-wrapper"><table class="head_to_head h2h_away"><colgroup><col width="1%"/><col width="1%"/><col width="48%"/><col width="48%"/><col width="1%"/><col width="1%"/></colgroup><thead><tr><td colspan="6" class="h-part last">Last matches: SHEFFIELD UTD</td></tr></thead><tbody><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_vZNmafet', null, false); });"><td><span class="date">1583265600</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span>Reading</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 2</strong><br/>(1 : 1)</span></td><td class="winLose"><a title="Draw/Win" class="wld wld--wo">W</a></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_KEv6PM1E', null, false); });"><td><span class="date">1580569200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Crystal Palace</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_SOiNQMcH', null, false); });"><td><span class="date">1579964400</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span>Millwall</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_pngbd3GU', null, false); });"><td><span class="date">1579359600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Arsenal</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_WdOfjAms', null, false); });"><td><span class="date">1577995200</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Liverpool</strong></span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_fcxuBNnL', null, false); });"><td><span class="date">1577642400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Manchester City</strong></span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 0</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_SrI1rMk3', null, false); });"><td><span class="date">1576940400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Brighton</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lOk1FNLG', null, false); });"><td><span class="date">1575813600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Norwich</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>1 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_xKAlm5xO', null, false); });"><td><span class="date">1575208800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Wolves</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_bDTTi86e', null, false); });"><td><span class="date">1573311600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Tottenham</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_UJz9RBhj', null, false); });"><td><span class="date">1572098400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>West Ham</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_jXcOJNfC', null, false); });"><td><span class="date">1570284000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Watford</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>0 : 0</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_xpER1kZQ', null, false); });"><td><span class="date">1569074400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Everton</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_KQdhisFi', null, false); });"><td><span class="date">1567260000</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Chelsea</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 2</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_OKxmKfYe', null, false); });"><td><span class="date">1565445600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Bournemouth</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_6Z6ljPZD', null, false); });"><td><span class="date">1564846200</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span><strong>Reims</strong></span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>3 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_veRqu6Vd', null, false); });"><td><span class="date">1564225200</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Barnsley</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>1 : 4</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Qiw2GMhd', null, false); });"><td><span class="date">1563906600</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Chesterfield</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 5</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_ClZcH2wj', null, false); });"><td><span class="date">1563631200</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Northampton</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 2</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_8K2MMVTR', null, false); });"><td><span class="date">1563302700</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span><strong>Burton</strong></span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 1</strong></span></td><td class="winLose"><a title="Loss" class="wld wld--l">L</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_0zXQarPT', null, false); });"><td><span class="date">1562958000</span></td><td class="flag_td "
title="Club Friendly (World)"><span class="flag fl_8"></span>CF</td><td class="name"><span>Betis</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_tQrF6Cfp', null, false); });"><td><span class="date">1557055800</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Stoke</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>2 : 2</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_AJ987XNt', null, false); });"><td><span class="date">1555941600</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Hull</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 3</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_4Qbtm6tc', null, false); });"><td><span class="date">1554921900</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Birmingham</span></td><td class="name highTeam"><span>Sheffield Utd</span></td><td class=""><span class="score"><strong>1 : 1</strong></span></td><td class="winLose"><a title="Draw" class="wld wld--d">D</a></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_bXiIRbEJ', null, false); });"><td><span class="date">1554559200</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Preston</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_2JBQb9vG', null, false); });"><td><span class="date">1552739400</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Leeds</span></td><td class="name highTeam"><span><strong>Sheffield Utd</strong></span></td><td class=""><span class="score"><strong>0 : 1</strong></span></td><td class="winLose"><a title="Win" class="wld wld--w">W</a></td></tr><tr class="hid"><td colspan="6" class="last lastR"><a class="show_more" onclick="glib_show_hidden('tab-h2h-away','h2h_away',5); return false" href="#">Show more matches <span class="arrow"></span></a></td></tr></tbody></table></div><div class="h2h-wrapper"><table class="head_to_head h2h_mutual"><colgroup><col width="1%"/><col width="1%"/><col width="48%"/><col width="48%"/><col width="2%"/></colgroup><thead><tr><td colspan="6" class="h-part last">Head-to-head matches: ASTON VILLA - SHEFFIELD UTD</td></tr></thead><tbody><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_G8pqgPk4', null, false); });"><td><span class="date">1549655100</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 3</strong></span></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_Kvptsq7H', null, false); });"><td><span class="date">1514050200</span></td><td class="flag_td "
title="Championship (England)"><span class="flag fl_198"></span>CHA</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>2 : 2</strong></span></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_z7vempF1', null, false); });"><td><span class="date">1388847600</span></td><td class="flag_td "
title="FA Cup (England)"><span class="flag fl_198"></span>FAC</td><td class="name"><span>Aston Villa</span></td><td class="name"><span><strong>Sheffield Utd</strong></span></td><td class="last"><span class="score"><strong>1 : 2</strong></span></td></tr><tr class="even highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_dCEqGp39', null, false); });"><td><span class="date">1178381700</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 0</strong></span></td></tr><tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_lUA7cUpC', null, false); });"><td><span class="date">753807600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>1 : 0</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_tEGW7kYb', null, false); });"><td><span class="date">728146800</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>3 : 1</strong></span></td></tr><tr class="odd highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_4n8isfBD', null, false); });"><td><span class="date">702050400</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span>Aston Villa</span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>1 : 1</strong></span></td></tr><tr class="even highlight hidden" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_CEEUjQfN', null, false); });"><td><span class="date">660063600</span></td><td class="flag_td "
title="Premier League (England)"><span class="flag fl_198"></span>PL</td><td class="name"><span><strong>Aston Villa</strong></span></td><td class="name"><span>Sheffield Utd</span></td><td class="last"><span class="score"><strong>2 : 1</strong></span></td></tr><tr class="hid"><td colspan="6" class="last"><a class="show_more" onclick="glib_show_hidden('tab-h2h-away','h2h_mutual',0); return false" href="#">Show more matches <span class="arrow"></span></a></td></tr></tbody></table></div></div><div class="spacer-block"></div><div id="secret_hash"></div>

480
html/h2h.england.html Executable file

File diff suppressed because one or more lines are too long

9426
html/odds.html Executable file

File diff suppressed because it is too large Load Diff

45
html/odds_full_time.tr.html Executable file
View File

@@ -0,0 +1,45 @@
<tr class="odd">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_bookmaker');">
<a href="/bookmaker/417/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="1xBet" class="elink">
<span style="background-image:url(/res/image/data/bookmakers/30-417.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span>
</a>
</div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/417/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));">
<span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_live-offer');">&nbsp;</span>
</div>
</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_1');">
<span class=" odds-wrap up" alt="3.01[u]3.24:">3.24</span>
</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_0');">
<span class=" odds-wrap down" alt="3.56[d]3.50:">3.50</span>
</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_2');">
<span class=" odds-wrap down" alt="2.49[d]2.37:">2.37</span>
</td>
</tr>
<tr class="even">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_bookmaker');">
<a href="/bookmaker/417/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="1xBet" class="elink">
<span style="background-image:url(/res/image/data/bookmakers/30-417.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span>
</a>
</div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/417/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));">
<span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_live-offer');">&nbsp;</span>
</div>
</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_1');">
<span class=" odds-wrap up" alt="3.01[u]3.24:">3.24</span>
</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_0');">
<span class=" odds-wrap down" alt="3.56[d]3.50:">3.50</span>
</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_2');">
<span class=" odds-wrap down" alt="2.49[d]2.37:">2.37</span>
</td>
</tr>

1
html/temp.html Executable file

File diff suppressed because one or more lines are too long

7
html/top-block Executable file
View File

@@ -0,0 +1,7 @@
AA÷h4n4CUOI¬AD÷1593630900¬ADE÷1593630900¬AB÷1¬CR÷1¬AC÷1¬CX÷West Ham¬ER÷Round 32¬RW÷0¬AX÷0¬BX÷-1¬WQ÷¬WM÷WHU¬AE÷West Ham¬JA÷bs9XOElg¬WU÷west-ham¬WN÷CHE¬AF÷Chelsea¬JB÷t6bxOY3a¬WV÷chelsea¬AW÷1¬AN÷n¬
AA÷pI7L1DA0¬AD÷1593104400¬ADE÷1593104400¬AB÷1¬CR÷1¬AC÷1¬CX÷Southampton¬ER÷Round 31¬RW÷0¬AX÷0¬BX÷-1¬WQ÷¬WM÷SOU¬AE÷Southampton¬JA÷0CQcsH5D¬WU÷southampton¬WN÷ARS¬AF÷Arsenal¬JB÷2H4t60rE¬WV÷arsenal¬AL÷{"1":[{"TVI":794,"BU":"https:\/\/www.mycanal.fr\/","BN":"CANAL+ Sport (Fra)"},{"TVI":11,"BU":"http:\/\/decouvrir.mycanal.fr\/","BN":"C+ Sport (Fra)"},{"TVI":680,"BU":"http:\/\/www.skysports.com\/","BN":"Sky Sports Premier League (UK\/Irl)"}]}¬BGS÷{"GB":[16],"CZ":[49,45],"SK":[411]}¬AW÷1¬AN÷n¬MW÷417|453|455|33|508|24|27|471|43¬
AA÷2y7t1tKk¬AD÷1592652600¬ADE÷1592652600¬AB÷1¬CR÷1¬AC÷1¬CX÷Watford¬ER÷Round 30¬RW÷0¬AX÷0¬BX÷-1¬WQ÷¬WM÷WAT¬AE÷Watford¬JA÷4bfJhj8c¬WU÷watford¬WN÷LEI¬AF÷Leicester¬JB÷bLgNiAN3¬WV÷leicester¬AL÷{"1":[{"TVI":361,"BU":"http:\/\/sport.bt.com\/","BN":"BT Sport"},{"TVI":73,"BU":"http:\/\/decouvrir.mycanal.fr\/","BN":"C+ France"}]}¬BGS÷{"GB":[16],"CZ":[49,45],"SK":[411]}¬AW÷1¬AN÷n¬MW÷417|453|454|455|33|544|508|24|27|471|43¬

124
main.xgo Executable file
View File

@@ -0,0 +1,124 @@
package flashscore
import (
"fmt"
//"io/ioutil"
//webspider "spider"
//"strconv"
//"strings"
//"time"
"bytes"
"github.com/PuerkitoBio/goquery"
//"github.com/kr/pretty"
//"html"
"flashscore/model"
)
//var (
// spider *webspider.Spider
//)
/*
err = ioutil.WriteFile("fixtures-unescaped", []byte(content), 0666)
if err != nil {
return
}
*/
var trsrc = `<html>
<body>
<table>
<tr class="odd highlight" onclick="cjs.Api.loader.get('cjs').call(function(_cjs){_cjs.fromGlobalScope.detail_open('g_0_z7bKeorA', null, false); });">
<td>
<span class="date">1583784000</span>
</td>
<td class="flag_td " title="Premier League (England)">
<span class="flag fl_198"></span>PL]
</td>
<td class="name">
<span><strong>Leicester</strong></span>
</td>
<td class="name highTeam">
<span>Aston Villa</span>
</td>
<td class="">
<span class="score"><strong>4 : 0</strong></span>
</td>
<td class="winLose">
<a title="Loss" class="wld wld--l">L</a>
</td>
</tr>
</table>
</body>
</html>`
func testParseTr(src string) (err error) {
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer([]byte(src)))
if err != nil {
return
}
/*
txt, err := doc.Html()
if err != nil {
return
}
fmt.Printf("%s\n", txt)
trs := doc.Find("tr")
fmt.Printf("trs: %d\n", trs.Length())
*/
trs := doc.Find(".highlight")
for trIdx := range trs.Nodes {
var m model.TeamMatch
//fmt.Printf("trIdx: %d\n", trIdx)
m, err = parseMatchResultTr(trs.Eq(trIdx))
if err != nil {
err = fmt.Errorf("parseTr: %s", err)
return
}
fmt.Printf("%v\n", m)
fmt.Println()
}
return
}
/*
*/
// cut
/*
txt, err := first.Html()
if err != nil {
return
}
err = ioutil.WriteFile("temp", []byte(html.UnescapeString(txt)), 0666)
if err != nil {
return
}
*/
// end cut
//n := first.Get(0)
//fmt.Printf("node: %#v\n\n", txt)
/*
Всё. Я написал. Парсит следующие страницы:
https://www.flashscore.com/football/england/premier-league/fixtures/ - расписание матчей
https://www.flashscore.com/match/lUKXoAEo/#h2h;overall - H2H (все 3 вкладки: overall, home, away)
https://www.flashscore.com/match/lUKXoAEo/#odds-comparison;1x2-odds;full-time - коэфф. на FullTime 1 x 2 (плюс определяет букмекеров)
https://www.flashscore.com/match/Sv4qwtWD/#match-summary - из отчета по матчу достает только счет 1го и 2го тайма
*/

21
model/errors.go Executable file
View File

@@ -0,0 +1,21 @@
package model
import "gordenko.dev/dima/fixme"
const (
EmptyValue fixme.Code = "100"
UnknownZone fixme.Code = "101"
UnknownChamp fixme.Code = "102"
UnknownTeam fixme.Code = "103"
Duplicate fixme.Code = "104"
)
var errorMessages = map[fixme.Code]string{
EmptyValue: "Пустое значение недопустимо",
UnknownZone: "Неизвестная страна/регион",
UnknownChamp: "Неизвестный чемпионат",
UnknownTeam: "Неизвестная команда",
Duplicate: "Дубликат!",
}
var fix = fixme.New(errorMessages)

416
model/football.sgo Normal file
View File

@@ -0,0 +1,416 @@
package model
import (
"fmt"
"strings"
"web/api"
)
func (s *Model) GetFootballZone(zoneID string) (_ *FootballZone, err error) {
zone := FootballZone{
ZoneID: zoneID,
}
found, err := s.qx.One(&zone)
if err != nil {
return
}
if !found {
return
}
return &zone, nil
}
func (s *Model) GetFootballChamp(champID string) (_ *FootballChamp, err error) {
champ := FootballChamp{
ChampID: champID,
}
found, err := s.qx.One(&champ)
if err != nil {
return
}
if !found {
return
}
return &champ, nil
}
func (s *Model) GetFootballTeam(teamID string) (_ *FootballTeam, err error) {
team := FootballTeam{
TeamID: teamID,
}
found, err := s.qx.One(&team)
if err != nil {
return
}
if !found {
return
}
return &team, nil
}
func (s *Model) GetFootballMatch(matchID string) (_ *FootballMatch, err error) {
m := FootballMatch{
MatchID: matchID,
}
found, err := s.qx.One(&m)
if err != nil {
return
}
if !found {
return
}
return &m, nil
}
func (s *Model) AddFootballZone(req FootballZone) (err error) {
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) AddFootballChamp(req FootballChamp) (err error) {
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
has, err := tx.Has("footballZones", "WHERE zoneID=?", req.ZoneID)
if err != nil {
return
}
if !has {
err = api.Err{
Code: UnknownZone,
Field: "zoneID",
}
return
}
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) UpdateFootballChampName(req FootballChamp) (err error) {
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
err = s.qx.Exec("UPDATE footballChamps SET name=? WHERE champID=?", req.Name, req.ChampID)
if err != nil {
return
}
return
}
func (s *Model) AddFootballTeam(req FootballTeam) (err error) {
req.CanonicalName = NormalizeNameToAlias(req.CanonicalName)
if req.CanonicalName == "" {
err = api.Err{
Code: EmptyValue,
Field: "canonicalName",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
_, err = tx.Insert(req)
if err != nil {
return
}
// Добавляем alias
_, err = tx.Insert(FootballAlias{
Name: req.CanonicalName,
TeamID: req.TeamID,
})
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) AddFootballTeamAlias(req FootballAlias) (err error) {
req.AliasID = 0
req.Name = NormalizeNameToAlias(req.Name)
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
hasTeam, err := tx.Has("footballTeams", "WHERE teamID=?", req.TeamID)
if err != nil {
return
}
if !hasTeam {
err = api.Err{
Code: UnknownTeam,
Field: "teamID",
}
return
}
hasAlias, err := tx.Has("footballTeamAliases", "WHERE teamID=? AND name=?",
req.TeamID, req.Name)
if err != nil {
return
}
if hasAlias {
err = api.Err{
Code: Duplicate,
Field: "name",
}
return
}
// Добавляем alias
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) SetFootballTeamWasLinked(teamID string) (err error) {
err = s.qx.Exec(`UPDATE footballTeams SET wasLinked=true WHERE teamID=?`, teamID)
return
}
func (s *Model) RemoveFootballTeamAlias(aliasID int64) (err error) {
return s.qx.Exec(`DELETE FROM footballTeamAliases WHERE aliasID=?`, aliasID)
}
func (s *Model) AddFootballMatch(req FootballMatch) (err error) {
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
req.MatchID = strings.TrimSpace(req.MatchID)
if req.MatchID == "" {
err = api.Err{
Code: EmptyValue,
Field: "matchID",
}
return
}
hasChamp, err := tx.Has("footballChamps", "WHERE champID=?", req.ChampID)
if err != nil {
return
}
if !hasChamp {
err = api.Err{
Code: UnknownChamp,
Field: "champID",
}
return
}
hasHomeTeam, err := tx.Has("footballTeams", "WHERE teamID=?", req.HomeTeamID)
if err != nil {
return
}
if !hasHomeTeam {
err = api.Err{
Code: UnknownTeam,
Field: "teamID",
}
return
}
hasAwayTeam, err := tx.Has("footballTeams", "WHERE teamID=?", req.AwayTeamID)
if err != nil {
return
}
if !hasAwayTeam {
err = api.Err{
Code: UnknownTeam,
Field: "teamID",
}
return
}
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
type UpdateFootballTeamCanonicalNameIn struct {
TeamID string
CanonicalName string
NewCanonicalName string
}
func (s *Model) UpdateFootballTeamCanonicalName(in UpdateFootballTeamCanonicalNameIn) (err error) {
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
// Старый canonicalName не нормализуем, ибо предполагается что имя мы получили из БД
in.NewCanonicalName = NormalizeNameToAlias(in.NewCanonicalName)
if in.NewCanonicalName == "" {
err = api.Err{
Code: EmptyValue,
Field: "newCanonicalName",
}
return
}
// Удаляем старый alias
err = tx.Exec("DELETE FROM footballTeamAliases WHERE teamID=? AND name=?",
in.TeamID, in.CanonicalName)
if err != nil {
return
}
// Обновляем canonicalName
err = tx.Exec("UPDATE footballTeams SET canonicalName=? WHERE teamID=?",
in.NewCanonicalName, in.TeamID)
if err != nil {
fmt.Printf("old: %q, new: %q, teamID: %s\n", in.CanonicalName,
in.NewCanonicalName, in.TeamID)
//return
err = nil
}
hasAlias, err := tx.Has("footballTeamAliases", "WHERE teamID=? AND name=?",
in.TeamID, in.NewCanonicalName)
if err != nil {
return
}
if !hasAlias {
// Добавляем новый alias
_, err = tx.Insert(FootballAlias{
Name: in.NewCanonicalName,
TeamID: in.TeamID,
})
if err != nil {
return
}
}
err = tx.Commit()
return
}
func (s *Model) ListFootballZones() (list []FootballZone, err error) {
err = s.qx.ListBy(&list, `ORDER BY name ASC`)
if err != nil {
return
}
return
}
func (s *Model) ListFootballTeamCandidates(name string) (matchIDs []string, err error) {
name = NormalizeNameToAlias(name)
err = s.qx.ListQuery(&matchIDs,
`SELECT teamID FROM footballTeamAliases WHERE name=?`, name)
if err != nil {
return
}
return
}
func (s *Model) ListFootballTeamsByZone(zoneID string) (list []FootballTeam, err error) {
err = s.qx.ListBy(&list, `WHERE zoneID=? ORDER BY canonicalName ASC`, zoneID)
if err != nil {
return
}
return
}
func (s *Model) ListFootballTeamAliases(teamID string) (list []FootballAlias, err error) {
err = s.qx.ListBy(&list, `WHERE teamID=? ORDER BY name ASC`, teamID)
if err != nil {
return
}
return
}
func (s *Model) ListFootballTeams() (list []FootballTeam, err error) {
err = s.qx.List(&list)
if err != nil {
return
}
return
}

163
model/football.sql Executable file
View File

@@ -0,0 +1,163 @@
CREATE TABLE footballZones (
zoneID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(zoneID),
UNIQUE(name)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE footballChamps (
champID VARCHAR(255) NOT NULL,
zoneID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(champID),
UNIQUE(zoneID, name),
FOREIGN KEY (zoneID) REFERENCES footballZones(zoneID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE footballTeams (
teamID VARCHAR(255) NOT NULL,
zoneID VARCHAR(255) NOT NULL,
canonicalName VARCHAR(255) NOT NULL,
wasLinked BOOL NOT NULL,
PRIMARY KEY(teamID),
--UNIQUE(zoneID, canonicalName),
FOREIGN KEY (zoneID) REFERENCES footballZones(zoneID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE footballTeamAliases (
aliasID INT NOT NULL AUTO_INCREMENT,
teamID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(aliasID),
UNIQUE(teamID, name),
FOREIGN KEY (teamID) REFERENCES footballTeams(teamID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE footballMatches (
matchID VARCHAR(255) NOT NULL,
startTime INT NOT NULL,
champID VARCHAR(255) NOT NULL,
homeTeamID VARCHAR(255) NOT NULL,
awayTeamID VARCHAR(255) NOT NULL,
homeGoals INT NOT NULL,
awayGoals INT NOT NULL,
hasStats BOOL NOT NULL,
p1HomeGoals INT NOT NULL,
p1AwayGoals INT NOT NULL,
p2HomeGoals INT NOT NULL,
p2AwayGoals INT NOT NULL,
PRIMARY KEY(matchID),
FOREIGN KEY (champID) REFERENCES footballChamps(champID),
FOREIGN KEY (homeTeamID) REFERENCES footballTeams(teamID),
FOREIGN KEY (awayTeamID) REFERENCES footballTeams(teamID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
--INSERT INTO footballChamps VALUES (NULL, "Seria A (Italy)");
--INSERT INTO footballChamps VALUES (NULL, "Bundesliga (Germany)");
--INSERT INTO footballChamps VALUES (NULL, "Premier League (England)");
delete from footballTeamAliases;
delete from footballMatches;
delete from footballTeams;
delete from footballChamps;
delete from footballZones;
-- HANDBALL
CREATE TABLE handballZones (
zoneID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(zoneID),
UNIQUE(name)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE handballChamps (
champID VARCHAR(255) NOT NULL,
zoneID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(champID),
UNIQUE(zoneID, name),
FOREIGN KEY (zoneID) REFERENCES handballZones(zoneID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE handballTeams (
teamID VARCHAR(255) NOT NULL,
zoneID VARCHAR(255) NOT NULL,
canonicalName VARCHAR(255) NOT NULL,
wasLinked BOOL NOT NULL,
PRIMARY KEY(teamID),
FOREIGN KEY (zoneID) REFERENCES handballZones(zoneID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE handballTeamAliases (
aliasID INT NOT NULL AUTO_INCREMENT,
teamID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(aliasID),
UNIQUE(teamID, name),
FOREIGN KEY (teamID) REFERENCES handballTeams(teamID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE handballMatches (
matchID VARCHAR(255) NOT NULL,
startTime INT NOT NULL,
champID VARCHAR(255) NOT NULL,
homeTeamID VARCHAR(255) NOT NULL,
awayTeamID VARCHAR(255) NOT NULL,
homeGoals INT NOT NULL,
awayGoals INT NOT NULL,
hasStats BOOL NOT NULL,
p1HomeGoals INT NOT NULL,
p1AwayGoals INT NOT NULL,
p2HomeGoals INT NOT NULL,
p2AwayGoals INT NOT NULL,
PRIMARY KEY(matchID),
FOREIGN KEY (champID) REFERENCES handballChamps(champID),
FOREIGN KEY (homeTeamID) REFERENCES handballTeams(teamID),
FOREIGN KEY (awayTeamID) REFERENCES handballTeams(teamID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
--INSERT INTO footballChamps VALUES (NULL, "Seria A (Italy)");
--INSERT INTO footballChamps VALUES (NULL, "Bundesliga (Germany)");
--INSERT INTO footballChamps VALUES (NULL, "Premier League (England)");
delete from handballTeamAliases;
delete from handballMatches;
delete from handballTeams;
delete from handballChamps;
delete from handballZones;

416
model/handball.sgo Normal file
View File

@@ -0,0 +1,416 @@
package model
import (
"fmt"
"strings"
"web/api"
)
func (s *Model) GetHandballZone(zoneID string) (_ *HandballZone, err error) {
zone := HandballZone{
ZoneID: zoneID,
}
found, err := s.qx.One(&zone)
if err != nil {
return
}
if !found {
return
}
return &zone, nil
}
func (s *Model) GetHandballChamp(champID string) (_ *HandballChamp, err error) {
champ := HandballChamp{
ChampID: champID,
}
found, err := s.qx.One(&champ)
if err != nil {
return
}
if !found {
return
}
return &champ, nil
}
func (s *Model) GetHandballTeam(teamID string) (_ *HandballTeam, err error) {
team := HandballTeam{
TeamID: teamID,
}
found, err := s.qx.One(&team)
if err != nil {
return
}
if !found {
return
}
return &team, nil
}
func (s *Model) GetHandballMatch(matchID string) (_ *HandballMatch, err error) {
m := HandballMatch{
MatchID: matchID,
}
found, err := s.qx.One(&m)
if err != nil {
return
}
if !found {
return
}
return &m, nil
}
func (s *Model) AddHandballZone(req HandballZone) (err error) {
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) AddHandballChamp(req HandballChamp) (err error) {
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
has, err := tx.Has("handballZones", "WHERE zoneID=?", req.ZoneID)
if err != nil {
return
}
if !has {
err = api.Err{
Code: UnknownZone,
Field: "zoneID",
}
return
}
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) UpdateHandballChampName(req HandballChamp) (err error) {
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
err = s.qx.Exec("UPDATE handballChamps SET name=? WHERE champID=?", req.Name, req.ChampID)
if err != nil {
return
}
return
}
func (s *Model) AddHandballTeam(req HandballTeam) (err error) {
req.CanonicalName = NormalizeNameToAlias(req.CanonicalName)
if req.CanonicalName == "" {
err = api.Err{
Code: EmptyValue,
Field: "canonicalName",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
_, err = tx.Insert(req)
if err != nil {
return
}
// Добавляем alias
_, err = tx.Insert(HandballAlias{
Name: req.CanonicalName,
TeamID: req.TeamID,
})
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) AddHandballTeamAlias(req HandballAlias) (err error) {
req.AliasID = 0
req.Name = NormalizeNameToAlias(req.Name)
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
hasTeam, err := tx.Has("handballTeams", "WHERE teamID=?", req.TeamID)
if err != nil {
return
}
if !hasTeam {
err = api.Err{
Code: UnknownTeam,
Field: "teamID",
}
return
}
hasAlias, err := tx.Has("handballTeamAliases", "WHERE teamID=? AND name=?",
req.TeamID, req.Name)
if err != nil {
return
}
if hasAlias {
err = api.Err{
Code: Duplicate,
Field: "name",
}
return
}
// Добавляем alias
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) SetHandballTeamWasLinked(teamID string) (err error) {
err = s.qx.Exec(`UPDATE handballTeams SET wasLinked=true WHERE teamID=?`, teamID)
return
}
func (s *Model) RemoveHandballTeamAlias(aliasID int64) (err error) {
return s.qx.Exec(`DELETE FROM handballTeamAliases WHERE aliasID=?`, aliasID)
}
func (s *Model) AddHandballMatch(req HandballMatch) (err error) {
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
req.MatchID = strings.TrimSpace(req.MatchID)
if req.MatchID == "" {
err = api.Err{
Code: EmptyValue,
Field: "matchID",
}
return
}
hasChamp, err := tx.Has("handballChamps", "WHERE champID=?", req.ChampID)
if err != nil {
return
}
if !hasChamp {
err = api.Err{
Code: UnknownChamp,
Field: "champID",
}
return
}
hasHomeTeam, err := tx.Has("handballTeams", "WHERE teamID=?", req.HomeTeamID)
if err != nil {
return
}
if !hasHomeTeam {
err = api.Err{
Code: UnknownTeam,
Field: "teamID",
}
return
}
hasAwayTeam, err := tx.Has("handballTeams", "WHERE teamID=?", req.AwayTeamID)
if err != nil {
return
}
if !hasAwayTeam {
err = api.Err{
Code: UnknownTeam,
Field: "teamID",
}
return
}
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
type UpdateHandballTeamCanonicalNameIn struct {
TeamID string
CanonicalName string
NewCanonicalName string
}
func (s *Model) UpdateHandballTeamCanonicalName(in UpdateHandballTeamCanonicalNameIn) (err error) {
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
// Старый canonicalName не нормализуем, ибо предполагается что имя мы получили из БД
in.NewCanonicalName = NormalizeNameToAlias(in.NewCanonicalName)
if in.NewCanonicalName == "" {
err = api.Err{
Code: EmptyValue,
Field: "newCanonicalName",
}
return
}
// Удаляем старый alias
err = tx.Exec("DELETE FROM handballTeamAliases WHERE teamID=? AND name=?",
in.TeamID, in.CanonicalName)
if err != nil {
return
}
// Обновляем canonicalName
err = tx.Exec("UPDATE handballTeams SET canonicalName=? WHERE teamID=?",
in.NewCanonicalName, in.TeamID)
if err != nil {
fmt.Printf("old: %q, new: %q, teamID: %s\n", in.CanonicalName,
in.NewCanonicalName, in.TeamID)
//return
err = nil
}
hasAlias, err := tx.Has("handballTeamAliases", "WHERE teamID=? AND name=?",
in.TeamID, in.NewCanonicalName)
if err != nil {
return
}
if !hasAlias {
// Добавляем новый alias
_, err = tx.Insert(HandballAlias{
Name: in.NewCanonicalName,
TeamID: in.TeamID,
})
if err != nil {
return
}
}
err = tx.Commit()
return
}
func (s *Model) ListHandballZones() (list []HandballZone, err error) {
err = s.qx.ListBy(&list, `ORDER BY name ASC`)
if err != nil {
return
}
return
}
func (s *Model) ListHandballTeamCandidates(name string) (matchIDs []string, err error) {
name = NormalizeNameToAlias(name)
err = s.qx.ListQuery(&matchIDs,
`SELECT teamID FROM handballTeamAliases WHERE name=?`, name)
if err != nil {
return
}
return
}
func (s *Model) ListHandballTeamsByZone(zoneID string) (list []HandballTeam, err error) {
err = s.qx.ListBy(&list, `WHERE zoneID=? ORDER BY canonicalName ASC`, zoneID)
if err != nil {
return
}
return
}
func (s *Model) ListHandballTeamAliases(teamID string) (list []HandballAlias, err error) {
err = s.qx.ListBy(&list, `WHERE teamID=? ORDER BY name ASC`, teamID)
if err != nil {
return
}
return
}
func (s *Model) ListHandballTeams() (list []HandballTeam, err error) {
err = s.qx.List(&list)
if err != nil {
return
}
return
}

82
model/model.go Executable file
View File

@@ -0,0 +1,82 @@
package model
import (
"regexp"
"strings"
"gordenko.dev/dima/qx"
"gordenko.dev/dima/textutil"
)
func New(qxdb *qx.Db) *Model {
s := new(Model)
s.qx = qxdb
return s
}
var reCountryCode = regexp.MustCompile(`\([a-z]{3}\)`)
// NormalizeNameToAlias - удаляет лишние пробельные символы. Заменяет whitespace символы на пробел,
// приводит к нижнему регистру
func NormalizeNameToAlias(name string) string {
name = strings.ToLower(name)
// вырезаем код страны если есть (пример: (Aut))
name = reCountryCode.ReplaceAllString(name, "")
name = strings.TrimSpace(name)
return textutil.NormalizeWhitespaces(name)
}
/*
func (s *Model) ListFootballChamps() (list []FootballChamp, err error) {
err = s.qx.ListBy(&list, "ORDER BY name ASC")
return
}
func (s *Model) ListFootballChampTeams(champID int64) (list []Team, err error) {
err = s.qx.ListBy(&list, "WHERE champID=? ORDER BY canonicalName ASC", champID)
return
}
func (s *Model) ListFootballTeamAliases(teamID int64) (list []Alias, err error) {
err = s.qx.ListBy(&list, "WHERE teamID=? ORDER BY name ASC", teamID)
return
}
func (s *Model) RemoveFootballTeam(teamID int64) (err error) {
return s.qx.Exec("DELETE FROM footballTeams WHERE teamID=?", teamID)
}
func (s *Model) RemoveFootballAlias(aliasID int64) (err error) {
return s.qx.Exec("DELETE FROM footballTeamAliases WHERE aliasID=?", aliasID)
}
func (s *Model) AddFootballAlias(req AddFootballAliasReq) (aliasID int64, err error) {
if req.Name == "" {
err = api.Err{
Code: EmptyValue,
Field: "name",
}
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
aliasID, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
*/

422
model/team.go Normal file
View File

@@ -0,0 +1,422 @@
package model
import (
"fmt"
"strings"
)
func (s *Model) GetTeamZone(zoneID string) (_ *TeamZone, err error) {
zone := TeamZone{
ZoneID: zoneID,
}
found, err := s.qx.One(&zone)
if err != nil {
return
}
if !found {
return
}
return &zone, nil
}
func (s *Model) GetTeamChamp(champID string) (_ *TeamChamp, err error) {
champ := TeamChamp{
ChampID: champID,
}
found, err := s.qx.One(&champ)
if err != nil {
return
}
if !found {
return
}
return &champ, nil
}
func (s *Model) GetTeam(teamID string) (_ *Team, err error) {
team := Team{
TeamID: teamID,
}
found, err := s.qx.One(&team)
if err != nil {
return
}
if !found {
return
}
return &team, nil
}
func (s *Model) GetTeamMatch(matchID string) (_ *TeamMatch, err error) {
m := TeamMatch{
MatchID: matchID,
}
found, err := s.qx.One(&m)
if err != nil {
return
}
if !found {
return
}
return &m, nil
}
func (s *Model) AddTeamZone(req TeamZone) (err error) {
if req.Name == "" {
err = fix.Field(EmptyValue, "name")
return
}
req.Name = strings.Title(strings.ToLower(req.Name))
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) AddTeamChamp(req TeamChamp) (err error) {
if req.Name == "" {
err = fix.Field(EmptyValue, "name")
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
has, err := tx.Has("teamZones", "WHERE zoneID=?", req.ZoneID)
if err != nil {
return
}
if !has {
err = fix.Field(UnknownZone, "zoneID")
return
}
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
/*
func (s *Model) UpdateTeamChampName(req TeamChamp) (err error) {
if req.Name == "" {
err = fix.Field(EmptyValue, "name")
return
}
err = s.qx.Exec("UPDATE teamChamps SET name=? WHERE champID=?", req.Name, req.ChampID)
if err != nil {
return
}
return
}
*/
func (s *Model) AddTeam(req Team) (err error) {
aliasName := NormalizeNameToAlias(req.CanonicalName)
req.CanonicalName = strings.Title(strings.ToLower(aliasName))
if req.CanonicalName == "" {
err = fix.Field(EmptyValue, "canonicalName")
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
_, err = tx.Insert(req)
if err != nil {
return
}
zone := TeamZone{
ZoneID: req.ZoneID,
}
_, err = tx.One(&zone)
if err != nil {
return
}
// Добавляем alias
_, err = tx.Insert(TeamAlias{
Name: aliasName,
TeamID: req.TeamID,
SportID: zone.SportID,
})
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) AddTeamAlias(req TeamAlias) (err error) {
req.AliasID = 0
req.Name = NormalizeNameToAlias(req.Name)
if req.Name == "" {
err = fix.Field(EmptyValue, "name")
return
}
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
// Чтобы узнать sportID сперва достаем Team
team := Team{
TeamID: req.TeamID,
}
found, err := tx.One(&team)
if err != nil {
return
}
if !found {
err = fix.Field(UnknownTeam, "teamID")
return
}
// Чтобы узнать sportID затем достаем teamZone
zone := TeamZone{
ZoneID: team.ZoneID,
}
_, err = tx.One(&zone)
if err != nil {
return
}
req.SportID = zone.SportID
/*
hasTeam, err := tx.Has("teams", "WHERE teamID=?", req.TeamID)
if err != nil {
return
}
if !hasTeam {
err = fix.Field(UnknownTeam, "teamID")
return
}
*/
hasAlias, err := tx.Has("teamAliases", "WHERE teamID=? AND name=?",
req.TeamID, req.Name)
if err != nil {
return
}
if hasAlias {
err = fix.Field(Duplicate, "name")
return
}
// Добавляем alias
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
func (s *Model) SetTeamWasLinked(teamID string) (err error) {
err = s.qx.Exec(`UPDATE teams SET wasLinked=true WHERE teamID=?`, teamID)
return
}
func (s *Model) RemoveTeamAlias(aliasID int64) (err error) {
return s.qx.Exec(`DELETE FROM teamAliases WHERE aliasID=?`, aliasID)
}
func (s *Model) AddTeamMatch(req TeamMatch) (err error) {
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
req.MatchID = strings.TrimSpace(req.MatchID)
if req.MatchID == "" {
err = fix.Field(EmptyValue, "matchID")
return
}
/*
hasChamp, err := tx.Has("teamChamps", "WHERE champID=?", req.ChampID)
if err != nil {
return
}
if !hasChamp {
err = fix.Field(UnknownChamp, "champID")
return
}
hasHomeTeam, err := tx.Has("teams", "WHERE teamID=?", req.HomeTeamID)
if err != nil {
return
}
if !hasHomeTeam {
err = fix.Field(UnknownTeam, "teamID")
return
}
hasAwayTeam, err := tx.Has("teams", "WHERE teamID=?", req.AwayTeamID)
if err != nil {
return
}
if !hasAwayTeam {
err = fix.Field(UnknownTeam, "teamID")
return
}
*/
_, err = tx.Insert(req)
if err != nil {
return
}
err = tx.Commit()
return
}
type UpdateTeamCanonicalNameIn struct {
TeamID string
CanonicalName string
NewCanonicalName string
}
func (s *Model) UpdateTeamCanonicalName(in UpdateTeamCanonicalNameIn) (err error) {
tx, err := s.qx.Tx()
if err != nil {
return
}
defer tx.Close(&err)
// Старый canonicalName не нормализуем, ибо предполагается что имя мы получили из БД
in.NewCanonicalName = strings.Title(strings.ToLower(NormalizeNameToAlias(in.NewCanonicalName)))
if in.NewCanonicalName == "" {
err = fix.Field(EmptyValue, "newCanonicalName")
return
}
// Удаляем старый alias
err = tx.Exec("DELETE FROM teamAliases WHERE teamID=? AND name=?",
in.TeamID, in.CanonicalName)
if err != nil {
return
}
// Обновляем canonicalName
err = tx.Exec("UPDATE teams SET canonicalName=? WHERE teamID=?",
in.NewCanonicalName, in.TeamID)
if err != nil {
fmt.Printf("old: %q, new: %q, teamID: %s\n", in.CanonicalName,
in.NewCanonicalName, in.TeamID)
//return
err = nil
}
hasAlias, err := tx.Has("teamAliases", "WHERE teamID=? AND name=?",
in.TeamID, in.NewCanonicalName)
if err != nil {
return
}
if !hasAlias {
// Добавляем новый alias
_, err = tx.Insert(TeamAlias{
Name: in.NewCanonicalName,
TeamID: in.TeamID,
})
if err != nil {
return
}
}
err = tx.Commit()
return
}
func (s *Model) ListTeamZones(sportID int) (list []TeamZone, err error) {
err = s.qx.ListBy(&list, `WHERE sportID=? ORDER BY name ASC`, sportID)
if err != nil {
return
}
return
}
func (s *Model) ListTeamCandidates(sportID int, name string) (matchIDs []string, err error) {
name = NormalizeNameToAlias(name)
err = s.qx.ListQuery(&matchIDs,
`SELECT teamID FROM teamAliases WHERE sportID=? AND name=?`, sportID, name)
if err != nil {
return
}
return
}
func (s *Model) ListTeamsByZone(zoneID string) (list []Team, err error) {
err = s.qx.ListBy(&list, `WHERE zoneID=? ORDER BY canonicalName ASC`, zoneID)
if err != nil {
return
}
return
}
func (s *Model) ListTeamAliases(teamID string) (list []TeamAlias, err error) {
err = s.qx.ListBy(&list, `WHERE teamID=? ORDER BY name ASC`, teamID)
if err != nil {
return
}
return
}
func (s *Model) ListTeams() (list []Team, err error) {
err = s.qx.List(&list)
if err != nil {
return
}
return
}

77
model/team.sql Normal file
View File

@@ -0,0 +1,77 @@
CREATE TABLE teamZones (
zoneID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
sportID INT NOT NULL, -- для вывода
PRIMARY KEY(zoneID),
UNIQUE(name)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE teamChamps (
champID VARCHAR(255) NOT NULL,
zoneID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(champID),
UNIQUE(zoneID, name),
FOREIGN KEY (zoneID) REFERENCES teamZones(zoneID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE teams (
teamID VARCHAR(255) NOT NULL,
zoneID VARCHAR(255) NOT NULL,
canonicalName VARCHAR(255) NOT NULL,
wasLinked BOOL NOT NULL,
PRIMARY KEY(teamID),
FOREIGN KEY (zoneID) REFERENCES teamZones(zoneID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE teamAliases (
aliasID INT NOT NULL AUTO_INCREMENT,
teamID VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
sportID INT NOT NULL, -- для поиска названий кандидатов без JOIN-ов
PRIMARY KEY(aliasID),
UNIQUE(teamID, name),
FOREIGN KEY (teamID) REFERENCES teams(teamID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
-- это просто cache
CREATE TABLE teamMatches (
matchID VARCHAR(255) NOT NULL,
homeGoals INT NOT NULL,
awayGoals INT NOT NULL,
hasScoreByPeriods BOOL NOT NULL,
p1HomeGoals INT NOT NULL,
p1AwayGoals INT NOT NULL,
p2HomeGoals INT NOT NULL,
p2AwayGoals INT NOT NULL,
isMatchScoreLoaded BOOL NOT NULL,
isScoreByPeriodsLoaded BOOL NOT NULL,
PRIMARY KEY(matchID)
)
ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
--INSERT INTO footballChamps VALUES (NULL, "Seria A (Italy)");
--INSERT INTO footballChamps VALUES (NULL, "Bundesliga (Germany)");
--INSERT INTO footballChamps VALUES (NULL, "Premier League (England)");
delete from teamAliases;
delete from teamMatches;
delete from teams;
delete from teamChamps;
delete from teamZones;

136
model/types.go Executable file
View File

@@ -0,0 +1,136 @@
package model
import "gordenko.dev/dima/qx"
type Model struct {
qx *qx.Db
}
// TEAM
type TeamZone struct {
ZoneID string `json:"zoneID" qx:"table=teamZones; pk"`
Name string `json:"name"`
SportID int `json:"sportID"`
}
type TeamMatch struct {
MatchID string `json:"matchID" qx:"table=teamMatches; pk"`
//SportID int `json:"sportID"`
//StartTime int64 `json:"startTime"`
//ChampID string `json:"champID"`
//HomeTeamID string `json:"homeTeamID"`
//AwayTeamID string `json:"awayTeamID"`
HomeGoals int `json:"homeGoals"`
AwayGoals int `json:"awayGoals"`
HasScoreByPeriods bool `json:"hasScoreByPeriods"`
P1HomeGoals int `json:"p1HomeGoals"`
P1AwayGoals int `json:"p1AwayGoals"`
P2HomeGoals int `json:"p2HomeGoals"`
P2AwayGoals int `json:"p2AwayGoals"`
IsMatchScoreLoaded bool `json:"isMatchScoreLoaded"`
IsScoreByPeriodsLoaded bool `json:"isScoreByPeriodsLoaded"`
}
type Team struct {
TeamID string `json:"teamID" qx:"table=teams; pk"`
ZoneID string `json:"zoneID"`
CanonicalName string `json:"canonicalName"`
WasLinked bool `json:"wasLinked"`
}
type TeamAlias struct {
AliasID int64 `json:"aliasID" qx:"table=teamAliases; pk; seq=auto"`
Name string `json:"name"`
TeamID string `json:"teamID"`
SportID int `json:"sportID"`
}
type TeamChamp struct {
ChampID string `json:"champID" qx:"table=teamChamps; pk"`
ZoneID string `json:"zoneID"`
Name string `json:"name"`
}
// FOOTBALL
/*
type FootballZone struct {
ZoneID string `json:"zoneID" qx:"table=footballZones; pk"`
Name string `json:"name"`
}
type FootballMatch struct {
MatchID string `json:"matchID" qx:"table=footballMatches; pk"`
StartTime int64 `json:"startTime"`
ChampID string `json:"champID"`
HomeTeamID string `json:"homeTeamID"`
AwayTeamID string `json:"awayTeamID"`
HomeGoals int `json:"homeGoals"`
AwayGoals int `json:"awayGoals"`
HasStats bool `json:"hasStats"`
P1HomeGoals int `json:"p1HomeGoals"`
P1AwayGoals int `json:"p1AwayGoals"`
P2HomeGoals int `json:"p2HomeGoals"`
P2AwayGoals int `json:"p2AwayGoals"`
}
type FootballTeam struct {
TeamID string `json:"teamID" qx:"table=footballTeams; pk"`
ZoneID string `json:"zoneID"`
CanonicalName string `json:"canonicalName"`
WasLinked bool `json:"wasLinked"`
}
type FootballAlias struct {
AliasID int64 `json:"aliasID" qx:"table=footballTeamAliases; pk; seq=auto"`
Name string `json:"name"`
TeamID string `json:"teamID"`
}
type FootballChamp struct {
ChampID string `json:"champID" qx:"table=footballChamps; pk"`
ZoneID string `json:"zoneID"`
Name string `json:"name"`
}
// HANDBALL
type HandballZone struct {
ZoneID string `json:"zoneID" qx:"table=handballZones; pk"`
Name string `json:"name"`
}
type HandballMatch struct {
MatchID string `json:"matchID" qx:"table=handballMatches; pk"`
StartTime int64 `json:"startTime"`
ChampID string `json:"champID"`
HomeTeamID string `json:"homeTeamID"`
AwayTeamID string `json:"awayTeamID"`
HomeGoals int `json:"homeGoals"`
AwayGoals int `json:"awayGoals"`
HasStats bool `json:"hasStats"`
P1HomeGoals int `json:"p1HomeGoals"`
P1AwayGoals int `json:"p1AwayGoals"`
P2HomeGoals int `json:"p2HomeGoals"`
P2AwayGoals int `json:"p2AwayGoals"`
}
type HandballTeam struct {
TeamID string `json:"teamID" qx:"table=handballTeams; pk"`
ZoneID string `json:"zoneID"`
CanonicalName string `json:"canonicalName"`
WasLinked bool `json:"wasLinked"`
}
type HandballAlias struct {
AliasID int64 `json:"aliasID" qx:"table=handballTeamAliases; pk; seq=auto"`
Name string `json:"name"`
TeamID string `json:"teamID"`
}
type HandballChamp struct {
ChampID string `json:"champID" qx:"table=handballChamps; pk"`
ZoneID string `json:"zoneID"`
Name string `json:"name"`
}
*/

1594
parser.go Executable file

File diff suppressed because it is too large Load Diff

527
parser_test.go Executable file
View File

@@ -0,0 +1,527 @@
package flashscore
import (
"bytes"
"fmt"
"os"
"strings"
"github.com/PuerkitoBio/goquery"
// xhtml "golang.org/x/net/html"
//"strings"
"encoding/json"
"testing"
)
/*
func TestParseFootballZoneAndChamp(t *testing.T) {
var src = []byte(`<span class="description__country">ENGLAND - SERIA A - DIVISION 1: <a href="#" onclick="window.open('/football/england/premier-league/'); return false;">Premier League - Round 30</a></span>
<div class="team-text tname-home">
<div class="tname">
<div class="tname__text">
<span style="display: none" class="dw-icon ico">&nbsp;</span>
<a href="#" class="participant-imglink" onclick="window.open('/team/newcastle-utd/p6ahwuwJ'); return false;">Newcastle &amp; Co</a>
</div>
</div>
</div>
<div class="team-text tname-away">
<div class="tname">
<div class="tname__text">
<span style="display: none" class="dw-icon ico">&nbsp;</span>
<a href="#" class="participant-imglink" onclick="window.open('/team/sheffield-utd/MeKPSerA'); return false;">Sheffield Utd (Eng)</a>
</div>
</div>
</div>`)
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(src))
if err != nil {
t.Fatal(err)
}
zone, champ, home, away, err := parseTeamMatchInfo(doc, Football)
if err != nil {
t.Fatal(err)
}
fmt.Printf("zone: %s\n", zone.Name)
fmt.Printf("zoneID: %s\n\n", zone.ZoneID)
fmt.Printf("champ.ChampID: %s\n", champ.ChampID)
fmt.Printf("champ.Name: %v\n", champ.Name)
fmt.Printf("champ.ZoneID: %v\n\n", champ.ZoneID)
fmt.Printf("home.CanonicalName: %s\n", home.CanonicalName)
fmt.Printf("home.TeamID: %s\n", home.TeamID)
fmt.Printf("home.ZoneID: %s\n\n", home.ZoneID)
fmt.Printf("away.CanonicalName: %s\n", away.CanonicalName)
fmt.Printf("away.TeamID: %s\n", away.TeamID)
fmt.Printf("away.ZoneID: %s\n", away.ZoneID)
}
*/
//var reChampName = regexp.MustCompile(`([^])`)
func TestTeamZone(t *testing.T) {
var teamZone = []byte(`<h2 class="tournament">
<div class="sportIcon sportIcon--soccer"></div>
<a href="/football/">Football</a>
<span class="breadcrumb__arrow"></span>
<span class="flag fl_198"></span>
<a href="/football/england/">England</a>
</h2>`)
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(teamZone))
if err != nil {
t.Fatal(err)
}
zone, err := parseZoneOnTeamPage(doc, Football)
if err != nil {
t.Fatal(err)
}
fmt.Printf("zone.ZoneID: %s\nzone.Name: %s\n", zone.ZoneID, zone.Name)
}
func TestFullTimeTotalOdds(t *testing.T) {
var src = []byte(`<div id="block-under-over-ft" class="odds-table-wrapper" style="display: block;">
<table id="odds_ou_0.5" class="odds sortable" cellspacing="0">
<colgroup>
<col/>
<col width="60"/>
<col width="60"/>
<col width="60"/>
</colgroup>
<thead>
<tr>
<th class="bookmaker no-padding unsortable">Bookmaker</th>
<th class="unsortable">Total</th>
<th>Over</th>
<th>Under</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/417/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="1xBet" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-417.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/417/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));"><span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_live-offer');">&nbsp;</span></div>
</td>
<td>0.5</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class=" odds-wrap up" alt="1.07[u]1.09:">1.09</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class=" odds-wrap down" alt="7.00[d]5.90:">5.90</span></td>
</tr>
<tr class="even">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/16/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="bet365" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-16.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/16/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));"><span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_live-offer');">&nbsp;</span></div>
</td>
<td>0.5</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class=" odds-wrap up" alt="1.10[u]1.11:">1.11</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class=" odds-wrap down" alt="7.00[d]6.50:">6.50</span></td>
</tr>
</tbody>
</table>
<table id="odds_ou_2.5" class="odds sortable" cellspacing="0">
<colgroup>
<col/>
<col width="60"/>
<col width="60"/>
<col width="60"/>
</colgroup>
<thead>
<tr>
<th class="bookmaker no-padding unsortable">Bookmaker</th>
<th class="unsortable">Total</th>
<th>Over</th>
<th>Under</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/417/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="1xBet" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-417.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/417/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));"><span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_live-offer');">&nbsp;</span></div>
</td>
<td>2.5</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class=" odds-wrap up" alt="2.41[u]2.91:">2.91</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class=" odds-wrap down" alt="1.54[d]1.52:">1.52</span></td>
</tr>
<tr class="even">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/16/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="bet365" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-16.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/16/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));"><span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_live-offer');">&nbsp;</span></div>
</td>
<td>2.5</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class=" odds-wrap up" alt="2.50[u]2.75:">2.75</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class=" odds-wrap down" alt="1.53[d]1.44:">1.44</span></td>
</tr>
<tr class="odd">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/15/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="William Hill" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-15.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
</td>
<td>2.5</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class="not-published odds-wrap " alt=":">2.38</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class="not-published odds-wrap " alt=":">1.53</span></td>
</tr>
</tbody>
</table>
</div>`)
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(src))
if err != nil {
t.Fatal(err)
}
odds, err := parseOddsTotal(doc)
if err != nil {
t.Fatal(err)
}
buf, _ := json.MarshalIndent(odds, "", " ")
fmt.Printf("Total odds: %s\n", buf)
}
// <table id="odds_1x2"
func TestParse1x2Odds(t *testing.T) {
var oddssrc = []byte(`<table id="odds_1x2"><tr class="odd">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_bookmaker');">
<a href="/bookmaker/417/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="1xBet" class="elink">
<span style="background-image:url(/res/image/data/bookmakers/30-417.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span>
</a>
</div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/417/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));">
<span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_live-offer');">&nbsp;</span>
</div>
</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_1');">
<span class=" odds-wrap up" alt="3.01[u]3.24:">3.24</span>
</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_0');">
<span class=" odds-wrap down" alt="3.56[d]3.50:">3.50</span>
</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_2');">
<span class=" odds-wrap down" alt="2.49[d]2.37:">2.37</span>
</td>
</tr>
<tr class="even">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_bookmaker');">
<a href="/bookmaker/17/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="1xBet" class="elink">
<span style="background-image:url(/res/image/data/bookmakers/30-417.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span>
</a>
</div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/417/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));">
<span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_live-offer');">&nbsp;</span>
</div>
</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_1');">
<span class=" odds-wrap up" alt="3.01[u]3.24:">2.24</span>
</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_0');">
<span class=" odds-wrap down" alt="3.56[d]3.50:">5.50</span>
</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-1x2_ft_2');">
<span class=" odds-wrap down" alt="2.49[d]2.37:">5.37</span>
</td>
</tr>
</table>`)
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(oddssrc))
if err != nil {
t.Fatal(err)
}
table1x2Odds := doc.Find("#odds_1x2")
offers, err := parseOdds1x2(table1x2Odds)
if err != nil {
t.Fatal(err)
}
buf, _ := json.MarshalIndent(offers, "", " ")
fmt.Printf("%s\n", buf)
}
func TestParseNextRoundFixtures(t *testing.T) {
/*
var handballSchedule = ``
Postponed: (разные чемпионаты)
~AA÷6R6Qjxol
¬AD÷1602514800
¬ADE÷1602514800
¬AB÷3
¬CR÷3
¬AC÷4
¬CX÷Butel Skopje
¬RW÷0
¬AX÷0
¬BX÷-1
¬WQ÷
¬WN÷TIK
¬AF÷Tikvesh
¬JB÷M10TCQre
¬WV÷tikvesh
¬WM÷BUT
¬AE÷Butel Skopje
¬JA÷rB1PD6Tl
¬WU÷butel-skopje¬
~AA÷CzK7rvtg
¬AD÷1602531000
¬ADE÷1602531000
¬AB÷3
¬CR÷3
¬AC÷4
¬CX÷FH Hafnarfjordur
¬RW÷0
¬AX÷0
¬BX÷-1
¬WQ÷
¬WN÷GRO
¬AF÷Grotta
¬JB÷06YZR1Pr
¬WV÷grotta
¬WM÷FHH
¬AE÷FH Hafnarfjordur
¬JA÷bcn8WsHR
¬WU÷fh-hafnarfjordur
¬AM÷Postponed due to safety reasons.¬
Normal:
~AA÷MyARroUi
¬AD÷1602520200
¬ADE÷1602520200
¬AB÷1
¬CR÷1
¬AC÷1
¬CX÷Ribe-Esbjerg
¬RW÷0
¬AX÷0
¬BX÷-1
¬WQ÷
¬WM÷RIB
¬AE÷Ribe-Esbjerg
¬JA÷xrSUrVlt
¬WU÷ribe-esbjerg
¬WN÷FRE
¬AF÷Fredericia
¬JB÷lIVYsk4n
¬WV÷fredericia
¬AN÷n
¬MW÷417|453|455|512|471¬
~AA÷xMZpVA2J
¬AD÷1602522000
¬ADE÷1602522000
¬AB÷1
¬CR÷1
¬AC÷1
¬CX÷Redbergslids
¬RW÷0
¬AX÷0
¬BX÷-1
¬WQ÷
¬WN÷SKO
¬AF÷Skovde
¬JB÷AyzHGCHi
¬WV÷skovde
¬WM÷RED
¬AE÷Redbergslids
¬JA÷dYVCHh2o
¬WU÷redbergslids
¬AL÷{"2":[{"BU":"\/bookmaker\/16\/?from=live-streaming&sport=0","IU":"\/res\/image\/data\/bookmakers\/17-16.png","BN":"bet365","BI":16,"BT":"GEO restrictions apply. Funded account required or to have placed a bet in the last 24 hours. GamblingTherapy.org. 18+"},{"BU":"\/bookmaker\/539\/?from=live-streaming&sport=0","IU":"\/res\/image\/data\/bookmakers\/17-539.png","BN":"Betclic.pl","BI":539},{"BU":"\/bookmaker\/45\/?from=live-streaming&sport=0","IU":"\/res\/image\/data\/bookmakers\/17-45.png","BN":"Chance","BI":45},{"BU":"\/bookmaker\/165\/?from=live-streaming&sport=0","IU":"\/res\/image\/data\/bookmakers\/17-165.png","BN":"STS.pl","BI":165},{"BU":"\/bookmaker\/49\/?from=live-streaming&sport=0","IU":"\/res\/image\/data\/bookmakers\/17-49.1.png","BN":"Tipsport","BI":49},{"BU":"\/bookmaker\/411\/?from=live-streaming&sport=0","IU":"\/res\/image\/data\/bookmakers\/17-411.1.png","BN":"Tipsport SK","BI":411},{"BU":"\/bookmaker\/454\/?from=live-streaming&sport=0","IU":"\/res\/image\/data\/bookmakers\/17-454.png","BN":"Winlinebet","BI":454},{"BU":"\/bookmaker\/523\/?from=live-streaming&sport=0","IU":"\/res\/image\/data\/bookmakers\/17-523.png","BN":"Wplay","BI":523}]}
¬AN÷y
¬MW÷16|417|392|419|453|454|455|49|45|33|165|539|544|38|512|508|24|27|471|411|43|507|549¬
res, err := parseNextRoundFixtures(handballSchedule)
if err != nil {
t.Fatal(err)
}
for _, m := range res.Matches {
fmt.Printf("%s: %s\n", m.StartTime, m.MatchID)
}
*/
}
/*
func TestListScheduledHandballMatches(t *testing.T) {
result, err := listScheduledHandballMatches()
if err != nil {
t.Fatal(err)
}
for _, m := range result {
fmt.Printf("%s: %s\n", m.StartTime, m.MatchID)
}
}
func TestGetUpcomingHandballMatchReport(t *testing.T) {
report, err := GetUpcomingMatchReport("MyARroUi")
if err != nil {
t.Fatal(err)
}
buf, _ := json.MarshalIndent(report, "", " ")
fmt.Printf("%s\n\n", buf)
//for _, m := range result {
// /fmt.Printf("%s: %s\n", time.Unix(m.StartTime, 0), m.MatchID)
//
}
*/
// HANDBALL
func TestParseHandballOdds(t *testing.T) {
// Строку возвращает отдельный запрос на адрес handballOddsURL
// flashscore format
buf, err := os.ReadFile("for_parse/oddsHandball.fsf")
if err != nil {
t.Fatal(err)
}
str := string(buf)
odds, err := parseFlashscoreEncodedOdds(str)
if err != nil {
t.Fatal(err)
}
buf, _ = json.MarshalIndent(odds, "", " ")
fmt.Printf("%s\n\n", buf)
}
func TestParseHandballH2H(t *testing.T) {
// flashscore format
buf, err := os.ReadFile("for_parse/h2hHandball.fsf")
if err != nil {
t.Fatal(err)
}
str := string(buf)
homeTeamName := "HSG Wetzlar"
awayTeamName := "MT Melsungen"
homeTeamMatches, awayTeamMatches, h2hMatches, err := parseFlashscoreEncodedH2H(str, homeTeamName, awayTeamName)
if err != nil {
t.Fatal(err)
}
buf, _ = json.MarshalIndent(homeTeamMatches, "", " ")
fmt.Printf("HomeTeamMatches:\n%s\n\n", buf)
buf, _ = json.MarshalIndent(awayTeamMatches, "", " ")
fmt.Printf("AwayTeamMatches:\n%s\n\n", buf)
buf, _ = json.MarshalIndent(h2hMatches, "", " ")
fmt.Printf("h2hMatches:\n%s\n\n", buf)
}
func TestParseAllGames(t *testing.T) {
// flashscore format
buf, err := os.ReadFile("for_parse/allGamesFootball.fsf")
//buf, err := os.ReadFile("for_parse/allGamesHandball.fsf")
if err != nil {
t.Fatal(err)
}
str := string(buf)
/*
list, err := ParseFlashscoreEncoding(str)
if err != nil {
t.Fatal(err)
}
for _,m := range list {
for k, v := range m {
fmt.Printf("%s: %s\n", k, v)
}
fmt.Println()
}
*/
matches, err := parseFlashscoreEncodedAllGames(str, Football)
//matches, err := parseFlashscoreEncodedAllGames(str, Handball)
if err != nil {
t.Fatal(err)
}
buf, _ = json.MarshalIndent(matches, "", " ")
fmt.Printf("%s\n\n", buf)
}
func TestParseChampNameOnChampPage(t *testing.T) {
buf := []byte(`<h2 class="tournament"><div class="sportIcon sportIcon--soccer"></div><a href="/football/">Football</a> <span class="breadcrumb__arrow"></span><span class="flag fl_81"></span><a href="/football/germany/">Germany</a></h2>
<div class="container__fsbody" id="fsbody"> <div id="fscountry">
<div id="fsi"></div>
<div id="fscon">
<div class="teamHeader">
<div class="teamHeader__info">
<div class="teamHeader__logo" style="background-image: url('/res/image/data/Q1BvWoB7-KfVhu7bI.png')"></div>
<div class="teamHeader__information">
<div class="teamHeader__name">Bundesliga
<div id="tomyleagues"></div>
</div>
<div class="teamHeader__name">2020/2021</div>
</div>
</div>
</div>`)
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
if err != nil {
t.Fatal(err)
}
// First -
f := doc.Find("div.teamHeader__name").First() //.Children()
txt := f.Text()
txt = strings.TrimSpace(txt)
champID := "/football/germany/bundesliga"
parts := strings.Split(champID, "/")
zoneID := strings.Join(parts[:3], "/")
fmt.Printf("%q\n\n%q\n\n", txt, zoneID)
//f := doc.Find("h2.tournament").First()
}
func TestParseEnvironmentJsVarOnMatchPageInJsVar(t *testing.T) {
// flashscore format
buf, err := os.ReadFile("for_parse/environmentJsVarOnMatchPage.html")
if err != nil {
t.Fatal(err)
}
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
if err != nil {
t.Fatal(err)
}
home, away, err := parseTeamsOnMatchPageInJsVar(doc)
if err != nil {
t.Fatal(err)
}
buf, _ = json.MarshalIndent(home, "", " ")
fmt.Printf("%s\n\n", buf)
buf, _ = json.MarshalIndent(away, "", " ")
fmt.Printf("%s\n\n", buf)
}

126
team_test.go Normal file
View File

@@ -0,0 +1,126 @@
package flashscore
import (
"encoding/json"
"fmt"
"log"
"os"
"testing"
"gordenko.dev/dima/flashscore/model"
"gordenko.dev/dima/qx"
)
/*
func TestGetUpcomingMatchReport(t *testing.T) {
var err error
//'req, err := http.NewRequest("GET", url, nil)
logger := log.New(os.Stdout, "", log.LstdFlags)
db, err := qx.Open("mysql", "root:zkx75fcy@/tipper")
//db, err := qx.Open("mysql", "root:lklnmf6@/tipper")
if err != nil {
panic(err)
}
defer db.Close()
fs := New(db, logger)
report, err := fs.GetUpcomingMatchReport(ScheduledMatch{
MatchID: "vqgqjCUl",
SportID: Handball,
StartTime: time.Now(),
ChampID: "/handball/denmark/1-division",
Zone: model.TeamZone{
ZoneID: "/handball/denmark",
Name: "Denmark",
},
})
//report, err := fs.GetUpcomingMatchReport("8vF2Fr1o", Football)
if err != nil {
fmt.Printf("GetUpcomingMatchReport: %s\n", err)
return
}
buf, _ := json.MarshalIndent(report, "", " ")
fmt.Printf("%s\n", buf)
}
*/
func TestListOfScheduledMatches(t *testing.T) {
var err error
//'req, err := http.NewRequest("GET", url, nil)
logger := log.New(os.Stdout, "", log.LstdFlags)
db, err := qx.Open("mysql", "root:zkx75fcy@/tipper")
//db, err := qx.Open("mysql", "root:lklnmf6@/tipper")
if err != nil {
panic(err)
}
defer db.Close()
fs := New(db, logger)
reports, err := fs.ListOfScheduledMatches(Handball)
if err != nil {
fmt.Printf("ListUpcomingMatchReports: %s\n", err)
return
}
_ = reports
//buf, _ := json.MarshalIndent(reports, "", " ")
//fmt.Printf("%s\n", buf)
}
func TestCheckUpcomingMatchReports(t *testing.T) {
var err error
//'req, err := http.NewRequest("GET", url, nil)
logger := log.New(os.Stdout, "", log.LstdFlags)
db, err := qx.Open("mysql", "root:zkx75fcy@/tipper")
//db, err := qx.Open("mysql", "root:lklnmf6@/tipper")
if err != nil {
panic(err)
}
defer db.Close()
fs := New(db, logger)
scheduledMatches, err := fs.ListOfScheduledMatches(Football)
if err != nil {
fmt.Printf("ListOfScheduledMatches: %s\n", err)
return
}
for _, match := range scheduledMatches[29:30] {
// Здесь лок не нужен, ибо нет race conditions, пишем в мапу в этом же потоке
report := UpcomingMatchReport{
MatchID: match.MatchID,
SportID: match.SportID,
StartTime: match.StartTime.Unix(),
Zone: model.TeamZone{
ZoneID: match.Zone.ZoneID,
Name: match.Zone.Name,
SportID: match.SportID,
},
Champ: model.TeamChamp{
ChampID: match.ChampID,
},
}
// Даже если отчет полный - синкаем, чтобы обновить odds
err = fs.SyncUpcomingMatchReport(&report)
if err != nil {
fmt.Printf("SyncUpcomingMatchReport: %s; matchID=%s", err, report.MatchID)
}
buf, _ := json.MarshalIndent(report, "", " ")
fmt.Printf("REPORT:\n\n%s\n\n", buf)
}
//buf, _ := json.MarshalIndent(reports, "", " ")
//fmt.Printf("%s\n", buf)
}

144
tennis.go Normal file
View File

@@ -0,0 +1,144 @@
package flashscore
import (
"regexp"
"strconv"
"strings"
)
type Surface string
type Sex string
const (
SurfaceHard Surface = "hard"
SurfaceClay Surface = "clay"
SurfaceGrass Surface = "grass"
SurfaceCarpet Surface = "carpet"
SexMen = "men"
SexWomen = "women"
)
var reBreakpoints = regexp.MustCompile(`(\d+)%\s*\((\d+)/(\d+)`)
type Breakpoints struct {
Qty int // всего сколько было брейкпоинтов
Converted int // реализованных
Percent int // процент реализации
}
// parseBreakpoints - извлекает статистику по брейкпоинтам из строки вида "50% (0/4)"
func parseBreakpoints(str string) *Breakpoints {
groups := reBreakpoints.FindStringSubmatch(str)
if len(groups) == 4 {
qty, _ := strconv.Atoi(groups[3])
converted, _ := strconv.Atoi(groups[2])
percent, _ := strconv.Atoi(groups[1])
return &Breakpoints{
Qty: qty,
Converted: converted,
Percent: percent,
}
}
return nil
}
// /type TennisSetStat
type TennisMatchStat struct {
// Сколько было брейкпоинтов и сколько реализовал первый теннисист
HomeBreakpoints *Breakpoints
// Сколько было брейкпоинтов и сколько реализовал второй теннисист
AwayBreakpoints *Breakpoints
}
func parseTennisMatchStat(str string) (stat TennisMatchStat, err error) {
blocks, err := ParseFlashscoreEncoding(str)
if err != nil {
return
}
//buf, _ := json.MarshalIndent(blocks, "", " ")
//fmt.Printf("%s\n\n", buf)
var period string
for _, block := range blocks {
if block["SE"] != "" {
if block["SE"] == "Match" {
period = "match"
} else {
period = ""
}
} else {
if period != "match" {
continue
}
switch block["SG"] {
case "Break Points Converted":
stat.HomeBreakpoints = parseBreakpoints(block["SH"])
stat.AwayBreakpoints = parseBreakpoints(block["SI"])
}
}
}
return
}
func ParseTennisSurfaceFromChampName(fullChampName string) Surface {
substrings := strings.Split(fullChampName, ",")
surface := substrings[len(substrings)-1]
surface = strings.ToLower(strings.TrimSpace(surface))
switch surface {
case "hard", "indoor hard", "indoor":
return SurfaceHard
case "clay", "indoor clay", "green clay", "red clay":
return SurfaceClay
case "grass":
return SurfaceGrass
case "carpet", "indoor carpet":
return SurfaceCarpet
default:
return ""
}
}
// ATP - SINGLES
// ATP - DOUBLES
// CHALLENGER MEN - SINGLES
// WTA - DOUBLES
// WTA - SINGLES
// ITF MEN - DOUBLES
// ITF WOMEN
// TEAMS - WOMEN
var reMen = regexp.MustCompile(`\b(atp|men)\b`)
var reWomen = regexp.MustCompile(`\b(wta|wom[ea]n)\b`)
func ParseTennisSexFromChampName(fullChampName string) string {
fullChampName = strings.ToLower(fullChampName)
if reMen.MatchString(fullChampName) {
return SexMen
}
if reWomen.MatchString(fullChampName) {
return SexWomen
}
return ""
}
func IsTennisDoublesChamp(fullChampName string) bool {
fullChampName = strings.ToLower(fullChampName)
return strings.Contains(fullChampName, "doubles")
}

31
tennis_api.go Normal file
View File

@@ -0,0 +1,31 @@
package flashscore
import (
"fmt"
webspider "gordenko.dev/dima/spider"
)
// Метод, который вызывается без объекта
func LoadTennisMatchStat(matchID string) (stat TennisMatchStat, err error) {
spider, err := webspider.NewSpider(nil)
if err != nil {
return
}
url := fmt.Sprintf(tennisMatchStatURLPattern, matchID)
buf, err := spider.Get(url, map[string]string{
"x-requested-with": "XMLHttpRequest",
"x-fsign": feedSign,
"x-geoip": "1",
"referer": "https://d.flashscore.com/x/feed/proxy-fetch",
})
if err != nil {
err = fmt.Errorf("LoadTennisMatchStat: %s; url=%s", err, url)
return
}
return parseTennisMatchStat(string(buf))
}

15
tennis_api_test.go Normal file
View File

@@ -0,0 +1,15 @@
package flashscore
/*
func TestLoadTennisMatchStat(t *testing.T) {
stat, err := LoadTennisMatchStat("6V2F8wIB")
if err != nil {
t.Log(err)
t.Failed()
} else {
buf, _ := json.MarshalIndent(stat, "", " ")
fmt.Printf("LoadTennisMatchStat: %s\n", buf)
}
}
*/

130
tennis_test.go Normal file
View File

@@ -0,0 +1,130 @@
package flashscore
import (
"encoding/json"
"fmt"
"os"
"testing"
)
func TestTennisParseAllGames(t *testing.T) {
// flashscore format
buf, err := os.ReadFile("for_parse/allGamesTennis.fsf")
//buf, err := ioutil.ReadFile("for_parse/allGamesHandball.fsf")
if err != nil {
t.Fatal(err)
}
str := string(buf)
/*
list, err := ParseFlashscoreEncoding(str)
if err != nil {
t.Fatal(err)
}
for _,m := range list {
for k, v := range m {
fmt.Printf("%s: %s\n", k, v)
}
fmt.Println()
}
*/
matches, err := parseFlashscoreEncodedAllGames(str, Tennis)
//matches, err := ParseFlashscoreEncoding(str)
if err != nil {
t.Fatal(err)
}
buf, _ = json.MarshalIndent(matches, "", " ")
fmt.Printf("%s\n\n", buf)
}
func TestTennisParseMatchScore(t *testing.T) {
str := "DA÷3¬DZ÷3¬DB÷3¬DD÷1618078710¬AW÷1¬DC÷1618074000¬DS÷0¬DE÷2¬DF÷0¬DG÷2¬DH÷0¬DI÷-1¬DK÷1618078747¬DL÷3¬DM÷¬DT÷¬DX÷ST,MH,MC,OD,HH,DR¬DEI÷https://www.flashscore.com/res/image/data/p2vtlMHG-xfKxQmxa-MiVx2mEg.png¬DR÷0¬DV÷2¬DJ÷H¬AZ÷1¬SC÷417¬SB÷3¬SD÷1xBet¬A1÷¬~"
homeScore, awayScore, err := parseHandballMatchScore(str)
if err != nil {
t.Fatal(err)
}
fmt.Printf("home - away: %d-%d\n\n", homeScore, awayScore)
}
func TestTennisParseScoreByPeriods(t *testing.T) {
// 2 sets
// str := "AC÷3¬BA÷6¬BB÷1¬RC÷0:44¬~BC÷6¬BD÷2¬RD÷0:42¬~RB÷1:26¬~A1÷¬~"
// 3 sets
//str := "AC÷3¬BA÷7¬DA÷7¬BB÷6¬DB÷2¬RC÷1:05¬~BC÷4¬BD÷6¬RD÷0:50¬~BE÷6¬BF÷2¬RE÷0:43¬~RB÷2:38¬~PSPH÷WC¬PSPA÷11¬A1÷¬~"
// 5 sets
str := "AC÷3¬BA÷3¬BB÷6¬RC÷0:36¬~BC÷7¬DC÷7¬BD÷6¬DD÷5¬RD÷1:18¬~BE÷7¬DE÷7¬BF÷6¬DF÷4¬RE÷1:08¬~BG÷0¬BH÷6¬RF÷0:29¬~BI÷6¬BJ÷3¬RG÷0:38¬~RB÷4:09¬~PSPH÷20¬PSPA÷12¬A1÷¬~"
score, hasScoreByPeriods, err := parseTennisTeamScoreByPeriods(str)
if err != nil {
t.Fatal(err)
}
buf, _ := json.MarshalIndent(score, "", " ")
fmt.Printf("hasScoreByPeriods: %t, %s\n\n", hasScoreByPeriods, buf)
}
func TestTennisParseOdds(t *testing.T) {
// Строку возвращает отдельный запрос на адрес handballOddsURL
// flashscore format
buf, err := os.ReadFile("for_parse/tennisOdds.fsf")
//buf, err := os.ReadFile("for_parse/oddsHandball.fsf")
if err != nil {
t.Fatal(err)
}
str := string(buf)
odds, err := parseFlashscoreEncodedOdds(str)
if err != nil {
t.Fatal(err)
}
//odds, err := ParseFlashscoreEncoding(str)
//if err != nil {
// return
//}
buf, _ = json.MarshalIndent(odds, "", " ")
fmt.Printf("%s\n\n", buf)
}
func TestTennisParseMatchStat(t *testing.T) {
// Строку возвращает отдельный запрос на адрес handballOddsURL
// flashscore format
buf, err := os.ReadFile("for_parse/tennisMatchStat.fsf")
if err != nil {
t.Fatal(err)
}
str := string(buf)
stat, err := parseTennisMatchStat(str)
if err != nil {
return
}
buf, _ = json.MarshalIndent(stat, "", " ")
fmt.Printf("%s\n\n", buf)
}
// TestParseBreakpointsString - строка из таблицы статистики по матчу.
func TestParseBreakpointsString(t *testing.T) {
bp := parseBreakpoints("50% (0/4)")
fmt.Printf("breakpoints: %#v\n", bp)
}
//
func TestParseTennisSurfaceFromChampName(t *testing.T) {
surface := ParseTennisSurfaceFromChampName("ITF MEN - SINGLES M25 Reus (Spain), clay")
fmt.Printf("Surface: %s\n", surface)
}

220
test/test_html.go Executable file
View File

@@ -0,0 +1,220 @@
package flashscore
// Оставил только счет по таймам. Вырезал ленту событий (голы, пенальти, карточки)
// Анализ:
// - находим .detailMS__headerText с контентом "1st Half" или "2nd Half"
// - переходим к след. Div (nextSibling), проверяем что это .detailMS__headerScore
// - внутри ищем span.p1_home и span.p1_away
var footballMatchSummary = `
<div class="detailMS">
<div class="detailMS__incidentsHeader stage-12">
<div class="detailMS__headerText">1st Half</div>
<div class="detailMS__headerScore">
<span class="p1_home" title="">1</span> - <span class="p1_away" title="">0</span>
</div>
</div>
<div class="detailMS__incidentRow incidentRow--home odd">
</div>
<div class="detailMS__incidentsHeader stage-13">
<div class="detailMS__headerText">2nd Half</div>
<div class="detailMS__headerScore">
<span class="p2_home" title="">3</span> -<span class="p2_away" title="">0</span>
</div>
</div>`
// Пример пустой статистики
var emptyMatchSummary = `
<div class="detailMS">
<div class="detailMS__incidentsHeader stage-9999">
<div class="detailMS__headerText"></div>
<div class="detailMS__headerScore">
<span class="p1_home" title="">0</span> -<span class="p1_away" title="">2</span>
</div>
</div>
<div class="detailMS__incidentRow --empty odd">
-
</div>
</div>
<div class="match-information-wrapper">
<div class="parts match-information">
<div>
<div class="stage-header">
<div class="h-part">Match Information</div>
</div>
<div class="match-information-data">
<div class="content">Venue: Carrow Road (Norwich)</div>
</div>
</div>
</div>`
// На странице h2h landing
var matchZoneChampAndRound = `
<span class="description__country">
ENGLAND:
<a href="#" onclick="window.open('/football/england/premier-league/'); return false;">
Premier League - Round 30
</a>
</span>`
// На странице h2h landing
// Самый простой способ - найти два Div с классом tomyteams. Первый - хозяева, второй - гости. TeamID достать из id атрибута
var teamID = `
<div class="team-text tname-home">
<div class="tname">
<div class="tname__text">
<span style="display: none" class="dw-icon ico">&nbsp;</span>
<a href="#" class="participant-imglink" onclick="window.open('/team/newcastle-utd/p6ahwuwJ'); return false;">Newcastle</a>
</div>
</div>
</div>
<div class="team-text tname-away">
<div class="tname">
<div class="tname__text">
<a href="#" class="participant-imglink" onclick="window.open('/team/norwich/Qo6off6p'); return false;">Norwich</a>
<span style="display: none" class="dw-icon ico">&nbsp;</span>
</div>
</div>
</div>
`
var footballMatchScore = `
<div class="match-info">
<div class="current-result" id="event_detail_current_result">
<span class="scoreboard">1</span>
<span>
<span class="scoreboard-divider">-</span>
<span class="scoreboard">0</span>
</span>
</div>
<div class="info-status mstat">
Finished
</div>
</div>`
var footballMatchNotStarted = `
<div class="match-info">
<div class="current-result" id="event_detail_current_result">
<span class="scoreboard-divider">-</span>
</div>
<div class="info-status mstat">
&nbsp;
</div>
</div>`
// Время матча отдельным запросом на адрес Request URL: https://d.flashscore.com/x/feed/dc_1_riQ962dS
// где riQ962dS - matchID
// Время матча в поле DD или DC
var encodedMatchTime = `DA÷1¬DZ÷1¬DB÷1¬DD÷1592586000¬AW÷1¬DC÷1592586000¬DS÷0¬DI÷-1¬DL÷1¬DM÷No spectators.¬DT÷¬DX÷MC,OD,HH,SCR,LT,TA,TV,NF¬DEI÷https://www.flashscore.com/res/image/data/CY8Xm4wS-Am48qVMh-Ozt88U7U.png¬DV÷1¬SC÷16¬SB÷1¬SD÷bet365¬A1÷¬~`
// Где счет и надпись Finished
var liveFootballMatchInfo = `<div class="match-info">
<div class="current-result" id="event_detail_current_result">
<span class="scoreboard-divider">-</span>
</div>
<div class="info-status mstat">
2nd Half
</div>
</div>`
// На странице команды
// Находим ссылки внутри h2.tournament и проверяем href
var teamZone = `<h2 class="tournament">
<div class="sportIcon sportIcon--soccer"></div>
<a href="/football/">Football</a>
<span class="breadcrumb__arrow"></span>
<span class="flag fl_198"></span>
<a href="/football/england/">England</a>
</h2>`
// Full Time OVER/UNDER внутри блока block-under-over-ft
// Последние курсы на тотал 2.5 имеют класс .not-published, что означает - удалены букмекером.
// На сайте flashscore отображаются перечеркнутыми
var overUnderOdds = `<div id="block-under-over-ft" class="odds-table-wrapper" style="display: block;">
<table id="odds_ou_0.5" class="odds sortable" cellspacing="0">
<colgroup>
<col/>
<col width="60"/>
<col width="60"/>
<col width="60"/>
</colgroup>
<thead>
<tr>
<th class="bookmaker no-padding unsortable">Bookmaker</th>
<th class="unsortable">Total</th>
<th>Over</th>
<th>Under</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/417/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="1xBet" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-417.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/417/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));"><span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_live-offer');">&nbsp;</span></div>
</td>
<td>0.5</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class=" odds-wrap up" alt="1.07[u]1.09:">1.09</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class=" odds-wrap down" alt="7.00[d]5.90:">5.90</span></td>
</tr>
<tr class="even">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/16/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="bet365" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-16.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/16/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));"><span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_live-offer');">&nbsp;</span></div>
</td>
<td>0.5</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class=" odds-wrap up" alt="1.10[u]1.11:">1.11</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class=" odds-wrap down" alt="7.00[d]6.50:">6.50</span></td>
</tr>
</tbody>
</table>
<table id="odds_ou_2.5" class="odds sortable" cellspacing="0">
<colgroup>
<col/>
<col width="60"/>
<col width="60"/>
<col width="60"/>
</colgroup>
<thead>
<tr>
<th class="bookmaker no-padding unsortable">Bookmaker</th>
<th class="unsortable">Total</th>
<th>Over</th>
<th>Under</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/417/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="1xBet" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-417.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/417/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));"><span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_live-offer');">&nbsp;</span></div>
</td>
<td>2.5</td>
<td class="kx max" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class=" odds-wrap up" alt="2.41[u]2.91:">2.91</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class=" odds-wrap down" alt="1.54[d]1.52:">1.52</span></td>
</tr>
<tr class="even">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/16/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="bet365" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-16.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
<div title="This match will be available for LIVE betting!" onmouseover="tt.show(this, event)" onmouseout="tt.hide(this)" class="live-icon-bookmaker" onclick="window.open('/bookmaker/16/?sport=1&from=live-detail' + (window.fs_detail && fs_detail.DA ? (fs_detail.DA == cjs.Api.constantsManager.getEventStageTypes().LIVE ? '-live-match' : '') : ''));"><span class="icon live-offer1" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_live-offer');">&nbsp;</span></div>
</td>
<td>2.5</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class=" odds-wrap up" alt="2.50[u]2.75:">2.75</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class=" odds-wrap down" alt="1.53[d]1.44:">1.44</span></td>
</tr>
<tr class="odd">
<td class="bookmaker">
<div class="blink" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_bookmaker');"><a href="/bookmaker/15/?sport=1&from=odds-comparison" onclick="return !window.open(this.href)" title="William Hill" class="elink"><span style="background-image:url(/res/image/data/bookmakers/30-15.png); background-size: 100%; background-repeat: no-repeat;" class="detail-blogos"></span></a></div>
</td>
<td>2.5</td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_over');"><span class="not-published odds-wrap " alt=":">2.38</span></td>
<td class="kx" onclick="e_t.track_click('bookmaker-button-click', 'block-under-over_ft_under');"><span class="not-published odds-wrap " alt=":">1.53</span></td>
</tr>
</tbody>
</table>
</div>`
// CHECK https://www.flashscore.com/match/vZNmafet/#match-summary EXTRA TIME

47
tmp Normal file
View File

@@ -0,0 +1,47 @@
DA÷3¬
DZ÷3¬
DB÷3¬
DD÷1227121200¬
AW÷¬
DC÷1227121200¬
DS÷0¬
DE÷24¬
DF÷20¬
DG÷24¬
DH÷20¬
DI÷-1¬
DL÷1¬
DM÷¬
DT÷¬
DX÷OD,HH,LT,TA¬
DEI÷https://www.flashscore.com/res/image/data/SSj7IRf5-WIYXsO4F-OMxrcHJ9.png¬
DV÷7¬
DJ÷H¬
AZ÷1¬
SC÷417¬
SB÷1¬
SD÷1xBet¬A1÷¬~
DA÷3¬DZ÷3¬DB÷3¬DD÷1602340391¬AW÷¬
DC÷1602334800¬
DS÷0¬
DE÷32¬
DF÷27¬
DG÷32¬
DH÷27¬
DI÷-1¬
DL÷1¬
DM÷¬
DT÷¬DX÷OD,HH,LT,TA¬DEI÷https://www.flashscore.com/res/image/data/SSj7IRf5-UixbnuN9-0M28Iczn.png¬DV÷7¬DJ÷H¬AZ÷1¬SC÷417¬SB÷1¬SD÷1xBet¬A1÷¬~
DA÷3¬DZ÷3¬DB÷10¬DD÷1552746928¬AW÷¬DC÷1552739400¬DS÷0¬
DE÷33¬
DF÷34¬
DG÷30¬
DH÷30¬
DI÷-1¬
DL÷1¬DM÷¬DT÷¬BZ÷1¬DX÷LI,OD,HH,DR¬DEI÷https://www.flashscore.com/res/image/data/pIQZwIAr-6m20Ta2F-rNqjPY6e.png¬DV÷7¬DJ÷A¬AZ÷0¬SC÷417¬SB÷1¬SD÷1xBet¬A1÷¬~

183
types.go Executable file
View File

@@ -0,0 +1,183 @@
package flashscore
import (
"log"
"time"
xhtml "golang.org/x/net/html"
"gordenko.dev/dima/flashscore/model"
webspider "gordenko.dev/dima/spider"
)
const (
//WSMessageFootballChamps = "footballChamps"
//WSMessageFootballMatchReports = "footballMatchReports"
//WSMessageFootballMatchUpdate = "footballMatchUpdate"
//WSMTeamMatchReport = "teamMatchReport"
// Channel = "main"
)
const (
// В файле https://www.flashscore.com/x/js/core_2_1825000000.js переменной feed_sign жестко задается значение
feedSign = "SW9D1eZo"
//url = "https://www.flashscore.com/football/england/premier-league/"
// Слэш в конце, чтобы избежать лишнего редиректа
teamURLPattern = "https://www.flashscore.com/team%s/"
// champID = /england/premier-league
//fixturesURLPattern = "https://www.flashscore.com/football%s/fixtures/"
//h2hURL = "https://www.flashscore.com/match/lUKXoAEo/#h2h;overall"
//h2hLandingURLPattern = "https://www.flashscore.com/match/%s/#h2h;overall"
h2hLandingURLPattern = "https://www.flashscore.com/match/%s/#/h2h/overall"
footballH2HTablesURLPattern = "https://d.flashscore.com/x/feed/d_hh_%s_en_1"
// Лента событий: голы, карточки, пенальти и т.д.
footballMatchSummaryURLPattern = "https://d.flashscore.com/x/feed/d_su_%s_en_1"
footballOddsURLPattern = "https://d.flashscore.com/x/feed/d_od_%s_en_1_eu" // en - lang, eu - price format
handballOddsURLPattern = "https://d.flashscore.com/x/feed/df_od_1_%s"
//basketballAllGamesURL = "https://d.flashscore.com/x/feed/f_3_0_2_en_1"
// basketball next day https://d.flashscore.com/x/feed/f_3_1_2_en_1
footballAllGamesTodayURL = "https://d.flashscore.com/x/feed/f_1_0_2_en_1"
footballAllGamesTomorrowURL = "https://d.flashscore.com/x/feed/f_1_1_2_en_1"
//footballAllGamesFeedURL = "https://d.flashscore.com/x/feed/f_1_0_3_en_1" ????
handballAllGamesTodayURL = "https://d.flashscore.com/x/feed/f_7_0_2_en_1"
handballAllGamesTomorrowURL = "https://d.flashscore.com/x/feed/f_7_1_2_en_1"
tennisAllGamesTodayURL = "https://d.flashscore.com/x/feed/f_2_0_3_en_1"
tennisAllGamesTomorrowURL = "https://d.flashscore.com/x/feed/f_2_1_3_en_1"
handballH2HTablesURLPattern = "https://d.flashscore.com/x/feed/df_hh_1_%s"
tennisH2HTablesURLPattern = "https://d.flashscore.com/x/feed/df_hh_1_%s"
handballScoreByPeriodsURLPattern = "https://d.flashscore.com/x/feed/df_sur_1_%s"
handballMatchScoreURLPattern = "https://d.flashscore.com/x/feed/dc_1_%s"
tennisMatchStatURLPattern = "https://d.flashscore.com/x/feed/df_st_1_%s"
teamChampURLPattern = "https://www.flashscore.com%s"
SymbolNot = "¬"
SymbolTilda = "~"
SymbolDivide = "÷"
)
const (
Football = 1
Handball = 2
Tennis = 3
Basketball = 4
)
const (
BookmakerBwin = 2
BookmakerBetAtHome = 3
BookmakerUnibet = 5
BookmakerWillHill = 15
BookmakerBet365 = 16
BookmakerBetfair = 429
Bookmaker1xBet = 417
)
var (
nodeTypes = map[xhtml.NodeType]string{
xhtml.ErrorNode: "ErrorNode",
xhtml.TextNode: "TextNode",
xhtml.DocumentNode: "DocumentNode",
xhtml.ElementNode: "ElementNode",
xhtml.CommentNode: "CommentNode",
xhtml.DoctypeNode: "DoctypeNode",
}
)
var (
sportPrefixes = map[int]string{
Football: "/football",
Handball: "/handball",
Basketball: "/basketball",
}
)
//type FlashscoreSnapshot struct {
// Handball []UpcomingMatchReport
//}
type ScheduledMatch struct {
MatchID string
StartTime time.Time
// Название (правда в верхнем регистре) и ID достаем со страницы AllGames
Zone model.TeamZone
// Если нет в БД - загружаем страницу с чемпионатом и достаем чистое название
// без приписок Play Offs, State 2 и т.д.
ChampID string
SportID int
FullChampName string
}
type UpcomingMatchReport struct {
MatchID string `json:"matchID"`
SportID int `json:"sportID"`
StartTime int64 `json:"startTime"`
ParseTime time.Time `json:"parseTime"`
Home model.Team `json:"home"`
Away model.Team `json:"away"`
Champ model.TeamChamp `json:"champ"`
Zone model.TeamZone `json:"zone"`
FullChampName string `json:"fullChampName"`
HomeTeamMatches []model.TeamMatch `json:"homeTeamMatches"`
AwayTeamMatches []model.TeamMatch `json:"awayTeamMatches"`
H2H []model.TeamMatch `json:"h2h"`
Odds Odds `json:"odds"`
IsTeamsLoaded bool `json:"isTeamsLoaded"`
IsHistoryLoaded bool `json:"isHistoryLoaded"`
}
type Flashscore struct {
model *model.Model
spider *webspider.Spider
logger *log.Logger
}
type teamScoreByPeriods struct {
HasStats bool
P1HomeGoals int
P1AwayGoals int
P2HomeGoals int
P2AwayGoals int
}
type Winner3Way struct {
Bookmaker int `json:"bookmaker"`
Home float64 `json:"home"`
Draw float64 `json:"draw"`
Away float64 `json:"away"`
}
type Winner2Way struct {
Bookmaker int `json:"bookmaker"`
Home float64 `json:"home"`
Away float64 `json:"away"`
}
type TotalOffer struct {
Bookmaker int `json:"bookmaker"`
Over float64 `json:"over"`
Under float64 `json:"under"`
}
type Total struct {
Total string `json:"total"`
Offers []TotalOffer `json:"offers"`
}
type Odds struct {
FullTimeTotal []Total `json:"fullTimeTotal"`
H1Total []Total `json:"h1Total"`
Winner []Winner3Way `json:"winner3way"` // footabll, handball
Winner2Way []Winner2Way `json:"winner2way"` // tennis
}