Skip to main content

CUP=X

Widgets

Real-time currency exchange rates directly on your iOS/iPadOS/macOS home screen with our custom widgets powered by Scriptable JS.

info

Prerequisites

You'll need the Scriptable app installed on your iOS/iPadOS device to create these widgets.

Scriptable is a powerful iOS/iPadOS app that allows you to create custom JavaScript-powered widgets, automations, and more.

Download Scriptable from the App Store

Custom Exchange Rate Widgets

Create beautiful, customizable widgets that display real-time Cuban Peso (CUP) exchange rates right on your iOS/iPadOS/macOS home screen.

Exchange Rate Widget Example

iOS Widgets

Widget Features

update

Real-time Updates

Widgets get the latest CUP=X exchange rates throughout the day.

palette

Customizable Design

Choose from different widget sizes and layouts to display.

touch_app

Interactive

Tap the widget to launch CUP=X website for more detailed information.

How to Install Your Widget

1

Install Scriptable

Download and install the Scriptable app from the App Store.

2

Copy the Widget Code


    /* --------------------------------------------------------------
    Description:
    iOS/iPad Scriptable widget - CUP=X Informal Currency Exchange Rates in Cuba
    Source: elToque: https://eltoque.com/tasas-de-cambio-de-moneda-en-cuba-hoy
    
    Version:
    1.0.0
    -------------------------------------------------------------- */
    const widgetUrl = 'https://peso-cubano.com';
    const apiUrl = `https://api.cambiocuba.money/api/v1/x-rates-by-date-range?trmi=true&period=7D`;
    const currencies = ['ECU', 'USD', 'MLC', 'CAD', 'MXN'];
    const images = [
    {
    name: 'eur',
    url: 'https://peso-cubano.com/img/eur.png'
    },
    {
    name: 'usd',
    url: 'https://peso-cubano.com/img/usd.png'
    },
    {
    name: 'cad',
    url: 'https://peso-cubano.com/img/cad.png'
    },
    {
    name: 'mxn',
    url: 'https://peso-cubano.com/img/mxn.png'
    },
    {
    name: 'mlc',
    url: 'https://peso-cubano.com/img/mlc.png'
    },
    ];
    
    const languages = {
    en: {
    shortTitle: 'CUP=X',
    subtitle: 'Exchange Rates',
    noData: 'No currency exchange rate data.',
    },
    es: {
    shortTitle: 'CUP=X',
    subtitle: 'Tasas de Cambio',
    noData: 'No hay datos de tasas de cambio de divisas.',
    }
    };
    
    async function init() {
    let widget;
    try {
    const data = await getData();
    const resources = await getImages();
    widget = createWidget(data, resources, widgetUrl);
    } catch (err) {
    console.error(`Error getting widget data. ${err}`);
    widget = createEmptyWidget();
    } finally {
    if (config.runsInWidget || config.runsInAccessoryWidget) {
    // create and show widget
    Script.setWidget(widget);
    Script.complete();
    }
    else {
    widget.presentMedium();
    }
    
    }
    }
    
    function createWidget(data, resources, widgetUrl) {
    if (config.runsInAccessoryWidget) {
    return createLockScreenWidget(data, widgetUrl);
    }
    else if (config.widgetFamily === 'small') {
    return createSmallWidget(data, resources, widgetUrl);
    }
    return createMediumWidget(data, resources, widgetUrl);
    }
    
    // assemble empty widget layout
    function createEmptyWidget() {
    const w = new ListWidget();
    w.backgroundColor = Color.white();
    w.url = widgetUrl;
    
    w.addSpacer();
    
    const infoText = w.addText(getNoDataText());
    infoText.textColor = Color.black();
    infoText.font = Font.boldMonospacedSystemFont(20);
    infoText.centerAlignText();
    
    const runsInSmallWidget = config.widgetFamily === 'small';
    
    if (runsInSmallWidget || config.runsInAccessoryWidget) {
    const fontSize = runsInSmallWidget ? 18 : 14;
    infoText.font = Font.boldMonospacedSystemFont(fontSize);
    infoText.leftAlignText();
    }
    
    w.addSpacer();
    
    return w;
    }
    
    // assemble medium widget layout
    function createMediumWidget(data, resources, widgetUrl) {
    const { USD, ECU, MLC, CAD, MXN } = data;
    
    const w = new ListWidget();
    w.backgroundColor = Color.white();
    w.url = widgetUrl;
    
    w.addSpacer(2);
    
    // header
    const headerStack = w.addStack();
    headerStack.layoutHorizontally();
    headerStack.centerAlignContent();
    headerStack.spacing = 8;
    
    // title
    const staticText = headerStack.addText(getTitleText());
    staticText.textColor = Color.black();
    staticText.font = Font.boldSystemFont(18);
    staticText.centerAlignText();
    
    w.addSpacer(4);
    
    // body
    const mainStack = w.addStack();
    mainStack.layoutHorizontally();
    mainStack.topAlignContent();
    mainStack.setPadding(0, 4, 0, 4);
    
    const leftStack = mainStack.addStack();
    leftStack.layoutVertically();
    leftStack.topAlignContent();
    
    // usd
    const usdStack = leftStack.addStack();
    usdStack.layoutHorizontally();
    usdStack.centerAlignContent();
    usdStack.spacing = 8;
    
    const usdImage = usdStack.addImage(getImage(resources, 'usd'));
    usdImage.imageSize = new Size(32, 32);
    
    const usdRateText = usdStack.addText(`= ${format(USD, 0)} CUP`);
    usdRateText.textColor = Color.black();
    usdRateText.font = Font.boldMonospacedSystemFont(16);
    
    // eur
    const eurStack = leftStack.addStack();
    eurStack.layoutHorizontally();
    eurStack.centerAlignContent();
    eurStack.spacing = 8;
    
    const eurImage = eurStack.addImage(getImage(resources, 'eur'));
    eurImage.imageSize = new Size(32, 32);
    
    const eurRateText = eurStack.addText(`= ${format(ECU, 0)} CUP`);
    eurRateText.textColor = Color.black();
    eurRateText.font = Font.boldMonospacedSystemFont(16);
    
    // cad
    const cadStack = leftStack.addStack();
    cadStack.layoutHorizontally();
    cadStack.centerAlignContent();
    cadStack.spacing = 8;
    
    const cadImage = cadStack.addImage(getImage(resources, 'cad'));
    cadImage.imageSize = new Size(32, 32);
    
    const cadRateText = cadStack.addText(`= ${format(CAD, 0)} CUP`);
    cadRateText.textColor = Color.black();
    cadRateText.font = Font.boldMonospacedSystemFont(16);
    
    
    mainStack.addSpacer();
    
    // right stack
    const rightStack = mainStack.addStack();
    rightStack.layoutVertically();
    rightStack.topAlignContent();
    
    // mlc
    const mlcStack = rightStack.addStack();
    mlcStack.layoutHorizontally();
    mlcStack.centerAlignContent();
    mlcStack.spacing = 8;
    
    const mlcImage = mlcStack.addImage(getImage(resources, 'mlc'));
    mlcImage.imageSize = new Size(32, 32);
    
    const mlcRateText = mlcStack.addText(`= ${format(MLC, 0)} CUP`);
    mlcRateText.textColor = Color.black();
    mlcRateText.font = Font.boldMonospacedSystemFont(16);
    
    // mxn
    const mxnStack = rightStack.addStack();
    mxnStack.layoutHorizontally();
    mxnStack.centerAlignContent();
    mxnStack.spacing = 8;
    
    const mxnImage = mxnStack.addImage(getImage(resources, 'mxn'));
    mxnImage.imageSize = new Size(32, 32);
    
    const mxnRateText = mxnStack.addText(`= ${format(MXN, 0)} CUP`);
    mxnRateText.textColor = Color.black();
    mxnRateText.font = Font.boldMonospacedSystemFont(16);
    
    // footer
    const date = new Date();
    const df = new DateFormatter();
    df.useFullDateStyle();
    
    const hf = new DateFormatter();
    hf.useShortTimeStyle();
    
    // add date and time
    const dateText = w.addText(`${df.string(date)} ${hf.string(date)}`);
    dateText.textColor = Color.black();
    dateText.font = Font.semiboldSystemFont(10);
    dateText.leftAlignText();
    
    return w;
    }
    
    // assemble small widget layout
    function createSmallWidget(data, resources, widgetUrl) {
    const { USD, ECU, MLC } = data;
    
    const w = new ListWidget();
    w.backgroundColor = Color.white();
    w.url = widgetUrl;
    
    // header
    w.addSpacer(8);
    
    const staticText = w.addText(getShortTitleText());
    staticText.textColor = Color.black();
    staticText.font = Font.boldSystemFont(12);
    staticText.leftAlignText();
    
    const exchangeText = w.addText(getSubtitleText());
    exchangeText.textColor = Color.black();
    exchangeText.font = Font.boldSystemFont(12);
    exchangeText.leftAlignText();
    
    // body
    const mainStack = w.addStack();
    mainStack.layoutVertically();
    mainStack.centerAlignContent();
    
    // usd
    const usdStack = mainStack.addStack();
    usdStack.layoutHorizontally();
    usdStack.centerAlignContent();
    usdStack.spacing = 4;
    
    const usdImage = usdStack.addImage(getImage(resources, 'usd'));
    usdImage.imageSize = new Size(32, 32);
    
    const usdRateText = usdStack.addText(`= ${format(USD, 0)} CUP`);
    usdRateText.textColor = Color.black();
    usdRateText.font = Font.boldMonospacedSystemFont(14);
    
    usdStack.addSpacer();
    
    // eur
    const eurStack = mainStack.addStack();
    eurStack.layoutHorizontally();
    eurStack.centerAlignContent();
    eurStack.spacing = 4;
    
    const eurImage = eurStack.addImage(getImage(resources, 'eur'));
    eurImage.imageSize = new Size(32, 32);
    
    const eurRateText = eurStack.addText(`= ${format(ECU, 0)} CUP`);
    eurRateText.textColor = Color.black();
    eurRateText.font = Font.boldMonospacedSystemFont(14);
    
    eurStack.addSpacer();
    
    // mlc
    const mlcStack = mainStack.addStack();
    mlcStack.layoutHorizontally();
    mlcStack.centerAlignContent();
    mlcStack.spacing = 4;
    
    const mlcImage = mlcStack.addImage(getImage(resources, 'mlc'));
    mlcImage.imageSize = new Size(32, 32);
    
    const mlcRateText = mlcStack.addText(`= ${format(MLC, 0)} CUP`);
    mlcRateText.textColor = Color.black();
    mlcRateText.font = Font.boldMonospacedSystemFont(14);
    
    mlcStack.addSpacer();
    
    // footer
    const date = new Date();
    const df = new DateFormatter();
    df.useShortDateStyle();
    
    const hf = new DateFormatter();
    hf.useShortTimeStyle();
    
    const dateText = w.addText(`${df.string(date)} ${hf.string(date)}`);
    dateText.textColor = Color.black();
    dateText.font = Font.semiboldSystemFont(8);
    dateText.centerAlignText();
    
    w.addSpacer(8);
    
    return w;
    }
    
    // assemble lock screen widget layout
    function createLockScreenWidget(data, widgetUrl) {
    const { USD, ECU, MLC } = data;
    
    const w = new ListWidget();
    w.url = widgetUrl;
    
    const mainStack = w.addStack();
    mainStack.layoutHorizontally();
    mainStack.centerAlignContent();
    
    // stack 1
    const stack1 = mainStack.addStack();
    stack1.layoutVertically();
    stack1.centerAlignContent();
    stack1.spacing = 4;
    
    const usdText = stack1.addText('USD');
    usdText.textColor = Color.white();
    usdText.font = Font.heavyMonospacedSystemFont(15);
    
    const eurText = stack1.addText('EUR');
    eurText.textColor = Color.white();
    eurText.font = Font.heavyMonospacedSystemFont(15);
    
    const mlcText = stack1.addText('MLC');
    mlcText.textColor = Color.white();
    mlcText.font = Font.heavyMonospacedSystemFont(15);
    
    mainStack.addSpacer(4);
    
    // stack 2
    const stack2 = mainStack.addStack();
    stack2.layoutVertically();
    stack2.centerAlignContent();
    stack2.spacing = 4;
    
    const usdRateText = stack2.addText(`= ${format(USD, 0)} CUP`);
    usdRateText.textColor = Color.white();
    usdRateText.font = Font.mediumMonospacedSystemFont(15);
    
    const eurRateText = stack2.addText(`= ${format(ECU, 0)} CUP`);
    eurRateText.textColor = Color.white();
    eurRateText.font = Font.mediumMonospacedSystemFont(15);
    
    const mlcRateText = stack2.addText(`= ${format(MLC, 0)} CUP`);
    mlcRateText.textColor = Color.white();
    mlcRateText.font = Font.mediumMonospacedSystemFont(15);
    
    return w;
    }
    
    function getTitleText() {
    const lang = Device.language();
    if (!languages[lang]) {
    return `${languages.en.shortTitle} ${languages.en.subtitle}`;
    }
    return `${languages[lang].shortTitle} ${languages[lang].subtitle}`;
    }
    
    function getShortTitleText() {
    const lang = Device.language();
    if (!languages[lang]) {
    return `${languages.en.shortTitle}`;
    }
    return `${languages[lang].shortTitle}`;
    }
    
    function getSubtitleText() {
    const lang = Device.language();
    if (!languages[lang]) {
    return `${languages.en.subtitle}`;
    }
    return `${languages[lang].subtitle}`;
    }
    
    function getNoDataText() {
    const lang = Device.language();
    if (!languages[lang]) {
    return languages.en.noData;
    }
    return languages[lang].noData;
    }
    
    async function getRate(currency) {
    const url = `${apiUrl}&cur=${currency}`;
    const req = new Request(url);
    const res = await req.loadJSON();
    const { min, max, avg, median } = res[res.length - 1];
    
    console.log(`${currency} - Min: ${min}, Max: ${max}, Avg: ${avg}, Median: ${median}`);
    
    return median;
    }
    
    async function getData() {
    const rates = {};
    const ratePromises = currencies.map(currency => getRate(currency));
    const results = await Promise.all(ratePromises);
    for (let i = 0; i < currencies.length; i++) { rates[currencies[i]]=results[i]; } return rates; } async function
        getImages() { const result={}; const imagePromises=images.map(image=> {
        const req = new Request(image.url);
        return req.loadImage().then(res => ({ name: image.name, image: res }));
        });
        const responses = await Promise.all(imagePromises);
        for (const r of responses) {
        const { name, image } = r;
        result[name] = image;
        }
        return result;
        }
    
        function getImage(resources, key) {
        return resources[key];
        }
    
        function format(value, fractionDigits = 2) {
        return Math.round(Number.parseFloat(value)).toFixed(fractionDigits);
        }
    
        //initialize
        await init();

Scriptable JS
3

Create a New Script in Scriptable

Open Scriptable, tap the + button to create a new script, and paste the code.

4

Add Widget to Home Screen

Long press on your home screen, tap the + button, search for Scriptable, select a widget size, add it, then select the script you created.