| 1 | <!--- |
|---|
| 2 | LICENSE INFORMATION: |
|---|
| 3 | |
|---|
| 4 | Copyright 2008, Adam Tuttle |
|---|
| 5 | |
|---|
| 6 | Licensed under the Apache License, Version 2.0 (the "License"); you may not |
|---|
| 7 | use this file except in compliance with the License. |
|---|
| 8 | |
|---|
| 9 | You may obtain a copy of the License at |
|---|
| 10 | |
|---|
| 11 | http://www.apache.org/licenses/LICENSE-2.0 |
|---|
| 12 | |
|---|
| 13 | Unless required by applicable law or agreed to in writing, software distributed |
|---|
| 14 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR |
|---|
| 15 | CONDITIONS OF ANY KIND, either express or implied. See the License for the |
|---|
| 16 | specific language governing permissions and limitations under the License. |
|---|
| 17 | |
|---|
| 18 | VERSION INFORMATION: |
|---|
| 19 | |
|---|
| 20 | This file is part of SweetTweetsCFC (1.4). |
|---|
| 21 | http://sweettweetscfc.riaforge.org/ |
|---|
| 22 | ---> |
|---|
| 23 | <!--- |
|---|
| 24 | Author: Adam Tuttle |
|---|
| 25 | Website: http://fusiongrokker.com |
|---|
| 26 | Instructions: See example.cfm (and other examples) for usage |
|---|
| 27 | ---> |
|---|
| 28 | <cfcomponent output="false"> |
|---|
| 29 | |
|---|
| 30 | <cfset variables.urlService = ""/> |
|---|
| 31 | <cfset variables.jsonService = ""/> |
|---|
| 32 | <cfset variables.cacheLocation = "application"/> |
|---|
| 33 | |
|---|
| 34 | <cffunction name="init" output="false"> |
|---|
| 35 | <cfargument name="useLocalCache" type="boolean" default="true"/> |
|---|
| 36 | <!--- save cache location ---> |
|---|
| 37 | <cfif arguments.useLocalCache> |
|---|
| 38 | <cfset variables.cacheLocation = "variables"/> |
|---|
| 39 | <cfelse> |
|---|
| 40 | <cfset variables.cacheLocation = "application" /> |
|---|
| 41 | </cfif> |
|---|
| 42 | <!--- Using shrinkURL from Andy Matthews: http://shrinkurl.riaforge.org/ ---> |
|---|
| 43 | <cfset variables.urlService = createObject("component","shrinkURL").init()/> |
|---|
| 44 | <!--- Using JSONUtil from Nathan Mische: http://jsonutil.riaforge.org/ ---> |
|---|
| 45 | <cfset variables.jsonService = createObject("component","JSONUtil").init()/> |
|---|
| 46 | <cfreturn this/> |
|---|
| 47 | </cffunction> |
|---|
| 48 | |
|---|
| 49 | <!--- public functions ---> |
|---|
| 50 | <cffunction name="getTweetbacks" access="public" output="false" returntype="array"> |
|---|
| 51 | <cfargument name="uri" type="string" required="true"/> |
|---|
| 52 | <cfscript> |
|---|
| 53 | var local = structNew(); |
|---|
| 54 | var cacheKey = ''; |
|---|
| 55 | |
|---|
| 56 | //first some business -- if being called remotely (ajax), jsonService and urlService will be blank! :( |
|---|
| 57 | if (isSimpleValue(variables.urlService)){variables.urlService = createObject("component", "shrinkURL").init();} |
|---|
| 58 | if (isSimpleValue(variables.jsonService)){variables.jsonService = createObject("component", "JSONUtil").init();} |
|---|
| 59 | |
|---|
| 60 | //strip any bookmarks from the url |
|---|
| 61 | arguments.uri = listFirst(arguments.uri,'##'); |
|---|
| 62 | |
|---|
| 63 | //setup cache |
|---|
| 64 | cacheKey = hash(arguments.uri); |
|---|
| 65 | setupCache(cacheKey); |
|---|
| 66 | |
|---|
| 67 | //check tweetback cache, updates every 5 minutes at most |
|---|
| 68 | if (not tweetCacheExpired(cacheKey)){ |
|---|
| 69 | local.tweets = getTweetCache(cacheKey); |
|---|
| 70 | }else{ |
|---|
| 71 | local.thisSearch = getTweetSearchUrl(arguments.uri); |
|---|
| 72 | local.shortened = getShortUrls(arguments.uri); |
|---|
| 73 | |
|---|
| 74 | local.tweets = makeTwitterSearchRequest(local.thisSearch).results; |
|---|
| 75 | local.tweets = killImpostors(local.tweets,local.shortened); |
|---|
| 76 | local.tweets = cleanup(local.tweets); |
|---|
| 77 | |
|---|
| 78 | //cache tweets for 5 minutes |
|---|
| 79 | setTweetCache(cacheKey, local.tweets, 5); |
|---|
| 80 | } |
|---|
| 81 | |
|---|
| 82 | return local.tweets; |
|---|
| 83 | </cfscript> |
|---|
| 84 | </cffunction> |
|---|
| 85 | <cffunction name="debug" access="public" output="true" returntype="any"> |
|---|
| 86 | <cfargument name="uri" type="string" required="true"/> |
|---|
| 87 | <cfscript> |
|---|
| 88 | var local = structNew(); |
|---|
| 89 | var cacheKey = ''; |
|---|
| 90 | |
|---|
| 91 | //strip any bookmarks from the url |
|---|
| 92 | arguments.uri = listFirst(arguments.uri,'##'); |
|---|
| 93 | |
|---|
| 94 | //setup cache |
|---|
| 95 | cacheKey = hash(arguments.uri); |
|---|
| 96 | setupCache(cacheKey); |
|---|
| 97 | |
|---|
| 98 | //use services |
|---|
| 99 | local.thisSearch = getTweetSearchUrl(arguments.uri); |
|---|
| 100 | local.shortened = getShortUrls(arguments.uri); |
|---|
| 101 | local.tweets = makeTwitterSearchRequest(local.thisSearch).results; |
|---|
| 102 | local.tweets = killImpostors(local.tweets,local.shortened); |
|---|
| 103 | local.tweets = cleanup(local.tweets); |
|---|
| 104 | |
|---|
| 105 | dump(local,true); |
|---|
| 106 | </cfscript> |
|---|
| 107 | </cffunction> |
|---|
| 108 | <cffunction name="getTweetbacksHTML" access="remote" output="false" returntype="string"> |
|---|
| 109 | <cfargument name="uri" type="string" required="true"/> |
|---|
| 110 | <cfargument name="limit" type="numeric" required="false" default="100" hint="Number of tweets to display, recent gets priority. 0 = unlimited"/> |
|---|
| 111 | <cfscript> |
|---|
| 112 | var local = structNew(); |
|---|
| 113 | local.dsp = structNew(); |
|---|
| 114 | |
|---|
| 115 | local.tweets = getTweetbacks(arguments.uri); |
|---|
| 116 | local.searchUrl = replace(getTweetSearchUrl(arguments.uri), ".json", "");//instead of linking to json, link to search results page |
|---|
| 117 | local.tweetCount = arrayLen(local.tweets); |
|---|
| 118 | local.limit = min(arguments.limit, local.tweetcount); |
|---|
| 119 | if (local.limit eq 0){local.limit=local.tweetcount;} |
|---|
| 120 | |
|---|
| 121 | //define header |
|---|
| 122 | if (local.tweetcount eq 0){ |
|---|
| 123 | local.dsp.header = "<h3>No Tweetbacks</h3>"; |
|---|
| 124 | }else{ |
|---|
| 125 | local.dsp.header = "<h3>#arrayLen(local.tweets)# Tweetbacks</h3>"; |
|---|
| 126 | if (local.tweetcount eq 1){local.dsp.header=replace(local.dsp.header,"Tweetbacks","Tweetback");} |
|---|
| 127 | } |
|---|
| 128 | //define view-all link |
|---|
| 129 | if (local.tweetcount lte local.limit or local.limit eq 0){ |
|---|
| 130 | local.dsp.allLink = ""; |
|---|
| 131 | }else{ |
|---|
| 132 | local.dsp.allLink = "Showing #local.limit# most recent - <a id='viewAllTweetbacks' href='#local.searchUrl#'>View All Tweetbacks</a>"; |
|---|
| 133 | } |
|---|
| 134 | </cfscript> |
|---|
| 135 | <!---streamlined html to be as small as possible since it very well might be returned via ajax---> |
|---|
| 136 | <cfsavecontent variable="local.tweetbackHTML"><cfoutput><div id="tweetbacks">#local.dsp.header##local.dsp.allLink#<ul><cfloop from="1" to="#local.limit#" index="local.t"><li style="clear:left;"><img src="#local.tweets[local.t].profile_image_url#" align="left" vspace="2" hspace="4"/> <a href="http://twitter.com/#local.tweets[local.t].from_user#" style="background:none;"><strong>#local.tweets[local.t].from_user#</strong></a> <span class="tweetback_tweet">#local.tweets[local.t].text#</span> <span class="tweetback_timestamp"><a href="http://twitter.com/#local.tweets[local.t].from_user#/statuses/#local.tweets[local.t].id#">#local.tweets[local.t].created_at#</a></span></li></cfloop></ul></div></cfoutput></cfsavecontent> |
|---|
| 137 | <cfreturn local.tweetbackHTML/> |
|---|
| 138 | </cffunction> |
|---|
| 139 | |
|---|
| 140 | <!--- data functions ---> |
|---|
| 141 | <cffunction name="getShortUrls" access="private" output="false" returnType="struct"> |
|---|
| 142 | <cfargument name="uri" type="string" required="true"/> |
|---|
| 143 | <cfscript> |
|---|
| 144 | var local = StructNew(); |
|---|
| 145 | local.shortened = structNew(); |
|---|
| 146 | //cligs |
|---|
| 147 | local.cligsParams = StructNew(); |
|---|
| 148 | local.cligsParams['appid'] = urlEncodedFormat('http://sweettweetscfc.riaforge.org'); |
|---|
| 149 | local.shortened.cligs = urlService.shrink('cligs',arguments.uri,local.cligsParams); |
|---|
| 150 | if (len(trim(local.shortened.cligs)) eq 0){ |
|---|
| 151 | structDelete(local.shortened, "cligs"); |
|---|
| 152 | } |
|---|
| 153 | //simple services |
|---|
| 154 | local.shortened.isgd = urlService.shrink('isgd',arguments.uri); |
|---|
| 155 | local.shortened.tinyurl = urlService.shrink('tinyurl', arguments.uri); |
|---|
| 156 | local.shortened.hexio = urlService.shrink('hexio', arguments.uri); |
|---|
| 157 | return local.shortened; |
|---|
| 158 | </cfscript> |
|---|
| 159 | </cffunction> |
|---|
| 160 | <cffunction name="getTweetSearchUrl" access="private" output="false" returnType="string"> |
|---|
| 161 | <cfargument name="uri" type="string" required="true"/> |
|---|
| 162 | <cfscript> |
|---|
| 163 | var local = structNew(); |
|---|
| 164 | var cacheKey = hash(arguments.uri); |
|---|
| 165 | //shortened url cache never expires |
|---|
| 166 | if (urlCacheExists(cacheKey)){ |
|---|
| 167 | local.shortened = getUrlCache(cacheKey); |
|---|
| 168 | }else{ |
|---|
| 169 | //get shortened versions of the url |
|---|
| 170 | local.shortened = getShortUrls(arguments.uri); |
|---|
| 171 | //and cache the result |
|---|
| 172 | setUrlCache(cacheKey, local.shortened); |
|---|
| 173 | } |
|---|
| 174 | |
|---|
| 175 | //compile twitter search url |
|---|
| 176 | local.api = 'http://search.twitter.com/search.json?rpp=100&q=&ors='; |
|---|
| 177 | local.thisSearch = local.api |
|---|
| 178 | & urlEncodedFormat(local.shortened.isgd) & '+' |
|---|
| 179 | & urlEncodedFormat(local.shortened.cligs) & '+' |
|---|
| 180 | & urlEncodedFormat(local.shortened.tinyurl) & '+' |
|---|
| 181 | & urlEncodedFormat(local.shortened.hexio); |
|---|
| 182 | |
|---|
| 183 | return local.thisSearch; |
|---|
| 184 | </cfscript> |
|---|
| 185 | </cffunction> |
|---|
| 186 | <cffunction name="makeTwitterSearchRequest" access="private" output="false" returnType="any"> |
|---|
| 187 | <cfargument name="req" type="String" required="true"/> |
|---|
| 188 | <cfset var result = ""/> |
|---|
| 189 | <cfhttp url="#arguments.req#" method="get" result="result" useragent="SweetTweets plugin v0.0 for Mango Blog|http://fusiongrokker.com"></cfhttp> |
|---|
| 190 | <cftry> |
|---|
| 191 | <cfset result = jsonService.deserialize(result.fileContent.toString())/> |
|---|
| 192 | <cfcatch type="any"><!--- catch errors thrown by jsonService (likely problem w/twitter search - down,etc), return empty set ---> |
|---|
| 193 | <cfset result = StructNew()/> |
|---|
| 194 | <cfset result.results = arrayNew(1)/> |
|---|
| 195 | </cfcatch> |
|---|
| 196 | </cftry> |
|---|
| 197 | <cfreturn result /> |
|---|
| 198 | </cffunction> |
|---|
| 199 | <cffunction name="cleanup" access="private" output="false" returnType="array"> |
|---|
| 200 | <cfargument name="tweets" type="array" required="true"/> |
|---|
| 201 | <cfscript> |
|---|
| 202 | var local = structNew(); |
|---|
| 203 | var i = 0; |
|---|
| 204 | local.linkRegex = "((https?|s?ftp|ssh)\:\/\/[^""\s\<\>]*[^.,;'"">\:\s\<\>\)\]\!])"; |
|---|
| 205 | local.atRegex = "@([_a-z0-9]+)"; |
|---|
| 206 | local.hashRegex = "##([_a-z0-9]+)"; |
|---|
| 207 | for (i=1;i lte arrayLen(arguments.tweets);i=i+1){ |
|---|
| 208 | //fix links |
|---|
| 209 | arguments.tweets[i].text = REReplaceNoCase(arguments.tweets[i].text,local.linkRegex,"<a href='\1'>\1</a>","all"); |
|---|
| 210 | arguments.tweets[i].text = REReplaceNoCase(arguments.tweets[i].text,local.atRegex,"<a href='http://twitter.com/\1'>@\1</a>","all"); |
|---|
| 211 | arguments.tweets[i].text = REReplaceNoCase(arguments.tweets[i].text,local.hashRegex,"<a href='http://www.hashtags.org/tag/\1'>##\1</a>"); |
|---|
| 212 | //remove ugly stuff from timestamp |
|---|
| 213 | arguments.tweets[i].created_at = Replace(arguments.tweets[i].created_at, "+0000", ""); |
|---|
| 214 | } |
|---|
| 215 | return arguments.tweets; |
|---|
| 216 | </cfscript> |
|---|
| 217 | </cffunction> |
|---|
| 218 | <cffunction name="killImpostors" access="private" output="false" returnType="array"> |
|---|
| 219 | <cfargument name="data" required="true" type="array"/> |
|---|
| 220 | <cfargument name="shorties" required="true" type="struct"/> |
|---|
| 221 | <cfscript> |
|---|
| 222 | //this function removes false positives returned because search is case-INsensitive, but shortened url's are case-sensitive |
|---|
| 223 | var i = 0; |
|---|
| 224 | var j = 0; |
|---|
| 225 | var keyNames = structKeyList(shorties); |
|---|
| 226 | var impostor = true; |
|---|
| 227 | for (i=1;i lte arrayLen(data);i=i+1){ |
|---|
| 228 | impostor = true; |
|---|
| 229 | for (j=1;j lte listLen(keyNames);j=j+1){ |
|---|
| 230 | if (find(shorties[listGetAt(keyNames,j)],data[i].text)){ |
|---|
| 231 | impostor = false; |
|---|
| 232 | break; |
|---|
| 233 | } |
|---|
| 234 | } |
|---|
| 235 | if (impostor){ |
|---|
| 236 | arrayDeleteAt(data,i); |
|---|
| 237 | i=i-1; |
|---|
| 238 | } |
|---|
| 239 | } |
|---|
| 240 | return data; |
|---|
| 241 | </cfscript> |
|---|
| 242 | </cffunction> |
|---|
| 243 | |
|---|
| 244 | <!--- caching functions ---> |
|---|
| 245 | <cffunction name="setupCache" access="private" output="false" returnType="void"> |
|---|
| 246 | <cfargument name="cacheKey" type="string" required="true" /> |
|---|
| 247 | <cfscript> |
|---|
| 248 | if (variables.cacheLocation eq "application"){ |
|---|
| 249 | if (not structKeyExists(application, "SweetTweetCache")){application.SweetTweetCache=StructNew();} |
|---|
| 250 | if (not structKeyExists(application.sweetTweetCache, "urls")){application.sweetTweetCache.urls=StructNew();} |
|---|
| 251 | if (not structKeyExists(application.sweetTweetCache, "tweetbacks")){application.sweetTweetCache.tweetbacks=StructNew();} |
|---|
| 252 | if (not structKeyExists(application.sweetTweetCache.tweetbacks, arguments.cacheKey)){application.sweetTweetCache.tweetbacks[arguments.cacheKey]=StructNew();} |
|---|
| 253 | }else{ |
|---|
| 254 | if (not structKeyExists(variables, "SweetTweetCache")){variables.SweetTweetCache=StructNew();} |
|---|
| 255 | if (not structKeyExists(variables.sweetTweetCache, "urls")){variables.sweetTweetCache.urls=StructNew();} |
|---|
| 256 | if (not structKeyExists(variables.sweetTweetCache, "tweetbacks")){variables.sweetTweetCache.tweetbacks=StructNew();} |
|---|
| 257 | if (not structKeyExists(variables.sweetTweetCache.tweetbacks, arguments.cacheKey)){variables.sweetTweetCache.tweetbacks[arguments.cacheKey]=StructNew();} |
|---|
| 258 | } |
|---|
| 259 | </cfscript> |
|---|
| 260 | </cffunction> |
|---|
| 261 | <cffunction name="tweetCacheExpired" access="private" output="false" returnType="boolean"> |
|---|
| 262 | <cfargument name="cacheKey" type="string" required="true"/> |
|---|
| 263 | <cfscript> |
|---|
| 264 | if (variables.cacheLocation eq "application"){ |
|---|
| 265 | return (not structKeyExists(application.sweetTweetCache.tweetbacks[arguments.cacheKey], "timeout") or |
|---|
| 266 | dateCompare(now(), application.sweetTweetCache.tweetbacks[arguments.cacheKey].timeout) eq 1); |
|---|
| 267 | }else{ |
|---|
| 268 | return (not structKeyExists(variables.sweetTweetCache.tweetbacks[arguments.cacheKey], "timeout") or |
|---|
| 269 | dateCompare(now(), variables.sweetTweetCache.tweetbacks[arguments.cacheKey].timeout) eq 1); |
|---|
| 270 | } |
|---|
| 271 | </cfscript> |
|---|
| 272 | </cffunction> |
|---|
| 273 | <cffunction name="getTweetCache" access="private" output="false" returnType="array"> |
|---|
| 274 | <cfargument name="cacheKey" type="string" required="true"/> |
|---|
| 275 | <cfscript> |
|---|
| 276 | if (variables.cacheLocation eq "application"){ |
|---|
| 277 | return application.sweetTweetCache.tweetbacks[arguments.cacheKey].tweets; |
|---|
| 278 | }else{ |
|---|
| 279 | //dump(variables.sweetTweetCache,true); |
|---|
| 280 | return variables.sweetTweetCache.tweetbacks[arguments.cacheKey].tweets; |
|---|
| 281 | } |
|---|
| 282 | </cfscript> |
|---|
| 283 | </cffunction> |
|---|
| 284 | <cffunction name="setTweetCache" access="private" output="false" returnType="void"> |
|---|
| 285 | <cfargument name="cacheKey" type="string" required="true"/> |
|---|
| 286 | <cfargument name="data" type="array" required="true"/> |
|---|
| 287 | <cfargument name="timeout" type="any" required="true"/> |
|---|
| 288 | <cfscript> |
|---|
| 289 | if (variables.cacheLocation eq "application"){ |
|---|
| 290 | application.sweetTweetCache.tweetbacks[arguments.cacheKey].tweets = arguments.data; |
|---|
| 291 | application.sweetTweetCache.tweetbacks[arguments.cacheKey].timeout = dateAdd("n",arguments.timeout,now()); |
|---|
| 292 | }else{ |
|---|
| 293 | variables.sweetTweetCache.tweetbacks[arguments.cacheKey].tweets = arguments.data; |
|---|
| 294 | variables.sweetTweetCache.tweetbacks[arguments.cacheKey].timeout = dateAdd("n",arguments.timeout,now()); |
|---|
| 295 | } |
|---|
| 296 | </cfscript> |
|---|
| 297 | </cffunction> |
|---|
| 298 | <cffunction name="urlCacheExists" access="private" output="false" returnType="boolean"> |
|---|
| 299 | <cfargument name="cacheKey" type="string" required="true"/> |
|---|
| 300 | <cfscript> |
|---|
| 301 | if (variables.cacheLocation eq "application"){ |
|---|
| 302 | return (structKeyExists(application.SweetTweetCache.urls, arguments.cacheKey)); |
|---|
| 303 | }else{ |
|---|
| 304 | return (structKeyExists(variables.SweetTweetCache.urls, arguments.cacheKey)); |
|---|
| 305 | } |
|---|
| 306 | </cfscript> |
|---|
| 307 | </cffunction> |
|---|
| 308 | <cffunction name="getUrlCache" access="private" output="false" returnType="struct"> |
|---|
| 309 | <cfargument name="cacheKey" type="string" required="true"/> |
|---|
| 310 | <cfscript> |
|---|
| 311 | if (variables.cacheLocation eq "application"){ |
|---|
| 312 | return application.sweetTweetCache.urls[arguments.cacheKey]; |
|---|
| 313 | }else{ |
|---|
| 314 | return variables.sweetTweetCache.urls[arguments.cacheKey]; |
|---|
| 315 | } |
|---|
| 316 | </cfscript> |
|---|
| 317 | </cffunction> |
|---|
| 318 | <cffunction name="setUrlCache" access="private" output="false" returnType="void"> |
|---|
| 319 | <cfargument name="cacheKey" type="string" required="true"/> |
|---|
| 320 | <cfargument name="data" type="struct" required="true"/> |
|---|
| 321 | <cfscript> |
|---|
| 322 | if (variables.cacheLocation eq "application"){ |
|---|
| 323 | application.sweetTweetCache.urls[arguments.cacheKey] = arguments.data; |
|---|
| 324 | }else{ |
|---|
| 325 | variables.sweetTweetCache.urls[arguments.cacheKey] = arguments.data; |
|---|
| 326 | } |
|---|
| 327 | </cfscript> |
|---|
| 328 | </cffunction> |
|---|
| 329 | |
|---|
| 330 | <cffunction name="dump" access="private" output="false"> |
|---|
| 331 | <cfargument name="var" required="true"/> |
|---|
| 332 | <cfargument name="abort" default="false"/> |
|---|
| 333 | <cfdump var="#arguments.var#"> |
|---|
| 334 | <cfif arguments.abort> |
|---|
| 335 | <cfabort> |
|---|
| 336 | </cfif> |
|---|
| 337 | </cffunction> |
|---|
| 338 | |
|---|
| 339 | </cfcomponent> |
|---|