twigots

package module
v0.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 29, 2024 License: MIT Imports: 18 Imported by: 3

README

twigots

Go Reference Go Report Card License - MIT

A go package to fetch ticket listings from the Twickets Live Feed.

Includes utilities to help filter the ticket listings and find the ones you want!

Powers (the similarly creatively named) twitchets, a tool to watch for event ticket listings on Twickets and notify you so you can snap them up! 🫰

Installation

go get -u github.com/ahobsonsayers/twigots

Getting an API Key

To use this tool, you will need a Twickets API key. Although Twickets doesn't provide a free API, you can easily obtain a key by following these steps:

  1. Visit the Twickets Live Feed
  2. Open your browser's Developer Tools (F12) and navigate to the Network tab
  3. Look for the GET request to https://www.twickets.live/services/catalogue and copy the api_key query parameter. You might need to refresh the page first if nothing appears in this tab.

This API key is not provided here due to liability concerns, but it appears to be a fixed, unchanging value.

Example Usage

package main

import (
	"context"
	"log"
	"log/slog"
	"time"

	"github.com/ahobsonsayers/twigots"
)

func main() {
	apiKey := "my_api_key"

	// Fetch ticket listings
	client := twigots.NewClient(nil) // Or use a custom http client
	listings, err := client.FetchTicketListings(
		context.Background(),
		twigots.FetchTicketListingsInput{
			// Required
			APIKey:  apiKey,
			Country: twigots.CountryUnitedKingdom, // Only UK is supported at the moment
			// Optional. See all options in godoc
			CreatedBefore: time.Now(),
			CreatedAfter:  time.Now().Add(time.Duration(-5 * time.Minute)), // 5 mins ago
			MaxNumber:     100,
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Fetched %d ticket listings", len(listings))

	// Filter ticket listing for the ones we want
	filteredListings, err := listings.Filter(
		// Filter for listings of Hamilton tickets
		twigots.Filter{Event: "Hamilton"},
		// Also filter for listings of Coldplay tickets.
		// Lets impose extra restrictions on these.
		twigots.Filter{
			Event:           "Coldplay", // Required
			EventSimilarity: 1,          // Avoid false positives
			Regions: []twigots.Region{
				twigots.RegionLondon,
				twigots.RegionSouth,
			},
			NumTickets:  2,   // Exactly 2 tickets in the listing
			MinDiscount: 0.1, // Discount of > 10%
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	for _, listing := range filteredListings {
		slog.Info(
			"Ticket listing found matching a filter",
			"Event", listing.Event.Name,
			"NumTickets", listing.NumTickets,
			"Price", listing.TotalPriceInclFee().String(),
			"OriginalPrice", listing.OriginalTicketPrice().String(),
			"URL", listing.URL(),
		)
	}
}

Why the name twigots?

Because its a stupid mash up of Tickets and Go... and also why not?

Hits

Documentation

Index

Constants

View Source
const (
	TwicketsURL = "https://www.twickets.live"
)

Variables

View Source
var (
	CountryUnitedKingdom = country.Add(Country{"GB"})

	Countries = country.Enum()
)
View Source
var (
	RegionEastAnglia     = region.Add(Region{"GBEA"})
	RegionLondon         = region.Add(Region{"GBLO"})
	RegionMidlands       = region.Add(Region{"GBMI"})
	RegionNorth          = region.Add(Region{"GBNO"})
	RegionNorthEast      = region.Add(Region{"GBNE"})
	RegionNorthernIsland = region.Add(Region{"GBNI"})
	RegionNorthWest      = region.Add(Region{"GBNW"})
	RegionScotland       = region.Add(Region{"GBSC"})
	RegionSouth          = region.Add(Region{"GBSO"})
	RegionSouthEast      = region.Add(Region{"GBSE"})
	RegionSouthWest      = region.Add(Region{"GBSW"})
	RegionWales          = region.Add(Region{"GBWA"})

	Regions = region.Enum()
)
View Source
var (
	CurrencyGBP = currency.Add(Currency{"GBP"})

	Currencies = currency.Enum()
)
View Source
var DefaultClient = NewClient(nil)

Functions

func FeedUrl

func FeedUrl(input FeedUrlInput) (string, error)

FeedUrl gets the url of a feed of ticket listings

Format is: https://www.twickets.live/services/catalogue?q=countryCode=GB&count=100&api_key=<api_key>

func ListingURL

func ListingURL(listingId string, numTickets int) string

ListingURL gets the url of a listing given its id and the number of tickets in the listing.

Format is: https://www.twickets.live/app/block/<ticketId>,<numTickets>

Types

type Artist

type Artist struct {
	Id   string `json:"id"`
	Name string `json:"name"`
	Slug string `json:"linkName"`
}

Artist contains the details of an artist.

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(httpClient *http.Client) *Client

NewClient creates a new Twickets client

func (*Client) FetchTicketListings

func (c *Client) FetchTicketListings(
	ctx context.Context,
	input FetchTicketListingsInput,
) (TicketListings, error)

FetchTicketListings gets ticket listings using the specified input.

func (*Client) FetchTicketListingsByFeedUrl

func (c *Client) FetchTicketListingsByFeedUrl(
	ctx context.Context,
	feedUrl string,
) (TicketListings, error)

FetchTicketListings gets ticket listings using the specified feel url.

type Country

type Country enum.Member[string]

func (*Country) UnmarshalJSON

func (c *Country) UnmarshalJSON(data []byte) error

func (*Country) UnmarshalText

func (c *Country) UnmarshalText(data []byte) error

type Currency

type Currency enum.Member[string]

func (Currency) Symbol

func (c Currency) Symbol() string

Symbol is the character that represents the currency e.g. $, £, €.

func (*Currency) UnmarshalJSON

func (c *Currency) UnmarshalJSON(data []byte) error

type Date

type Date struct{ time.Time }

Date is a date (with no time).

func (*Date) UnmarshalJSON

func (d *Date) UnmarshalJSON(data []byte) error

type DateTime

type DateTime struct{ time.Time }

DateTime is a date and time.

func (*DateTime) UnmarshalJSON

func (dt *DateTime) UnmarshalJSON(data []byte) error

type Event

type Event struct {
	Id       string `json:"id"`
	Name     string `json:"eventName"`
	Category string `json:"category"`

	Date      Date      `json:"date"`
	Time      Time      `json:"showStartingTime"`
	OnSale    *DateTime `json:"onSaleTime"` // 2023-11-17T10:00:00Z
	Announced *DateTime `json:"created"`    // 2023-11-17T10:00:00Z

	Venue  Venue    `json:"venue"`
	Lineup []Lineup `json:"participants"`
}

Event contains the details of an event.

type FeedUrlInput

type FeedUrlInput struct {
	// Required fields
	APIKey  string
	Country Country

	// Optional fields
	Regions     []Region  // Defaults to all country regions
	NumListings int       // Defaults to 10 ticket listings
	BeforeTime  time.Time // Defaults to current time
}

func (FeedUrlInput) Validate added in v0.1.3

func (f FeedUrlInput) Validate() error

Validate the input struct used to get the feed url. This is used internally to check the input, but can also be used externally.

type FetchTicketListingsInput

type FetchTicketListingsInput struct {
	// Required fields
	APIKey  string
	Country Country

	// Regions for which to fetch ticket listings from.
	// Leave this unset or empty to fetch listings from any region.
	// Defaults to any region (unset).
	Regions []Region

	// MaxNumber is the maximum number of ticket listings to fetch.
	// If getting ticket listings within in a time period using `CreatedAfter`, set this to an arbitrarily
	// large number (e.g. 250) to ensure all listings in the period are fetched, while preventing
	// accidentally fetching too many listings and possibly being rate limited or blocked.
	// Defaults to 10.
	// Set to -1 if no limit is desired. This is dangerous and should only be used with well constrained time periods.
	MaxNumber int

	// CreatedAfter is the time which ticket listings must have been created after to be fetched.
	// Set this to fetch listings within a time period.
	// Set `MaxNumber` to an arbitrarily large number (e.g. 250) to ensure all listings in the period are fetched,
	// while preventing  accidentally fetching too many listings and possibly being rate limited or blocked.
	CreatedAfter time.Time

	// CreatedBefore is the time which ticket listings must have been created before to be fetched.
	// Set this to fetch listings within a time period.
	// Defaults to current time.
	CreatedBefore time.Time

	// NumPerRequest is the number of ticket listings to fetch in each request.
	// Not all requested listings are fetched at once - instead a series of requests are made,
	// each fetching the number of listings specified here. In theory this can be arbitrarily
	// large to prevent having to make too many requests, however it has been known that any
	// other number than 10 can sometimes not work.
	// Defaults to 10. Usually can be ignored.
	NumPerRequest int
}

FetchTicketListingsInput defines parameters when getting ticket listings.

Ticket listings can either be fetched by maximum number or by time period. The default is to get a maximum number of ticket listings.

If both a maximum number and a time period are set, whichever condition is met first will stop the fetching of ticket listings.

func (FetchTicketListingsInput) Validate added in v0.1.3

func (f FetchTicketListingsInput) Validate() error

Validate the input struct used to get ticket listings. This is used internally to check the input, but can also be used externally.

type Filter

type Filter struct {
	// Name of event on ticket listing to match.
	// Required.
	Event string

	// Similarity of event name on ticket listing to the event name specified in the filter.
	// Specified as a float, between 0 and 1 (with 1 representing an exact match).
	// Leave this unset or set to <=0 to use the default.
	// Default is 0.85 which accounts for variances in event names.
	// e.g. Taylor Swift and Taylor Swift: The Eras Tour should match.
	EventSimilarity float64

	// Regions on ticket listings to match.
	// Leave this unset or empty to match any region.
	// Defaults to unset.
	Regions []Region

	// Number of tickets in a listing to match.
	// Leave this unset or set to <=0 to match any number of tickets.
	// Defaults to unset.
	NumTickets int

	// Minimum discount (including fee) of tickets in a listing to match.
	// Specified as a float, between 0 and 1 (with 1 representing 100% off).
	// Leave this unset or set to <=0 to match any discount (including no discount).
	// Defaults to unset.
	MinDiscount float64

	// Time a listing must be created before to be match
	// Leave this unset to match any creation time.
	// Defaults to unset.
	CreatedBefore time.Time

	// Time a listing must be created after to be match
	// Leave this unset to match any creation time.
	// Defaults to unset.
	CreatedAfter time.Time
}

A filter to use on ticket listing(s). A ticket listing can either match the filter or not.

A ticket listing must satisfy all specified filter fields to match, making this an AND filter.

func (Filter) Validate added in v0.1.3

func (f Filter) Validate() error

Validate the filter. This is used internally to check a filter, but can also be used externally.

type Lineup

type Lineup struct {
	Artist  Artist `json:"participant"`
	Billing int    `json:"billing"`
}

Lineup contains the details of the event lineup.

type Location

type Location struct {
	Id       string  `json:"id"`
	Name     string  `json:"shortName"`
	FullName string  `json:"name"`
	Country  Country `json:"countryCode"`
	Region   Region  `json:"regionCode"`
}

Venue contains the details of an event location.

type Price

type Price struct {
	Currency Currency `json:"currencyCode"`

	// Amount is the cost in Cents, Pennies etc.
	// Prefer using `Number`
	Amount int `json:"amountInCents"`
}

func (Price) Add

func (p Price) Add(other Price) Price

Add prices together. Currency will be kept. Returns a new price.

func (Price) Divide

func (p Price) Divide(num int) Price

Divide prices. Currency will be kept. Returns a new price.

func (Price) Multiply

func (p Price) Multiply(num int) Price

Multiply prices together. Returns a new price.

func (Price) Number

func (p Price) Number() float64

Number is the numerical value of the price. E.g. Dollars, Pounds, Euros etc. Use this over `Amount“.

func (Price) String

func (p Price) String() string

The price as a string. e.g. $30.62

func (Price) Subtract

func (p Price) Subtract(other Price) Price

Subtract price from another. Currency will be kept. Returns a new price.

type Region

type Region enum.Member[string]

func (*Region) UnmarshalJSON

func (r *Region) UnmarshalJSON(data []byte) error

func (*Region) UnmarshalText

func (r *Region) UnmarshalText(data []byte) error

type TicketListing

type TicketListing struct {
	Id        string   `json:"blockId"`
	CreatedAt UnixTime `json:"created"`
	ExpiresAt UnixTime `json:"expires"`

	// Number of tickets in the listing
	NumTickets int `json:"ticketQuantity"`

	// TotalPriceExclFee is the total price of all tickets, excluding fee.
	// Use TotalPriceInclFee to get the total price of all tickets, including fee.
	// Use TicketPriceExclFee to get the price of a single ticket, excluding fee.
	// Use TicketPriceInclFee to get the price of a single ticket, including fee.
	TotalPriceExclFee Price `json:"totalSellingPrice"`

	// TwicketsFee is the total twickets fee for all tickets.
	// Use TwicketsFeePerTicket to get the twickets fee per ticket.
	TwicketsFee Price `json:"totalTwicketsFee"`

	// OriginalTotalPrice is the original total price of all tickets, including any fee.
	// Use OriginalTicketPrice to get the original price of a single ticket, including any fee.
	OriginalTotalPrice Price `json:"faceValuePrice"`

	SellerWillConsiderOffers bool `json:"sellerWillConsiderOffers"`

	// The type of the ticket e.g. seated, Standing, Box etc.
	TicketType   string `json:"priceTier"`
	SeatAssigned bool   `json:"seatAssigned"`
	Section      string `json:"section"` // Can be empty
	Row          string `json:"row"`     // Can be empty

	Event Event `json:"event"`
	Tour  Tour  `json:"tour"`
}

TicketListing is a listing of ticket(s) on Twickets

func UnmarshalTwicketsFeedJson

func UnmarshalTwicketsFeedJson(data []byte) ([]TicketListing, error)

func (TicketListing) Discount

func (l TicketListing) Discount() float64

Discount is the discount on the original price of a single ticket, including any fee.

Discount is returned as a value between 0 and 1 (with 1 representing 100% off). If ticket is being sold at its original price, the addition of the twickets fee will cause discount to be < 0 i.e. the total ticket price will have gone up.

func (TicketListing) DiscountString

func (l TicketListing) DiscountString() string

DiscountString is the discount on the original price of a single ticket, including any fee as a percentage string between 0-100 with a % suffix.

If ticket is being sold at its original price, the addition of the twickets fee will cause discount to be < 0% i.e. the total ticket price will have gone up. If this is the / case "none" will be returned instead of a negative percentage

func (TicketListing) MatchesAnyFilter

func (l TicketListing) MatchesAnyFilter(filters ...Filter) (bool, error)

MatchesAnyFilter checks if a ticket listing matches any of the filters provided.

func (TicketListing) OriginalTicketPrice

func (l TicketListing) OriginalTicketPrice() Price

OriginalTotalPrice is the original price of a single ticket, including any fee.

Use OriginalTotalPrice to get the original total price of all tickets, including any fee.

func (TicketListing) TicketPriceExclFee

func (l TicketListing) TicketPriceExclFee() Price

TicketPriceExclFee is price of a single ticket, excluding fee.

Use TotalPriceExclFee to get the total price of all tickets, excluding fee.

Use TotalPriceInclFee to get the total price of all tickets, including fee.

Use TicketPriceInclFee to get the price of a single ticket, including fee.

func (TicketListing) TicketPriceInclFee

func (l TicketListing) TicketPriceInclFee() Price

TicketPriceExclFee is price of a single ticket, including fee.

Use TotalPriceExclFee to get the total price of all tickets, excluding fee.

Use TotalPriceInclFee to get the total price of all tickets, including fee.

Use TicketPriceExclFee to get the price of a single ticket, excluding fee.

func (TicketListing) TotalPriceInclFee

func (l TicketListing) TotalPriceInclFee() Price

TotalPriceExclFee is the total price of all tickets, including fee.

Use TotalPriceExclFee to get the total price of all tickets, excluding fee.

Use TicketPriceExclFee to get the price of a single ticket, excluding fee.

Use TicketPriceInclFee to get the price of a single ticket, including fee.

func (TicketListing) TwicketsFeePerTicket

func (l TicketListing) TwicketsFeePerTicket() Price

TwicketsFeePerTicket is the twickets fee per ticket.

Use TwicketsFee to get the total fee for all tickets.

func (TicketListing) URL

func (l TicketListing) URL() string

URL of the ticket listing

Format is: https://www.twickets.live/app/block/<ticketId>,<quanitity>

type TicketListings

type TicketListings []TicketListing

TicketListings is a slice of ticket listings.

func (TicketListings) Filter

func (l TicketListings) Filter(filters ...Filter) (TicketListings, error)

Filter ticket listings based on whether they match any of the filters provided.

func (TicketListings) GetById

func (l TicketListings) GetById(id string) *TicketListing

GetById gets the first ticket listing with a matching id, or returns nil if one does not exist.

type Time

type Time struct{ time.Time }

Date is a time (with no date).

func (*Time) UnmarshalJSON

func (t *Time) UnmarshalJSON(data []byte) error

type Tour

type Tour struct {
	Id         string   `json:"tourId"`
	Name       string   `json:"tourName"`
	Slug       string   `json:"slug"`
	FirstEvent *Date    `json:"minDate"`      // 2024-06-06
	LastEvent  *Date    `json:"maxDate"`      // 2024-11-14
	Countries  []string `json:"countryCodes"` // TODO use enum - requires all countries to be added
}

Event contains the details of a tour.

type UnixTime

type UnixTime struct{ time.Time }

UnixTime is a time from unix time.

func (*UnixTime) UnmarshalJSON

func (t *UnixTime) UnmarshalJSON(data []byte) error

type Venue

type Venue struct {
	Id       string   `json:"id"`
	Name     string   `json:"name"`
	Location Location `json:"location"`
	Postcode string   `json:"postcode"`
}

Venue contains the details of an event venue.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL