Forum: PC-Programmierung Webseiten "anpassen": Spaß mit Forward-Proxies


von Sheeva P. (sheevaplug)


Angehängte Dateien:

Lesenswert?

Hallo,

bisweilen wird hier gefragt, ob und wie man andere Nutzer hier 
"ausblenden" könne. Die Forensoftware bietet diese Funktion jedoch nicht 
an -- alles gut, das muß und soll sie auch nicht, immerhin ist das hier 
ein Forum.

Etwas weiter gedacht möchte ich aber auf einigen Webseiten Inhalte aus- 
oder einblenden können. In manchen Browsern geht das zwar mit einer 
user.css, das funktioniert jedoch leider nur eingeschränkt.

An dieser und ein paar anderen Stellen kürze ich jetzt mal ab, am Ende 
hab ich mir einen Forward-Webproxy entwickelt. Der terminiert die 
TLS-Verschlüsselung, manipuliert das entschlüsselte HTML-Dokument, und 
übergibt es dem Browser.

Am Ende sieht ein Thread hier dann etwa wie im Anhang "UcProxy_0.webp" 
aus: die Beiträge werden in HTML "details" Tags [1] eingebettet, deren 
"summary" nur den Benutzernamen enthält; ein Klick auf den Benutzernamen 
macht dessen Beitrag sichtbar, wie im Anhang jenen von "lichtmensch".

Gleichzeitig möchte ich manche Benutzer gerne erst einmal ausblenden und 
ihre Beiträge nur dann lesen, wenn ich das ausdrücklich will. Deswegen 
gibt es die Liste von Benutzern, von deren Beiträgen ich zunächst nur 
die "summary" sehen möchte. Die Datei wird ständig überwacht und bei 
Änderungen neu geladen.

Nachdem mein kleiner Proxy schon sehr schick funktioniert, denke ich 
darüber nach, wie er sich erweitern ließe. Mit einer Domain Specific 
Language (DSL), die abhängig vom URL bestimmte Transformationen 
vornimmt? Oder hat einer von Euch womöglich klügere Ideen?

Liebe Grüße,
Sheeva

PS: Mein Code ist (weitgehend) vom wunderbaren Eli Bendersky kopiert 
[2].

[1] https://www.w3schools.com/tags/tag_details.asp
[2] 
https://eli.thegreenplace.net/2022/go-and-proxy-servers-part-1-http-proxies/

von Alexander (alecxs)


Lesenswert?

​​

von Max (max_u)


Angehängte Dateien:

Lesenswert?

Ich will dir deine Proxy Idee jetzt nicht ausreden. Und hab da jetzt 
auch keine Vorschläge zu. Insgesamt wäre es wahrscheinlich leichter 
gewesen, dass als Tampermonkey (o.ä.) Script zu implementieren. Das ist 
ein Browserplugin, dass in definierte Webseiten JS injecten kann.

Wie gehst du mit ungültigen TLS Zertifikaten um? Wird das einfach 
abgelehnt oder ignoriert?

Ich hab mal schnell ein einfaches Tampermonkey Script coden lassen. Ist 
im Anhang. Da bist jetzt nur du auf der Blockliste. Da man das Script 
mit 2 Klicks editieren kann, kann man evtl. einfach ne statische 
Blockliste fahren. Es wäre aber auch kein großer Aufwand, dem Forum 
einen "blocken" Button hinzuzufügen...

(Meines Wissens hat auf Mobilgeräten nur Firefox Plugins. Falls du das 
auch mobil nutzen willst.)

von Frank D. (Firma: LAPD) (frank_s634)


Lesenswert?

Max schrieb:
> gewesen, dass als Tampermonkey (o.ä.) Script zu implementieren. Das ist
> ein Browserplugin, dass in definierte Webseiten JS injecten kann.

Diese Browseraddons ala Grease|Violent|....-Monkey oder selbst reine CSS 
Manipulierer die ohne JS auskommen, sind irgendwie alle schrott, bremsen 
den Browser merklich aus, je mehr Tabs man offen hat, selbst wenn man 
nur wenige Seiten über diese Filter ändert.

Ein separater Proxy der das macht ist vielleicht nicht so bequem aber 
nervt auf Dauer weniger.

von Dieter D. (Firma: Hobbytheoretiker) (dieter_1234)


Lesenswert?

Sheeva P. schrieb:
> bisweilen wird hier gefragt, ob und wie man andere Nutzer hier
> "ausblenden" könne.

Diese Funktion brauchen aber nur User, die bereits überfordert sind 
unter dem orangen Balken einen Usernamen zu lesen, erkennen und 
weiterzuspringen. Das sind solche Personen, wie die in Bochum, die auf 
der Buehne den Schauspieler, der einen Boesewicht spielt, sogar 
koerperlich angreifen.

Alle anderen koennen :o)) weiterspringen.

von Manuel H. (Firma: Universität Tartu) (xenos1984)


Lesenswert?

Ich nutze für solche Anwendungen Privoxy. Funktioniert zum Ausfiltern 
von Werbung genau wie für andere unerwünschte Inhalte.

von Frank D. (Firma: LAPD) (frank_s634)


Lesenswert?

Manuel H. schrieb:
> Privoxy.

Das gibts noch? Das war mal vor 20 Jahren oder so populär, ich glaube 
davor war wwwuffwwwuff oder wie das hies angesagt.

von Manfred P. (pruckelfred)


Lesenswert?

Frank D. schrieb:
> Manuel H. schrieb:
>> Privoxy.
> Das gibts noch? Das war mal vor 20 Jahren oder so populär

Der spielt sogar noch unter Windows_10 und ergänzt als Filterproxy 
bestens den Adblock. Dessen Programmierer tragen die Bezeichnung noch zu 
recht, eine kleine .exe und nicht einfach irgendwas mit buntem 
Windows-Zeug zusammengeclickt, wie es zunehmend üblicher ist.

von Manuel H. (Firma: Universität Tartu) (xenos1984)


Lesenswert?

Manfred P. schrieb:
> Der spielt sogar noch unter Windows_10 und ergänzt als Filterproxy
> bestens den Adblock. Dessen Programmierer tragen die Bezeichnung noch zu
> recht, eine kleine .exe und nicht einfach irgendwas mit buntem
> Windows-Zeug zusammengeclickt, wie es zunehmend üblicher ist.

Bei mir läuft er auf einer kleinen ARMv7 Box mit 2GB RAM.

von Sheeva P. (sheevaplug)


Lesenswert?

Max schrieb:
> Ich will dir deine Proxy Idee jetzt nicht ausreden.

Vielen Danke, aber... Es ist einerseits keine Idee mehr, denn der 
Prototyp funktioniert bereits sehr zufriedenstellend, und andererseits 
gehöre ich zu einer Sorte Mensch, der sich nur selten etwas ein- oder 
ausreden läßt. :-)

> Insgesamt wäre es wahrscheinlich leichter
> gewesen, dass als Tampermonkey (o.ä.) Script zu implementieren.

Meine Erfahrung mit den diversen <XYZ>-monkeys ist leider eine ähnliche 
wie jene, die Frank D. (frank_s634) beschreibt: die sind leider nicht 
sonderlich performant. Lustigerweise scheint mein Proxy zumindest auf 
dieser Seite hier sogar schneller zu sein -- vielleicht liegt das daran, 
daß er den "chunked"-Transfer von mikrocontroller.net oder Cloudflare 
auflöst, aber das habe ich noch nicht genauer angeschaut.

> Wie gehst du mit ungültigen TLS Zertifikaten um? Wird das einfach
> abgelehnt oder ignoriert?

Das ist noch nicht implementiert, geplant ist, einen Fehler auszugeben 
und eventuell zu fragen, ob der Request mit InsecureSkipVerify 
wiederholt werden soll. Aktuell wird jedoch noch blockiert.

> Es wäre aber auch kein großer Aufwand, dem Forum
> einen "blocken" Button hinzuzufügen...

Bisher hat Andreas, der dieses Forum betreibt und meines Wissens auch 
die Forensoftware entwickelt, auf entsprechende Anfragen nicht reagiert. 
Also vermute ich, daß er daran kein Interesse hat.

> (Meines Wissens hat auf Mobilgeräten nur Firefox Plugins. Falls du das
> auch mobil nutzen willst.)

Tjaaa, mein Mobilchen ist nur ein Samsung A54 und sein Feuerfuchs wird 
jetzt schon ab einer gewissen Anzahl an Tabs und Serviceworkern spürbar 
zäh -- den möchte ich daher nicht auch noch mit Plugins belasten.

von Sheeva P. (sheevaplug)


Lesenswert?

Dieter D. schrieb:
> Diese Funktion brauchen aber nur User, die bereits überfordert sind
> unter dem orangen Balken einen Usernamen zu lesen, erkennen und
> weiterzuspringen.

Tja, Dieter, das könnte man meinen, aber... ich bin gar nicht 
überfordert, sondern bin nur nur sehr, sehr faul. Außerdem habe ich 
beobachtet, daß ich immer erst auf den Inhalt eines Beitrages schaue. 
Der Benutzername ist für mich völlig uninteressant, den lese ich daher 
nur, wenn mir beim Lesen des Beitrags etwas besonders positiv oder 
negativ auffällt.

Darum ist die Sache aus meiner Sicht ganz einfach: wenn ich Deine 
"Beiträge" ohnehin nicht lesen möchte, oder nur in Ausnahmefällen, dann 
muß ich sie auch nicht standardmäßig angezeigt bekommen. Das ist dann 
quasi mein Beitrag zur psychischen Hygiene meines armen Browsers, der eh 
schon genug ertragen muß.

> Alle anderen koennen :o)) weiterspringen.

Ja, genau, und mir persönlich wird das Weiterspringen nochmals sehr 
deutlich vereinfacht, wenn ich Deinen Unfug gar nicht erst sehen muß. 
:-)

Insofern möchte ich Dir für Deine besondere Mitwirkung bei der 
Entwicklung dieser Software meinen ausdrücklichen Dank aussprechen.

von Sheeva P. (sheevaplug)


Lesenswert?

Manuel H. schrieb:
> Ich nutze für solche Anwendungen Privoxy. Funktioniert zum Ausfiltern
> von Werbung genau wie für andere unerwünschte Inhalte.

Squid, Privoxy und Burp hatte ich mir angeschaut, und bei allen hat mich 
was gestört. Privoxy habe ich mit Regulären Ausdrücken ausprobiert, was 
zu einem nicht besonders eleganten Zeichensalat geführt hat und ein 
"external-filter" hat bei meinen Versuchen leider auch keinen Spaß 
gemacht. Außerdem habe ich mich ohnehin mal wieder mit HTTP, TLS und 
goquery beschäftigen wollen, daher lag die Entwicklung eines eigenen 
Proxy nahe.

von Alexander (alecxs)


Lesenswert?

​​​

Beitrag #8016535 wurde von einem Moderator gelöscht.
Beitrag #8016607 wurde von einem Moderator gelöscht.
von Frank O. (fop)


Lesenswert?

Oh ja, funktioniert gut. Die Beiträge von Alexander (alecxs) werden 
einwandfrei weggefiltert.

;-P

von Alexander (alecxs)


Lesenswert?

​​​​

Beitrag #8016692 wurde von einem Moderator gelöscht.
von Sheeva P. (sheevaplug)


Lesenswert?

Frank O. schrieb:
> Oh ja, funktioniert gut. Die Beiträge von Alexander (alecxs) werden
> einwandfrei weggefiltert.

Immerhin! :-)

von Sheeva P. (sheevaplug)


Lesenswert?

Mittlerweile habe ich mich nun zu einigen Entscheidungen durchgerungen.

Zum Ersten wird es für defekte TLS-Zertifikate nur Fehlermeldungen geben 
und keine Möglichkeiten, sie zu umgehen. Ich möchte weder mich selbst, 
noch User dazu animieren, TLS-Fehler zu ignorieren oder zu umgehen.

Zweitens wird die Filterarchitektur zukünftig mit Plugins realisiert, so 
daß nicht mehr nur mikrocontroller.net bearbeitet werden kann.

Privoxy hat zudem ein paar nette Features wie "external-filter" oder 
seine Config-Seite, die mir sehr gefallen.

Zu gegebener Zeit möchte ich meinen Code gerne veröffentlichen, aber so 
weit bin ich leider noch nicht. Insbesondere möchte ich weder dem Forum, 
noch dem Betreiber, seinen Moderatoren oder sonst jemandem schaden. 
Solange ich keine Lösung dafür gefunden habe, möchte ich die Sourcen 
nicht öffentlich machen, vielen Dank für Euer Verständnis.

Lieber Alexander (alecxs), lieben Dank für Deine "Beiträge" in diesem 
Thread und für Deine besondere Inspiration bei der Entwicklung der 
Software. Jedoch ist uns hier jetzt sattsam bekannt, daß Du in diesem 
Thread nichts beitragen möchtest oder kannst, insofern: guten Tag, bis 
demnächst. :-)

PS. Das hatte ich fast vergessen, ich bitte um Verzeihung: lieber 
Moderator, lieben Dank für Dein segensreiches Wirken in diesem Thread! 
:-)

Edit: PS hinzugefügt.

: Bearbeitet durch User
von Max (max_u)


Lesenswert?

Sheeva P. schrieb:
> Zum Ersten wird es für defekte TLS-Zertifikate nur Fehlermeldungen geben
> und keine Möglichkeiten, sie zu umgehen. Ich möchte weder mich selbst,
> noch User dazu animieren, TLS-Fehler zu ignorieren oder zu umgehen.

Ich würde wenigstens in irgendwelchen Optionen das wenigstens fürs 
lokale Netz doch zulassen. Viele lokale Webuis (Router, 3d Drucker, 
IOT-gedöns, ...) haben einfach keine erreichbare URL und können damit 
kein vertrauenswürdiges Zertifikat anbieten. Theoretisch könnte man 
natürlich ein self-signed cert ausrollen. Aber wer macht das schon?

Sowieso finde ich es erschreckend, wie oft ich auf abgelaufene 
Zertifikate auch im Web stoße.

edit In welcher Sprache hast du das eigentlich geschrieben?

: Bearbeitet durch User
von Martin S. (sirnails)


Lesenswert?

Ich hab mir dazu ein einfaches Greasemonkey Script gebaut.

Das einzig "umständliche" ist die User-ID herauszufinden (geht aber mit 
den Dev-Tools im Browser recht gut) oder aber halt über den Namen (der 
aber nicht eindeutig sein muss).

Leistet mir seit Jahren treue Dienste.

Viel Spaß damit.
1
// ==UserScript==
2
// @name         Forum Highlight Script
3
// @namespace    http://tampermonkey.net/
4
// @version      0.1
5
// @description  Zeigt eine Message Box auf bestimmten Seiten an.
6
// @author       Du
7
// @match        https://www.mikrocontroller.net/topic*
8
// @grant        none
9
// ==/UserScript==
10
11
(function() {
12
  'use strict';
13
14
  // User, die positiv auffallen
15
  const User_id_hghlght_pos = [];                                  
16
  const User_name_hghlght_pos = [""];
17
  
18
  // User, die negativ auffallen
19
  const User_id_hghlght_neg = [];            
20
  const User_name_hghlght_neg = [""];
21
22
  // User, die auf der Watchlist sind
23
   const User_id_watchlist = [];                                    
24
  const User_name_watchlist = [""];
25
26
  // User, die ganz ausgeblendet werden sollen
27
  const User_to_blank_out = [83793,21698,122383,56220,65398,104429,77258,88141];                                            
28
  const User_name_ignore = [""];
29
30
  // Maximales Alter der Posts in Tagen
31
  const Post_max_age_days = 30;                                                 
32
33
  //Farbliche Hervorhebungen
34
  const color_negative = '#fae2e1'
35
  const color_positive = '#ecfaeb'
36
  const color_watchlist = '#fff3cc'
37
  const color_old_post = '#f0edff'
38
39
  // Der Text, der bei ignorierten Benutzern angezeigt werden soll
40
  const ignore_text = 'Beiträge dieses Benutzers werden nicht angezeigt.'
41
42
  // ----------------------------------------------------------------------
43
    
44
  function highlightUsers() {
45
    // Suche alle DIVs, die das Attribut 'data-user-id' haben
46
    const divs = document.querySelectorAll('div[data-user-id]');
47
48
    Array.from(divs).slice(1).forEach(div => {
49
      // Hole die Benutzer-ID aus dem 'data-user-id' Attribut
50
      const userId = parseInt(div.getAttribute('data-user-id'));
51
52
      // Extrahiere den Benutzernamen aus dem span mit der Klasse 'name'
53
      const nameElement = div.querySelector('div.info div.author span.name a');
54
      let userName = '';
55
      
56
      if (nameElement) {
57
        userName = nameElement.textContent.trim();
58
      }
59
      
60
      if ((User_id_hghlght_neg.includes(userId) || User_name_hghlght_neg.includes(userName)) && !isNaN(parseFloat(userId))) {
61
        // Negativ
62
        div.style.backgroundColor = color_negative;
63
      } else if ((User_id_hghlght_pos.includes(userId) || User_name_hghlght_pos.includes(userName)) && !isNaN(parseFloat(userId))) {
64
        // Positiv
65
        div.style.backgroundColor = color_positive
66
      } else if ((User_id_watchlist.includes(userId) || User_name_watchlist.includes(userName)) && !isNaN(parseFloat(userId))) {
67
        // Watchlist
68
        div.style.backgroundColor = color_watchlist;
69
      } else if ((User_to_blank_out.includes(userId) || User_name_ignore.includes(userName)) && !isNaN(parseFloat(userId))) {
70
        // Ignore
71
        div.innerHTML = ignore_text;
72
      }
73
      
74
      const timeElement = div.querySelector('span.created time.datetime');
75
      
76
      if (timeElement) {
77
        const postDate = new Date(timeElement.getAttribute('datetime'));
78
        const currentDate = new Date();
79
        const diffTime = Math.abs(currentDate - postDate);
80
        const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); // Unterschied in Tagen
81
        
82
        // Prüfen, ob das Erstellungsdatum älter als Post_max_age_days ist
83
        if (diffDays > Post_max_age_days) {
84
          // Ändere die Hintergrundfarbe der gesamten Seite auf Pink
85
          document.querySelector('td#main').style.backgroundColor = color_old_post;
86
        }
87
      }
88
    });
89
  }
90
  
91
    function handleDeletedPosts() {
92
        const allDivs = document.querySelectorAll('div'); // Alle Divs auf der Seite sammeln
93
        let consecutiveDeleted = []; // Liste der aufeinanderfolgenden 'deleted-post-notice' Divs
94
95
        allDivs.forEach((div) => {
96
            if (div.classList.contains('deleted-post-notice')) {
97
                // Wenn das aktuelle Div ein 'deleted-post-notice' ist, füge es zur Gruppe hinzu
98
                consecutiveDeleted.push(div);
99
            } else if (consecutiveDeleted.length > 0) {
100
                // Wenn ein anderes Div kommt und wir eine Gruppe gesammelt haben, wende das Styling und die Textänderung an
101
                applyStylingAndTextToDeletedPosts(consecutiveDeleted);
102
                consecutiveDeleted = []; // Zurücksetzen der Gruppe
103
            }
104
        });
105
106
        // Wenn die letzte Gruppe nicht verarbeitet wurde
107
        if (consecutiveDeleted.length > 0) {
108
            applyStylingAndTextToDeletedPosts(consecutiveDeleted);
109
        }
110
    }
111
112
    function applyStylingAndTextToDeletedPosts(posts) {
113
        if (posts.length === 1) {
114
            // Einzelnes 'deleted-post-notice' -> Rot einfärben und Text ändern
115
            //posts[0].style.backgroundColor = 'red';
116
            posts[0].textContent = "Beitrag durch Moderator gelöscht.";
117
        } else if (posts.length > 1) {
118
            // Mehrere aufeinanderfolgende 'deleted-post-notice' -> Grün einfärben und Text zusammenfassen
119
120
            //posts[0].style.backgroundColor = 'green';
121
            posts[0].textContent = "Mehrere Beiträge in Folge durch Moderator gelöscht.";
122
                      for (let i = 1; i < posts.length; i++) {
123
                posts[i].remove();
124
            }
125
        }
126
    }
127
128
  
129
  window.addEventListener('load', highlightUsers);
130
  window.addEventListener('load', handleDeletedPosts);
131
})();

von Sheeva P. (sheevaplug)


Lesenswert?

Max schrieb:
> Ich würde wenigstens in irgendwelchen Optionen das wenigstens fürs
> lokale Netz doch zulassen. Viele lokale Webuis (Router, 3d Drucker,
> IOT-gedöns, ...) haben einfach keine erreichbare URL und können damit
> kein vertrauenswürdiges Zertifikat anbieten. Theoretisch könnte man
> natürlich ein self-signed cert ausrollen. Aber wer macht das schon?

Ich bekenne: ich mache sowas, mkcert [1] ist mein Freund. :-) 
Andererseits laufen hier noch Netzwerkgeräte wie ein alter HP-4000, der 
kann kein TLS.

Ein Proxy hat IMHO den Vorteil, daß alle mir bekannten Netzwerkprogramme 
dafür über die Umgebungsvariablen "http_proxy" und "https_proxy" 
konfiguriert werden können. In den seltenen Fällen, in welchen dieser 
Proxy ein Problem darstellen sollte, kann der Browser einfach ohne die 
Umgebungsvariablen gestartet werden, und benutzt den Proxy dann einfach 
nicht.

Außerdem bin ich gerade dabei, die Software auf ein Pluginssystem 
umzubauen, dessen API gerade eine neue Funktion "ClientMaker" erhalten 
hat. Damit kann (Überraschung!) ein net/http.Client mit besonderen 
Settings erzeugt werden, für Deinen Anwendungsfall also mit 
"InsecureSkipVerify = true". Vielen Dank für Deinen klugen Hinweis!

> Sowieso finde ich es erschreckend, wie oft ich auf abgelaufene
> Zertifikate auch im Web stoße.

Ja... und das erschreckt mich umso mehr, als es ja mittlerweile 
kostenlose Zertifikate, und Programme zur Automatisierung von 
Letsencrypt und anderen ACME-Providern gibt. Daraus schließe ich, daß 
eine Vielzahl von Webservern nicht besonders gut gewartet wird. :-(

> edit In welcher Sprache hast du das eigentlich geschrieben?

In Go -- das ist sicherlich kein harter Numbercruncher wie C, C++, Rust 
oder Fortran, aber bei Netzwerksoftware geht es ja eh primär um I/O. Das 
Teuerste ist wohl das Parsen und Manipulieren von HTML-Code mit goquery 
[2], aber das habe ich noch nicht genauer gemessen, ich folge da Kent 
Beck: "make it work, make it right, make it fast" und bin noch bei "make 
it work". :-)

[1] https://github.com/FiloSottile/mkcert
[2] https://github.com/PuerkitoBio/goquery

von Sheeva P. (sheevaplug)


Lesenswert?

Martin S. schrieb:
> Ich hab mir dazu ein einfaches Greasemonkey Script gebaut.
>
> Das einzig "umständliche" ist die User-ID herauszufinden (geht aber mit
> den Dev-Tools im Browser recht gut) oder aber halt über den Namen (der
> aber nicht eindeutig sein muss).

Lieben Dank für Dein Greasemonkey-Skript, das ich allerdings lieber in 
einem Anhang gesehen hätte. Wie bereits oben erwähnt, habe ich mit den 
Monkeys die Erfahrung gemacht, daß die Performance meist darunter leidet 
-- insbesondere bei vielen Tabs und wenn der Browser lange durchläuft.

Außerdem kenne ich bisher leider noch keinen Javascript-Interpreter in 
einem Webbrowser, der keine gravierenden Memory Leaks aufweisen würde. 
Vielleicht können Node.JS oder V8 das, aber darum geht es hier ja nicht.

Nebenbei bemerkt gibt es hier [1] einen Thread von Chris D. (myfairtux), 
der einige Tipps und Tricks mit der userContent.css zeigt. Da sich auch 
Andreas dort gemeldet hat, dürfte er dem Unterfangen nicht abgeneigt 
sein.

Dennoch machen die CSS-Dateien von Chris etwas anderes: sie blenden 
einfach alles Unerwünschte aus. Das tut mein Proxy ausdrücklich nicht, 
er versteckt das Unerwünschte lediglich, und gibt es auf Knopfdruck 
dennoch frei.

[1] Beitrag "Sammlung Modifikationen µC-Website"

PS: Verweis auf anderen Thread hinzugefügt.

: Bearbeitet durch User
Beitrag #8019526 wurde von einem Moderator gelöscht.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.