Skip to content

Instantly share code, notes, and snippets.

@mattpetters
Last active January 30, 2017 21:42
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattpetters/ccf87678ccce0c354398 to your computer and use it in GitHub Desktop.
Save mattpetters/ccf87678ccce0c354398 to your computer and use it in GitHub Desktop.
Corrected Twitter Package - CS193p Lecture 10 - Updated for XCode 6.3
//
// MediaItem.swift
// Twitter
//
// Created by CS193p Instructor.
// Copyright (c) 2015 Stanford University. All rights reserved.
//
import Foundation
// holds the network url and aspectRatio of an image attached to a Tweet
// created automatically when a Tweet object is created
public struct MediaItem
{
public let url: NSURL!
public let aspectRatio: Double
public var description: String { return (url.absoluteString ?? "no url") + " (aspect ratio = \(aspectRatio))" }
// MARK: - Private Implementation
init?(data: NSDictionary?) {
var valid = false
if let urlString = data?.valueForKeyPath(TwitterKey.MediaURL) as? NSString {
if let url = NSURL(string: urlString as String) {
self.url = url
let h = data?.valueForKeyPath(TwitterKey.Height) as? NSNumber
let w = data?.valueForKeyPath(TwitterKey.Width) as? NSNumber
if h != nil && w != nil && h?.doubleValue != 0 {
aspectRatio = w!.doubleValue / h!.doubleValue
return
}
}
}
return nil
}
struct TwitterKey {
static let MediaURL = "media_url_https"
static let Width = "sizes.small.w"
static let Height = "sizes.small.h"
}
}
//
// Tweet.swift
// Twitter
//
// Created by CS193p Instructor.
// Copyright (c) 2015 Stanford University. All rights reserved.
//
import Foundation
// a simple container class which just holds the data in a Tweet
// IndexedKeywords are substrings of the Tweet's text
// for example, a hashtag or other user or url that is mentioned in the Tweet
// note carefully the comments on the two range properties in an IndexedKeyword
// Tweet instances re created by fetching from Twitter using a TwitterRequest
public class Tweet : Printable
{
public var text: String
public var user: User
public let created: NSDate
public let id: String?
public var media = [MediaItem]()
public var hashtags = [IndexedKeyword]()
public var urls = [IndexedKeyword]()
public var userMentions = [IndexedKeyword]()
public struct IndexedKeyword: Printable
{
public var keyword: String // will include # or @ or http:// prefix
public var range: Range<String.Index> // index into the Tweet's text property only
public var nsrange: NSRange = NSRange() // index into an NS[Attributed]String made from the Tweet's text
public init?(data: NSDictionary?, inText: String, prefix: String?) {
let indices = data?.valueForKeyPath(TwitterKey.Entities.Indices) as? NSArray
if let startIndex = (indices?.firstObject as? NSNumber)?.integerValue {
if let endIndex = (indices?.lastObject as? NSNumber)?.integerValue {
let length = count(inText)
if length > 0 {
let start = max(min(startIndex, length-1), 0)
let end = max(min(endIndex, length), 0)
if end > start {
range = advance(inText.startIndex, start)...advance(inText.startIndex, end-1)
keyword = inText.substringWithRange(range)
if prefix != nil && !keyword.hasPrefix(prefix!) && start > 0 {
range = advance(inText.startIndex, start-1)...advance(inText.startIndex, end-2)
keyword = inText.substringWithRange(range)
}
if prefix == nil || keyword.hasPrefix(prefix!) {
nsrange = inText.rangeOfString(keyword, nearRange: NSMakeRange(startIndex, endIndex-startIndex))
if nsrange.location != NSNotFound {
return
}
}
}
}
}
}
return nil
}
public var description: String { get { return "\(keyword) (\(nsrange.location), \(nsrange.location+nsrange.length-1))" } }
}
public var description: String { return "\(user) - \(created)\n\(text)\nhashtags: \(hashtags)\nurls: \(urls)\nuser_mentions: \(userMentions)" + (id == nil ? "" : "\nid: \(id!)") }
// MARK: - Private Implementation
init?(data: NSDictionary?) {
if let user = User(data: data?.valueForKeyPath(TwitterKey.User) as? NSDictionary) {
self.user = user
if let text = data?.valueForKeyPath(TwitterKey.Text) as? String {
self.text = text
if let created = (data?.valueForKeyPath(TwitterKey.Created) as? String)?.asTwitterDate {
self.created = created
id = data?.valueForKeyPath(TwitterKey.ID) as? String
if let mediaEntities = data?.valueForKeyPath(TwitterKey.Media) as? NSArray {
for mediaData in mediaEntities {
if let mediaItem = MediaItem(data: mediaData as? NSDictionary) {
media.append(mediaItem)
}
}
}
let hashtagMentionsArray = data?.valueForKeyPath(TwitterKey.Entities.Hashtags) as? NSArray
hashtags = getIndexedKeywords(hashtagMentionsArray, inText: text, prefix: "#")
let urlMentionsArray = data?.valueForKeyPath(TwitterKey.Entities.URLs) as? NSArray
urls = getIndexedKeywords(urlMentionsArray, inText: text, prefix: "h")
let userMentionsArray = data?.valueForKeyPath(TwitterKey.Entities.UserMentions) as? NSArray
userMentions = getIndexedKeywords(userMentionsArray, inText: text, prefix: "@")
return
}
}
}
// we've failed
// but compiler won't let us out of here with non-optional values unset
// so set them to anything just to able to return nil
// we could make these implicitly-unwrapped optionals, but they should never be nil, ever
self.text = ""
self.user = User()
self.created = NSDate()
self.id = ""
return nil
}
private func getIndexedKeywords(dictionary: NSArray?, inText: String, prefix: String? = nil) -> [IndexedKeyword] {
var results = [IndexedKeyword]()
if let indexedKeywords = dictionary {
for indexedKeywordData in indexedKeywords {
if let indexedKeyword = IndexedKeyword(data: indexedKeywordData as? NSDictionary, inText: inText, prefix: prefix) {
results.append(indexedKeyword)
}
}
}
return results
}
struct TwitterKey {
static let User = "user"
static let Text = "text"
static let Created = "created_at"
static let ID = "id_str"
static let Media = "entities.media"
struct Entities {
static let Hashtags = "entities.hashtags"
static let URLs = "entities.urls"
static let UserMentions = "entities.user_mentions"
static let Indices = "indices"
}
}
}
private extension NSString {
func rangeOfString(substring: NSString, nearRange: NSRange) -> NSRange {
var start = max(min(nearRange.location, length-1), 0)
var end = max(min(nearRange.location + nearRange.length, length), 0)
var done = false
while !done {
let range = rangeOfString(substring as String, options: NSStringCompareOptions.allZeros, range: NSMakeRange(start, end-start))
if range.location != NSNotFound {
return range
}
done = true
if start > 0 { start-- ; done = false }
if end < length { end++ ; done = false }
}
return NSMakeRange(NSNotFound, 0)
}
}
private extension String {
var asTwitterDate: NSDate? {
get {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
return dateFormatter.dateFromString(self)
}
}
}
//
// TwitterRequest.swift
// Twitter
//
// Created by CS193p Instructor.
// Copyright (c) 2015 Stanford University. All rights reserved.
//
import Foundation
import Accounts
import Social
import CoreLocation
// Simple Twitter query class
// Create an instance of it using one of the initializers
// Set the requestType and parameters (if not using a convenience init that sets those)
// Call fetch (or fetchTweets if fetching Tweets)
// The handler passed in will be called when the information comes back from Twitter
// Once a successful fetch has happened,
// a follow-on TwitterRequest to get more Tweets (newer or older) can be created
// using the requestFor{Newer,Older} methods
private var twitterAccount: ACAccount?
public class TwitterRequest
{
public let requestType: String
public var parameters = Dictionary<String, String>()
// designated initializer
public init(_ requestType: String, _ parameters: Dictionary<String, String> = [:]) {
self.requestType = requestType
self.parameters = parameters
}
// convenience initializer for creating a TwitterRequest that is a search for Tweets
public convenience init(search: String, count: Int = 0, _ resultType: SearchResultType = .Mixed, _ region: CLCircularRegion? = nil) {
var parameters = [TwitterKey.Query : search]
if count > 0 {
parameters[TwitterKey.Count] = "\(count)"
}
switch resultType {
case .Recent: parameters[TwitterKey.ResultType] = TwitterKey.ResultTypeRecent
case .Popular: parameters[TwitterKey.ResultType] = TwitterKey.ResultTypePopular
default: break
}
if let geocode = region {
parameters[TwitterKey.Geocode] = "\(geocode.center.latitude),\(geocode.center.longitude),\(geocode.radius/1000.0)km"
}
self.init(TwitterKey.SearchForTweets, parameters)
}
public enum SearchResultType {
case Mixed
case Recent
case Popular
}
// convenience "fetch" for when self is a request that returns Tweet(s)
// handler is not necessarily invoked on the main queue
public func fetchTweets(handler: ([Tweet]) -> Void) {
fetch { results in
var tweets = [Tweet]()
var tweetArray: NSArray?
if let dictionary = results as? NSDictionary {
if let tweets = dictionary[TwitterKey.Tweets] as? NSArray {
tweetArray = tweets
} else if let tweet = Tweet(data: dictionary) {
tweets = [tweet]
}
} else if let array = results as? NSArray {
tweetArray = array
}
if tweetArray != nil {
for tweetData in tweetArray! {
if let tweet = Tweet(data: tweetData as? NSDictionary) {
tweets.append(tweet)
}
}
}
handler(tweets)
}
}
public typealias PropertyList = AnyObject
// send an arbitrary request off to Twitter
// calls the handler (not necessarily on the main queue)
// with the JSON results converted to a Property List
public func fetch(handler: (results: PropertyList?) -> Void) {
performTwitterRequest(SLRequestMethod.GET, handler: handler)
}
// generates a request for older Tweets than were returned by self
// only makes sense if self has done a fetch already
// only makes sense for requests for Tweets
public var requestForOlder: TwitterRequest? {
return min_id != nil ? modifiedRequest(parametersToChange: [TwitterKey.MaxID : min_id!]) : nil
}
// generates a request for newer Tweets than were returned by self
// only makes sense if self has done a fetch already
// only makes sense for requests for Tweets
public var requestForNewer: TwitterRequest? {
return (max_id != nil) ? modifiedRequest(parametersToChange: [TwitterKey.SinceID : max_id!], clearCount: true) : nil
}
// MARK: - Private Implementation
// creates an appropriate SLRequest using the specified SLRequestMethod
// then calls the other version of this method that takes an SLRequest
// handler is not necessarily called on the main queue
func performTwitterRequest(method: SLRequestMethod, handler: (PropertyList?) -> Void) {
var jsonExtension = (self.requestType.rangeOfString(JSONExtension) == nil) ? JSONExtension : ""
let request = SLRequest(
forServiceType: SLServiceTypeTwitter,
requestMethod: method,
URL: NSURL(string: "\(TwitterURLPrefix)\(self.requestType)\(jsonExtension)"),
parameters: self.parameters
)
performTwitterRequest(request, handler: handler)
}
// sends the request to Twitter
// unpackages the JSON response into a Property List
// and calls handler (not necessarily on the main queue)
func performTwitterRequest(request: SLRequest, handler: (PropertyList?) -> Void) {
if let account = twitterAccount {
request.account = account
request.performRequestWithHandler { (jsonResponse, httpResponse, _) in
var propertyListResponse: PropertyList?
if jsonResponse != nil {
propertyListResponse = NSJSONSerialization.JSONObjectWithData(
jsonResponse,
options: NSJSONReadingOptions.MutableLeaves,
error: nil
)
if propertyListResponse == nil {
let error = "Couldn't parse JSON response."
self.log(error)
propertyListResponse = error
}
} else {
let error = "No response from Twitter."
self.log(error)
propertyListResponse = error
}
self.synchronize {
self.captureFollowonRequestInfo(propertyListResponse)
}
handler(propertyListResponse)
}
} else {
let accountStore = ACAccountStore()
let twitterAccountType = accountStore.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierTwitter)
accountStore.requestAccessToAccountsWithType(twitterAccountType, options: nil) { (granted, _) in
if granted {
if let account = accountStore.accountsWithAccountType(twitterAccountType)?.last as? ACAccount {
twitterAccount = account
self.performTwitterRequest(request, handler: handler)
} else {
let error = "Couldn't discover Twitter account type."
self.log(error)
handler(error)
}
} else {
let error = "Access to Twitter was not granted."
self.log(error)
handler(error)
}
}
}
}
private var min_id: String? = nil
private var max_id: String? = nil
// modifies parameters in an existing request to create a new one
private func modifiedRequest(#parametersToChange: Dictionary<String,String>, clearCount: Bool = false) -> TwitterRequest {
var newParameters = parameters
for (key, value) in parametersToChange {
newParameters[key] = value
}
if clearCount { newParameters[TwitterKey.Count] = nil }
return TwitterRequest(requestType, newParameters)
}
// captures the min_id and max_id information
// to support requestForNewer and requestForOlder
private func captureFollowonRequestInfo(propertyListResponse: PropertyList?) {
if let responseDictionary = propertyListResponse as? NSDictionary {
self.max_id = responseDictionary.valueForKeyPath(TwitterKey.SearchMetadata.MaxID) as? String
if let next_results = responseDictionary.valueForKeyPath(TwitterKey.SearchMetadata.NextResults) as? String {
for queryTerm in next_results.componentsSeparatedByString(TwitterKey.SearchMetadata.Separator) {
if queryTerm.hasPrefix("?\(TwitterKey.MaxID)=") {
let next_id = queryTerm.componentsSeparatedByString("=")
if next_id.count == 2 {
self.min_id = next_id[1]
}
}
}
}
}
}
// debug println with identifying prefix
private func log(whatToLog: AnyObject) {
debugPrintln("TwitterRequest: \(whatToLog)")
}
// synchronizes access to self across multiple threads
private func synchronize(closure: () -> Void) {
objc_sync_enter(self)
closure()
objc_sync_exit(self)
}
// constants
let JSONExtension = ".json"
let TwitterURLPrefix = "https://api.twitter.com/1.1/"
// keys in Twitter responses/queries
struct TwitterKey {
static let Count = "count"
static let Query = "q"
static let Tweets = "statuses"
static let ResultType = "result_type"
static let ResultTypeRecent = "recent"
static let ResultTypePopular = "popular"
static let Geocode = "geocode"
static let SearchForTweets = "search/tweets"
static let MaxID = "max_id"
static let SinceID = "since_id"
struct SearchMetadata {
static let MaxID = "search_metadata.max_id_str"
static let NextResults = "search_metadata.next_results"
static let Separator = "&"
}
}
}
//
// User.swift
// Twitter
//
// Created by CS193p Instructor.
// Copyright (c) 2015 Stanford University. All rights reserved.
//
import Foundation
// container to hold data about a Twitter user
public struct User: Printable
{
public let screenName: String
public let name: String
public var profileImageURL: NSURL?
public var verified: Bool = false
public var id: String!
public var description: String { var v = verified ? " ✅" : ""; return "@\(screenName) (\(name))\(v)" }
// MARK: - Private Implementation
init?(data: NSDictionary?) {
let name = data?.valueForKeyPath(TwitterKey.Name) as? String
let screenName = data?.valueForKeyPath(TwitterKey.ScreenName) as? String
if name != nil && screenName != nil {
self.name = name!
self.screenName = screenName!
self.id = data?.valueForKeyPath(TwitterKey.ID) as? String
if let verified = data?.valueForKeyPath(TwitterKey.Verified)?.boolValue {
self.verified = verified
}
if let urlString = data?.valueForKeyPath(TwitterKey.ProfileImageURL) as? String {
self.profileImageURL = NSURL(string: urlString)
}
} else {
return nil
}
}
var asPropertyList: AnyObject {
var dictionary = Dictionary<String,String>()
dictionary[TwitterKey.Name] = self.name
dictionary[TwitterKey.ScreenName] = self.screenName
dictionary[TwitterKey.ID] = self.id
dictionary[TwitterKey.Verified] = verified ? "YES" : "NO"
dictionary[TwitterKey.ProfileImageURL] = profileImageURL?.absoluteString
return dictionary
}
init() {
screenName = "Unknown"
name = "Unknown"
}
struct TwitterKey {
static let Name = "name"
static let ScreenName = "screen_name"
static let ID = "id_str"
static let Verified = "verified"
static let ProfileImageURL = "profile_image_url"
}
}
@SketchySwift
Copy link

Hey! Could you please update to Xcode 7.2 swift 2.1?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment