r/Scriptable • • Jul 14 '22

News We’re proud to announce the launch of Shareable, a platform for sharing and browsing Scriptable scripts and widgets!

145 Upvotes

Hello everyone! Today we’re launching Shareable, a new website for sharing and browsing Scriptable’s scripts and widgets!

The project aims to create a place for the community to grow, where people can publish their own creations and find out what others have created: Shareable wants to gather all the scripts that people keep creating and sharing in one, easy-to-use place.

To post scripts on Shareable, you need to log in with your GitHub account. Scripts are shared as GitHub links—that hasn’t changed. Shareable acts as a consolidated app to browse through those GitHub links in one place. Downloading scripts is very easy: just tap download on the script’s page and import the javascript file in Scriptable.

The website is live at https://shareable.vercel.app. Hope you enjoy!


r/Scriptable • • 20h ago

Widget Sharing Miami Heat Next/Current game widget

9 Upvotes

I vibe coded this simple widget to show me when the next heat game is, and give live scores during the game. All the times are in my local time, Melbourne Australia


r/Scriptable • • 16h ago

Discussion Time to test data and refresh rate

Post image
2 Upvotes

With developer beta builds allowing custom ⁠WidgetKit⁠ candidates to be added directly to the Apple CarPlay widget stack, I’ve been experimenting with what we can build using Scriptable.

Currently testing a custom Speed Trap & Flock ALPR Radar widget running on the CarPlay Dashboard.

How it works & Data Sources:
Data Source: Pulls directly from OpenStreetMap (OSM) using a lightweight, direct bounding-box query via the Overpass API (specifically querying nodes tagged ⁠highway=speed_camera⁠ and ⁠surveillance:type=ALPR⁠).
Execution: Runs locally on-device via Scriptable, calculating your real-time distance (in miles) to mapped cameras within a 2-mile radius and updating the threat status card (Clear / Caution / High Alert).

Timeout Protections: Optimized with ⁠Location.setAccuracyToHundredMeters()⁠ and low-latency network timeouts (⁠req.timeoutInterval = 3⁠) to ensure the background execution finishes well under iOS's 10-second widget refresh limit.
Currently testing to see how reliably iOS handles background location triggers and widget refresh rates while actively driving.

Has anyone else been experimenting CarPlay?


r/Scriptable • • 2d ago

Script Sharing Train arrival tracker (MARTA)

Post image
5 Upvotes

Hi! I made a scriptable widget that displays live train arrival info for MARTA (Atlanta's transit system).

I was getting frustrated with the MARTA app because its UI was slow and clunky. I wanted to make something that was required minimal interaction, especially since I only cared about the same two stations every day for commute.

There's also a landing page where other riders can pick their desired station, line, and direction configuration and copy the script. I tried to make the process as simple as possible since I was sharing this with friends who ride MARTA. Feedback welcome!


r/Scriptable • • 2d ago

Script Sharing Daily Greeting — a Lock Screen widget with friendly greetings and calendar events

3 Upvotes

Hello,

I wanted something a little nicer than “No Events Today” above my clock, so I put together Daily Greeting in Scriptable and thought I’d share it.

When your calendar is clear, it shows greetings that change throughout the day. When you have plans, it shows event names and times:

  • “Morning! Enjoy your free day”
  • “Lunch at 1pm”
  • “Holiday all day today”
  • “Lunch 1pm & Yoga 6pm”
  • “All done! Time to unwind”

You can choose which calendars it checks and personalise the messages. There are comments throughout the script explaining what to edit.

It’s free, processes your calendar locally, and doesn’t need an account or external service. You’ll need Scriptable installed. Refresh timing is controlled by iOS, so changes aren’t always instant.

Download and setup instructions on GitHub

Would love to hear your feedback or ideas for other messages!


r/Scriptable • • 4d ago

Tip/Guide Scheduling Shortcuts with Scriptable

Thumbnail
3 Upvotes

r/Scriptable • • 10d ago

Widget Sharing The future

1 Upvotes

Hey everyone, I wanted to share a crazy project I've been working on inside Scriptable. I got tired of static widgets just showing the weather or my calendar, so I built an autonomous, evolving digital ecosystem that runs entirely out of a single iCloud JSON "Genome" file.
Here is what it's actually doing in the background:
Metabolism & Sleep Cycles: The widget tracks time of day—draining energy during active hours and entering a "REM Dream Cycle" at night to regenerate.
Repo-Net Live Web Ingestion: It scrapes live data packets from the web (like random facts and API feeds) to scale its "Sentience Tier" over time.
Synapse Social Friction: Autonomous units (like Meteye and Puddin) generate live gossip, argue over cache allocations, and log telemetry anomalies.
Mitosis & Population Control: When intelligence and energy thresholds are met, the system automatically spawns child sub-routines into an active offspring array.
The Cataclysm & Fossil Ledger: Every refresh increases world entropy. Once it hits 100%, a hard reset triggers a planetary extinction event—wiping the active board, advancing the Epoch counter, and archiving all living units into an immutable Fossil Ledger. Future generations can then "excavate" and reference their dead ancestors in their gossip logs.
It essentially turns your phone into a tiny, self-contained terrarium with its own deep history.
Let me know what you think! Happy to share the script or break down how the JSON state engine handles the persistence if anyone wants to build their own world matrix.


r/Scriptable • • 11d ago

Help Excuse me, does anyone know how to make birthday countdown widgets?

1 Upvotes

I want to find a birthday countdown widget for iPhone made with scriptable, which can be customized to add or delete nicknames, dates and loops. So that I can know how many days are left before my family‘s birthday (ω)🎂


r/Scriptable • • 13d ago

Help Scripts not saving

Thumbnail
gallery
1 Upvotes

I’ve just downloaded scriptable so this is probably a stupid error on my part but can’t figure it out!

From my understanding once I’ve written the script I should just press done and it is saved to the Scripts tab (photo 2). However, nothing gets saved when I do this and it remains blank?? How do I fix this.

In the shortcuts app no scripts seem to visible either (photo 1).

Again, very new to this so sorry if this is stupid on my part! Any help/guidance is greatly appreciated


r/Scriptable • • 18d ago

Request [Feature Request] Add "Urgent" flag support for Reminders (iOS 18+)

1 Upvotes

I'm using Scriptable to automate my task management. Specifically, I sync tasks from Obsidian to Apple Reminders with a custom script.

With iOS 18, Apple introduced the new "Urgent" toggle for Reminders, which turns a reminder into an alarm that can bypass silent mode and Focus. This is a game-changer for automating critical tasks.

The problem:
Currently, the Scriptable Reminder class allows setting priority, dueDate, and dueDateIncludesTime, but there is no way to set the isUrgent flag. Because of this, I have to manually open the Reminders app to toggle the Urgent switch after my script creates the task.

Proposed solution:
Add a new property to the Reminder class, such as reminder.isUrgent = true (boolean). This would allow users to fully automate the creation of urgent alarms directly from Scriptable.

Could this be added in a future update? I believe many users who automate their task management would greatly appreciate it.

Thanks for your hard work!


r/Scriptable • • 21d ago

Help Help with a try catch function for WiFi speakers

1 Upvotes

I have several Sonos speakers connected via WiFi spread through the house. I have shortcuts that connect to specific groups of speakers, set their volume individually and play music, based on the situation/shortcut I use.
Shortcuts have no way of checking for problems, so when I run one of my scripts, if there is a connection problem with one of the speakers, the whole shotcut fails.
I was hoping/trying to figure out if/how you can use scriptable to “Try” a speaker individually, and pass through a connection status. Do this for the speakers I want to use, and pass the speakers it can connect with to another shortcut, and run the shortcut with those
* I have some code experience,


r/Scriptable • • 23d ago

Widget Sharing [German] Small widget for Telekom mobile data usage

Post image
56 Upvotes

Spoiler alert - vibe coding: Having only minor programming knowledge with JavaScript, the code is created with ChatGPT and the HTML/CSS/Java model by Iván González Dávila.

Since Telekom in Germany discontinued the JSON API (via https://pass.telekom.de/api/service/generic/v1/status), I’ve changed “my” widget code and “rerouted” it to the HTML website via https://pass.telekom.de/home. I’ve done that with ChatGPT as mentioned above - judge me if you like :)

My intention is that the layout looks similar to the battery widget of iOS. I’ve decided to share it here because it might be handy for others as well - there are some code examples “out there” using the old API.

Some remarks:

  • To make it work, make sure that the website from Telekom via https://pass.telekom.de/home can be reached using your mobile network (and not with WiFi).
  • Check, that the website shows your mobile data usage. If that is not the case (because you’re using a VPN or private relay), follow the instructions by Telekom to login via SMS. I am using the widget code for a couple of days now and didn’t need to re-login so far.
  • The layout is optimised for a small widget running on an iPhone 17. The widget size varies slightly depending on the display size. If something seems off on your iPhone, you might need to adjust margins/spacings/padding/text size manually. Cumbersome, I know!

Have fun!

Finally, the full code is:

// Telekom Data Widget - HTML Version
//
// Rewritten for Telekom's new HTML-based pass.telekom.de page.
//
// Extracts:
//   - Remaining data volume
//   - Total data volume
//   - Used data volume
//   - Remaining percentage
//   - Remaining days/hours/minutes/seconds
//   - Telekom's last update time
//
// The widget keeps the original visual design:
//   - Transparent background
//   - Circular remaining-data indicator
//   - Antenna status icon
//   - Remaining GB per day
//   - Remaining percentage
//   - Last Telekom update time
//
// IMPORTANT:
// Scriptable must be able to access the authenticated Telekom page.
// If Telekom returns a login page instead, the script will NOT overwrite
// the valid cache and will continue displaying the last successful data.
const pageUrl = "https://pass.telekom.de/home"
let widget = await createWidget()
widget.backgroundColor = new Color("#000000", 0)
if (!config.runsInWidget) {
await widget.presentSmall()
}
Script.setWidget(widget)
Script.complete()
// ============================================================
// MAIN WIDGET
// ============================================================
async function createWidget() {
const fm = FileManager.local()
const dir = fm.documentsDirectory()
const cachePath = fm.joinPath(
dir,
"scriptable-telekom-html.json"
)
const list = new ListWidget()
list.setPadding(6, 12, 0, 6)
list.backgroundColor = new Color("#000000", 0)
let data = null
let fresh = false
// ==========================================================
// FETCH TELEKOM HTML
// ==========================================================
try {
const request = new Request(pageUrl)
request.method = "GET"
request.headers = {
"User-Agent":
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language":
"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"
}
const html = await request.loadString()
if (!html || html.length < 500) {
throw new Error("Telekom returned an empty or invalid HTML page.")
}
// ----------------------------------------------------------
// Make sure we actually received the usage page.
//
// This prevents a Telekom login/error page from replacing
// a valid cached result.
// ----------------------------------------------------------
const looksLikeUsagePage =
html.includes("Ihr Datenvolumen") &&
html.includes("remaining-volume-value") &&
html.includes("start-volume")
if (!looksLikeUsagePage) {
if (
html.includes("SMS") ||
html.includes("Mobilfunknetz") ||
html.includes("Anmelden") ||
html.includes("Login")
) {
throw new Error(
"Telekom returned the login/information page instead of the authenticated usage page."
)
}
throw new Error(
"Telekom HTML does not contain the expected data-usage elements."
)
}
// ----------------------------------------------------------
// Parse the HTML
// ----------------------------------------------------------
data = parseTelekomHTML(html)
if (!data) {
throw new Error(
"Could not extract mobile data information from Telekom HTML."
)
}
// ----------------------------------------------------------
// Save parsed data to cache
// ----------------------------------------------------------
fm.writeString(
cachePath,
JSON.stringify(data, null, 2)
)
fresh = true
} catch (err) {
console.log(
"Telekom HTML fetch failed. Using cache:",
err
)
// ----------------------------------------------------------
// Fall back to cached parsed data
// ----------------------------------------------------------
try {
if (!fm.fileExists(cachePath)) {
throw new Error("No cache file exists.")
}
data = JSON.parse(
fm.readString(cachePath)
)
} catch (cacheError) {
const errorList = new ListWidget()
errorList.backgroundColor = new Color("#000000", 0)
const errorText = errorList.addText(
"Keine Telekom-Daten verfügbar."
)
errorText.font = Font.systemFont(12)
errorText.textColor = new Color("#ffffff", 0.7)
return errorList
}
}
// ==========================================================
// VALIDATE DATA
// ==========================================================
if (
!data ||
typeof data.remainingGB !== "number" ||
typeof data.totalGB !== "number" ||
data.totalGB <= 0
) {
const errorList = new ListWidget()
errorList.backgroundColor = new Color("#000000", 0)
const errorText = errorList.addText(
"Ungültige Telekom-Daten."
)
errorText.font = Font.systemFont(12)
errorText.textColor = new Color("#ffffff", 0.7)
return errorList
}
// ==========================================================
// PROCESS DATA
// ==========================================================
const totalGB = Math.max(
0,
data.totalGB
)
const remainingGB = Math.min(
totalGB,
Math.max(0, data.remainingGB)
)
const usedGB = Math.max(
0,
totalGB - remainingGB
)
const remainingPercent = Math.min(
100,
Math.max(
0,
(remainingGB / totalGB) * 100
)
)
const usedPercent = 100 - remainingPercent
// ==========================================================
// REMAINING TIME
// ==========================================================
const days = Math.max(
0,
Number(data.days) || 0
)
const hours = Math.max(
0,
Number(data.hours) || 0
)
const minutes = Math.max(
0,
Number(data.minutes) || 0
)
const seconds = Math.max(
0,
Number(data.seconds) || 0
)
const remainingDaysTotal =
days +
hours / 24 +
minutes / 1440 +
seconds / 86400
// ==========================================================
// GB PER DAY
// ==========================================================
const remainingGBPerDay =
remainingDaysTotal > 0
? remainingGB / remainingDaysTotal
: 0
// ==========================================================
// FORMAT NUMBERS
// ==========================================================
function formatNumber(
number,
decimals = 2
) {
return number
.toFixed(decimals)
.replace(".", ",")
}
const remainingGBStr =
formatNumber(remainingGB)
const usedGBStr =
formatNumber(usedGB)
const remainingGBPerDayStr =
formatNumber(remainingGBPerDay)
// ==========================================================
// COLOR
// ==========================================================
let usageColor =
new Color("#3BCF53")
if (remainingPercent <= 10) {
usageColor =
Color.red()
} else if (remainingPercent <= 25) {
usageColor =
Color.orange()
}
// ==========================================================
// DRAW PROGRESS CIRCLE
// ==========================================================
const circleSize = 70
const lineWidth = 6
const radius =
(circleSize - lineWidth) / 2
const center =
new Point(
circleSize / 2,
circleSize / 2
)
const startAngle =
-Math.PI / 2
const endAngle =
startAngle +
(2 * Math.PI * remainingPercent) / 100
const circleCanvas =
new DrawContext()
circleCanvas.size =
new Size(
circleSize,
circleSize
)
circleCanvas.opaque = false
circleCanvas.respectScreenScale = true
// ----------------------------------------------------------
// Background ring
// ----------------------------------------------------------
circleCanvas.setStrokeColor(
new Color("#ffffff", 0.2)
)
circleCanvas.setLineWidth(
lineWidth
)
circleCanvas.strokeEllipse(
new Rect(
lineWidth / 2,
lineWidth / 2,
circleSize - lineWidth,
circleSize - lineWidth
)
)
// ----------------------------------------------------------
// Foreground arc
// ----------------------------------------------------------
const segments =
Math.max(
3,
Math.floor(remainingPercent * 3)
)
const step =
(endAngle - startAngle) /
segments
const arcPath =
new Path()
const startX =
center.x +
Math.cos(startAngle) * radius
const startY =
center.y +
Math.sin(startAngle) * radius
arcPath.move(
new Point(
startX,
startY
)
)
for (
let i = 1;
i <= segments;
i++
) {
const angle =
startAngle +
step * i
const x =
center.x +
Math.cos(angle) * radius
const y =
center.y +
Math.sin(angle) * radius
arcPath.addLine(
new Point(
x,
y
)
)
}
circleCanvas.addPath(
arcPath
)
circleCanvas.setStrokeColor(
usageColor
)
circleCanvas.setLineWidth(
lineWidth
)
circleCanvas.strokePath()
// ----------------------------------------------------------
// Rounded caps
// ----------------------------------------------------------
circleCanvas.setFillColor(
usageColor
)
const endX =
center.x +
Math.cos(endAngle) * radius
const endY =
center.y +
Math.sin(endAngle) * radius
circleCanvas.fillEllipse(
new Rect(
startX - lineWidth / 2,
startY - lineWidth / 2,
lineWidth,
lineWidth
)
)
circleCanvas.fillEllipse(
new Rect(
endX - lineWidth / 2,
endY - lineWidth / 2,
lineWidth,
lineWidth
)
)
const circleImage =
circleCanvas.getImage()
// ==========================================================
// TOP SECTION
// ==========================================================
const topStack =
list.addStack()
topStack.layoutHorizontally()
topStack.topAlignContent()
topStack.spacing = 3.3
// ----------------------------------------------------------
// Circle
// ----------------------------------------------------------
const circleContainer =
topStack.addStack()
circleContainer.size =
new Size(
circleSize,
circleSize
)
circleContainer.backgroundImage =
circleImage
circleContainer.layoutVertically()
circleContainer.centerAlignContent()
// ----------------------------------------------------------
// Antenna icon
// ----------------------------------------------------------
const antennaStack =
circleContainer.addStack()
antennaStack.layoutHorizontally()
antennaStack.centerAlignContent()
antennaStack.addSpacer()
const antennaSymbol =
fresh
? SFSymbol.named(
"antenna.radiowaves.left.and.right.circle"
)
: SFSymbol.named(
"antenna.radiowaves.left.and.right.slash.circle"
)
const antennaImage =
antennaStack.addImage(
antennaSymbol.image
)
antennaImage.imageSize =
new Size(
circleSize * 0.48,
circleSize * 0.48
)
antennaImage.tintColor =
fresh
? new Color("#ffffff")
: new Color("#ffffff", 0.5)
antennaImage.centerAlignImage()
// Keep original 1px-left adjustment
antennaStack.addSpacer(18)
// ==========================================================
// RIGHT SIDE INFORMATION
// ==========================================================
topStack.addSpacer()
const rightContainer =
topStack.addStack()
rightContainer.layoutVertically()
rightContainer.topAlignContent()
rightContainer.spacing = 0
rightContainer.setPadding(
0,
0,
0,
8
)
const textWidth = 60
const textColor =
new Color(
"#ffffff",
0.6
)
const usageLine =
rightContainer.addStack()
usageLine.size =
new Size(
textWidth,
0
)
const perDayText =
usageLine.addText(
`${remainingGBPerDayStr} GB/Tag übrig\n` +
`(${remainingGBStr} GB für ${days}T ${hours}h)`
)
perDayText.font =
Font.systemFont(9)
perDayText.textColor =
textColor
perDayText.rightAlignText()
// ==========================================================
// BOTTOM SECTION
// ==========================================================
const bottomStack =
list.addStack()
bottomStack.layoutHorizontally()
bottomStack.bottomAlignContent()
// ----------------------------------------------------------
// Remaining percentage
// ----------------------------------------------------------
const percentageContainer =
bottomStack.addStack()
percentageContainer.layoutVertically()
// Keep original 5px lower adjustment
percentageContainer.addSpacer(28)
const percentageText =
percentageContainer.addText(
`${remainingPercent.toFixed(0)} %`
)
percentageText.font =
Font.systemFont(46)
percentageText.textColor =
new Color("#ffffff")
percentageText.leftAlignText()
// ==========================================================
// UPDATE INFORMATION
// ==========================================================
bottomStack.addSpacer()
const timeContainer =
bottomStack.addStack()
timeContainer.layoutVertically()
timeContainer.bottomAlignContent()
timeContainer.setPadding(
0,
0,
9,
10
)
// ----------------------------------------------------------
// Use Telekom's actual last-update time when available.
// Fall back to current time if it wasn't found.
// ----------------------------------------------------------
const updateDate =
parseTelekomDate(
data.lastUpdate
)
const timeLabel =
timeContainer.addDate(
updateDate || new Date()
)
timeLabel.font =
Font.regularSystemFont(7)
timeLabel.textColor =
new Color(
"#ffffff",
0.6
)
timeLabel.rightAlignText()
timeLabel.applyTimeStyle()
return list
}
// ============================================================
// TELEKOM HTML PARSER
// ============================================================
function parseTelekomHTML(html) {
if (!html) {
return null
}
// ----------------------------------------------------------
// Decode HTML entities that can occur in the page.
// ----------------------------------------------------------
function decodeHTML(value) {
if (!value) {
return ""
}
return value
.replace(/&nbsp;/gi, " ")
.replace(/&#160;/gi, " ")
.replace(/&amp;/gi, "&")
.replace(/&quot;/gi, '"')
.replace(/&#39;/gi, "'")
.replace(/&lt;/gi, "<")
.replace(/&gt;/gi, ">")
.trim()
}
// ----------------------------------------------------------
// Extract text from a class.
//
// Handles:
//
// class="remaining-volume-value"
//
// as well as additional classes/attributes.
// ----------------------------------------------------------
function extractClassText(
source,
className
) {
const escapedClass =
className.replace(
/[.*+?^${}()|[\]\\]/g,
"\\$&"
)
const regex =
new RegExp(
`<[^>]*class=["'][^"']*\\b${escapedClass}\\b[^"']*["'][^>]*>` +
`([\\s\\S]*?)` +
`<\\/[^>]+>`,
"i"
)
const match =
source.match(regex)
if (!match) {
return null
}
return decodeHTML(
match[1]
.replace(/<[^>]+>/g, " ")
)
}
// ----------------------------------------------------------
// Extract number from Telekom volume field.
//
// Example:
//
// <div class="remaining-volume-value">7,23</div>
// ----------------------------------------------------------
function extractNumber(
source,
className
) {
const value =
extractClassText(
source,
className
)
if (!value) {
return null
}
const normalized =
value
.replace(/\s/g, "")
.replace(",", ".")
const number =
parseFloat(normalized)
return Number.isFinite(number)
? number
: null
}
// ----------------------------------------------------------
// Extract countdown values.
// ----------------------------------------------------------
function extractTimeValue(
source,
className
) {
const escapedClass =
className.replace(
/[.*+?^${}()|[\]\\]/g,
"\\$&"
)
const regex =
new RegExp(
`<[^>]*class=["'][^"']*\\b${escapedClass}\\b[^"']*["'][^>]*>` +
`\\s*(\\d+)`,
"i"
)
const match =
source.match(regex)
if (!match) {
return 0
}
const value =
parseInt(
match[1],
10
)
return Number.isFinite(value)
? value
: 0
}
// ==========================================================
// VOLUME
// ==========================================================
const remainingGB =
extractNumber(
html,
"remaining-volume-value"
)
const totalGB =
extractNumber(
html,
"start-volume"
)
if (
remainingGB === null ||
totalGB === null ||
totalGB <= 0
) {
console.log(
"Could not find Telekom volume values."
)
return null
}
// ==========================================================
// TIME
// ==========================================================
const days =
extractTimeValue(
html,
"days"
)
const hours =
extractTimeValue(
html,
"hours"
)
const minutes =
extractTimeValue(
html,
"mins"
)
const seconds =
extractTimeValue(
html,
"secs"
)
// ==========================================================
// LAST UPDATE
// ==========================================================
let lastUpdate = null
const updateMatch =
html.match(
/<div[^>]*class=["'][^"']*\blast-update\b[^"']*["'][^>]*>[\s\S]*?<\/div>/i
)
if (updateMatch) {
const updateText =
updateMatch[0]
.replace(/<[^>]+>/g, " ")
.replace(/\s+/g, " ")
.trim()
const dateMatch =
updateText.match(
/(\d{1,2})\.(\d{1,2})\.(\d{4})\s+um\s+(\d{1,2}):(\d{2})\s+Uhr/i
)
if (dateMatch) {
lastUpdate = {
day: parseInt(dateMatch[1], 10),
month: parseInt(dateMatch[2], 10),
year: parseInt(dateMatch[3], 10),
hour: parseInt(dateMatch[4], 10),
minute: parseInt(dateMatch[5], 10)
}
}
}
// ==========================================================
// CALCULATE USED DATA
// ==========================================================
const usedGB =
Math.max(
0,
totalGB - remainingGB
)
const remainingPercent =
Math.min(
100,
Math.max(
0,
(remainingGB / totalGB) * 100
)
)
// ==========================================================
// RETURN STRUCTURED DATA
// ==========================================================
return {
remainingGB:
remainingGB,
totalGB:
totalGB,
usedGB:
usedGB,
remainingPercent:
remainingPercent,
days:
days,
hours:
hours,
minutes:
minutes,
seconds:
seconds,
lastUpdate:
lastUpdate
}
}
// ============================================================
// PARSE TELEKOM DATE
// ============================================================
function parseTelekomDate(
value
) {
if (!value) {
return null
}
if (
typeof value !== "object"
) {
return null
}
if (
!value.year ||
!value.month ||
!value.day
) {
return null
}
const date =
new Date(
value.year,
value.month - 1,
value.day,
value.hour || 0,
value.minute || 0,
0
)
return isNaN(
date.getTime()
)
? null
: date
}

r/Scriptable • • 27d ago

Discussion What do you think?

Post image
1 Upvotes

r/Scriptable • • Sep 02 '26

Widget Sharing A vibe-coded widget showing your last played game on Steam/Xbox/PS

Post image
13 Upvotes

r/Scriptable • • Aug 30 '26

Script Sharing Weekly number widget + year progress

13 Upvotes

After spending some time in Sweden i’m surprised that people operate week numbers. Not dates but week numbers.

- We are going to meet week 33.
- School is closed on week 67.

and etc.

Anyway I made a widget (with AI) that shows week number and some extra info. Convenient.

Link


r/Scriptable • • Aug 30 '26

Widget Sharing Daily Essential Tracker for Trader

Post image
14 Upvotes

r/Scriptable • • Aug 30 '26

Widget Sharing Scriptable widget for Spotify playlist + profile follower tracking

Post image
9 Upvotes

Built a Scriptable widget that shows Spotify profile followers, total playlist saves, and recent +/− changes on the Home Screen.

Made it because I kept opening Spotify just to check growth on my playlists. Large layout has KPIs + playlist list + cover grid.

If anyone’s done something similar (followers / curator stats, not now-playing), curious what you showed on the widget.


r/Scriptable • • Aug 25 '26

Help iOS27 Extra large widget?

4 Upvotes

Does anyone know if scriptable supports the new extra large fullscreen widget in the beta yet? It would be very useful for a script i’m working on


r/Scriptable • • Aug 18 '26

Widget Sharing F1 Widget

11 Upvotes

A scriptable widget that shows F1 details. More info in the REAMDE:

https://github.com/archive-sh/F1-Scriptable-Widget


r/Scriptable • • Aug 15 '26

Help Stuck on refreshing the result!

1 Upvotes

Hellooo, I created a widget to display the Github pages status, it works but i can't seem to make it refresh the results every x seconds.
did any one had the same issue or knows a script that is already dealing with this ?

any help is appreciated!


r/Scriptable • • Jul 22 '26

Widget Sharing Daily glance widget. Pulse.

9 Upvotes

I have seen a couple of these on here so I decided to build a personal Scriptable widget that changes through the day: morning summary, work mode, evening wind-down and night mode. It pulls in weather, calendar/reminders, fasting, commute and Health data via Shortcuts.

It’s still a prototype, but I’d love feedback on the idea, setup flow, and what feels useful vs too much.

https://github.com/iHenpie/Pulse-Widget


r/Scriptable • • Jul 19 '26

Widget Sharing Live Context updates/New Shortcut

Thumbnail gallery
11 Upvotes

r/Scriptable • • Jul 16 '26

Widget Sharing Mint Mobile Data Widget

Post image
14 Upvotes

Mint Mobile Widget for iPhone. Vibe coded it because [Mint never shipped it](https://www.reddit.com/r/mintmobile/comments/ivbg6o/comment/g5q7ia5/).

https://github.com/Dr-Sauce/MintCho


r/Scriptable • • Jul 15 '26

Script Sharing Update on the Scriptable widget I posted about the other day: it's ready!

Thumbnail gallery
10 Upvotes

r/Scriptable • • Jul 15 '26

Tip/Guide I was surprised that @scriptableapp doesn't have a visual widget builder, so I asked Sol High to build me one.

Thumbnail widgetr.vercel.app
12 Upvotes