* Adium is the legal property of its developers, whose names are listed in the copyright file included * with this source distribution. * This program is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * You should have received a copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #import "AILaconicaAccount.h" #import "AITwitterURLParser.h" #import <Adium/AIContactObserverManager.h> #import <Adium/AIChatControllerProtocol.h> @interface AITwitterAccount() - (BOOL)checkForCursorSupport; @implementation AILaconicaAccount textLimitConfigDownload = nil; [adium.preferenceController registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], LACONICA_PREFERENCE_SSL, nil] forGroup:LACONICA_PREF_GROUP supportsCursors = [self checkForCursorSupport]; [self setLastDisconnectionError:AILocalizedString(@"No Host set", nil)]; * @brief Our default server if none is provided. * Do not set a default server. - (NSString *)defaultServer * The API path extension for the given host. // We need to guarantee this is an NSString, so -stringByAppendingPathComponent works. NSString *path = [self preferenceForKey:LACONICA_PREFERENCE_PATH group:LACONICA_PREF_GROUP] ?: @""; return [path stringByAppendingPathComponent:@"api"]; * @brief Our source token * On Laconica, our given source token is "adium". - (NSString *)sourceToken * @brief Our explicit formatted UID * This includes "additional necessary identifying information". - (NSString *)explicitFormattedUID return [NSString stringWithFormat:@"%@ (%@)", self.UID, self.host]; * @brief Use our host for the servername when storing password - (BOOL)useHostForPasswordServerName * @brief Not all StatusNet instances support HTTPS connections. return [[self preferenceForKey:LACONICA_PREFERENCE_SSL group:LACONICA_PREF_GROUP] boolValue]; * @brief Laconica does not yet support OAuth. * @brief Connection successful * Pull all the usual stuff, but also check for the max notice length, * provided by StatusNet 0.9 and later. textLimitConfigDownload = nil; AIGroupChat *timelineChat = [adium.chatController existingChatWithName:self.timelineChatName [self updateTimelineChat: timelineChat]; * @brief Query the StatusNet API for the site/textlimit config variable. * Returns the limit if present, or the default of 140. // Hardcoded default for older servers that don't report their configured limit. NSString *path = [[@"/" stringByAppendingPathComponent:self.apiPath] stringByAppendingPathComponent:@"statusnet/config.xml"]; NSURL *url = [[[NSURL alloc] initWithScheme:(self.useSSL ? @"https" : @"http") NSURLRequest *configRequest = [NSURLRequest requestWithURL:url]; if (textLimitConfigDownload) { [textLimitConfigDownload cancel]; [textLimitConfigDownload release]; textLimitConfigDownload = nil; textLimitConfigDownload = [[NSURLConnection alloc] initWithRequest:configRequest delegate:self]; * @brief Downloads the configuration xml file from the server. -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data if ([connection isEqual:textLimitConfigDownload]) [configData appendData:data]; -(void)connectionDidFinishLoading:(NSURLConnection *)connection if ([connection isEqual:textLimitConfigDownload]) { NSXMLDocument *config = [[NSXMLDocument alloc] initWithData:configData NSArray *nodes = [config nodesForXPath:@"/config/site/textlimit" textlimit = [[(NSXMLNode *)[nodes objectAtIndex: 0] stringValue] intValue]; AILogWithSignature(@"Failed fetching StatusNet server config for %@: %ld %@", self.host, [err code], [err localizedDescription]); [configData release]; configData = nil; [textLimitConfigDownload release]; textLimitConfigDownload = nil; * @brief This method is called when there is an error -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error [textLimitConfigDownload release]; textLimitConfigDownload = nil; [configData release]; configData = nil; AILogWithSignature(@"%@",[NSString stringWithFormat:@"Fetch failed: %@", [error localizedDescription]]); * @brief Returns the link URL for a specific type of link - (NSString *)addressForLinkType:(AITwitterLinkType)linkType userID:(NSString *)userID statusID:(NSString *)statusID context:(NSString *)context NSString *address = [super addressForLinkType:linkType userID:userID statusID:statusID context:context]; NSString *fullAddress = [self.host stringByAppendingPathComponent:[self preferenceForKey:LACONICA_PREFERENCE_PATH group:LACONICA_PREF_GROUP]]; NSString *protocol = self.useSSL ? @"https" : @"http"; if (linkType == AITwitterLinkStatus) { address = [NSString stringWithFormat:@"%@://%@/notice/%@", protocol, fullAddress, statusID]; } else if (linkType == AITwitterLinkFriends) { address = [NSString stringWithFormat:@"%@://%@/%@/subscriptions", protocol, fullAddress, userID]; } else if (linkType == AITwitterLinkFollowers) { address = [NSString stringWithFormat:@"%@://%@/%@/subscribers", protocol, fullAddress, userID]; } else if (linkType == AITwitterLinkUserPage) { address = [NSString stringWithFormat:@"%@://%@/%@", protocol, fullAddress, userID]; } else if (linkType == AITwitterLinkSearchHash) { address = [NSString stringWithFormat:@"http://%@/tag/%@", fullAddress, context]; } else if (linkType == AITwitterLinkGroup) { address = [NSString stringWithFormat:@"http://%@/group/%@", fullAddress, context]; } else if (linkType == AITwitterLinkRetweet) { * @brief Parse an attributed string into a linkified version. - (NSAttributedString *)linkifiedAttributedStringFromString:(NSAttributedString *)inString NSAttributedString *attributedString = [super linkifiedAttributedStringFromString:inString]; static NSCharacterSet *groupCharacters = nil; NSMutableCharacterSet *disallowedCharacters = [[NSCharacterSet punctuationCharacterSet] mutableCopy]; [disallowedCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]]; groupCharacters = [[disallowedCharacters invertedSet] retain]; [disallowedCharacters release]; attributedString = [AITwitterURLParser linkifiedStringFromAttributedString:attributedString forLinkType:AITwitterLinkGroup validCharacterSet:groupCharacters]; * @brief Retweet the selected tweet. * Attempts to retweet a tweet. * Prints a status message in the chat on success/failure, behaves identical to sending a new tweet. * @returns YES if the account could send a retweet message, NO if the account doesn't support it. * XXX When Laconica officially supports a retweet API, remove this method entirely. - (BOOL)retweetTweet:(NSString *)tweetID * @brief Check if the server supports cursor based userlists. * @returns YES if the support cursor lists, NO if the account doesn't support it. * XXX This should probably do some actual checking so we don't have to touch this when it goes live. - (BOOL)checkForCursorSupport * @brief The name of our timeline chat - (NSString *)timelineChatName return [NSString stringWithFormat:LACONICA_TIMELINE_NAME, self.host, self.UID]; * @brief The remote group name we'll stuff the timeline into - (NSString *)timelineGroupName return LACONICA_REMOTE_GROUP_NAME; * @brief Returns the maximum number of characters available for a post, or 0 if unlimited. * For StatusNet servers, this may have been provided via API.