Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Upcoming Matches

This easy widget let's you see upcoming matches of your favourite football team.

Guide

  1. Copy this code in a new script in Scriptable app
  2. Find teamId here: https://site.api.espn.com/apis/site/v2/sports/soccer/:league/teams
  3. Find league id: some league abbreviation (EX: 'eng.1' for EPL, 'usa.1' for MLS, 'ned.1' for Dutch Eredivisie)
  4. Change competitionId variable (line 4) with your competition id
  5. Change teamId variable (line 5) with your favourite team id
  6. Enjoy!

Version 0.0.2

  • Added medium Widget support

Version 0.0.3 - Thanks to @Jensderond for new api endpoint

  • Fixed api endpoint
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-gray; icon-glyph: futbol;
const competitionId = "ned.1"
const teamId = 142;
const fixturesUrl = `https://site.api.espn.com/apis/site/v2/sports/soccer/${competitionId}/teams/${teamId}/schedule?fixture=true`;
const widgetSize = config.widgetFamily
const maxEvents = widgetSize === "large" ? 4 : 2
async function getTeamImg(imageUrl) {
let imgReq = new Request(imageUrl)
let img = await imgReq.loadImage()
return img
}
function createDivider() {
const drawContext = new DrawContext()
drawContext.size = new Size(543, 1)
const path = new Path()
path.addLine(new Point(1000, 20))
drawContext.addPath(path)
drawContext.setStrokeColor(Device.isUsingDarkAppearance() ? new Color("#fff", 1) : new Color("#000000", 1))
drawContext.setLineWidth(1)
drawContext.strokePath()
return drawContext.getImage()
}
async function createWidget() {
let req = new Request(fixturesUrl);
let res = await req.loadJSON();
let events = res.events;
let teamImg = await getTeamImg(res.team.logo);
let w = new ListWidget();
w.backgroundColor = Device.isUsingDarkAppearance() ? new Color("#2C2C2E", 1) : new Color("#ffffff", 1)
w.useDefaultPadding()
const limitedEvents = events.slice(0, maxEvents)
const imageSize = widgetSize === "large" ? 32 : 26;
w.addSpacer()
if (widgetSize === "large") {
const teamName =
events[0].competitions[0].competitors[0].id == teamId
? events[0].competitions[0].competitors[0].team.displayName
: events[0].competitions[0].competitors[1].team.displayName;
let titleStack = w.addStack()
let title = titleStack.addText(`${teamName}'s upcoming matches`)
title.font = Font.boldSystemFont(16);
w.addSpacer()
}
for (let i = 0; i < limitedEvents.length; i++) {
let e = events[i]
const competitors = e.competitions[0].competitors;
if (widgetSize === "large" || i > 0) {
w.addSpacer(10)
}
let homeImg = ""
let awayImg = ""
if (competitors[0].id == teamId) {
homeImg = teamImg
awayImg = await getTeamImg(competitors[1].team.logos[0].href)
} else {
homeImg = await getTeamImg(competitors[0].team.logos[0].href)
awayImg = teamImg
}
let rowStack = w.addStack()
rowStack.centerAlignContent()
rowStack.addSpacer(20)
// home team image
let homeImageStack = rowStack.addStack();
let homeImage = homeImageStack.addImage(homeImg);
homeImage.imageSize = new Size(imageSize, imageSize)
homeImageStack.addSpacer(10)
// home team name
let homeNameStack = rowStack.addStack();
let homeName = homeNameStack.addText(competitors[0].team.displayName);
homeName.font = Font.mediumSystemFont(12);
homeNameStack.size = new Size(90, 14)
homeNameStack.addSpacer()
let separatorStack = rowStack.addStack();
let separator = separatorStack.addText('-')
separator.font = Font.mediumSystemFont(12)
separatorStack.size = new Size(24, 12)
separatorStack.addSpacer(10)
// away team name
let awayNameStack = rowStack.addStack();
awayNameStack.addSpacer()
let awayName = awayNameStack.addText(competitors[1].team.displayName);
awayName.font = Font.mediumSystemFont(12);
awayNameStack.size = new Size(100, 14)
awayNameStack.addSpacer(10)
// away team image
let awayImageStack = rowStack.addStack();
let awayImage = awayImageStack.addImage(awayImg);
awayImage.imageSize = new Size(imageSize, imageSize);
w.addSpacer(5)
let infoRowStack = w.addStack()
infoRowStack.centerAlignContent()
infoRowStack.addSpacer()
let dateStack = infoRowStack.addStack()
const dateFormatter = new DateFormatter()
dateFormatter.useMediumDateStyle()
dateFormatter.useShortTimeStyle()
let parsedDate = new Date(Date.parse(e.date))
let formattedDate = dateFormatter.string(parsedDate)
let date = dateStack.addText(formattedDate)
date.font = Font.mediumSystemFont(10)
date.textOpacity = 0.5
dateStack.addSpacer(10)
infoRowStack.addSpacer()
if (i !== maxEvents - 1) {
w.addSpacer(10)
let dividerStack = w.addStack()
let divider = dividerStack.addImage(createDivider())
divider.imageOpacity = 0.5
}
}
w.addSpacer()
return w
}
const widget = await createWidget()
Script.setWidget(widget)
Script.complete()
await widget.presentLarge()
@geongon2

This comment has been minimized.

Copy link

@geongon2 geongon2 commented Oct 12, 2020

FE317BCD-62C9-4EE8-A56B-AA11A9D9A904

Why do I become like this? I did what I was told to do.

@jay-pee

This comment has been minimized.

Copy link

@jay-pee jay-pee commented Oct 22, 2020

Great Widget! Thanks!
Unfortunately, when I try to use it for the Seattle seahawks it doesn't work properly.
The dates shown are not correct. Maybe it has something to do with time zones difference.
Team ID: 134949

@Leibinger015

This comment has been minimized.

Copy link

@Leibinger015 Leibinger015 commented Oct 28, 2020

Is it also possible to write the script with the APIs so that the last game with result and the next future game is shown.

See photomontage:
B8C58FF4-BE1D-4167-86D8-CD5994E214D2

@DieserHannes

This comment has been minimized.

Copy link

@DieserHannes DieserHannes commented Nov 1, 2020

What Leibinger015 says.

@Jensderond

This comment has been minimized.

Copy link

@Jensderond Jensderond commented Dec 3, 2020

@DieserHannes @Leibinger015

I've edited a few things to show the score.

Added code is this:

    let homescoreStack = rowStack.addStack();
    if(e.intHomeScore !== null) {
        let homescore = homescoreStack.addText(`${e.intHomeScore}`)
        homescore.font = Font.mediumSystemFont(12)
    }
    homescoreStack.size = new Size(12, 12)
    homescoreStack.addSpacer(5)

...
    let awayscoreStack = rowStack.addStack();
    if(e.intAwayScore !== null) {
        let awayscore = awayscoreStack.addText(`${e.intAwayScore}`)
        awayscore.font = Font.mediumSystemFont(12)
    }
    awayscoreStack.size = new Size(12, 12)
    awayscoreStack.addSpacer(5)

See full code snippet:

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-gray; icon-glyph: futbol;
const teamId = 133681;
const teamDetailUrl = "https://www.thesportsdb.com/api/v1/json/1/lookupteam.php?id=";

const leagueDetailUrl = "https://www.thesportsdb.com/api/v1/json/1/lookupleague.php?id="

const teamUrl = teamDetailUrl + teamId;
let r = new Request(teamUrl);
let teamDetail = await r.loadJSON();

const widgetSize = config.widgetFamily

const maxEvents = widgetSize === "large" ? 4 : 2

async function getTeamImg(id) {
  let teamUrl = teamDetailUrl + id;
  let req = new Request(teamUrl)
  let res = await req.loadJSON()
  let imageUrl = res.teams[0].strTeamBadge + "/preview"
  let imgReq = new Request(imageUrl)
  let img = await imgReq.loadImage()
  return img
}

async function getLeagueImg(id) {
  let leagueUrl = leagueDetailUrl + id;
  let req = new Request(leagueUrl)
  let res = await req.loadJSON()
  let imageUrl = res.leagues[0].strBadge
  let imgReq = new Request(imageUrl)
  let img = await imgReq.loadImage()
  return img
}

function createDivider() {
  const drawContext = new DrawContext()
  drawContext.size = new Size(543, 1)
  const path = new Path()
  path.addLine(new Point(1000, 20))
  drawContext.addPath(path)
  drawContext.setStrokeColor(Device.isUsingDarkAppearance() ? new Color("#fff", 1) : new Color("#000000", 1))
  drawContext.setLineWidth(1)
  drawContext.strokePath()
  return drawContext.getImage()
}

async function createWidget() {
  const eventsUrl = "https://www.thesportsdb.com/api/v1/json/1/eventsnext.php?id=" + teamId;
  let req = new Request(eventsUrl);
  let res = await req.loadJSON();
  let events = res.events;

  let teamImg = await getTeamImg(teamId)

  let w = new ListWidget();

  w.backgroundColor = Device.isUsingDarkAppearance() ? new Color("#2C2C2E", 1) : new Color("#ffffff", 1)
  w.useDefaultPadding()

  const limitedEvents = events.slice(0, maxEvents)

  const imageSize = widgetSize === "large" ? 32 : 26;

  w.addSpacer()

  if (widgetSize === "large") {

    const teamName = events[0].idHomeTeam == teamId ? events[0].strHomeTeam : events[0].strAwayTeam
    let titleStack = w.addStack()
    let title = titleStack.addText(`${teamName}'s upcoming matches`)
    title.font = Font.boldSystemFont(16);

    w.addSpacer()

  }

  for (let i = 0; i < limitedEvents.length; i++) {
    let e = events[i]

    if (widgetSize === "large" || i > 0) {
      w.addSpacer(10)
    }

    let homeImg = ""
    let awayImg = ""

    if (e.idHomeTeam == teamId) {
      homeImg = teamImg
      awayImg = await getTeamImg(e.idAwayTeam)
    } else {
      homeImg = await getTeamImg(e.idHomeTeam)
      awayImg = teamImg
    }

    let rowStack = w.addStack()
    rowStack.centerAlignContent()

    // home team image
    let homeImageStack = rowStack.addStack();
    let homeImage = homeImageStack.addImage(homeImg);
    homeImage.imageSize = new Size(imageSize, imageSize)
    homeImageStack.addSpacer(10)

    // home team name
    let homeNameStack = rowStack.addStack();
    let homeName = homeNameStack.addText(e.strHomeTeam);
    homeName.font = Font.mediumSystemFont(12);
    homeNameStack.size = new Size(90, 14)
    homeNameStack.addSpacer()

    let homescoreStack = rowStack.addStack();
    if(e.intHomeScore !== null) {
        let homescore = homescoreStack.addText(`${e.intHomeScore}`)
        homescore.font = Font.mediumSystemFont(12)
    }
    homescoreStack.size = new Size(12, 12)
    homescoreStack.addSpacer(5)

    let separatorStack = rowStack.addStack();
    let separator = separatorStack.addText('-')
    separator.font = Font.mediumSystemFont(12)
    separatorStack.size = new Size(24, 12)
    separatorStack.addSpacer(10)

    let awayscoreStack = rowStack.addStack();
    if(e.intAwayScore !== null) {
        let awayscore = awayscoreStack.addText(`${e.intAwayScore}`)
        awayscore.font = Font.mediumSystemFont(12)
    }
    awayscoreStack.size = new Size(12, 12)
    awayscoreStack.addSpacer(5)

    // away team name
    let awayNameStack = rowStack.addStack();
    awayNameStack.addSpacer()
    let awayName = awayNameStack.addText(e.strAwayTeam);
    awayName.font = Font.mediumSystemFont(12);
    awayNameStack.size = new Size(100, 14)
    awayNameStack.addSpacer(10)

    // away team image
    let awayImageStack = rowStack.addStack();
    let awayImage = awayImageStack.addImage(awayImg);
    awayImage.imageSize = new Size(imageSize, imageSize);

    w.addSpacer(5)

    let infoRowStack = w.addStack()
    infoRowStack.centerAlignContent()
    infoRowStack.addSpacer()

    let dateStack = infoRowStack.addStack()
    const dateFormatter = new DateFormatter()
    dateFormatter.useMediumDateStyle()
    dateFormatter.useShortTimeStyle()
    let parsedDate = new Date(Date.parse(e.strTimestamp))
    let formattedDate = dateFormatter.string(parsedDate)

    let date = dateStack.addText(formattedDate)
    date.font = Font.mediumSystemFont(10)
    date.textOpacity = 0.5

    dateStack.addSpacer(10)

    //league image
    if (widgetSize === "large") {
      let leagueImg = await getLeagueImg(e.idLeague)
      let leagueImageStack = infoRowStack.addStack()
      let leagueImage = leagueImageStack.addImage(leagueImg)
      leagueImage.size = new Size(10, 10)
    }

    infoRowStack.addSpacer()

    if (i !== maxEvents - 1) {
      w.addSpacer(10)

      let dividerStack = w.addStack()
      let divider = dividerStack.addImage(createDivider())
      divider.imageOpacity = 0.5
    }
  }

  w.addSpacer()

  return w
}

const widget = await createWidget()

Script.setWidget(widget)
Script.complete()

await widget.presentLarge()
@ichsanfathoni

This comment has been minimized.

Copy link

@ichsanfathoni ichsanfathoni commented Feb 12, 2021

Mine show error: The data couldn't be read because it isn't in the correct format. How to fix this?

@gmaschine

This comment has been minimized.

Copy link

@gmaschine gmaschine commented Feb 18, 2021

Mine show error: The data couldn't be read because it isn't in the correct format. How to fix this?

Same for me.

@ilyichvismara

This comment has been minimized.

Copy link
Owner Author

@ilyichvismara ilyichvismara commented Feb 18, 2021

Maybe I’ll give it a look in the next few days. I’m sorry to inform you that I stopped working on this project so there won’t probably be an update very soon. Btw I’m afraid it is caused by the API.

@ilyichvismara

This comment has been minimized.

Copy link
Owner Author

@ilyichvismara ilyichvismara commented Feb 18, 2021

Just checked the api, it became premium so It won’t work anymore.

@Jensderond

This comment has been minimized.

Copy link

@Jensderond Jensderond commented Feb 18, 2021

Yeah too bad, eventsnext.php is premium, but you could use eventslast.php for results of past events.

@Jensderond

This comment has been minimized.

Copy link

@Jensderond Jensderond commented Feb 18, 2021

@oeksnxk

This comment has been minimized.

Copy link

@oeksnxk oeksnxk commented Feb 21, 2021

May I know the full code?

@Jensderond

This comment has been minimized.

Copy link

@Jensderond Jensderond commented Feb 22, 2021

Using ESPN's hidden API endpoints

How to find teamId: https://site.api.espn.com/apis/site/v2/sports/soccer/:league/teams

params:

  • league: some league abbreviation (EX: 'eng.1' for EPL, 'usa.1' for MLS, 'ned.1' for Dutch Eredivisie)
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-gray; icon-glyph: futbol;
const competitionId = "ned.1"
const teamId = 142;
const fixturesUrl = `https://site.api.espn.com/apis/site/v2/sports/soccer/${competitionId}/teams/${teamId}/schedule?fixture=true`;
const widgetSize = config.widgetFamily
const maxEvents = widgetSize === "large" ? 4 : 2

async function getTeamImg(imageUrl) {
  let imgReq = new Request(imageUrl)
  let img = await imgReq.loadImage()
  return img
}

function createDivider() {
  const drawContext = new DrawContext()
  drawContext.size = new Size(543, 1)
  const path = new Path()
  path.addLine(new Point(1000, 20))
  drawContext.addPath(path)
  drawContext.setStrokeColor(Device.isUsingDarkAppearance() ? new Color("#fff", 1) : new Color("#000000", 1))
  drawContext.setLineWidth(1)
  drawContext.strokePath()
  return drawContext.getImage()
}

async function createWidget() {
  let req = new Request(fixturesUrl);
  let res = await req.loadJSON();
  let events = res.events;

  let teamImg = await getTeamImg(res.team.logo);

  let w = new ListWidget();
  w.backgroundColor = Device.isUsingDarkAppearance() ? new Color("#2C2C2E", 1) : new Color("#ffffff", 1)
  w.useDefaultPadding()

  const limitedEvents = events.slice(0, maxEvents)

  const imageSize = widgetSize === "large" ? 32 : 26;

  w.addSpacer()

  if (widgetSize === "large") {

    const teamName =
        events[0].competitions[0].competitors[0].id == teamId
            ? events[0].competitions[0].competitors[0].team.displayName
            : events[0].competitions[0].competitors[1].team.displayName;
    let titleStack = w.addStack()
    let title = titleStack.addText(`${teamName}'s upcoming matches`)
    title.font = Font.boldSystemFont(16);

    w.addSpacer()

  }

  for (let i = 0; i < limitedEvents.length; i++) {
    let e = events[i]
    const competitors = e.competitions[0].competitors;

    if (widgetSize === "large" || i > 0) {
      w.addSpacer(10)
    }

    let homeImg = ""
    let awayImg = ""

    if (competitors[0].id == teamId) {
      homeImg = teamImg
      awayImg = await getTeamImg(competitors[1].team.logos[0].href)
    } else {
      homeImg = await getTeamImg(competitors[0].team.logos[0].href)
      awayImg = teamImg
    }

    let rowStack = w.addStack()
    rowStack.centerAlignContent()
    rowStack.addSpacer(20)

    // home team image
    let homeImageStack = rowStack.addStack();
    let homeImage = homeImageStack.addImage(homeImg);
    homeImage.imageSize = new Size(imageSize, imageSize)
    homeImageStack.addSpacer(10)

    // home team name
    let homeNameStack = rowStack.addStack();
    let homeName = homeNameStack.addText(competitors[0].team.displayName);
    homeName.font = Font.mediumSystemFont(12);
    homeNameStack.size = new Size(90, 14)
    homeNameStack.addSpacer()

    let separatorStack = rowStack.addStack();
    let separator = separatorStack.addText('-')
    separator.font = Font.mediumSystemFont(12)
    separatorStack.size = new Size(24, 12)
    separatorStack.addSpacer(10)

    // away team name
    let awayNameStack = rowStack.addStack();
    awayNameStack.addSpacer()
    let awayName = awayNameStack.addText(competitors[1].team.displayName);
    awayName.font = Font.mediumSystemFont(12);
    awayNameStack.size = new Size(100, 14)
    awayNameStack.addSpacer(10)

    // away team image
    let awayImageStack = rowStack.addStack();
    let awayImage = awayImageStack.addImage(awayImg);
    awayImage.imageSize = new Size(imageSize, imageSize);

    w.addSpacer(5)

    let infoRowStack = w.addStack()
    infoRowStack.centerAlignContent()
    infoRowStack.addSpacer()

    let dateStack = infoRowStack.addStack()
    const dateFormatter = new DateFormatter()
    dateFormatter.useMediumDateStyle()
    dateFormatter.useShortTimeStyle()
    let parsedDate = new Date(Date.parse(e.date))
    let formattedDate = dateFormatter.string(parsedDate)

    let date = dateStack.addText(formattedDate)
    date.font = Font.mediumSystemFont(10)
    date.textOpacity = 0.5

    dateStack.addSpacer(10)

    infoRowStack.addSpacer()

    if (i !== maxEvents - 1) {
      w.addSpacer(10)

      let dividerStack = w.addStack()
      let divider = dividerStack.addImage(createDivider())
      divider.imageOpacity = 0.5
    }
  }

  w.addSpacer()

  return w
}

const widget = await createWidget()

Script.setWidget(widget)
Script.complete()

await widget.presentLarge()
@DieserHannes

This comment has been minimized.

Copy link

@DieserHannes DieserHannes commented Feb 22, 2021

Yeah. Works very well, thanks 👌

edit: Unfortunately the Champions League games are missing

@kunaldua

This comment has been minimized.

Copy link

@kunaldua kunaldua commented Feb 22, 2021

Does the ESPN API have an endpoint that lets you view a team's fixtures independent of the competition? So you can get the fixtures in Europe and domestic cup competitions as well — in addition to the league — without having to check multiple feeds.

@kerstin2021

This comment has been minimized.

Copy link

@kerstin2021 kerstin2021 commented Mar 19, 2021

Hello,
Can someone please help me . I always become error :
2021-03-19 21:16:29: Error on line 33:42: TypeError: undefined is not an object (evaluating 'res.team.logo')
I have taljen the ID from teams by the Link .
Pleas help me.
Thank you

@kerstin2021

This comment has been minimized.

Copy link

@kerstin2021 kerstin2021 commented Mar 19, 2021

How to find the Team id from my team ?
The Link ist wrong

@bubbafett23

This comment has been minimized.

Copy link

@bubbafett23 bubbafett23 commented Sep 4, 2021

Anyone try this with college football?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment