Skip to content
This repository was archived by the owner on Dec 14, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cf.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ Usage:
cf race [<specifier>...]
cf pull [ac] [<specifier>...]
cf clone [ac] [<handle>]
cf contests
cf register [<specifier>...]
cf unregister [<specifier>...]
cf upgrade

Options:
Expand Down Expand Up @@ -107,6 +110,9 @@ Examples:
cf pull Pull the latest codes of current problem into current
path.
cf clone xalanq Clone all codes of xalanq.
cf contests List upcoming contests
cf register 1270 Register for contest "Good Bye 2019"
cf unregister 1270 Unregister from a contest "Good Bye 2019"
cf upgrade Upgrade the "cf" to the latest version from GitHub.

File:
Expand Down
119 changes: 119 additions & 0 deletions client/contests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package client

import (
"bytes"
"fmt"
"io"
"io/ioutil"
"strings"

"github.com/PuerkitoBio/goquery"
)

// ContestInfo contests information
type ContestInfo struct {
ID string
Name string
Start string
Length string
State string
Registration string
Registered bool
}

func getRegistrationStatus(cell *goquery.Selection) (string, bool) {
participants := cell.Find(".contestParticipantCountLinkMargin")
participantCount := participants.Text()
participants.Remove()
if cell.Find(".welldone").Length() != 0 {
text := cleanText(cell.Text())
return text + "\n" + participantCount, true
}
countdown := cell.Find(".countdown")
text := ""
if strings.Contains(cell.Text(), "»") {
text = "Registering"
parent := countdown.Parent()
countdownText := countdown.Text()
countdown.Remove()
note := cleanText(parent.Text())
if strings.Contains(cell.Text(), "*") {
countdownText += "*"
}
return fmt.Sprintf("%s %s\n%s %s", text, participantCount, note, countdownText), false
}
return cleanText(cell.Text()), false
}

func getState(cell *goquery.Selection) string {
cell.Find("a").Remove()
countdown := cell.Find(".countdown").Text()
cell.Find(".countdown").Remove()
text := cleanText(cell.Text())

return text + "\n" + countdown
}

func findContests(body io.ReadCloser, utcOffset string) ([]ContestInfo, error) {
doc, err := goquery.NewDocumentFromReader(body)
if err != nil {
return nil, err
}
table := doc.Find(".datatable").First().Find("tbody")
rows := table.Find("tr").Slice(1, goquery.ToEnd)
contests := []ContestInfo{}
rows.Each(func(i int, row *goquery.Selection) {
contest := ContestInfo{}
contest.ID, _ = row.Attr("data-contestid")
row.Find("td").Each(func(j int, cell *goquery.Selection) {
switch j {
case 0:
cell.Find("a").Remove()
name := cleanText(cell.Text())
contest.Name = strings.Replace(name, " (", "\n(", 1)
case 2:
contest.Start = parseWhen(cell.Find(".format-time").Text(), utcOffset)
case 3:
duration := cleanText(cell.Text())
if strings.Count(duration, ":") == 2 {
duration = strings.TrimSuffix(duration, ":00")
}
contest.Length = duration
case 4:
contest.State = getState(cell)
case 5:
contest.Registration, contest.Registered = getRegistrationStatus(cell)
}
})
contests = append(contests, contest)
})
if err != nil {
return nil, err
}
return contests, nil
}

// StatisContest get upcoming contests
func (c *Client) GetContests() (contests []ContestInfo, err error) {
URL := c.host + "/contests?complete=true"
resp, err := c.client.Get(URL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Status code error: %d %s", resp.StatusCode, resp.Status)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if _, err = findHandle(body); err != nil {
return
}
utcOffset, err := findCfOffset(body)
if err != nil {
return nil, err
}
return findContests(ioutil.NopCloser(bytes.NewReader(body)), utcOffset)
}
96 changes: 96 additions & 0 deletions client/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package client

import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"net/url"
"strings"

"github.com/PuerkitoBio/goquery"
"github.com/fatih/color"
"github.com/xalanq/cf-tool/util"
)

// Register for a contest
func (c *Client) Register(contestID string) error {
URL := fmt.Sprintf("%v/contestRegistration/%v", c.host, contestID)
resp, err := c.client.Get(URL)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if _, err = findHandle(body); err != nil {
return err
}
if msg := findCodeforcesMessage(body); msg != "" {
return errors.New(msg)
}
doc, err := goquery.NewDocumentFromReader(ioutil.NopCloser(bytes.NewReader(body)))
if err != nil {
return err
}
color.HiCyan(findTitle(doc))
if !agreesToTerms(doc) {
return errors.New("You cannot participate without agreeing to the terms")
}
formData, err := getFormData(doc, c)
if err != nil {
return err
}
resp, err = c.client.PostForm(URL, formData)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Println(findCodeforcesMessage(body))
return nil
}

func findTitle(doc *goquery.Document) string {
title := doc.Find("h2").Text()
return cleanText(title)
}

func agreesToTerms(doc *goquery.Document) bool {
label := cleanText(doc.Find("label[for=registrationTerms]").Text())
terms := cleanText(doc.Find("#registrationTerms").Text())
terms = strings.ReplaceAll(terms, "\n*", "\n *")
color.Green(label)
fmt.Println(terms)
return util.YesOrNo("Do you argree to the terms? (y/n)")
}

func getFormData(doc *goquery.Document, c *Client) (url.Values, error) {
form := doc.Find(".contestRegistration").First()
data := url.Values{}
var err error
form.Find("input").Each(func(i int, input *goquery.Selection) {
key, k := input.Attr("name")
value, v := input.Attr("value")
if key != "" {
if !k || !v {
err = errors.New("Unable to get form data")
return
}
data.Set(key, value)
}
})
if data.Get("_tta") == "" {
tta, err := getTta(c)
if err != nil {
return data, nil
}
data.Set("_tta", tta)
}
return data, err
}
104 changes: 104 additions & 0 deletions client/unregister.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package client

import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"

"github.com/PuerkitoBio/goquery"
)

// Unregister from a contest
func (c *Client) Unregister(contestID string) error {
resp, err := getRegistrantsPage(c, contestID, 1)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if _, err = findHandle(body); err != nil {
return err
}
if msg := findCodeforcesMessage(body); msg != "" {
return errors.New(msg)
}
doc, err := goquery.NewDocumentFromReader(ioutil.NopCloser(bytes.NewReader(body)))
if err != nil {
return err
}
formData, err := getUnregisterFormData(doc, c, contestID)
if err != nil {
return err
}
URL := fmt.Sprintf("%v/data/contestRegistration/%v", c.host, contestID)
resp, err = c.client.PostForm(URL, formData)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if strings.Contains(string(body), `{"success":"true"}`) {
fmt.Println("Succesfully unregistered from the contest")
} else {
return errors.New("Can't unregister. Possible reason: you made at least one action in the contest")
}
return err
}

func getUnregisterFormData(doc *goquery.Document, c *Client, contestID string) (url.Values, error) {
pageCount := getPageCount(doc)
for page := 1; page <= pageCount; page++ {
if page != 1 {
resp, err := getRegistrantsPage(c, contestID, page)
if err != nil {
return nil, err
}
defer resp.Body.Close()
doc, err = goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, err
}
}
user := doc.Find(".deleteParty").First()
participantID, ok := user.Attr("participantid")
if !ok {
continue
}
data := url.Values{}
token := getCsrfToken(doc)
data.Add("participantId", participantID)
data.Add("action", "deleteParty")
data.Add("csrf_token", token)
return data, nil
}
return nil, errors.New("You are not registered in this contest")
}

func getRegistrantsPage(c *Client, contestID string, page int) (r *http.Response, err error) {
URL := fmt.Sprintf("%v/contestRegistrants/%v/friends/true/page/%d", c.host, contestID, page)
resp, err := c.client.Get(URL)
if err != nil {
return nil, err
}
return resp, nil
}

func getPageCount(doc *goquery.Document) int {
count, ok := doc.Find(".page-index").Last().Attr("pageindex")
if ok {
c, _ := strconv.Atoi(count)
return c
}
return 1
}
Loading