summary refs log tree commit diff
path: root/synapse/rest/media/v1/preview_url_resource.py
diff options
context:
space:
mode:
authorMatthew Hodgson <matthew@matrix.org>2016-03-31 14:15:09 +0100
committerMatthew Hodgson <matthew@matrix.org>2016-03-31 14:15:09 +0100
commitbb9a2ca87c280e1c6ff6740ee9d2764e1b5226a5 (patch)
tree3bd71a3a317c1049c7e825ca70616ca7dd8c2d6e /synapse/rest/media/v1/preview_url_resource.py
parentsync in changes from matrixfederationclient (diff)
downloadsynapse-bb9a2ca87c280e1c6ff6740ee9d2764e1b5226a5.tar.xz
synthesise basig OG metadata from pages lacking it
Diffstat (limited to 'synapse/rest/media/v1/preview_url_resource.py')
-rw-r--r--synapse/rest/media/v1/preview_url_resource.py47
1 files changed, 47 insertions, 0 deletions
diff --git a/synapse/rest/media/v1/preview_url_resource.py b/synapse/rest/media/v1/preview_url_resource.py
index ca2529cc10..b1d5cabfaa 100644
--- a/synapse/rest/media/v1/preview_url_resource.py
+++ b/synapse/rest/media/v1/preview_url_resource.py
@@ -23,6 +23,7 @@ from synapse.http.client import SpiderHttpClient
 from synapse.http.server import request_handler, respond_with_json, respond_with_json_bytes
 
 import os
+import re
 import ujson as json
 
 import logging
@@ -70,6 +71,7 @@ class PreviewUrlResource(BaseMediaResource):
 
                 # define our OG response for this media
             elif self._is_html(media_info['media_type']):
+                # TODO: somehow stop a big HTML tree from exploding synapse's RAM
                 tree = html.parse(media_info['filename'])
 
                 # suck it up into lxml and define our OG response.
@@ -82,17 +84,58 @@ class PreviewUrlResource(BaseMediaResource):
                 # "og:image"       : "https://pbs.twimg.com/profile_images/500400952029888512/yI0qtFi7_400x400.png"
                 # "og:description" : "Synapse 0.12 is out! Lots of polishing, performance &amp;amp; bugfixes: /sync API, /r0 prefix, fulltext search, 3PID invites https://t.co/5alhXLLEGP"
                 # "og:site_name"   : "Twitter"
+                
+                # or:
+
+                # "og:type"         : "video",
+                # "og:url"          : "https://www.youtube.com/watch?v=LXDBoHyjmtw",
+                # "og:site_name"    : "YouTube",
+                # "og:video:type"   : "application/x-shockwave-flash",
+                # "og:description"  : " ",
+                # "og:title"        : "RemoteJam - Matrix team hack for Disrupt Europe Hackathon",
+                # "og:image"        : "https://i.ytimg.com/vi/LXDBoHyjmtw/maxresdefault.jpg",
+                # "og:video:url"    : "http://www.youtube.com/v/LXDBoHyjmtw?version=3&autohide=1",
+                # "og:video:width"  : "1280"
+                # "og:video:height" : "720",
+                # "og:video:secure_url": "https://www.youtube.com/v/LXDBoHyjmtw?version=3&autohide=1",
 
                 og = {}
                 for tag in tree.xpath("//*/meta[starts-with(@property, 'og:')]"):
                     og[tag.attrib['property']] = tag.attrib['content']
 
+                if not og:
+                    # do some basic spidering of the HTML
+                    title = tree.xpath("(//title)[1] | (//h1)[1] | (//h2)[1] | (//h3)[1]")
+                    og['og:title'] = title[0].text if title else None
+
+                    images = tree.xpath("//img")
+                    big_images = [ i for i in images if (
+                        'width' in i and 'height' in i and
+                        i.attrib['width'] > 64 and i.attrib['height'] > 64
+                    )] or images
+                    og['og:image'] = images[0].attrib['src'] if images else None
+
+                    text_nodes = tree.xpath("//h1/text() | //h2/text() | //h3/text() | //p/text() | //div/text() | //span/text() | //a/text()")
+                    text = ''
+                    for text_node in text_nodes:
+                        if len(text) < 1024:
+                            text += text_node + ' '
+                        else:
+                            break
+                    text = re.sub(r'[\t ]+', ' ', text)
+                    text = re.sub(r'[\t \r\n]*[\r\n]+', '\n', text)
+                    text = text.strip()[:1024]
+                    og['og:description'] = text if text else None
+
+                # TODO: turn any OG media URLs into mxc URLs to capture and thumbnail them too
                 # TODO: store our OG details in a cache (and expire them when stale)
                 # TODO: delete the content to stop diskfilling, as we only ever cared about its OG
             else:
                 logger.warn("Failed to find any OG data in %s", url)
                 og = {}
 
+            logger.warn(og)
+
             respond_with_json_bytes(request, 200, json.dumps(og), send_cors=True)
         except:
             # XXX: if we don't explicitly respond here, the request never returns.
@@ -111,6 +154,10 @@ class PreviewUrlResource(BaseMediaResource):
 
     @defer.inlineCallbacks
     def _download_url(self, url, user):
+        # TODO: we should probably honour robots.txt... except in practice
+        # we're most likely being explicitly triggered by a human rather than a
+        # bot, so are we really a robot?
+
         # XXX: horrible duplication with base_resource's _download_remote_file()
         file_id = random_string(24)