SFSafariViewController内のShareボタンからShare Extensionを起動するとページタイトルが取得できない。
hipchatや自アプリからSFSafariViewControllerを起動しShareボタンをタップ、公式Twitterアプリ等でShareしようとするとURLだけがフィルされたシェアダイアログが出現する。
SafariアイコンからSafariで開き、SafariからShareを行うとページタイトルとURLがフィルされたシェアダイアログが出現する。
SafariのSheraの場合、contentTextに予めページタイトルがフィルされ、public.url によってURLを取得できる。
SFSafariViewControllerではcontentTextは空であり、public.data / public.item / public.url からURLを取得できるがページタイトルは取得できなかった。
Shared NSUserDefaults によってページタイトルを引き渡す。
class SafariDelegate: NSObject, SFSafariViewControllerDelegate {
// SFSafariViewController don't set page title to SLComposeServiceViewController's textView.text
func safariViewController(controller: SFSafariViewController, activityItemsForURL URL: NSURL, title: String?) -> [UIActivity] {
if let ud = NSUserDefaults.init(suiteName: "group.pw.aska.justaway") {
ud.setURL(URL, forKey: "shareURL")
ud.setObject(title ?? "", forKey: "shareTitle")
ud.synchronize()
}
return []
}
}
Shareタップ時に発火する activityItemsForURL によってページタイトルをセットし、Extension側でこれを受信する。
これはSFSafariViewControllerを開くアプリとExtensionが同じ作成元というケースでだけ有効なので、前述のhipchat等からもShareを受け付けたい場合は利用できない。
Extension側でページをスクレイピングしてタイトルを取得する
let completion = { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
guard let data = data, title = self.parseTitle(data) else {
return
}
NSOperationQueue.mainQueue().addOperationWithBlock {
self.textView.text = title + " " + self.textView.text
self.textView.selectedRange = NSRange.init(location: 0, length: 0)
}
}
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
session.dataTaskWithURL(pageURL, completionHandler: completion).resume()
httpサイトのページタイトルをスクレイピングするにはiOS9から導入されたATSを無効化する必要がある、つらい。
ExtensionPreprocessingJS を使う
SFSafariViewControllerからのshareでは使いない!残念!
FacebookのGraph APIを使う
let urlComponents = NSURLComponents(string: "https://graph.facebook.com/")!
urlComponents.queryItems = [
NSURLQueryItem.init(name: "scrape", value: "true"),
NSURLQueryItem.init(name: "id", value: pageURL.absoluteString),
]
guard let url = urlComponents.URL else {
return
}
let req = NSMutableURLRequest.init(URL: url)
req.HTTPMethod = "POST"
let ogpCompletion = { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
guard let data = data else {
return
}
do {
let json: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: [])
if let dic = json as? NSDictionary, title = dic["title"] as? String {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.textView.text = title + " " + self.textView.text
self.textView.selectedRange = NSRange.init(location: 0, length: 0)
}
}
} catch _ as NSError {
}
}
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
session.dataTaskWithRequest(req, completionHandler: ogpCompletion).resume()
トークン不要のAPIがあるが本来OGPキャッシュをクリアするためのものなので却下
Amazon Lambda / API Gateway を使う
'use strict';
console.log('Loading function');
const http = require('http');
const https = require('https');
const regexs = [
new RegExp('<meta property=["\']og:title["\'] content=["\']([^>]+)["\']', 'i'),
new RegExp('<meta content=["\']([^>]+)["\'] property=["\']og:title["\']', 'i'),
new RegExp('<title>([^<]*)</title>', 'i'),
];
exports.handler = (event, context, callback) => {
console.log('Received event:', JSON.stringify(event, null, 2));
const url = event.url;
const module = url.indexOf('https://') !== -1 ? https : http;
module.get(url, function(res) {
var body = '';
res.setEncoding('utf8');
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
for (var i = 0; i < regexs.length; i ++) {
var regex = regexs[i];
var match = regex.exec(body);
if (match && match[1]) {
callback(null, {"url": url, "title": match[1]});
return
}
}
callback(null, {"url": url, "title": ""});
});
}).on('error', function(e) {
callback(null, {"url": url, "title": "", "error": e.message});
});
};