I made a thing that might help today (2 Viewers)

shmmeee

Well-Known Member
Got ChatGPT to code me a widget for my phone that shows the top five thread titles on the CCFC forum. So in theory I don’t have to come here and get sucked in and can just watch for a thread about a new signing. It works as a piece of software it does not work as a way to stop my coming on SBT too much. Thought I’d share.

iOS you need to download an app called Scriptable then create a new script, past the below in, save it with a name, and then add the scriptable widget to your Home Screen and choose run script and select the one you made.

Here’s what it looks like:
IMG_4855.png

Here’s the script:




Code:
// SkyBlueTalk — CCFC Top Threads (polished)
// Medium/Small widget. Auto dark/light, club sky-blue gradient.
// Taps: header → forum, each row → thread.

// ── CONFIG ─────────────────────────────────────────────────────────────────────
const FEED_URL = "https://www.skybluestalk.co.uk/forums/coventry-city-general-chat.7/index.rss";
const MAX_ITEMS = 5;             // keep 3–6 for Medium, 2–4 for Small
const TITLE_LINE_LIMIT = 1;      // 1 = tidy, 2 = roomier
const SHOW_REL_TIME = true;      // show “· 1h” after title
const TRIM_PREFIXES = [
  /^match\s*thread[:\-\s]*/i,
  /^pre[-\s]?match[:\-\s]*/i,
  /^post[-\s]?match[:\-\s]*/i,
  /^transfer(s)?[:\-\s]*/i,
  /^\[\s*rumou?r\s*\]\s*/i
];
// Colors
const SKY = Color.dynamic(new Color("#69b9ff"), new Color("#2b86d1"));
const DEEP = Color.dynamic(new Color("#1a6db3"), new Color("#154e80"));
const TEXT = Color.dynamic(Color.black(), Color.white());
const SUBTLE = Color.dynamic(new Color("#334155"), new Color("#b0c4d9"));
// ───────────────────────────────────────────────────────────────────────────────

async function fetchRSS(url){
  const req = new Request(url);
  req.headers = { "User-Agent": "ScriptableWidget/1.1 (+iOS)" };
  return await req.loadString();
}

function parseRSS(xml){
  const items = [];
  const blocks = xml.match(/<item>[\s\S]*?<\/item>/g) || [];
  for (let i=0; i<Math.min(blocks.length, MAX_ITEMS); i++){
    const b = blocks[i];
    const t = textOf(b, "title");
    items.push({
      title: prettifyTitle(t),
      link: textOf(b, "link"),
      date: new Date(textOf(b, "pubDate") || Date.now())
    });
  }
  return items;
}

function textOf(block, tag){
  const m = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, "i").exec(block);
  return (m ? m[1] : "").replace(/<!\[CDATA\[|\]\]>/g,"").trim();
}

function prettifyTitle(t){
  let s = t.replace(/\s+/g," ").trim();
  for (const rx of TRIM_PREFIXES) s = s.replace(rx,"");
  // collapse bracketed prefixes like [Tickets], (H), etc. at start
  s = s.replace(/^(\[.*?\]|\(.*?\))\s+/g,"");
  return s;
}

function timeAgo(d){
  const secs = Math.max(1, (Date.now() - d.getTime())/1000);
  if (secs < 60) return `${Math.floor(secs)}s`;
  const mins = secs/60;
  if (mins < 60) return `${Math.floor(mins)}m`;
  const hrs = mins/60;
  if (hrs < 24) return `${Math.floor(hrs)}h`;
  const days = hrs/24;
  return `${Math.floor(days)}d`;
}

function header(w){
  const h = w.addStack();
  h.layoutHorizontally();
  h.centerAlignContent();
  h.url = "https://www.skybluestalk.co.uk/forums/coventry-city-general-chat.7/";
  const badge = SFSymbol.named("soccerball");
  const img = h.addImage(badge.image);
  img.imageSize = new Size(14,14);
  img.tintColor = TEXT;
  h.addSpacer(6);
  const t = h.addText("SkyBlueTalk — CCFC");
  t.font = Font.boldSystemFont(12);
  t.textColor = TEXT;
  h.addSpacer();
  const refresh = h.addText(new Date().toLocaleTimeString([], {hour:"2-digit", minute:"2-digit"}));
  refresh.font = Font.systemFont(10);
  refresh.textColor = SUBTLE;
}

function row(w, item){
  const s = w.addStack();
  s.layoutHorizontally();
  s.url = item.link;

  // bullet
  const dot = s.addText("•");
  dot.font = Font.boldSystemFont(13);
  dot.textColor = TEXT;
  s.addSpacer(6);

  // title
  const t = s.addText(item.title);
  t.font = Font.systemFont(13);
  t.textColor = TEXT;
  t.lineLimit = TITLE_LINE_LIMIT;

  if (SHOW_REL_TIME){
    s.addSpacer(6);
    const ago = s.addText(`· ${timeAgo(item.date)}`);
    ago.font = Font.systemFont(11);
    ago.textColor = SUBTLE;
  }
}

function makeWidget(items){
  const w = new ListWidget();
  const grad = new LinearGradient();
  grad.colors = [SKY, DEEP];
  grad.locations = [0, 1];
  grad.startPoint = new Point(0, 0);
  grad.endPoint = new Point(1, 1);
  w.backgroundGradient = grad;
  w.setPadding(10, 12, 10, 12);

  header(w);
  w.addSpacer(6);
  items.forEach((it, idx) => {
    row(w, it);
    if (idx < items.length-1) w.addSpacer(4);
  });

  return w;
}

async function run(){
  try{
    const xml = await fetchRSS(FEED_URL);
    const items = parseRSS(xml);
    const widget = makeWidget(items);
    if (config.runsInWidget) Script.setWidget(widget);
    else await widget.presentMedium();
  } catch (e){
    const w = new ListWidget();
    w.addText("SkyBlueTalk — error").font = Font.boldSystemFont(12);
    w.addText(String(e)).font = Font.systemFont(10);
    Script.setWidget(w);
  }
  Script.complete();
}
await run();
 

Grendel

Well-Known Member
When I read the title I thought it was a commitment from you to not make any more idiotic posts
 

shmmeee

Well-Known Member
Should be able to get the RSS link for a certain forum 😁

If it works though, it works 🤣

I didn’t even realise SBT had an rss feed. I didn’t even realise rss feeds were still a thing.
 

Captain Dart

Well-Known Member

Users who are viewing this thread

Top