Repository: zulip/zulip

Issue:
bugdown: Add preview for vimeo videos.
If you search for youtube in `zerver/lib/bugdown/__init__.py`, you can see the code that does a special preview for youtube videos. We should have similar functionality for vimeo. 

You can test the youtube functionality by sending a youtube link to "#test here" on chat.zulip.org.
Note also the youtube links are about to change; if you take this issue before #7549 is resolved, follow the new format (with the name) rather than the old format (with the visible link).

Make sure to add appropriate tests for this. You can search for (and mostly copy) the youtube tests by doing `git grep -i youtube zerver/tests/test_bugdown.py`.


Discussion:
Hello @zulip/server-markdown members, this issue was labeled with the **area: markdown** label, so you may want to check it out!

<!-- areaLabelNotification -->
@zulipbot claim

Welcome to Zulip, @nax3n! We just sent you an invite to collaborate on this repository at https://github.com/zulip/zulip/invitations. Please accept this invite in order to claim this issue and begin a fun, rewarding experience contributing to Zulip!

Here's some tips to get you off to a good start:
* Join me on the [Zulip developers' server](https://chat.zulip.org), to get help, chat about this issue, and meet the other developers.
* Sign the [Dropbox Contributor License Agreement](https://opensource.dropbox.com/cla/), so that Zulip can use your code.
* [Unwatch this repository](https://help.github.com/articles/unwatching-repositories/), so that you don't get 100 emails a day.

As you work on this issue, you'll also want to refer to the [Zulip code contribution guide](http://zulip.readthedocs.io/en/latest/contributing/index.html), as well as the rest of the developer documentation on that site.

See you on the other side (that is, the pull request side)!
Hello @nax3n, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 14 days.

You can reclaim this issue or claim any other issue by commenting `@zulipbot claim` on that issue.

Thanks for your contributions, and hope to see you again soon!
@zulipbot claim

Resolving patch:
diff --git a/zerver/lib/bugdown/__init__.py b/zerver/lib/bugdown/__init__.py
--- a/zerver/lib/bugdown/__init__.py
+++ b/zerver/lib/bugdown/__init__.py
@@ -253,13 +253,31 @@ def add_embed(root: Element, link: Text, extracted_data: Dict[Text, Any]) -> Non
         a.set("target", "_blank")
         a.set("title", title)
         a.text = title
-
     description = extracted_data.get('description')
     if description:
         description_elm = markdown.util.etree.SubElement(data_container, "div")
         description_elm.set("class", "message_embed_description")
         description_elm.text = description
 
+def add_vimeo_preview(root: Element, link: Text, extracted_data: Dict[Text, Any], vm_id: Text) -> None:
+    container = markdown.util.etree.SubElement(root, "div")
+    container.set("class", "vimeo-video message_inline_image")
+
+    img_link = extracted_data.get('image')
+    if img_link:
+        parsed_img_link = urllib.parse.urlparse(img_link)
+        # Append domain where relative img_link url is given
+        if not parsed_img_link.netloc:
+            parsed_url = urllib.parse.urlparse(link)
+            domain = '{url.scheme}://{url.netloc}/'.format(url=parsed_url)
+            img_link = urllib.parse.urljoin(domain, img_link)
+        anchor = markdown.util.etree.SubElement(container, "a")
+        anchor.set("href", link)
+        anchor.set("target", "_blank")
+        anchor.set("data-id", vm_id)
+        anchor.set("title", link)
+        img = markdown.util.etree.SubElement(anchor, "img")
+        img.set("src", img_link)
 
 @cache_with_key(lambda tweet_id: tweet_id, cache_name="database", with_statsd_key="tweet_data")
 def fetch_tweet_data(tweet_id: Text) -> Optional[Dict[Text, Any]]:
@@ -327,13 +345,11 @@ def fetch_open_graph_image(url: Text) -> Optional[Dict[str, Any]]:
     # a closing tag if it has not been closed yet.
     last_closed = True
     head = []
-
     # TODO: What if response content is huge? Should we get headers first?
     try:
         content = requests.get(url, timeout=1).text
     except Exception:
         return None
-
     # Extract the head and meta tags
     # All meta tags are self closing, have no children or are closed
     # automatically.
@@ -529,6 +545,27 @@ def youtube_image(self, url: Text) -> Optional[Text]:
             return "https://i.ytimg.com/vi/%s/default.jpg" % (yt_id,)
         return None
 
+    def vimeo_id(self, url: Text) -> Optional[Text]:
+        if not image_preview_enabled_for_realm():
+            return None
+        #(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)
+        # If it matches, match.group('id') is the video id.
+
+        vimeo_re = r'^((http|https)?:\/\/(www\.)?vimeo.com\/' + \
+                   r'(?:channels\/(?:\w+\/)?|groups\/' + \
+                   r'([^\/]*)\/videos\/|)(\d+)(?:|\/\?))$'
+        match = re.match(vimeo_re, url)
+        if match is None:
+            return None
+        return match.group(5)
+
+    def vimeo_image(self, url: Text) -> Optional[Text]:
+        vm_id = self.vimeo_id(url)
+
+        if vm_id is not None:
+            return "http://i.vimeocdn.com/video/%s.jpg" % (vm_id,)
+        return None
+
     def twitter_text(self, text: Text,
                      urls: List[Dict[Text, Text]],
                      user_mentions: List[Dict[Text, Any]],
@@ -841,8 +878,14 @@ def run(self, root: Element) -> None:
             except NotFoundInCache:
                 current_message.links_for_preview.add(url)
                 continue
+            vimeo = self.vimeo_image(url)
             if extracted_data:
-                add_embed(root, url, extracted_data)
+                if vimeo is not None:
+                    vm_id = self.vimeo_id(url)
+                    add_vimeo_preview(root, url, extracted_data, vm_id)
+                    continue
+                else:
+                    add_embed(root, url, extracted_data)
 
 
 class Avatar(markdown.inlinepatterns.Pattern):


Test patch:
diff --git a/zerver/tests/test_bugdown.py b/zerver/tests/test_bugdown.py
--- a/zerver/tests/test_bugdown.py
+++ b/zerver/tests/test_bugdown.py
@@ -290,6 +290,17 @@ def test_inline_youtube(self) -> None:
 
         self.assertEqual(converted, '<p><a href="http://www.youtube.com/watch?v=hx1mjT73xYE" target="_blank" title="http://www.youtube.com/watch?v=hx1mjT73xYE">http://www.youtube.com/watch?v=hx1mjT73xYE</a></p>\n<div class="youtube-video message_inline_image"><a data-id="hx1mjT73xYE" href="http://www.youtube.com/watch?v=hx1mjT73xYE" target="_blank" title="http://www.youtube.com/watch?v=hx1mjT73xYE"><img src="https://i.ytimg.com/vi/hx1mjT73xYE/default.jpg"></a></div>')
 
+    def test_inline_vimeo(self) -> None:
+        msg = 'Check out the debate: https://vimeo.com/246979354'
+        converted = bugdown_convert(msg)
+
+        self.assertEqual(converted, '<p>Check out the debate: <a href="https://vimeo.com/246979354" target="_blank" title="https://vimeo.com/246979354">https://vimeo.com/246979354</a></p>')
+
+        msg = 'https://vimeo.com/246979354'
+        converted = bugdown_convert(msg)
+
+        self.assertEqual(converted, '<p><a href="https://vimeo.com/246979354" target="_blank" title="https://vimeo.com/246979354">https://vimeo.com/246979354</a></p>')
+
     @override_settings(INLINE_IMAGE_PREVIEW=True)
     def test_inline_image_preview(self) -> None:
         with_preview = '<p>Test: <a href="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg" target="_blank" title="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg">http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg</a></p>\n<div class="message_inline_image"><a href="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg" target="_blank" title="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg"><img src="https://external-content.zulipcdn.net/389b5d7148a0cbc7475ed564e1b03ceb476bdacb/687474703a2f2f63646e2e77616c6c70617065727361666172692e636f6d2f31332f362f313665566a782e6a706567"></a></div>'