Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to MMM-MessageCenter are documented in this file.

## 0.8.1 - 2026-10-11

### Changed

- Put the leading team and higher score first in numeric head-to-head halftime,
score, and final messages, with visible lead, tie, and final indicators.
- Preserve provider ordering for non-numeric and ranked results instead of
guessing at sport-specific standings.

## 0.8.0 - 2026-10-10

### Added
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,12 @@ Use MyScoreboard's `leagueEvents` overrides to opt low-scoring leagues into
MLS goals are included while NBA games continue to use the quieter global
start, halftime, and final list.

For numeric head-to-head scores, MessageCenter puts the leading team and higher
score first with a visible lead, tie, or final marker. Non-numeric scores and
ranked events retain the provider's original order rather than applying
head-to-head assumptions; the source module remains responsible for standings,
golf, racing, and similar sport-specific meaning.

The source module remains responsible for selecting teams, retrieving scores,
and deciding when a game reaches each milestone. MessageCenter only validates,
normalizes, and presents the event. Either module continues to operate normally
Expand Down
40 changes: 39 additions & 1 deletion integrations/my-scoreboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,44 @@
return null;
}

function numericScore(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value !== "string" || !/^-?\d+(?:\.\d+)?$/.test(value.trim())) return null;
const number = Number(value);
return Number.isFinite(number) ? number : null;
}

function scoreBody(module, event, away, awayScore, home, homeScore, fallbackKey) {
if (!["game.halftime", "game.score", "game.final"].includes(event)) {
return module.translate(fallbackKey, { away, awayScore, home, homeScore });
}

const awayNumber = numericScore(awayScore);
const homeNumber = numericScore(homeScore);
if (awayNumber === null || homeNumber === null) {
return module.translate(fallbackKey, { away, awayScore, home, homeScore });
}

if (awayNumber === homeNumber) {
const key = event === "game.final" ? "SPORTS_FINAL_TIED" : event === "game.halftime"
? "SPORTS_HALFTIME_TIED"
: "SPORTS_SCORE_TIED";
return module.translate(key, { away, awayScore, home, homeScore });
}

const awayLeads = awayNumber > homeNumber;
const values = {
leader: awayLeads ? away : home,
leaderScore: awayLeads ? awayScore : homeScore,
trailer: awayLeads ? home : away,
trailerScore: awayLeads ? homeScore : awayScore
};
const key = event === "game.final" ? "SPORTS_FINAL_WINNER" : event === "game.halftime"
? "SPORTS_HALFTIME_LEADER"
: "SPORTS_SCORE_LEADER";
return module.translate(key, values);
}

root.MessageCenterAdapters.register({
id: "my-scoreboard",
priority: 800,
Expand Down Expand Up @@ -76,7 +114,7 @@
source,
entityId: payload.gameId.trim(),
title: module.translate("SPORTS_MATCHUP", { away, home }),
body: module.translate(eventConfig.bodyKey, { away, awayScore, home, homeScore }),
body: scoreBody(module, payload.event, away, awayScore, home, homeScore, eventConfig.bodyKey),
urgency: eventConfig.urgency,
retention: eventConfig.retention,
timestamp,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mmm-messagecenter",
"version": "0.8.0",
"version": "0.8.1",
"description": "Hardware-independent notification and attention hub for MagicMirror²",
"private": true,
"type": "commonjs",
Expand Down
36 changes: 31 additions & 5 deletions test/MMM-MessageCenter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1334,14 +1334,14 @@ test("normalizes MyScoreboard start, halftime, and final milestones with calm li
id: "NBA:MIA@ORL:game.final",
type: "sports.game.final",
title: "Miami Heat at Orlando Magic",
body: "Final. Miami Heat 101, Orlando Magic 108.",
body: "✓ Orlando Magic 108 · Miami Heat 101 — Final",
urgency: "attention"
},
{
id: "NBA:MIA@ORL:game.halftime",
type: "sports.game.halftime",
title: "Miami Heat at Orlando Magic",
body: "Halftime. Miami Heat 49, Orlando Magic 55.",
body: "▲ Orlando Magic 55 · Miami Heat 49 — Leads at halftime",
urgency: "attention"
},
{
Expand Down Expand Up @@ -1382,16 +1382,42 @@ test("normalizes distinct opt-in MyScoreboard score events", () => {
assert.deepEqual(module.messages.map(({ id, body }) => ({ id, body })), [
{
id: "USA_MLS:CLB@ORL:game.score:0-2",
body: "Score update. Columbus Crew 0, Orlando City SC 2."
body: "▲ Orlando City SC 2 · Columbus Crew 0 — Leads"
},
{
id: "USA_MLS:CLB@ORL:game.score:0-1",
body: "Score update. Columbus Crew 0, Orlando City SC 1."
body: "▲ Orlando City SC 1 · Columbus Crew 0 — Leads"
}
]);
assert.equal(module.messages[0].expires, base.timestamp + 60000 + 2 * 60 * 60000);
});

test("makes tied head-to-head scores clear at a glance", () => {
const module = instance();

module.notificationReceived("MYSCOREBOARD_GAME_EVENT", {
event: "game.score",
gameId: "USA_MLS:CLB@ORL",
away: { name: "Columbus Crew", score: "1" },
home: { name: "Orlando City SC", score: "1" }
}, { name: "MMM-MyScoreboard" });

assert.equal(module.messages[0].body, "= Columbus Crew 1 · Orlando City SC 1 — Tied");
});

test("preserves the provider order when scores are not directly comparable", () => {
const module = instance();

module.notificationReceived("MYSCOREBOARD_GAME_EVENT", {
event: "game.final",
gameId: "ranked:event",
away: { name: "Driver One", score: "P1" },
home: { name: "Driver Two", score: "P2" }
}, { name: "MMM-MyScoreboard" });

assert.equal(module.messages[0].body, "Final. Driver One P1, Driver Two P2.");
});

test("allows MyScoreboard event lifetimes to be configured", () => {
const module = instance({
internalNotifications: {
Expand Down Expand Up @@ -1432,7 +1458,7 @@ test("updates a duplicate MyScoreboard milestone silently and preserves read sta
);

assert.equal(module.messages.length, 1);
assert.equal(module.messages[0].body, "Halftime. Miami Heat 49, Orlando Magic 56.");
assert.equal(module.messages[0].body, "▲ Orlando Magic 56 · Miami Heat 49 — Leads at halftime");
assert.equal(module.messages[0].unread, false);
assert.equal(module.notifications.some(({ name }) => name === "SHOW_ALERT"), false);
});
Expand Down
6 changes: 6 additions & 0 deletions translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
"SPORTS_GAME_HALFTIME": "Halbzeit. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_GAME_SCORE": "Spielstand. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_GAME_FINAL": "Endstand. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_HALFTIME_LEADER": "▲ {leader} {leaderScore} · {trailer} {trailerScore} — Führt zur Halbzeit",
"SPORTS_HALFTIME_TIED": "= {away} {awayScore} · {home} {homeScore} — Remis zur Halbzeit",
"SPORTS_SCORE_LEADER": "▲ {leader} {leaderScore} · {trailer} {trailerScore} — Führt",
"SPORTS_SCORE_TIED": "= {away} {awayScore} · {home} {homeScore} — Remis",
"SPORTS_FINAL_WINNER": "✓ {leader} {leaderScore} · {trailer} {trailerScore} — Endstand",
"SPORTS_FINAL_TIED": "= {away} {awayScore} · {home} {homeScore} — Endstand",
"SOURCE_HOME_ASSISTANT": "Home Assistant",
"SOURCE_SMARTTHINGS_HOME_ASSISTANT": "SmartThings über Home Assistant",
"SOURCE_SMARTTHINGS": "SmartThings"
Expand Down
6 changes: 6 additions & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
"SPORTS_GAME_HALFTIME": "Halftime. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_GAME_SCORE": "Score update. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_GAME_FINAL": "Final. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_HALFTIME_LEADER": "▲ {leader} {leaderScore} · {trailer} {trailerScore} — Leads at halftime",
"SPORTS_HALFTIME_TIED": "= {away} {awayScore} · {home} {homeScore} — Tied at halftime",
"SPORTS_SCORE_LEADER": "▲ {leader} {leaderScore} · {trailer} {trailerScore} — Leads",
"SPORTS_SCORE_TIED": "= {away} {awayScore} · {home} {homeScore} — Tied",
"SPORTS_FINAL_WINNER": "✓ {leader} {leaderScore} · {trailer} {trailerScore} — Final",
"SPORTS_FINAL_TIED": "= {away} {awayScore} · {home} {homeScore} — Final",
"SOURCE_HOME_ASSISTANT": "Home Assistant",
"SOURCE_SMARTTHINGS_HOME_ASSISTANT": "SmartThings via Home Assistant",
"SOURCE_SMARTTHINGS": "SmartThings"
Expand Down
6 changes: 6 additions & 0 deletions translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
"SPORTS_GAME_HALFTIME": "Descanso. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_GAME_SCORE": "Marcador. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_GAME_FINAL": "Final. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_HALFTIME_LEADER": "▲ {leader} {leaderScore} · {trailer} {trailerScore} — Lidera al descanso",
"SPORTS_HALFTIME_TIED": "= {away} {awayScore} · {home} {homeScore} — Empate al descanso",
"SPORTS_SCORE_LEADER": "▲ {leader} {leaderScore} · {trailer} {trailerScore} — Lidera",
"SPORTS_SCORE_TIED": "= {away} {awayScore} · {home} {homeScore} — Empate",
"SPORTS_FINAL_WINNER": "✓ {leader} {leaderScore} · {trailer} {trailerScore} — Final",
"SPORTS_FINAL_TIED": "= {away} {awayScore} · {home} {homeScore} — Final",
"SOURCE_HOME_ASSISTANT": "Home Assistant",
"SOURCE_SMARTTHINGS_HOME_ASSISTANT": "SmartThings mediante Home Assistant",
"SOURCE_SMARTTHINGS": "SmartThings"
Expand Down
6 changes: 6 additions & 0 deletions translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
"SPORTS_GAME_HALFTIME": "Mi-temps. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_GAME_SCORE": "Mise à jour du score. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_GAME_FINAL": "Score final. {away} {awayScore}, {home} {homeScore}.",
"SPORTS_HALFTIME_LEADER": "▲ {leader} {leaderScore} · {trailer} {trailerScore} — Mène à la mi-temps",
"SPORTS_HALFTIME_TIED": "= {away} {awayScore} · {home} {homeScore} — Égalité à la mi-temps",
"SPORTS_SCORE_LEADER": "▲ {leader} {leaderScore} · {trailer} {trailerScore} — Mène",
"SPORTS_SCORE_TIED": "= {away} {awayScore} · {home} {homeScore} — Égalité",
"SPORTS_FINAL_WINNER": "✓ {leader} {leaderScore} · {trailer} {trailerScore} — Score final",
"SPORTS_FINAL_TIED": "= {away} {awayScore} · {home} {homeScore} — Score final",
"SOURCE_HOME_ASSISTANT": "Home Assistant",
"SOURCE_SMARTTHINGS_HOME_ASSISTANT": "SmartThings via Home Assistant",
"SOURCE_SMARTTHINGS": "SmartThings"
Expand Down
Loading