├── example ├── .meteor │ ├── .gitignore │ ├── release │ ├── platforms │ ├── .id │ ├── .finished-upgraders │ ├── packages │ └── versions ├── packages │ └── acemtp:meta-extractor ├── example.css ├── example.html └── example.js ├── .npm └── package │ ├── .gitignore │ ├── npm-shrinkwrap.json │ └── README ├── .versions ├── package.js ├── LICENSE.txt ├── README.md └── meta-extractor.js /example/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.npm/package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /example/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.2.1 2 | -------------------------------------------------------------------------------- /example/packages/acemtp:meta-extractor: -------------------------------------------------------------------------------- 1 | ../.. -------------------------------------------------------------------------------- /example/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /example/example.css: -------------------------------------------------------------------------------- 1 | /* CSS declarations go here */ 2 | -------------------------------------------------------------------------------- /.npm/package/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "he": { 4 | "version": "0.5.0" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | acemtp:meta-extractor@1.0.4 2 | base64@1.0.4 3 | check@1.1.0 4 | ejson@1.0.7 5 | http@1.1.1 6 | meteor@1.1.10 7 | underscore@1.0.4 8 | url@1.0.5 9 | -------------------------------------------------------------------------------- /example/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | mrs3cw1lsrdzr1d32he5 8 | -------------------------------------------------------------------------------- /example/example.html: -------------------------------------------------------------------------------- 1 | 2 | Meta Extractor Example 3 | 4 | 5 | 6 | {{> extract}} 7 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /example/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | -------------------------------------------------------------------------------- /.npm/package/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'acemtp:meta-extractor', 3 | version: '1.0.4', 4 | summary: 'Extract meta tags (Opengraph/Facebook, Twitter, meta) from an url or a string on client & server.', 5 | git: 'https://github.com/efounders/meteor-meta-extractor', 6 | documentation: 'README.md' 7 | }); 8 | 9 | Package.onUse(function(api) { 10 | api.versionsFrom('0.9.0'); 11 | Npm.depends({ 12 | "he": "0.5.0" 13 | }); 14 | api.use(['http', 'check'], ['server']); 15 | api.addFiles('meta-extractor.js'); 16 | api.export('extractMeta'); 17 | }); 18 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | Session.set('metas', ''); 3 | 4 | Template.extract.helpers({ 5 | metas() { return Session.get('metas'); }, 6 | }); 7 | 8 | Template.extract.events({ 9 | 'keyup #url'(e) { 10 | if (e.keyCode !== 13) return; 11 | const url = $('#url').val(); 12 | console.log('extract', url); 13 | Session.set('metas', 'Extracting ' + url + '...'); 14 | extractMeta(url, (err, res) => { 15 | if (err) { 16 | console.error('err while extracting metas', err); 17 | Session.set('metas', 'Error: ' + err); 18 | } else { 19 | Session.set('metas', JSON.stringify(res, null, ' ')); 20 | } 21 | }); 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /example/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base # Packages every Meteor app needs to have 8 | mobile-experience # Packages for a great mobile UX 9 | mongo # The database Meteor supports right now 10 | blaze-html-templates # Compile .html files into Meteor Blaze views 11 | session # Client-side reactive dictionary for your app 12 | jquery # Helpful client-side library 13 | tracker # Meteor's client-side reactive programming library 14 | 15 | standard-minifiers # JS/CSS minifiers run for production mode 16 | es5-shim # ECMAScript 5 compatibility for older browsers. 17 | ecmascript # Enable ECMAScript2015+ syntax in app code 18 | 19 | autopublish # Publish all data to the clients (for prototyping) 20 | insecure # Allow all DB writes from clients (for prototyping) 21 | acemtp:meta-extractor 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Chris Mather 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | meteor-meta-extractor 2 | ===================== 3 | 4 | Extract meta tags (opengraph / facebook, twitter, meta) from a html string or an url. Work on client and server. 5 | 6 | Install 7 | ------- 8 | ``` 9 | meteor add acemtp:meta-extractor 10 | ``` 11 | 12 | Usage 13 | ----- 14 | 15 | This package is almost isomorphic. It has only one function `extractMeta()` that returns an object containing, if found a `title`, `description`, `image`, `url`. 16 | 17 | **On the client**, the function does a `Meteor.call()` because only the server can get the content of the url. It's async because there's no fiber on the client. So you have to pass a callback to get the answer: 18 | 19 | extractMeta('http://efounders.co', function (err, res) { console.log(res); }); 20 | 21 | **On the server**, the function is sync and returns the meta object: 22 | 23 | console.log(extractMeta('http://efounders.co')); 24 | 25 | Both example will display something like: 26 | 27 | { 28 | description: 'eFounders is a startup Studio. Together with entrepreneurs, we turn unique ideas into successful companies. We act as the perfect co-founder to build strong and independent startups. ', 29 | title: 'eFounders • Startup Studio', 30 | image: 'http://efounders.co/public/images/630_homepage.jpg', 31 | url: 'http://efounders.co/' 32 | } 33 | 34 | Example 35 | ------- 36 | 37 | Go to `example` directory and run meteor inside it. 38 | -------------------------------------------------------------------------------- /example/.meteor/versions: -------------------------------------------------------------------------------- 1 | acemtp:meta-extractor@1.0.4 2 | autopublish@1.0.4 3 | autoupdate@1.2.4 4 | babel-compiler@5.8.24_1 5 | babel-runtime@0.1.4 6 | base64@1.0.4 7 | binary-heap@1.0.4 8 | blaze@2.1.3 9 | blaze-html-templates@1.0.1 10 | blaze-tools@1.0.4 11 | boilerplate-generator@1.0.4 12 | caching-compiler@1.0.0 13 | caching-html-compiler@1.0.2 14 | callback-hook@1.0.4 15 | check@1.1.0 16 | ddp@1.2.2 17 | ddp-client@1.2.1 18 | ddp-common@1.2.2 19 | ddp-server@1.2.2 20 | deps@1.0.9 21 | diff-sequence@1.0.1 22 | ecmascript@0.1.6 23 | ecmascript-runtime@0.2.6 24 | ejson@1.0.7 25 | es5-shim@4.1.14 26 | fastclick@1.0.7 27 | geojson-utils@1.0.4 28 | hot-code-push@1.0.0 29 | html-tools@1.0.5 30 | htmljs@1.0.5 31 | http@1.1.1 32 | id-map@1.0.4 33 | insecure@1.0.4 34 | jquery@1.11.4 35 | launch-screen@1.0.4 36 | livedata@1.0.15 37 | logging@1.0.8 38 | meteor@1.1.10 39 | meteor-base@1.0.1 40 | minifiers@1.1.7 41 | minimongo@1.0.10 42 | mobile-experience@1.0.1 43 | mobile-status-bar@1.0.6 44 | mongo@1.1.3 45 | mongo-id@1.0.1 46 | npm-mongo@1.4.39_1 47 | observe-sequence@1.0.7 48 | ordered-dict@1.0.4 49 | promise@0.5.1 50 | random@1.0.5 51 | reactive-dict@1.1.3 52 | reactive-var@1.0.6 53 | reload@1.1.4 54 | retry@1.0.4 55 | routepolicy@1.0.6 56 | session@1.1.1 57 | spacebars@1.0.7 58 | spacebars-compiler@1.0.7 59 | standard-minifiers@1.0.2 60 | templating@1.1.5 61 | templating-tools@1.0.0 62 | tracker@1.0.9 63 | ui@1.0.8 64 | underscore@1.0.4 65 | url@1.0.5 66 | webapp@1.2.3 67 | webapp-hashing@1.0.5 68 | -------------------------------------------------------------------------------- /meta-extractor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | if (Meteor.isClient) { 4 | 5 | // Client side example (async): 6 | // extractMeta('http://efounders.co', function (err, res) { console.log(err, res); }); 7 | 8 | extractMeta = function (params, callback) { 9 | Meteor.call('extractMeta', params, callback); 10 | }; 11 | 12 | } 13 | 14 | if (Meteor.isServer) { 15 | var he = Npm.require('he'); 16 | 17 | extractMeta = function (params) { 18 | var html; 19 | var match; 20 | var META = {}; 21 | 22 | if (params.substr(0, 4) === 'http') { 23 | try { 24 | var result = HTTP.call('GET', params); 25 | if (result.statusCode !== 200) { 26 | return META; 27 | } 28 | html = result.content; 29 | } catch (e) { 30 | console.log('catch error', e); 31 | return META; 32 | } 33 | } else { 34 | html = params; 35 | } 36 | 37 | 38 | // search for a 39 | var title_regex = /<title>(.*)<\/title>/gmi; 40 | 41 | while ((match = title_regex.exec(html)) !== null) { 42 | if (match.index === title_regex.lastIndex) { 43 | title_regex.lastIndex++; 44 | } 45 | META.title = match[1]; 46 | } 47 | 48 | // search and parse all <meta> 49 | var meta_tag_regex = /<meta.*?(?:name|property|http-equiv)=['"]([^'"]*?)['"][\w\W]*?content=['"]([^'"]*?)['"].*?>/gmi; 50 | 51 | var tags = { 52 | title: ['title', 'og:title', 'twitter:title'], 53 | description: ['description', 'og:description', 'twitter:description'], 54 | image: ['image', 'og:image', 'twitter:image'], 55 | url: ['url', 'og:url', 'twitter:url'] 56 | }; 57 | 58 | while ((match = meta_tag_regex.exec(html)) !== null) { 59 | if (match.index === meta_tag_regex.lastIndex) { 60 | meta_tag_regex.lastIndex++; 61 | } 62 | 63 | for (var item in tags) { 64 | tags[item].forEach(function(prop) { 65 | 66 | if (match[1] === prop) { 67 | 68 | var property = tags[item][0]; 69 | var content = match[2]; 70 | 71 | // Only push content to our 'META' object if 'META' doesn't already 72 | // contain content for that property. 73 | if (!META[property]) { 74 | META[property] = he.decode(content); 75 | } 76 | 77 | } 78 | 79 | }); 80 | } 81 | } 82 | 83 | return META; 84 | }; 85 | 86 | Meteor.methods({ 87 | extractMeta: function (params) { 88 | check(params, String); 89 | return extractMeta(params); 90 | } 91 | }); 92 | 93 | } 94 | --------------------------------------------------------------------------------