adium/adium
Clone
Summary
Browse
Changes
Graph
Merged matthewkotila/adium/tip into default
default
2016-09-05, Robbie
92fa28ff1873
Merged matthewkotila/adium/tip into default
Fixes #16970 ("Transition from SenTestingKit to XCTest") in order to build on OS X 10.11 / Xcode 7.2.
/*
* 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 "AIMessageAliasPlugin.h"
#import <Adium/AIAccountControllerProtocol.h>
#import <AIUtilities/AIAttributedStringAdditions.h>
#import <AIUtilities/AIDateFormatterAdditions.h>
#import <Adium/AIAccount.h>
#import <Adium/AIContentMessage.h>
#import <Adium/AIListContact.h>
@interface
AIMessageAliasPlugin
()
-
(
NSMutableAttributedString
*
)
replaceKeywordsInString
:
(
NSAttributedString
*
)
original
context
:
(
id
)
context
;
@end
@implementation
AIMessageAliasPlugin
-
(
void
)
installPlugin
{
//Register us as a filter
[
adium
.
contentController
registerContentFilter
:
self
ofType
:
AIFilterDisplay
direction
:
AIFilterIncoming
];
[
adium
.
contentController
registerContentFilter
:
self
ofType
:
AIFilterAutoReplyContent
direction
:
AIFilterOutgoing
];
[
adium
.
contentController
registerContentFilter
:
self
ofType
:
AIFilterTooltips
direction
:
AIFilterIncoming
];
[
adium
.
contentController
registerContentFilter
:
self
ofType
:
AIFilterContactList
direction
:
AIFilterIncoming
];
}
-
(
void
)
uninstallPlugin
{
[
adium
.
contentController
unregisterContentFilter
:
self
];
}
-
(
NSAttributedString
*
)
filterAttributedString:
(
NSAttributedString
*
)
inAttributedString
context:
(
id
)
context
{
if
(
!
inAttributedString
||
!
[
inAttributedString
length
])
return
inAttributedString
;
//Filter keywords in the message
NSMutableAttributedString
*
filteredMessage
=
[
self
replaceKeywordsInString
:
inAttributedString
context
:
context
];;
//Filter keywords in URLs (For AIM subprofile links, mostly)
NSInteger
length
=
[(
filteredMessage
?
filteredMessage
:
inAttributedString
)
length
];
NSRange
scanRange
=
NSMakeRange
(
0
,
0
);
while
(
NSMaxRange
(
scanRange
)
<
length
)
{
id
linkURL
=
[(
filteredMessage
?
filteredMessage
:
inAttributedString
)
attribute
:
NSLinkAttributeName
atIndex
:
NSMaxRange
(
scanRange
)
effectiveRange
:
&
scanRange
];
if
(
linkURL
)
{
NSString
*
linkURLString
;
if
([
linkURL
isKindOfClass
:
[
NSURL
class
]])
{
linkURLString
=
(
__bridge_transfer
NSString
*
)
CFURLCreateStringByReplacingPercentEscapes
(
kCFAllocatorDefault
,
(
__bridge
CFStringRef
)[(
NSURL
*
)
linkURL
absoluteString
],
/* characters to leave escaped */
CFSTR
(
""
));
}
else
{
linkURLString
=
(
NSString
*
)
linkURL
;
}
if
(
linkURLString
)
{
//If we found a URL, replace any keywords within it
NSString
*
result
=
[[
self
replaceKeywordsInString
:
[
NSAttributedString
stringWithString
:
linkURLString
]
context
:
context
]
string
];
if
(
result
)
{
NSURL
*
newURL
;
NSString
*
escapedLinkURLString
;
NSString
*
charactersToLeaveUnescaped
=
@"#"
;
if
(
!
filteredMessage
)
filteredMessage
=
[
inAttributedString
mutableCopy
];
escapedLinkURLString
=
(
__bridge_transfer
NSString
*
)
CFURLCreateStringByAddingPercentEscapes
(
/* allocator */
kCFAllocatorDefault
,
(
__bridge
CFStringRef
)
result
,
(
__bridge
CFStringRef
)
charactersToLeaveUnescaped
,
/* legal characters to escape */
NULL
,
kCFStringEncodingUTF8
);
newURL
=
[
NSURL
URLWithString
:
escapedLinkURLString
];
if
(
newURL
)
{
[
filteredMessage
addAttribute
:
NSLinkAttributeName
value
:
newURL
range
:
scanRange
];
}
}
}
}
}
return
(
filteredMessage
?
filteredMessage
:
inAttributedString
);
}
-
(
CGFloat
)
filterPriority
{
return
DEFAULT_FILTER_PRIORITY
;
}
-
(
BOOL
)
string:
(
NSString
*
)
str
containsValidKeyword:
(
NSString
*
)
keyword
{
NSRange
range
=
[
str
rangeOfString
:
keyword
options
:
NSLiteralSearch
];
BOOL
validKeyword
=
NO
;
if
(
range
.
location
!=
NSNotFound
)
{
if
((
range
.
location
==
0
)
||
((
NSMaxRange
(
range
)
==
[
str
length
])))
{
/* At the beginning or end of the line */
validKeyword
=
YES
;
}
else
{
NSCharacterSet
*
alphanumericCharacterSet
=
[
NSCharacterSet
alphanumericCharacterSet
];
if
(
!
[
alphanumericCharacterSet
characterIsMember
:
[
str
characterAtIndex
:
(
range
.
location
-
1
)]]
&&
!
[
alphanumericCharacterSet
characterIsMember
:
[
str
characterAtIndex
:
NSMaxRange
(
range
)]])
{
validKeyword
=
YES
;
}
}
}
return
validKeyword
;
}
/*!
* @brief Replace any AIM-style keywords (%n, %d, %t) in the passed string
*
* @param attributedString The string
* @param context The object for which we are filtering, if known
*
* @result A mutable version of the passed string if keywords have been replaced. Otherwise returns nil.
*/
-
(
NSMutableAttributedString
*
)
replaceKeywordsInString:
(
NSAttributedString
*
)
attributedString
context:
(
id
)
context
{
NSString
*
str
=
[
attributedString
string
];
NSMutableAttributedString
*
newAttributedString
=
nil
;
//Abort early if there are no potential keywords
if
([
str
rangeOfString
:
@"%"
options
:
NSLiteralSearch
].
location
==
NSNotFound
)
return
nil
;
//Our Name
//If we're passed content, our account will be the destination of that content
//If we're passed a list object, we can use the name of the preferred account for that object
if
([
self
string
:
str
containsValidKeyword
:
@"%n"
])
{
NSString
*
replacement
=
nil
;
if
([
context
isKindOfClass
:
[
AIContentObject
class
]])
{
replacement
=
[[
context
destination
]
UID
];
//This exists primarily for AIM compatibility; AIM uses the UID (no formatting).
}
else
if
([
context
isKindOfClass
:
[
AIListContact
class
]])
{
replacement
=
[[
adium
.
accountController
preferredAccountForSendingContentType
:
CONTENT_MESSAGE_TYPE
toContact
:
context
]
formattedUID
];
}
if
(
replacement
)
{
if
(
!
newAttributedString
)
newAttributedString
=
[
attributedString
mutableCopy
];
[
newAttributedString
replaceOccurrencesOfString
:
@"%n"
withString
:
replacement
options
:
NSLiteralSearch
range
:
NSMakeRange
(
0
,
[
newAttributedString
length
])];
}
}
//Current Date
if
([
self
string
:
str
containsValidKeyword
:
@"%d"
])
{
NSDate
*
currentDate
=
[
NSDate
date
];
__block
NSString
*
calendarFormat
;
[
NSDateFormatter
withLocalizedShortDateFormatterPerform
:^
(
NSDateFormatter
*
dateFormatter
){
calendarFormat
=
[
dateFormatter
dateFormat
];
}];
if
(
!
newAttributedString
)
newAttributedString
=
[
attributedString
mutableCopy
];
[
newAttributedString
replaceOccurrencesOfString
:
@"%d"
withString
:[
currentDate
descriptionWithCalendarFormat
:
calendarFormat
timeZone
:
nil
locale
:
nil
]
options
:
NSLiteralSearch
range
:
NSMakeRange
(
0
,
[
newAttributedString
length
])];
}
//Current Time
if
([
self
string
:
str
containsValidKeyword
:
@"%t"
])
{
NSDate
*
currentDate
=
[
NSDate
date
];
if
(
!
newAttributedString
)
newAttributedString
=
[
attributedString
mutableCopy
];
[
NSDateFormatter
withLocalizedDateFormatterShowingSeconds
:
YES
showingAMorPM
:
YES
perform
:^
(
NSDateFormatter
*
localDateFormatter
){
[
newAttributedString
replaceOccurrencesOfString
:
@"%t"
withString
:[
localDateFormatter
stringFromDate
:
currentDate
]
options
:
NSLiteralSearch
range
:
NSMakeRange
(
0
,
[
newAttributedString
length
])];
}];
}
return
newAttributedString
;
}
@end