Si comincia svegliandosi sulla panchina dei vecchietti dopo una notte brava: gli occhi che si aprono a fatica, la piazza sfocata e doppia; e dal Barone il telefonino col sito di San Piero, che si apre a tutto schermo, e il Muro del Sanpierino vero coi messaggi del 2007, «Aggiorna» compreso (Andrea)

- Il risveglio (src/engine/risveglio.js): primo piano sopra la scena, con le battute mentre le palpebre si aprono e
  i vecchietti che danno la notizia del palio; «Salta» lo salta.
- Il telefonino del Barone (src/engine/telefono.js): sale dal basso come il libro, la home di sanpiero50037.it dalla
  Wayback Machine nello schermo, il mattone del muro apre il muro.
- Il muro (assets/muro, tools/muro.py): la pagina vera rifatta statica dal muro riesumato, coi 24 messaggi del dump
  (senza email né IP) e quelli del gioco, che arrivano con la storia; «Scrivi» spento.
- Il gioco sgancia i service worker di altre app rimasti sul suo indirizzo (la Bussola su localhost:8080).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
2026-10-31 09:52:34 +01:00
co-authored by Claude Opus 5.5
parent 1ea78161d1
commit c5953f04c0
93 changed files with 1112 additions and 68 deletions
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Il Muro del Sanpierino dentro il gioco (Andrea, 31 ottobre): dal dump del muro vero riesumato
(~/Workspaces/misc/spswall) prepara assets/muro/ per la pagina statica del telefonino del Barone.
- messaggi.json: i messaggi veri (soprannome, data, testo), con gli accenti rimessi a posto. Niente email né IP.
Solo quelli del dump (i primi 24, novembre 2007): i 654 ritrovati nella Wayback Machine restano nel muro riesumato
(spswall/archivio/importa.py), fuori dal gioco (Andrea, 31 ottobre).
- img/: solo gli smile usati e le icone della pagina; muro.css è il CSS originale; le due pagine di testo, com'erano.
python3 tools/muro.py [percorso del dump] [cartella www del muro]
"""
import json, re, shutil, sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SPS = Path.home() / 'Workspaces/misc/spswall'
DUMP = Path(sys.argv[1]) if len(sys.argv) > 1 else SPS / 'db/01-spswall.sql'
WWW = Path(sys.argv[2]) if len(sys.argv) > 2 else SPS / 'www'
OUT = ROOT / 'assets/muro'
# Le icone della pagina, oltre agli smile.
PAGE = ['logo_trasparente_obliquo.gif', 'spacer.gif', 'pagefirst.gif', 'pageback.gif', 'postmessage.gif', 'refresh.gif',
'home.gif', 'pageahead.gif', 'pagelast.gif', 'logomanzolo.jpg']
def rows(sql, table):
"""Le righe di INSERT INTO `table` del dump di phpMyAdmin, come liste di valori."""
m = re.search(r'INSERT INTO `%s` \(([^)]*)\) VALUES\s*(.*?);\n' % table, sql, re.S)
cols = [c.strip(' `') for c in m.group(1).split(',')]
body, out, row, val, i, quoted = m.group(2), [], [], '', 0, False
while i < len(body):
ch = body[i]
if quoted:
if ch == '\\': val += {'n': '\n', 'r': '\r', 't': '\t', '0': ''}.get(body[i + 1], body[i + 1]); i += 2; continue
if ch == "'" and body[i + 1:i + 2] == "'": val += "'"; i += 2; continue
if ch == "'": quoted = False
else: val += ch
elif ch == "'": quoted = True
elif ch == '(': row, val = [], ''
elif ch == ',': row.append(val.strip()); val = ''
elif ch == ')': row.append(val.strip()); out.append(dict(zip(cols, row)))
elif not ch.isspace(): val += ch
i += 1
return out
def fix(text):
"""Il muro scriveva UTF-8 su una connessione latin1: nel dump gli accenti sono «Ã¨». Si rimettono a posto."""
for enc in ('latin1', 'cp1252'):
try: return text.encode(enc).decode('utf8')
except (UnicodeEncodeError, UnicodeDecodeError): pass
return text
def main():
sql = DUMP.read_text('utf8')
smiles = {r['codsml']: r['linksml'] for r in rows(sql, 'tpsmiles')}
# Le immagini prese da fuori (http://...) non si caricano: il vecchio dominio oggi può essere di chiunque.
outside = lambda t: re.sub(r'<img[^>]*src="?http[^>]*>', '', t, flags=re.I)
messages = [{'nick': fix(r['nikute']), 'date': r['datmsg'], 'text': outside(fix(r['desmsg']))} for r in rows(sql, 'tpmsgute')]
messages.sort(key=lambda m: m['date'], reverse=True)
# Gli smile usati: dai messaggi veri e da quelli del gioco (story.closeups.muro_sanpierino.messages).
ours = (ROOT / 'src/story/story.js').read_text('utf8')
used = {code: link for code, link in smiles.items() if code in ours or any(code in m['text'] or code in m['nick'] for m in messages)}
shutil.rmtree(OUT / 'img', ignore_errors=True)
(OUT / 'img').mkdir(parents=True)
pictures = {n for m in messages for n in re.findall(r'src="\./img/([^"]+)"', m['text'] + m['nick'])}
for name in PAGE + sorted(set(used.values()) | pictures):
shutil.copy(WWW / 'imgwall' / name, OUT / 'img' / name)
shutil.copy(WWW / 'class/thewall.css', OUT / 'muro.css')
# Le due pagine di testo, com'erano; in fondo un link per tornare al muro (nel telefonino non c'è il «Indietro»).
for page in ('Responsabilita.htm', 'guida_al_muro_del_sanpierino.htm'):
html = (WWW / page).read_bytes()
back = b'<p align="center"><a href="index.html" style="font: bold 14px Tahoma; color: #800000">&laquo; Torna al Muro</a></p>'
(OUT / page).write_bytes(re.sub(rb'</body>', back + b'</body>', html, count=1, flags=re.I))
(OUT / 'messaggi.json').write_text(json.dumps({'smiles': used, 'messages': messages}, ensure_ascii=False, indent=1) + '\n')
print(f'{len(messages)} messaggi, {len(used)} smile -> {OUT.relative_to(ROOT)}')
if __name__ == '__main__':
main()