/* A widget that displays a counter and growth chart, powered by your ilo.so dashboard. You will need to put in your counter ID in the widget's Parameter field. You can find your counter ID by clicking the ↗ Share icon in your ilo account. Sign up to ilo at https://ilo.so Version 1.1.0 (November 11, 2024) by Dan Rowden (@dr) */ // Chart config const daysInChart = 7 // How many days you want to show in the chart // Some colours you can change const chartOpacity = 0.1 const colors = { light: { widgetBgColor: "#ffffff", // White chartColor: "#666666", // Dark grey usernameColor: '#888888', // Grey followersColor: '#111111', // Almost black dailyChangeColor: '#888888', // Grey }, dark: { widgetBgColor: "#222222", // Almost black chartColor: "#cccccc", // Light grey usernameColor: '#bbbbbb', // Grey followersColor: '#eeeeee', // Almost white dailyChangeColor: '#bbbbbb', // Grey } } function getColors() { let appearance = (isUsingDarkAppearance()) ? 'dark' : 'light' console.log(appearance) return colors[appearance] } // ===================================================================== // Here starts all the widget code. Feel free to edit below if you want! // Share your widget on Twitter! I'm @dr :) // ===================================================================== let counterId = args.widgetParameter let url = "https://api.ilo.so/v2/counters/"+counterId+"/?include=today,info,account,history&days="+daysInChart let req = new Request(url) const json = await req.loadJSON() const count = parseInt(json['count']).toLocaleString() const today = parseInt(json['today']).toLocaleString() const chartData = json['history'] const counterName = json['name'].toLowerCase() const accountName = json['account']['name'] // LineChart by https://kevinkub.de/ // Used as the widget background class LineChart { constructor(width, height, values) { this.ctx = new DrawContext(); this.ctx.size = new Size(width, height); this.values = getCountsFromData(values); } _calculatePath() { let maxValue = Math.max(...this.values); let minValue = Math.min(...this.values); let difference = maxValue - minValue; let count = this.values.length; let step = this.ctx.size.width / (count - 1); let points = this.values.map((current, index, all) => { let x = step*index; let y = this.ctx.size.height - (current - minValue) / difference * (this.ctx.size.height * 0.5); return new Point(x, y); }); return this._getSmoothPath(points); } _getSmoothPath(points) { let path = new Path(); path.move(new Point(0, this.ctx.size.height)); path.addLine(points[0]); for(let i = 0; i < points.length-1; i++) { let xAvg = (points[i].x + points[i+1].x) / 2; let yAvg = (points[i].y + points[i+1].y) / 2; let avg = new Point(xAvg, yAvg); let cp1 = new Point((xAvg + points[i].x) / 2, points[i].y); let next = new Point(points[i+1].x, points[i+1].y); let cp2 = new Point((xAvg + points[i+1].x) / 2, points[i+1].y); path.addQuadCurve(avg, cp1); path.addQuadCurve(next, cp2); } path.addLine(new Point(this.ctx.size.width, this.ctx.size.height)); path.closeSubpath(); return path; } configure(fn) { let path = this._calculatePath(); if(fn) { fn(this.ctx, path); } else { this.ctx.addPath(path); this.ctx.fillPath(path); } return this.ctx; } } // Convert API data into a simple list function getCountsFromData(values) { let chartValues = [] for(let data in values) { let thisDay = values[data] let keys = Object.keys(thisDay); for (let i = 0; i < keys.length; i++) { let val = thisDay[keys[i]]; chartValues.push(val); } } return chartValues; } // Fonts const smallText = new Font("HelveticaNeue", 14); const largeText = new Font("HelveticaNeue-Medium", 16); // Create the widget let w = new ListWidget() w.backgroundColor = new Color(getColors().widgetBgColor); w.url = 'https://ilo.so/dashboard/' var refreshDate = Date.now() + 1000*60*5 // Refresh after at least 5 minutes w.refreshAfterDate = new Date(refreshDate) // ilo icon let icon = await loadImage('https://ilo.so/static/favicon.png') let iconImg = w.addImage(icon) iconImg.imageSize = new Size(20,20) w.addSpacer(20) // Show username textUsername = w.addText(accountName) textUsername.textColor = new Color(getColors().usernameColor); textUsername.font = smallText; w.addSpacer(4) // Show current follower count const followerStr = count + " " + counterName textFollowers = w.addText(followerStr) textFollowers.textColor = new Color(getColors().followersColor); textFollowers.font = largeText w.addSpacer(4) // Show today's follower change const followerTodayStr = (today > -1 ? '+' : '') + today + " today" textChange = w.addText(followerTodayStr) textChange.textColor = new Color(getColors().dailyChangeColor); textChange.font = smallText; // Line chart as bg // This will create a square chart let chart = new LineChart(1200, 1200, chartData).configure((ctx, path) => { ctx.opaque = false; ctx.setFillColor(new Color(getColors().chartColor, chartOpacity)); ctx.addPath(path); ctx.fillPath(path); }).getImage(); w.backgroundImage = chart // Download an image from a given url async function loadImage(imgUrl) { const req = new Request(imgUrl) return await req.loadImage() } if (config.runsInWidget) { Script.setWidget(w) } else { await w.presentSmall() } if (config.runsWithSiri) { Speech.speak('You have '+count+' '+counterName) } function isUsingDarkAppearance() { return !(Color.dynamic(Color.white(),Color.black()).red) } Script.complete()