fix(kontakte): LinkedIn-Foto nur aus der Vorschau, klarere Meldung
Anlass: Bei einem Kontakt kam das Foto, beim naechsten nicht. Der Vergleich der beiden Profilseiten zeigt warum - das zweite Profil liefert als og:image nur LinkedIns Ersatzgrafik (static.licdn.com/aero-v1/sc/h/...), weil die Sichtbarkeit des Fotos auf "Meine Kontakte" steht. Angemeldet sieht man es, der Abruf hier ist es nicht. Zwei Konsequenzen: * Die Rueckfallebene "erste media.licdn.com-Adresse im Quelltext" faellt weg. Dieselbe Seite enthaelt 24 solcher Adressen: Firmenlogos, Beitragsbilder und die Fotos der "aehnlichen Profile" am Rand. Nur die Open-Graph-Vorschau gehoert nachweislich zum aufgerufenen Profil - alles andere haette irgendwann ein fremdes Gesicht an einen Kontakt gehaengt. * Die Meldung sagt jetzt, was wirklich los ist: "nur fuer Kontakte sichtbar oder es gibt keines" statt "hat kein eigenes Foto". Von aussen sind die beiden Faelle nicht unterscheidbar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0a2cf52e8a
commit
46983a7e2c
@@ -68,9 +68,14 @@ export function normalizeProfileUrl(input) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bild-URL aus dem HTML der Profilseite ziehen. og:image ist der
|
||||
* zuverlaessigste Treffer; als Rueckfallebene wird nach einer Foto-URL des
|
||||
* LinkedIn-CDN im Seitenquelltext gesucht.
|
||||
* Bild-URL aus dem HTML der Profilseite ziehen - ausschliesslich aus der
|
||||
* Open-Graph-Vorschau (`og:image`, ersatzweise `twitter:image`).
|
||||
*
|
||||
* Bewusst wird NICHT im uebrigen Quelltext nach einer Bildadresse gesucht: eine
|
||||
* Profilseite enthaelt Dutzende Adressen des LinkedIn-CDN - Firmenlogos,
|
||||
* Beitragsbilder und die Fotos der "aehnlichen Profile" am Rand. Die erste
|
||||
* davon zu nehmen, haengt irgendwann einem Kontakt das Gesicht einer fremden
|
||||
* Person an. Nur die Vorschau gehoert nachweislich zum aufgerufenen Profil.
|
||||
*/
|
||||
function extractImageUrl(html) {
|
||||
const metaPattern = /<meta[^>]+>/gi;
|
||||
@@ -80,9 +85,7 @@ function extractImageUrl(html) {
|
||||
const content = tag.match(/content\s*=\s*["']([^"']+)["']/i);
|
||||
if (content?.[1]) return decodeEntities(content[1]);
|
||||
}
|
||||
|
||||
const direct = html.match(/https:\/\/media[^"'\s\\]*\.licdn\.com\/dms\/image\/[^"'\s\\]+/i);
|
||||
return direct ? decodeEntities(direct[0]) : null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Im HTML stehen die URLs mit maskierten Und-Zeichen. */
|
||||
@@ -151,13 +154,18 @@ export async function fetchLinkedInPhoto(profileInput) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Profile ohne eigenes Foto bekommen von LinkedIn eine graue Platzhalter-
|
||||
* Silhouette als Vorschaubild. Die soll nicht als Kontaktfoto landen.
|
||||
* Echte Profilfotos liegen unter media.licdn.com/dms/image/... - alles andere
|
||||
* ist LinkedIns Ersatzbild: die graue Silhouette ('ghost') oder eine feste
|
||||
* Grafik von static.licdn.com (/aero-v1/sc/h/...). Das bekommt zu sehen, wer
|
||||
* nicht angemeldet ist und dessen Profilfoto nur fuer Kontakte sichtbar ist -
|
||||
* von aussen laesst sich das nicht von "gar kein Foto" unterscheiden, deshalb
|
||||
* nennt die Meldung beide Moeglichkeiten.
|
||||
*/
|
||||
const path = parsedImage.pathname.toLowerCase();
|
||||
if (path.includes('ghost') || !path.includes('/dms/image/')) {
|
||||
throw new PhotoError(
|
||||
'Das Profil hat kein eigenes Foto hinterlegt.',
|
||||
'LinkedIn gibt zu diesem Profil kein Foto heraus: Es ist nur für Kontakte ' +
|
||||
'sichtbar oder es gibt keines. Bitte das Foto manuell hochladen.',
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
@@ -71,12 +71,24 @@ check(
|
||||
extractImageUrl('<meta name="twitter:image" content="https://media.licdn.com/dms/image/t">') ===
|
||||
'https://media.licdn.com/dms/image/t'
|
||||
);
|
||||
check('Seite ohne Vorschau liefert null', extractImageUrl('<html><body>nichts</body></html>') === null);
|
||||
/*
|
||||
* Wichtig: ohne og:image wird NICHT im uebrigen Quelltext gesucht. Eine
|
||||
* Profilseite ist voller fremder Bilder - Firmenlogos, Beitragsbilder, Fotos
|
||||
* der "aehnlichen Profile". Eines davon zu nehmen, haenge dem Kontakt das
|
||||
* Gesicht einer fremden Person an.
|
||||
*/
|
||||
check(
|
||||
'Ohne Vorschau greift die Suche im Quelltext',
|
||||
extractImageUrl('var x = "https://media.licdn.com/dms/image/v2/zzz/profile-displayphoto";') ===
|
||||
'https://media.licdn.com/dms/image/v2/zzz/profile-displayphoto'
|
||||
'Fremde Bilder im Quelltext werden nicht genommen',
|
||||
extractImageUrl(
|
||||
'<html><body><img src="https://media.licdn.com/dms/image/v2/xy/profile-displayphoto-shrink_400_400/0/1"></body></html>'
|
||||
) === null
|
||||
);
|
||||
check(
|
||||
'Ersatzgrafik von static.licdn.com kommt als solche zurueck',
|
||||
extractImageUrl('<meta property="og:image" content="https://static.licdn.com/aero-v1/sc/h/abc">') ===
|
||||
'https://static.licdn.com/aero-v1/sc/h/abc'
|
||||
);
|
||||
check('Seite ohne Bild liefert null', extractImageUrl('<html><body>nichts</body></html>') === null);
|
||||
|
||||
console.log(failures === 0 ? '\nAlle Pruefungen bestanden.' : `\n${failures} Pruefung(en) fehlgeschlagen.`);
|
||||
process.exit(failures === 0 ? 0 : 1);
|
||||
|
||||
Reference in New Issue
Block a user