Skip to content

Instantly share code, notes, and snippets.

@ilyich-erd
Last active January 3, 2022 20:16
Show Gist options
  • Star 26 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ilyich-erd/7895bb479e14a999f3cfd2c391f10597 to your computer and use it in GitHub Desktop.
Save ilyich-erd/7895bb479e14a999f3cfd2c391f10597 to your computer and use it in GitHub Desktop.

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
Copy link

FE317BCD-62C9-4EE8-A56B-AA11A9D9A904

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

@jay-pee
Copy link

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
Copy link

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
Copy link

What Leibinger015 says.

@Jensderond
Copy link

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
Copy link

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

@yusufozdennn
Copy link

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

Same for me.

@ilyich-erd
Copy link
Author

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.

@ilyich-erd
Copy link
Author

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

@Jensderond
Copy link

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

@Jensderond
Copy link

@oeksnxk
Copy link

oeksnxk commented Feb 21, 2021

May I know the full code?

@Jensderond
Copy link

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
Copy link

DieserHannes commented Feb 22, 2021

Yeah. Works very well, thanks 👌

edit: Unfortunately the Champions League games are missing

@kunaldua
Copy link

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
Copy link

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
Copy link

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

@bubbafett23
Copy link

Anyone try this with college football?

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