Messages récents

Pages: [1] 2 3 4 5 6 ... 10
2
SFR Actus SFR Altice / Installation de Connect TV d'SFR sur box Android TV
« Dernier message par SuperBaobab le Hier à 22:56:28 »
Salut à tous,

Désolé je ne passe pas souvent mais j'essaie de le faire quand je peux.

J'ai analysé rapidement le fonctionnement de l'APK Update Manager, qui permet de télécharger et mettre à jour l'APK Connect TV, les appli de replay et TV (qui sont spécifiques SFR).

Du coup j'ai fait un petit script Python à l'arrache pour télécharger tous ces APKs sans avoir besoin d'un boitier Connect TV ou de Update Manager.

Ça ressemble à ça :


On peux voir d'ailleurs qu'une version 8.0.0 arrive (la 7.3.0 est toujours la dernière version stable déployée).

Et le script :
#!/usr/bin/env python3
"""
Téléchargeur d'APKs SFR Connect TV
Récupère les APKs depuis le CDN SFR Update Manager avec menu interactif.
"""

import json
import hashlib
import urllib.request
import urllib.error
import sys
from pathlib import Path

# ── Configuration ────────────────────────────────────────────────────────────
CDN_CONF_URL = (
    "https://k69977.cdn.sfr.net"
    "/CONF/ANDROIDTV/com.altice.androidtv.appsUpdateManager"
    "/conf/v2/update-manager-conf.json"
)
OUTPUT_DIR = Path(__file__).parent / "APKs"

# Modes à exclure (apps système non installables manuellement)
EXCLUDED_MODES = {"hidden"}
# ─────────────────────────────────────────────────────────────────────────────

MODE_COLORS = {
    "mandatory": "\033[92m",   # vert
    "promoted":  "\033[94m",   # bleu
    "recommended": "\033[96m", # cyan
    "hidden":    "\033[90m",   # gris
}
RESET  = "\033[0m"
BOLD   = "\033[1m"
DIM    = "\033[2m"
YELLOW = "\033[93m"
RED    = "\033[91m"
GREEN  = "\033[92m"


def clr(text, code): return f"{code}{text}{RESET}"


def fetch_json(url: str) -> list:
    req = urllib.request.Request(url, headers={"User-Agent": "SFR-UpdateManager/2.7.2"})
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read().decode("utf-8"))


def md5_file(path: Path) -> str:
    h = hashlib.md5()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)
    return h.hexdigest()


def human_size(n: int) -> str:
    for unit in ("o", "Ko", "Mo", "Go"):
        if n < 1024:
            return f"{n:.1f} {unit}"
        n /= 1024
    return f"{n:.1f} To"


def safe_name(name: str) -> str:
    return "".join(c if c.isalnum() or c in " ._-" else "_" for c in name).strip()


def group_by_app(catalog: list) -> dict[str, list]:
    """Regroupe toutes les entrées par appName, triées par versionCode décroissant."""
    groups: dict[str, list] = {}
    for entry in catalog:
        if not entry.get("binaries"):
            continue
        if entry.get("rules", {}).get("mode") in EXCLUDED_MODES:
            continue
        name = entry["appName"]
        groups.setdefault(name, []).append(entry)
    for name in groups:
        groups[name].sort(key=lambda e: e.get("versionCode", 0), reverse=True)
    return dict(sorted(groups.items()))


def ask(prompt: str) -> str:
    try:
        return input(prompt).strip()
    except (KeyboardInterrupt, EOFError):
        print()
        return ""


def print_header(title: str):
    print()
    print(clr("═" * 60, BOLD))
    print(clr(f"  {title}", BOLD))
    print(clr("═" * 60, BOLD))


def download_file(url: str, dest: Path, expected_md5: str, expected_size: int) -> bool:
    """Télécharge un fichier avec barre de progression et vérification MD5."""
    if dest.exists():
        if md5_file(dest) == expected_md5:
            print(clr(f"    ✓ Déjà téléchargé et valide : {dest.name}", GREEN))
            return True
        print(clr("    ⚠ Fichier corrompu, re-téléchargement...", YELLOW))
        dest.unlink()

    print(f"    {clr('↓', YELLOW)} {url}")
    print(f"    {clr('→', YELLOW)} {dest.name}  ({human_size(expected_size)})")

    dest.parent.mkdir(parents=True, exist_ok=True)
    tmp = dest.with_suffix(".tmp")

    try:
        req = urllib.request.Request(url, headers={"User-Agent": "SFR-UpdateManager/2.7.2"})
        with urllib.request.urlopen(req, timeout=120) as resp, open(tmp, "wb") as f:
            downloaded = 0
            while True:
                chunk = resp.read(65536)
                if not chunk:
                    break
                f.write(chunk)
                downloaded += len(chunk)
                if expected_size > 0:
                    pct = downloaded * 100 // expected_size
                    bar = clr("█" * (pct // 5), GREEN) + clr("░" * (20 - pct // 5), DIM)
                    print(f"\r    [{bar}] {pct:3d}%  {human_size(downloaded)}", end="", flush=True)
        print()

        actual_md5 = md5_file(tmp)
        if actual_md5 != expected_md5:
            print(clr(f"    ✗ MD5 invalide ! attendu={expected_md5}  obtenu={actual_md5}", RED))
            tmp.unlink()
            return False

        tmp.rename(dest)
        print(clr("    ✓ OK — MD5 vérifié", GREEN))
        return True

    except urllib.error.URLError as e:
        print(clr(f"\n    ✗ Erreur réseau : {e}", RED))
        if tmp.exists():
            tmp.unlink()
        return False
    except KeyboardInterrupt:
        print(clr("\n    ✗ Interrompu", YELLOW))
        if tmp.exists():
            tmp.unlink()
        raise


def do_download(entries: list, label: str):
    """Télécharge toutes les binaries d'une liste d'entrées."""
    ok, fail = 0, 0
    for entry in entries:
        app_dir = OUTPUT_DIR / safe_name(entry["appName"])
        mode = entry.get("rules", {}).get("mode", "?")
        mc = MODE_COLORS.get(mode, "")
        print(f"\n  {clr(entry['appName'], BOLD)}  v{entry['versionName']}  [{clr(mode, mc)}]")
        for binary in entry.get("binaries", []):
            url = binary["url"]
            dest = app_dir / url.split("/")[-1]
            try:
                if download_file(url, dest, binary.get("checksum", ""), binary.get("size", 0)):
                    ok += 1
                else:
                    fail += 1
            except KeyboardInterrupt:
                print(clr("\n[!] Téléchargement interrompu.", YELLOW))
                sys.exit(1)

    print()
    print(clr("─" * 50, DIM))
    print(clr(f"  ✓ Succès : {ok}", GREEN))
    if fail:
        print(clr(f"  ✗ Échecs : {fail}", RED))
    print(f"  Dossier  : {OUTPUT_DIR}")
    print(clr("─" * 50, DIM))


def menu_app_versions(app_name: str, versions: list):
    """Sous-menu : liste des versions d'une app, choix à télécharger."""
    while True:
        print_header(f"{app_name}  —  {len(versions)} version(s) disponible(s)")
        for i, entry in enumerate(versions, 1):
            mode = entry.get("rules", {}).get("mode", "?")
            mc = MODE_COLORS.get(mode, "")
            size = sum(b.get("size", 0) for b in entry.get("binaries", []))
            date = entry.get("releaseDate", "")
            vc = entry.get("versionCode", "")
            latest_tag = clr(" ◀ LATEST", GREEN) if i == 1 else ""
            print(f"  {clr(str(i), BOLD)}.  v{entry['versionName']:<18}  "
                  f"[{clr(mode, mc):<10}]  {human_size(size):>10}  "
                  f"{clr(date, DIM)}  (code {vc}){latest_tag}")

        print()
        print(f"  {clr('a', BOLD)}. Télécharger toutes les versions")
        print(f"  {clr('r', BOLD)}. Retour")
        print()
        choice = ask("  Choix > ")

        if choice.lower() == "r" or choice == "":
            return
        if choice.lower() == "a":
            do_download(versions, app_name)
            ask("\n  [Entrée pour continuer]")
            return
        if choice.isdigit():
            idx = int(choice) - 1
            if 0 <= idx < len(versions):
                do_download([versions[idx]], app_name)
                ask("\n  [Entrée pour continuer]")
                return
        print(clr("  Choix invalide.", YELLOW))


def menu_main(groups: dict[str, list]):
    """Menu principal : liste des apps."""
    app_names = list(groups.keys())

    while True:
        print_header("SFR Connect TV — Téléchargeur d'APKs")
        total_apps = len(app_names)
        for i, name in enumerate(app_names, 1):
            versions = groups[name]
            latest = versions[0]
            mode = latest.get("rules", {}).get("mode", "?")
            mc = MODE_COLORS.get(mode, "")
            nb = len(versions)
            size = sum(b.get("size", 0) for b in latest.get("binaries", []))
            nb_tag = clr(f"({nb} ver.)", DIM) if nb > 1 else clr("(1 ver.) ", DIM)
            print(f"  {clr(str(i), BOLD):>4}.  {name:<32}  "
                  f"v{latest['versionName']:<18}  "
                  f"[{clr(mode, mc):<10}]  {human_size(size):>10}  {nb_tag}")

        print()
        total_size = sum(
            b.get("size", 0)
            for versions in groups.values()
            for entry in [versions[0]]
            for b in entry.get("binaries", [])
        )
        print(clr(f"  {total_apps} applications  |  Taille totale (dernières versions) : {human_size(total_size)}", DIM))
        print()
        print(f"  {clr('t', BOLD)}. Tout télécharger (dernières versions)")
        print(f"  {clr('q', BOLD)}. Quitter")
        print()
        choice = ask("  Numéro d'app ou commande > ")

        if choice.lower() == "q" or choice == "":
            print("\nAu revoir.\n")
            sys.exit(0)

        if choice.lower() == "t":
            latest_all = [v[0] for v in groups.values()]
            total = sum(b.get("size", 0) for e in latest_all for b in e.get("binaries", []))
            print(f"\n  {len(latest_all)} APKs à télécharger — {human_size(total)} au total")
            ans = ask("  Confirmer ? [O/n] ")
            if ans.lower() != "n":
                do_download(latest_all, "Tout")
                ask("\n  [Entrée pour continuer]")
            continue

        if choice.isdigit():
            idx = int(choice) - 1
            if 0 <= idx < total_apps:
                menu_app_versions(app_names[idx], groups[app_names[idx]])
                continue
        print(clr("  Choix invalide.", YELLOW))


def main():
    print(clr("\n[*] Récupération du catalogue CDN SFR...", DIM))
    try:
        catalog = fetch_json(CDN_CONF_URL)
    except Exception as e:
        print(clr(f"[✗] Impossible de récupérer le catalogue : {e}", RED))
        sys.exit(1)

    groups = group_by_app(catalog)
    print(clr(f"[*] {len(catalog)} entrées  |  {len(groups)} applications trouvées", DIM))

    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    menu_main(groups)


if __name__ == "__main__":
    main()

Si ça peut servir à quelqu'un  ;)
3
C'est tellement sécure le contrôle d'identité par un tiers c'est certain. On a des nouvelles de Sumsub ?
4
Orange fibre Actus Orange / Répéteur wifi 7 enfin?
« Dernier message par canope le Hier à 20:27:02 »
Et toujours pas de 6Ghz ..
Orange va de toute façon flinguer tout le monde avec la proposition d’utiliser les bandes « voisines » initialement prévues pour  le Wifi 7 pour la 6G.


Voici un résumé des puissances de transmission pour un Wi-Fi au top de 33 dBm par rapport à une antenne 5G NR de 46 dBW.

Malgré les filtres, les antennes 6G vont interférer avec les réseaux Wi-Fi en 6 GHz.

Le rapport / ratio de puissance est trop disproportionné ..

Summary of Power per MHz in Watts and EIRP

Wi-Fi (80 MHz):
EIRP: 33 dBm (~2.0 W)
Power per MHz: 25 mW/MHz (0.025 W/MHz)

Wi-Fi (160 MHz):
EIRP: 33 dBm (~2.0 W)
Power per MHz: 12.5 mW/MHz (0.0125 W/MHz)

Wi-Fi (320 MHz):
EIRP: 33 dBm (~2.0 W)
Power per MHz: 6.25 mW/MHz (0.00625 W/MHz)

5G NR (80 MHz):
EIRP: 46 dBW (approximately 39810.7171 W)
Power per MHz: 497.634 W/MHz
5
Frais de mise en service / raccordement : 0 à 3 000 € (en ville dense)

Abonnement mensuel : 80 à 1 000 € / mois selon les options et le débit


Et très souvent 36 mois d'engagement pour avoir des FAS réduits ou nuls
6
OVH OVH FAI / Besoin d'aide : OVH FTTH + EdgeRouter + IPv6
« Dernier message par konki le Hier à 19:41:20 »
@thenico;, merci pour tes explications, mais je pense que le gestionnaire online n'active toujours pas IPv6.

Peux-tu me guider pour le faire via l'API? Je ne maîtrise pas sans tuto!
J'ai déjà utilisé l'api avec Certbot, on trouve facilement la recette, ce qui n'est pas le cas pour cette opération.

konki
7
Orange fibre Actus Orange / Migration offre et queelques questions...
« Dernier message par forcebio le Hier à 19:22:21 »
Bonsoir !

Migration effectuée en boutique Orange cet après-midi. LB7W7 fournie, et installée dans la foulée.
Le premier démarrage est plutôt long, mais une fois effectué, je retrouve bien tout configuré, même le WIFI ! Même SSID, même password, rien à reparamétrer, top !

RX avant : 23.01
RX après : 21.079

J'ai testé les débits. La LB7W7 indique 8150 en down et 7933 en up ! De la folie...
Bon, en pratique, mon PC ayant une carte réseau 2,5GBits, je n'en profite pas forcément, mais j'ai quand même du 2000 / 2400 (pourquoi "que" 2000 en down, mystère... si quelqu'un a une idée d'ailleurs...).

Pour le moment, RAS !

Merci pour vos conseils :)

8
Free Incidents Free / Microcoupure FTTH ?
« Dernier message par Did777 le Hier à 19:02:13 »
Environ 20s
9
Renseigne toi bien.

Sur quelques adresses que j'ai testé, OVH c'est 900 €à l'installation + 12 mois d'engagement (6 mois à 80 €et 6 mois à 160 €). Donc en gros ~3k€ l'année. ça fait un budget, mais c'est 1/10 des 30 k€. ça peut être la dernière solution choisie..

J'ai testé OVH, mais il n'y a pas que eux.

Pour l'installation, logiquement ça doit passer par la gaine télécom comme ta fibre actuelle.

J'ai exactement cette proposition sur l'adresse. C'est cher, c'est sûr mais c'est toujours bon de savoir qu'il y a cette solution dans le pire des cas.

Je pense que par ordre de priorité, je vais choisir
  • La transfert de la ligne Free actuelle des propriétaires
  • Une box 5G
  • La FTTO
10
Free Incidents Free / Microcoupure FTTH ?
« Dernier message par Gears le Hier à 18:42:43 »
La coupure a durée combien de secondes ?
Pages: [1] 2 3 4 5 6 ... 10