PageSourceSearch

https://futemaxtv.com.br/js/ai-service.js?v=1

js futemaxtv.com.br collected 2026-10-10 00:50:58 UTC 7,445 bytes, 198 lines download raw bytes

1// js/ai-service.js — IA via backend (Groq / OpenRouter / Google), chaves no servidor
2
3const AiService = {
4    _cache: new Map(),
5    _endpoint: 'ai.php',
6
7    url() {
8        return apiResolve(this._endpoint);
9    },
10
11    async complete(task, payload, opts = {}) {
12        const lang = opts.lang || getLang();
13        const cacheKey = `${task}:${lang}:${JSON.stringify(payload)}`;
14        const ttl = opts.ttl ?? (task === 'translate' ? 3600000 : 300000);
15        const hit = this._cache.get(cacheKey);
16        if (hit && Date.now() - hit.ts < ttl) return hit.data;
17
18        let data;
19        try {
20            const res = await fetch(this.url(), {
21                method: 'POST',
22                headers: { 'Content-Type': 'application/json' },
23                body: JSON.stringify({ task, lang, payload })
24            });
25            const text = await res.text();
26            data = text ? JSON.parse(text) : {};
27            if (!res.ok) throw new Error(data?.error || `HTTP ${res.status}`);
28        } catch (err) {
29            if (opts.optional) return null;
30            throw err;
31        }
32
33        this._cache.set(cacheKey, { ts: Date.now(), data });
34        return data;
35    },
36
37    async translate(text, opts = {}) {
38        if (!text || !AiPreview?.needsTranslation?.(text)) {
39            return AiPreview?.fixOrthography?.(text, getLang()) || text;
40        }
41        try {
42            const res = await this.complete('translate', { text }, { ...opts, optional: true, ttl: 3600000 });
43            if (res?.text) return AiPreview.fixOrthography(AiPreview.stripMarkdown(res.text), getLang());
44        } catch (_) { /* fallback */ }
45        return AiPreview.translateChunks(text);
46    },
47
48    leagueName(match) {
49        if (typeof AiPreview?.leagueName === 'function') return AiPreview.leagueName(match);
50        return match.league_name || '';
51    },
52
53    compactContext(match, bundles = {}) {
54        const { event, meta, prediction, odds, espn, s365, sofa, sm } = bundles;
55        const m = event || match;
56        const mr = prediction?.markets?.match_result;
57        const ctx = {
58            home: m.home_team,
59            away: m.away_team,
60            league: this.leagueName(m),
61            date: m.event_date,
62            status: m.status,
63            score: `${m.home_score ?? '-'} x ${m.away_score ?? '-'}`,
64            venue: m.venue_name || event?.venue?.name || s365?.venue?.name || sofa?.venue?.name,
65            weather: m.weather?.description || null,
66            derby: Boolean(m.is_local_derby || event?.is_local_derby)
67        };
68
69        if (mr) {
70            ctx.model = {
71                probHome: mr.prob_home,
72                probDraw: mr.prob_draw,
73                probAway: mr.prob_away,
74                favorite: mr.predicted || prediction?.recommendations?.favorite,
75                likelyScore: prediction?.markets?.score?.most_likely,
76                xg: prediction?.markets?.expected_goals
77            };
78        }
79
80        const h2h = event?.head_to_head || m.head_to_head;
81        if (h2h?.total_matches >= 2) {
82            ctx.h2h = {
83                total: h2h.total_matches,
84                homeWins: h2h.home_wins,
85                draws: h2h.draws,
86                awayWins: h2h.away_wins,
87                avgGoals: h2h.avg_total_goals
88            };
89        }
90
91        if (odds) {
92            ctx.odds = {
93                home: odds.home_win,
94                draw: odds.draw,
95                away: odds.away_win,
96                over25: odds.over_25_goals,
97                bttsYes: odds.btts_yes
98            };
99        }
100
101        if (meta?.funfacts?.length) {
102            ctx.funfacts = meta.funfacts.slice(0, 3).map(f => f.sentence || f.text).filter(Boolean);
103        }
104
105        if (espn?.summary) {
106            const leaders = EspnAPI?.parseLeaders?.(espn.summary) || [];
107            ctx.espn = { leaders: leaders.slice(0, 4).map(l => `${l.label}: ${l.name}`) };
108        }
109
110        if (s365) {
111            ctx.scores365 = {
112                venue: s365.venue?.name,
113                stages: s365.stages?.map(s => `${s.shortName || s.name}: ${s.homeCompetitorScore}-${s.awayCompetitorScore}`),
114                poll: s365.predictions?.[0]?.options?.map(o => `${o.name} ${o.vote?.percentage}%`)
115            };
116        }
117
118        if (sofa) {
119            ctx.sofascore = {
120                tournament: sofa.tournament,
121                odds1x2: sofa.odds1x2,
122                stats: (sofa.statistics || []).slice(0, 4).map(s => `${s.name}: ${s.home}-${s.away}`)
123            };
124        }
125
126        if (sm) {
127            ctx.sportmonks = {
128                league: sm.league,
129                stats: (sm.stats || []).slice(0, 4).map(s => `${s.name}: ${s.home}-${s.away}`)
130            };
131        }
132
133        return ctx;
134    },
135
136    async gatherBundles(match) {
137        const id = match.id;
138        return Promise.all([
139            BzzoiroAPI.getEvent(id).catch(() => match),
140            BzzoiroAPI.getEventMetadata(id).catch(() => null),
141            BzzoiroAPI.getEventPrediction(id).catch(() => null),
142            BzzoiroAPI.getEventOdds(id).catch(() => null),
143            typeof getEspnEnrichment === 'function' ? getEspnEnrichment(match).catch(() => null) : null,
144            typeof getScores365Enrichment === 'function' ? getScores365Enrichment(match).catch(() => null) : null,
145            typeof getSofascoreEnrichment === 'function' ? getSofascoreEnrichment(match).catch(() => null) : null,
146            typeof getSportmonksEnrichment === 'function' ? getSportmonksEnrichment(match).catch(() => null) : null
147        ]).then(([event, meta, prediction, oddsData, espn, s365, sofa, sm]) => ({
148            event, meta, prediction,
149            odds: oddsData?.odds || null,
150            espn, s365, sofa, sm
151        }));
152    },
153
154    async matchPreview(match, opts = {}) {
155        try {
156            const bundles = await this.gatherBundles(match);
157            const payload = this.compactContext(match, bundles);
158            const res = await this.complete('match_preview', payload, { ...opts, optional: true });
159            if (res?.text) return AiPreview.stripMarkdown(res.text);
160        } catch (_) { /* fallback */ }
161        return null;
162    },
163
164    oddsPayload(pick) {
165        return {
166            home: pick.event?.home_team,
167            away: pick.event?.away_team,
168            league: OddsIntel?.leagueDisplayName?.(pick.event) || pick.event?.league_name,
169            pick: pick.shortPick,
170            modelProb: pick.modelProb,
171            marketOdd: pick.marketOdd,
172            marketProb: pick.marketProb,
173            edge: pick.edge,
174            confidence: pick.confidence,
175            bookmaker: pick.bookmakerName,
176            ouPick: pick.ouPick?.label
177        };
178    },
179
180    async oddsInsight(pick, opts = {}) {
181        try {
182            const res = await this.complete('odds_insight', this.oddsPayload(pick), { ...opts, optional: true });
183            if (res?.text) return AiPreview.stripMarkdown(res.text);
184        } catch (_) { /* fallback */ }
185        return null;
186    },
187
188    async oddsSummary(picks, opts = {}) {
189        try {
190            const payload = {
191                picks: (picks || []).slice(0, 8).map(p => this.oddsPayload(p))
192            };
193            const res = await this.complete('odds_summary', payload, { ...opts, optional: true });
194            if (res?.text) return AiPreview.stripMarkdown(res.text);
195        } catch (_) { /* fallback */ }
196        return null;
197    }
198};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.