├── .gitignore
├── res
├── icon.xcf
└── promo.xcf
├── img
├── icon16.png
├── icon48.png
└── icon128.png
├── settings.example.js
├── onload.js
├── background.html
├── Makefile
├── README
├── chrome_ex_oauth.html
├── PRIVACY
├── manifest.json
├── LICENSE
├── background.js
├── LICENSE.chrome_ex_oauth
├── LICENSE.twitter-text
├── chrome_ex_oauthsimple.js
├── chrome_ex_oauth.js
└── twitter-text.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.zip
2 | settings.js
3 |
--------------------------------------------------------------------------------
/res/icon.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dwyer/chromnitweet/HEAD/res/icon.xcf
--------------------------------------------------------------------------------
/img/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dwyer/chromnitweet/HEAD/img/icon16.png
--------------------------------------------------------------------------------
/img/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dwyer/chromnitweet/HEAD/img/icon48.png
--------------------------------------------------------------------------------
/res/promo.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dwyer/chromnitweet/HEAD/res/promo.xcf
--------------------------------------------------------------------------------
/img/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dwyer/chromnitweet/HEAD/img/icon128.png
--------------------------------------------------------------------------------
/settings.example.js:
--------------------------------------------------------------------------------
1 | const TWITTER_CONSUMER_KEY = 'YOUR KEY HERE';
2 | const TWITTER_CONSUMER_SECRET = 'YOUR SECRET HERE';
3 |
--------------------------------------------------------------------------------
/onload.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | window.onload = function() {
6 | ChromeExOAuth.initCallbackPage();
7 | }
8 |
--------------------------------------------------------------------------------
/background.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ZIP=zip
2 |
3 | # compiled files
4 | PACKAGE=chromnitweet.zip
5 |
6 | # source files
7 | MANIFEST=manifest.json
8 | HTML=background.html chrome_ex_oauth.html
9 | PNG=img/*
10 | JS=background.js settings.js chrome_ex_oauth.js chrome_ex_oauthsimple.js onload.js twitter-text.js
11 |
12 | all: $(PACKAGE)
13 |
14 | $(PACKAGE): $(MANIFEST) $(HTML) $(JS) $(PNG)
15 | $(ZIP) $@ $^
16 |
17 | clean:
18 | $(RM) $(PACKAGE)
19 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Chromnitweet is a Google Chrome (or Chromium) extension that acts as a Twitter
2 | client, allowing the user to post tweet (status update or direct message)
3 | directly from the Omnibox (Omnibar, awesome bar, URL bar, location bar, etc.)
4 | with the syntax "tw [some string of 280 or less characters]".
5 |
6 | https://chrome.google.com/webstore/detail/ofhffnelacegjkgcbohojhebkbehikep?hl=en-US
7 |
8 | Useless fact: Chromnitweet is a portmanteau of Chrome, Omnibox and Tweet.
9 |
10 | INSTALLATION
11 | ============
12 |
13 | - Copy settings.example.js to settings.js.
14 | - Open settings.js and add your Twitter OAuth info.
15 | - If you have GNU make and zip installed, run `make` to create a package.
16 |
--------------------------------------------------------------------------------
/chrome_ex_oauth.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | OAuth Redirect Page
10 |
16 |
17 |
18 |
19 |
20 |
21 | Redirecting...
22 |
23 |
24 |
--------------------------------------------------------------------------------
/PRIVACY:
--------------------------------------------------------------------------------
1 | CHROMNITWEET PRIVACY POLICY
2 |
3 | Chromnitweet is a Twitter client installed on your computer as a Chrome
4 | extension. All communication performed in Chromnitweet is directly between
5 | Twitter and its users. Chromnitweet does not rely on, or have access to, any
6 | third-party servers. Therefore, Chromnitweet and its creators do not, and can
7 | not, record, keep or access any data on any of its users.
8 |
9 | CHANGES TO THIS PRIVACY POLICY
10 |
11 | We may update our Privacy Policy from time to time. Thus, we advise you to
12 | review this page periodically for any changes. We will notify you of any changes
13 | by posting the new Privacy Policy on this page. These changes are effective
14 | immediately, after they are posted on this page.
15 |
16 | CONTACT US
17 |
18 | Questions and suggestions about this privacy policy are welcome by email at
19 | caseydwyer@gmail.com.
20 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Chromnitweet",
3 | "short_name": "Chromnitweet",
4 | "manifest_version": 2,
5 | "version": "2.8.3",
6 | "description": "Update your Twitter status right from Chrome's Omnibox (URL bar).",
7 | "omnibox": {"keyword": "tw"},
8 | "icons": {
9 | "128": "img/icon128.png",
10 | "48": "img/icon48.png",
11 | "16": "img/icon16.png"
12 | },
13 | "background": {
14 | "scripts": [
15 | "chrome_ex_oauthsimple.js",
16 | "chrome_ex_oauth.js",
17 | "onload.js",
18 | "twitter-text.js",
19 | "settings.js",
20 | "background.js"
21 | ],
22 | "persistent": false
23 | },
24 | "web_accessible_resources": ["chrome_ex_oauth.html"],
25 | "permissions": ["notifications", "tabs", "https://api.twitter.com/", "https://*.twimg.com/"],
26 | "content_security_policy": "script-src 'self'; object-src 'self'; img-src 'self' https://*.twimg.com"
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2014, The FreeKCD Project. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | 1. Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 |
13 | This software is provided by the copyright holders and contributors "as
14 | is" and any express or implied warranties, including, but not limited
15 | to, the implied warranties of merchantability and fitness for a
16 | particular purpose are disclaimed. In no event shall the copyright
17 | holder or contributors be liable for any direct, indirect, incidental,
18 | special, exemplary, or consequential damages (including, but not limited
19 | to, procurement of substitute goods or services; loss of use, data, or
20 | profits; or business interruption) however caused and on any theory of
21 | liability, whether in contract, strict liability, or tort (including
22 | negligence or otherwise) arising in any way out of the use of this
23 | software, even if advised of the possibility of such damage.
24 |
--------------------------------------------------------------------------------
/background.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) The FreeKCD Project. See LICENSE.
3 | */
4 |
5 | const TWITTER_UPDATE_URL = 'https://api.twitter.com/1.1/statuses/update.json';
6 | const NOTIFICATION_TIMEOUT = 3000;
7 |
8 | var oauth = ChromeExOAuth.initBackgroundPage({
9 | request_url: 'https://api.twitter.com/oauth/request_token',
10 | authorize_url: 'https://api.twitter.com/oauth/authorize',
11 | access_url: 'https://api.twitter.com/oauth/access_token',
12 | consumer_key: TWITTER_CONSUMER_KEY,
13 | consumer_secret: TWITTER_CONSUMER_SECRET,
14 | scope: TWITTER_UPDATE_URL,
15 | app_name: 'Chromnitweet'
16 | });
17 |
18 | function notify(iconUrl, title, message) {
19 | chrome.notifications.create('chromnitweet', {
20 | type: 'basic',
21 | iconUrl: iconUrl,
22 | title: title,
23 | message: message
24 | }, function(id) {
25 | window.setTimeout(function() {
26 | chrome.notifications.clear(id, function() {});
27 | }, NOTIFICATION_TIMEOUT);
28 | });
29 | }
30 |
31 | oauth.authorize(function() {
32 | chrome.omnibox.onInputChanged.addListener(function(text) {
33 | chrome.omnibox.setDefaultSuggestion({
34 | description: String(280 - twttr.txt.getTweetLength(text)) + ' characters remaining.'
35 | });
36 | });
37 | chrome.omnibox.onInputEntered.addListener(function(text, disposition) {
38 | function sendSignedRequestCallback(response, xhr) {
39 | var result = JSON.parse(response);
40 | if (result.errors !== undefined) {
41 | notify('img/icon128.png', 'Oops! There was an error.',
42 | result.errors[0].message);
43 | } else {
44 | notify(result.user.profile_image_url_https, result.user.name,
45 | result.text);
46 | }
47 | }
48 | oauth.sendSignedRequest(TWITTER_UPDATE_URL, sendSignedRequestCallback, {
49 | method: 'POST',
50 | parameters: {status: text}
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/LICENSE.chrome_ex_oauth:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Chromium Authors. All rights reserved.
2 | //
3 | // Redistribution and use in source and binary forms, with or without
4 | // modification, are permitted provided that the following conditions are
5 | // met:
6 | //
7 | // * Redistributions of source code must retain the above copyright
8 | // notice, this list of conditions and the following disclaimer.
9 | // * Redistributions in binary form must reproduce the above
10 | // copyright notice, this list of conditions and the following disclaimer
11 | // in the documentation and/or other materials provided with the
12 | // distribution.
13 | // * Neither the name of Google Inc. nor the names of its
14 | // contributors may be used to endorse or promote products derived from
15 | // this software without specific prior written permission.
16 | //
17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | This extension uses code from the following two JavaScript libraries:
30 |
31 | http://unitedheroes.net/OAuthSimple/js/OAuthSimple.js
32 | =====================================================
33 | /* OAuthSimple
34 | * A simpler version of OAuth
35 | *
36 | * author: jr conlin
37 | * mail: src@anticipatr.com
38 | * copyright: unitedHeroes.net
39 | * version: 1.0
40 | * url: http://unitedHeroes.net/OAuthSimple
41 | *
42 | * Copyright (c) 2009, unitedHeroes.net
43 | * All rights reserved.
44 | *
45 | * Redistribution and use in source and binary forms, with or without
46 | * modification, are permitted provided that the following conditions are met:
47 | * * Redistributions of source code must retain the above copyright
48 | * notice, this list of conditions and the following disclaimer.
49 | * * Redistributions in binary form must reproduce the above copyright
50 | * notice, this list of conditions and the following disclaimer in the
51 | * documentation and/or other materials provided with the distribution.
52 | * * Neither the name of the unitedHeroes.net nor the
53 | * names of its contributors may be used to endorse or promote products
54 | * derived from this software without specific prior written permission.
55 | *
56 | * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
57 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
58 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
59 | * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
60 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
61 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
62 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
63 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
64 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
65 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
66 | */
67 |
68 | http://pajhome.org.uk/crypt/md5/sha1.js
69 | =======================================
70 | /*
71 | * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
72 | * in FIPS PUB 180-1
73 | * Version 2.1a Copyright Paul Johnston 2000 - 2002.
74 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
75 | * Distributed under the BSD License
76 | * See http://pajhome.org.uk/crypt/md5 for details.
77 | */
78 |
--------------------------------------------------------------------------------
/LICENSE.twitter-text:
--------------------------------------------------------------------------------
1 | Copyright 2011 Twitter, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this work except in compliance with the License.
5 | You may obtain a copy of the License below, or at:
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
15 | Apache License
16 | Version 2.0, January 2004
17 | http://www.apache.org/licenses/
18 |
19 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
20 |
21 | 1. Definitions.
22 |
23 | "License" shall mean the terms and conditions for use, reproduction,
24 | and distribution as defined by Sections 1 through 9 of this document.
25 |
26 | "Licensor" shall mean the copyright owner or entity authorized by
27 | the copyright owner that is granting the License.
28 |
29 | "Legal Entity" shall mean the union of the acting entity and all
30 | other entities that control, are controlled by, or are under common
31 | control with that entity. For the purposes of this definition,
32 | "control" means (i) the power, direct or indirect, to cause the
33 | direction or management of such entity, whether by contract or
34 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
35 | outstanding shares, or (iii) beneficial ownership of such entity.
36 |
37 | "You" (or "Your") shall mean an individual or Legal Entity
38 | exercising permissions granted by this License.
39 |
40 | "Source" form shall mean the preferred form for making modifications,
41 | including but not limited to software source code, documentation
42 | source, and configuration files.
43 |
44 | "Object" form shall mean any form resulting from mechanical
45 | transformation or translation of a Source form, including but
46 | not limited to compiled object code, generated documentation,
47 | and conversions to other media types.
48 |
49 | "Work" shall mean the work of authorship, whether in Source or
50 | Object form, made available under the License, as indicated by a
51 | copyright notice that is included in or attached to the work
52 | (an example is provided in the Appendix below).
53 |
54 | "Derivative Works" shall mean any work, whether in Source or Object
55 | form, that is based on (or derived from) the Work and for which the
56 | editorial revisions, annotations, elaborations, or other modifications
57 | represent, as a whole, an original work of authorship. For the purposes
58 | of this License, Derivative Works shall not include works that remain
59 | separable from, or merely link (or bind by name) to the interfaces of,
60 | the Work and Derivative Works thereof.
61 |
62 | "Contribution" shall mean any work of authorship, including
63 | the original version of the Work and any modifications or additions
64 | to that Work or Derivative Works thereof, that is intentionally
65 | submitted to Licensor for inclusion in the Work by the copyright owner
66 | or by an individual or Legal Entity authorized to submit on behalf of
67 | the copyright owner. For the purposes of this definition, "submitted"
68 | means any form of electronic, verbal, or written communication sent
69 | to the Licensor or its representatives, including but not limited to
70 | communication on electronic mailing lists, source code control systems,
71 | and issue tracking systems that are managed by, or on behalf of, the
72 | Licensor for the purpose of discussing and improving the Work, but
73 | excluding communication that is conspicuously marked or otherwise
74 | designated in writing by the copyright owner as "Not a Contribution."
75 |
76 | "Contributor" shall mean Licensor and any individual or Legal Entity
77 | on behalf of whom a Contribution has been received by Licensor and
78 | subsequently incorporated within the Work.
79 |
80 | 2. Grant of Copyright License. Subject to the terms and conditions of
81 | this License, each Contributor hereby grants to You a perpetual,
82 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
83 | copyright license to reproduce, prepare Derivative Works of,
84 | publicly display, publicly perform, sublicense, and distribute the
85 | Work and such Derivative Works in Source or Object form.
86 |
87 | 3. Grant of Patent License. Subject to the terms and conditions of
88 | this License, each Contributor hereby grants to You a perpetual,
89 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
90 | (except as stated in this section) patent license to make, have made,
91 | use, offer to sell, sell, import, and otherwise transfer the Work,
92 | where such license applies only to those patent claims licensable
93 | by such Contributor that are necessarily infringed by their
94 | Contribution(s) alone or by combination of their Contribution(s)
95 | with the Work to which such Contribution(s) was submitted. If You
96 | institute patent litigation against any entity (including a
97 | cross-claim or counterclaim in a lawsuit) alleging that the Work
98 | or a Contribution incorporated within the Work constitutes direct
99 | or contributory patent infringement, then any patent licenses
100 | granted to You under this License for that Work shall terminate
101 | as of the date such litigation is filed.
102 |
103 | 4. Redistribution. You may reproduce and distribute copies of the
104 | Work or Derivative Works thereof in any medium, with or without
105 | modifications, and in Source or Object form, provided that You
106 | meet the following conditions:
107 |
108 | (a) You must give any other recipients of the Work or
109 | Derivative Works a copy of this License; and
110 |
111 | (b) You must cause any modified files to carry prominent notices
112 | stating that You changed the files; and
113 |
114 | (c) You must retain, in the Source form of any Derivative Works
115 | that You distribute, all copyright, patent, trademark, and
116 | attribution notices from the Source form of the Work,
117 | excluding those notices that do not pertain to any part of
118 | the Derivative Works; and
119 |
120 | (d) If the Work includes a "NOTICE" text file as part of its
121 | distribution, then any Derivative Works that You distribute must
122 | include a readable copy of the attribution notices contained
123 | within such NOTICE file, excluding those notices that do not
124 | pertain to any part of the Derivative Works, in at least one
125 | of the following places: within a NOTICE text file distributed
126 | as part of the Derivative Works; within the Source form or
127 | documentation, if provided along with the Derivative Works; or,
128 | within a display generated by the Derivative Works, if and
129 | wherever such third-party notices normally appear. The contents
130 | of the NOTICE file are for informational purposes only and
131 | do not modify the License. You may add Your own attribution
132 | notices within Derivative Works that You distribute, alongside
133 | or as an addendum to the NOTICE text from the Work, provided
134 | that such additional attribution notices cannot be construed
135 | as modifying the License.
136 |
137 | You may add Your own copyright statement to Your modifications and
138 | may provide additional or different license terms and conditions
139 | for use, reproduction, or distribution of Your modifications, or
140 | for any such Derivative Works as a whole, provided Your use,
141 | reproduction, and distribution of the Work otherwise complies with
142 | the conditions stated in this License.
143 |
144 | 5. Submission of Contributions. Unless You explicitly state otherwise,
145 | any Contribution intentionally submitted for inclusion in the Work
146 | by You to the Licensor shall be under the terms and conditions of
147 | this License, without any additional terms or conditions.
148 | Notwithstanding the above, nothing herein shall supersede or modify
149 | the terms of any separate license agreement you may have executed
150 | with Licensor regarding such Contributions.
151 |
152 | 6. Trademarks. This License does not grant permission to use the trade
153 | names, trademarks, service marks, or product names of the Licensor,
154 | except as required for reasonable and customary use in describing the
155 | origin of the Work and reproducing the content of the NOTICE file.
156 |
157 | 7. Disclaimer of Warranty. Unless required by applicable law or
158 | agreed to in writing, Licensor provides the Work (and each
159 | Contributor provides its Contributions) on an "AS IS" BASIS,
160 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
161 | implied, including, without limitation, any warranties or conditions
162 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
163 | PARTICULAR PURPOSE. You are solely responsible for determining the
164 | appropriateness of using or redistributing the Work and assume any
165 | risks associated with Your exercise of permissions under this License.
166 |
167 | 8. Limitation of Liability. In no event and under no legal theory,
168 | whether in tort (including negligence), contract, or otherwise,
169 | unless required by applicable law (such as deliberate and grossly
170 | negligent acts) or agreed to in writing, shall any Contributor be
171 | liable to You for damages, including any direct, indirect, special,
172 | incidental, or consequential damages of any character arising as a
173 | result of this License or out of the use or inability to use the
174 | Work (including but not limited to damages for loss of goodwill,
175 | work stoppage, computer failure or malfunction, or any and all
176 | other commercial damages or losses), even if such Contributor
177 | has been advised of the possibility of such damages.
178 |
179 | 9. Accepting Warranty or Additional Liability. While redistributing
180 | the Work or Derivative Works thereof, You may choose to offer,
181 | and charge a fee for, acceptance of support, warranty, indemnity,
182 | or other liability obligations and/or rights consistent with this
183 | License. However, in accepting such obligations, You may act only
184 | on Your own behalf and on Your sole responsibility, not on behalf
185 | of any other Contributor, and only if You agree to indemnify,
186 | defend, and hold each Contributor harmless for any liability
187 | incurred by, or claims asserted against, such Contributor by reason
188 | of your accepting any such warranty or additional liability.
189 |
--------------------------------------------------------------------------------
/chrome_ex_oauthsimple.js:
--------------------------------------------------------------------------------
1 | /* OAuthSimple
2 | * A simpler version of OAuth
3 | *
4 | * author: jr conlin
5 | * mail: src@anticipatr.com
6 | * copyright: unitedHeroes.net
7 | * version: 1.0
8 | * url: http://unitedHeroes.net/OAuthSimple
9 | *
10 | * Copyright (c) 2009, unitedHeroes.net
11 | * All rights reserved.
12 | *
13 | * Redistribution and use in source and binary forms, with or without
14 | * modification, are permitted provided that the following conditions are met:
15 | * * Redistributions of source code must retain the above copyright
16 | * notice, this list of conditions and the following disclaimer.
17 | * * Redistributions in binary form must reproduce the above copyright
18 | * notice, this list of conditions and the following disclaimer in the
19 | * documentation and/or other materials provided with the distribution.
20 | * * Neither the name of the unitedHeroes.net nor the
21 | * names of its contributors may be used to endorse or promote products
22 | * derived from this software without specific prior written permission.
23 | *
24 | * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
25 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 | * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
28 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 | */
35 | var OAuthSimple;
36 |
37 | if (OAuthSimple === undefined)
38 | {
39 | /* Simple OAuth
40 | *
41 | * This class only builds the OAuth elements, it does not do the actual
42 | * transmission or reception of the tokens. It does not validate elements
43 | * of the token. It is for client use only.
44 | *
45 | * api_key is the API key, also known as the OAuth consumer key
46 | * shared_secret is the shared secret (duh).
47 | *
48 | * Both the api_key and shared_secret are generally provided by the site
49 | * offering OAuth services. You need to specify them at object creation
50 | * because nobody ing uses OAuth without that minimal set of
51 | * signatures.
52 | *
53 | * If you want to use the higher order security that comes from the
54 | * OAuth token (sorry, I don't provide the functions to fetch that because
55 | * sites aren't horribly consistent about how they offer that), you need to
56 | * pass those in either with .setTokensAndSecrets() or as an argument to the
57 | * .sign() or .getHeaderString() functions.
58 | *
59 | * Example:
60 |
61 | var oauthObject = OAuthSimple().sign({path:'http://example.com/rest/',
62 | parameters: 'foo=bar&gorp=banana',
63 | signatures:{
64 | api_key:'12345abcd',
65 | shared_secret:'xyz-5309'
66 | }});
67 | document.getElementById('someLink').href=oauthObject.signed_url;
68 |
69 | *
70 | * that will sign as a "GET" using "SHA1-MAC" the url. If you need more than
71 | * that, read on, McDuff.
72 | */
73 |
74 | /** OAuthSimple creator
75 | *
76 | * Create an instance of OAuthSimple
77 | *
78 | * @param api_key {string} The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use.
79 | * @param shared_secret (string) The shared secret. This value is also usually provided by the site you wish to use.
80 | */
81 | OAuthSimple = function (consumer_key,shared_secret)
82 | {
83 | /* if (api_key == undefined)
84 | throw("Missing argument: api_key (oauth_consumer_key) for OAuthSimple. This is usually provided by the hosting site.");
85 | if (shared_secret == undefined)
86 | throw("Missing argument: shared_secret (shared secret) for OAuthSimple. This is usually provided by the hosting site.");
87 | */ this._secrets={};
88 | this._parameters={};
89 |
90 | // General configuration options.
91 | if (consumer_key !== undefined) {
92 | this._secrets['consumer_key'] = consumer_key;
93 | }
94 | if (shared_secret !== undefined) {
95 | this._secrets['shared_secret'] = shared_secret;
96 | }
97 | this._default_signature_method= "HMAC-SHA1";
98 | this._action = "GET";
99 | this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
100 |
101 |
102 | this.reset = function() {
103 | this._parameters={};
104 | this._path=undefined;
105 | return this;
106 | };
107 |
108 | /** set the parameters either from a hash or a string
109 | *
110 | * @param {string,object} List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash)
111 | */
112 | this.setParameters = function (parameters) {
113 | if (parameters === undefined) {
114 | parameters = {};
115 | }
116 | if (typeof(parameters) == 'string') {
117 | parameters=this._parseParameterString(parameters);
118 | }
119 | this._parameters = parameters;
120 | if (this._parameters['oauth_nonce'] === undefined) {
121 | this._getNonce();
122 | }
123 | if (this._parameters['oauth_timestamp'] === undefined) {
124 | this._getTimestamp();
125 | }
126 | if (this._parameters['oauth_method'] === undefined) {
127 | this.setSignatureMethod();
128 | }
129 | if (this._parameters['oauth_consumer_key'] === undefined) {
130 | this._getApiKey();
131 | }
132 | if(this._parameters['oauth_token'] === undefined) {
133 | this._getAccessToken();
134 | }
135 |
136 | return this;
137 | };
138 |
139 | /** convienence method for setParameters
140 | *
141 | * @param parameters {string,object} See .setParameters
142 | */
143 | this.setQueryString = function (parameters) {
144 | return this.setParameters(parameters);
145 | };
146 |
147 | /** Set the target URL (does not include the parameters)
148 | *
149 | * @param path {string} the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
150 | */
151 | this.setURL = function (path) {
152 | if (path == '') {
153 | throw ('No path specified for OAuthSimple.setURL');
154 | }
155 | this._path = path;
156 | return this;
157 | };
158 |
159 | /** convienence method for setURL
160 | *
161 | * @param path {string} see .setURL
162 | */
163 | this.setPath = function(path){
164 | return this.setURL(path);
165 | };
166 |
167 | /** set the "action" for the url, (e.g. GET,POST, DELETE, etc.)
168 | *
169 | * @param action {string} HTTP Action word.
170 | */
171 | this.setAction = function(action) {
172 | if (action === undefined) {
173 | action="GET";
174 | }
175 | action = action.toUpperCase();
176 | if (action.match('[^A-Z]')) {
177 | throw ('Invalid action specified for OAuthSimple.setAction');
178 | }
179 | this._action = action;
180 | return this;
181 | };
182 |
183 | /** set the signatures (as well as validate the ones you have)
184 | *
185 | * @param signatures {object} object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:}
186 | */
187 | this.setTokensAndSecrets = function(signatures) {
188 | if (signatures)
189 | {
190 | for (var i in signatures) {
191 | this._secrets[i] = signatures[i];
192 | }
193 | }
194 | // Aliases
195 | if (this._secrets['api_key']) {
196 | this._secrets.consumer_key = this._secrets.api_key;
197 | }
198 | if (this._secrets['access_token']) {
199 | this._secrets.oauth_token = this._secrets.access_token;
200 | }
201 | if (this._secrets['access_secret']) {
202 | this._secrets.oauth_secret = this._secrets.access_secret;
203 | }
204 | // Gauntlet
205 | if (this._secrets.consumer_key === undefined) {
206 | throw('Missing required consumer_key in OAuthSimple.setTokensAndSecrets');
207 | }
208 | if (this._secrets.shared_secret === undefined) {
209 | throw('Missing required shared_secret in OAuthSimple.setTokensAndSecrets');
210 | }
211 | if ((this._secrets.oauth_token !== undefined) && (this._secrets.oauth_secret === undefined)) {
212 | throw('Missing oauth_secret for supplied oauth_token in OAuthSimple.setTokensAndSecrets');
213 | }
214 | return this;
215 | };
216 |
217 | /** set the signature method (currently only Plaintext or SHA-MAC1)
218 | *
219 | * @param method {string} Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now)
220 | */
221 | this.setSignatureMethod = function(method) {
222 | if (method === undefined) {
223 | method = this._default_signature_method;
224 | }
225 | //TODO: accept things other than PlainText or SHA-MAC1
226 | if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) === undefined) {
227 | throw ('Unknown signing method specified for OAuthSimple.setSignatureMethod');
228 | }
229 | this._parameters['oauth_signature_method']= method.toUpperCase();
230 | return this;
231 | };
232 |
233 | /** sign the request
234 | *
235 | * note: all arguments are optional, provided you've set them using the
236 | * other helper functions.
237 | *
238 | * @param args {object} hash of arguments for the call
239 | * {action:, path:, parameters:, method:, signatures:}
240 | * all arguments are optional.
241 | */
242 | this.sign = function (args) {
243 | if (args === undefined) {
244 | args = {};
245 | }
246 | // Set any given parameters
247 | if(args['action'] !== undefined) {
248 | this.setAction(args['action']);
249 | }
250 | if (args['path'] !== undefined) {
251 | this.setPath(args['path']);
252 | }
253 | if (args['method'] !== undefined) {
254 | this.setSignatureMethod(args['method']);
255 | }
256 | this.setTokensAndSecrets(args['signatures']);
257 | if (args['parameters'] !== undefined){
258 | this.setParameters(args['parameters']);
259 | }
260 | // check the parameters
261 | var normParams = this._normalizedParameters();
262 | this._parameters['oauth_signature']=this._generateSignature(normParams);
263 | return {
264 | parameters: this._parameters,
265 | signature: this._oauthEscape(this._parameters['oauth_signature']),
266 | signed_url: this._path + '?' + this._normalizedParameters(),
267 | header: this.getHeaderString()
268 | };
269 | };
270 |
271 | /** Return a formatted "header" string
272 | *
273 | * NOTE: This doesn't set the "Authorization: " prefix, which is required.
274 | * I don't set it because various set header functions prefer different
275 | * ways to do that.
276 | *
277 | * @param args {object} see .sign
278 | */
279 | this.getHeaderString = function(args) {
280 | if (this._parameters['oauth_signature'] === undefined) {
281 | this.sign(args);
282 | }
283 |
284 | var result = 'OAuth ';
285 | for (var pName in this._parameters)
286 | {
287 | if (!pName.match(/^oauth/)) {
288 | continue;
289 | }
290 | if ((this._parameters[pName]) instanceof Array)
291 | {
292 | var pLength = this._parameters[pName].length;
293 | for (var j=0;j>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
398 | }
399 |
400 |
401 | this._normalizedParameters = function() {
402 | var elements = new Array();
403 | var paramNames = [];
404 | var ra =0;
405 | for (var paramName in this._parameters)
406 | {
407 | if (ra++ > 1000) {
408 | throw('runaway 1');
409 | }
410 | paramNames.unshift(paramName);
411 | }
412 | paramNames = paramNames.sort();
413 | pLen = paramNames.length;
414 | for (var i=0;i 1000) {
427 | throw('runaway 1');
428 | }
429 | elements.push(this._oauthEscape(paramName) + '=' +
430 | this._oauthEscape(sorted[j]));
431 | }
432 | continue;
433 | }
434 | elements.push(this._oauthEscape(paramName) + '=' +
435 | this._oauthEscape(this._parameters[paramName]));
436 | }
437 | return elements.join('&');
438 | };
439 |
440 | this._generateSignature = function() {
441 |
442 | var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+
443 | this._oauthEscape(this._secrets.oauth_secret);
444 | if (this._parameters['oauth_signature_method'] == 'PLAINTEXT')
445 | {
446 | return secretKey;
447 | }
448 | if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1')
449 | {
450 | var sigString = this._oauthEscape(this._action)+'&'+this._oauthEscape(this._path)+'&'+this._oauthEscape(this._normalizedParameters());
451 | return this.b64_hmac_sha1(secretKey,sigString);
452 | }
453 | return null;
454 | };
455 |
456 | return this;
457 | };
458 | }
459 |
--------------------------------------------------------------------------------
/chrome_ex_oauth.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | /**
6 | * Constructor - no need to invoke directly, call initBackgroundPage instead.
7 | * @constructor
8 | * @param {String} url_request_token The OAuth request token URL.
9 | * @param {String} url_auth_token The OAuth authorize token URL.
10 | * @param {String} url_access_token The OAuth access token URL.
11 | * @param {String} consumer_key The OAuth consumer key.
12 | * @param {String} consumer_secret The OAuth consumer secret.
13 | * @param {String} oauth_scope The OAuth scope parameter.
14 | * @param {Object} opt_args Optional arguments. Recognized parameters:
15 | * "app_name" {String} Name of the current application
16 | * "callback_page" {String} If you renamed chrome_ex_oauth.html, the name
17 | * this file was renamed to.
18 | */
19 | function ChromeExOAuth(url_request_token, url_auth_token, url_access_token,
20 | consumer_key, consumer_secret, oauth_scope, opt_args) {
21 | this.url_request_token = url_request_token;
22 | this.url_auth_token = url_auth_token;
23 | this.url_access_token = url_access_token;
24 | this.consumer_key = consumer_key;
25 | this.consumer_secret = consumer_secret;
26 | this.oauth_scope = oauth_scope;
27 | this.app_name = opt_args && opt_args['app_name'] ||
28 | "ChromeExOAuth Library";
29 | this.key_token = "oauth_token";
30 | this.key_token_secret = "oauth_token_secret";
31 | this.callback_page = opt_args && opt_args['callback_page'] ||
32 | "chrome_ex_oauth.html";
33 | this.auth_params = {};
34 | if (opt_args && opt_args['auth_params']) {
35 | for (key in opt_args['auth_params']) {
36 | if (opt_args['auth_params'].hasOwnProperty(key)) {
37 | this.auth_params[key] = opt_args['auth_params'][key];
38 | }
39 | }
40 | }
41 | };
42 |
43 | /*******************************************************************************
44 | * PUBLIC API METHODS
45 | * Call these from your background page.
46 | ******************************************************************************/
47 |
48 | /**
49 | * Initializes the OAuth helper from the background page. You must call this
50 | * before attempting to make any OAuth calls.
51 | * @param {Object} oauth_config Configuration parameters in a JavaScript object.
52 | * The following parameters are recognized:
53 | * "request_url" {String} OAuth request token URL.
54 | * "authorize_url" {String} OAuth authorize token URL.
55 | * "access_url" {String} OAuth access token URL.
56 | * "consumer_key" {String} OAuth consumer key.
57 | * "consumer_secret" {String} OAuth consumer secret.
58 | * "scope" {String} OAuth access scope.
59 | * "app_name" {String} Application name.
60 | * "auth_params" {Object} Additional parameters to pass to the
61 | * Authorization token URL. For an example, 'hd', 'hl', 'btmpl':
62 | * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
63 | * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
64 | */
65 | ChromeExOAuth.initBackgroundPage = function(oauth_config) {
66 | window.chromeExOAuthConfig = oauth_config;
67 | window.chromeExOAuth = ChromeExOAuth.fromConfig(oauth_config);
68 | window.chromeExOAuthRedirectStarted = false;
69 | window.chromeExOAuthRequestingAccess = false;
70 |
71 | var url_match = chrome.extension.getURL(window.chromeExOAuth.callback_page);
72 | var tabs = {};
73 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
74 | if (changeInfo.url &&
75 | changeInfo.url.substr(0, url_match.length) === url_match &&
76 | changeInfo.url != tabs[tabId] &&
77 | window.chromeExOAuthRequestingAccess == false) {
78 | chrome.tabs.create({ 'url' : changeInfo.url }, function(tab) {
79 | tabs[tab.id] = tab.url;
80 | chrome.tabs.remove(tabId);
81 | });
82 | }
83 | });
84 |
85 | return window.chromeExOAuth;
86 | };
87 |
88 | /**
89 | * Authorizes the current user with the configued API. You must call this
90 | * before calling sendSignedRequest.
91 | * @param {Function} callback A function to call once an access token has
92 | * been obtained. This callback will be passed the following arguments:
93 | * token {String} The OAuth access token.
94 | * secret {String} The OAuth access token secret.
95 | */
96 | ChromeExOAuth.prototype.authorize = function(callback) {
97 | if (this.hasToken()) {
98 | callback(this.getToken(), this.getTokenSecret());
99 | } else {
100 | window.chromeExOAuthOnAuthorize = function(token, secret) {
101 | callback(token, secret);
102 | };
103 | chrome.tabs.create({ 'url' :chrome.extension.getURL(this.callback_page) });
104 | }
105 | };
106 |
107 | /**
108 | * Clears any OAuth tokens stored for this configuration. Effectively a
109 | * "logout" of the configured OAuth API.
110 | */
111 | ChromeExOAuth.prototype.clearTokens = function() {
112 | delete localStorage[this.key_token + encodeURI(this.oauth_scope)];
113 | delete localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
114 | };
115 |
116 | /**
117 | * Returns whether a token is currently stored for this configuration.
118 | * Effectively a check to see whether the current user is "logged in" to
119 | * the configured OAuth API.
120 | * @return {Boolean} True if an access token exists.
121 | */
122 | ChromeExOAuth.prototype.hasToken = function() {
123 | return !!this.getToken();
124 | };
125 |
126 | /**
127 | * Makes an OAuth-signed HTTP request with the currently authorized tokens.
128 | * @param {String} url The URL to send the request to. Querystring parameters
129 | * should be omitted.
130 | * @param {Function} callback A function to be called once the request is
131 | * completed. This callback will be passed the following arguments:
132 | * responseText {String} The text response.
133 | * xhr {XMLHttpRequest} The XMLHttpRequest object which was used to
134 | * send the request. Useful if you need to check response status
135 | * code, etc.
136 | * @param {Object} opt_params Additional parameters to configure the request.
137 | * The following parameters are accepted:
138 | * "method" {String} The HTTP method to use. Defaults to "GET".
139 | * "body" {String} A request body to send. Defaults to null.
140 | * "parameters" {Object} Query parameters to include in the request.
141 | * "headers" {Object} Additional headers to include in the request.
142 | */
143 | ChromeExOAuth.prototype.sendSignedRequest = function(url, callback,
144 | opt_params) {
145 | var method = opt_params && opt_params['method'] || 'GET';
146 | var body = opt_params && opt_params['body'] || null;
147 | var params = opt_params && opt_params['parameters'] || {};
148 | var headers = opt_params && opt_params['headers'] || {};
149 |
150 | var signedUrl = this.signURL(url, method, params);
151 |
152 | ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) {
153 | if (xhr.readyState == 4) {
154 | callback(xhr.responseText, xhr);
155 | }
156 | });
157 | };
158 |
159 | /**
160 | * Adds the required OAuth parameters to the given url and returns the
161 | * result. Useful if you need a signed url but don't want to make an XHR
162 | * request.
163 | * @param {String} method The http method to use.
164 | * @param {String} url The base url of the resource you are querying.
165 | * @param {Object} opt_params Query parameters to include in the request.
166 | * @return {String} The base url plus any query params plus any OAuth params.
167 | */
168 | ChromeExOAuth.prototype.signURL = function(url, method, opt_params) {
169 | var token = this.getToken();
170 | var secret = this.getTokenSecret();
171 | if (!token || !secret) {
172 | throw new Error("No oauth token or token secret");
173 | }
174 |
175 | var params = opt_params || {};
176 |
177 | var result = OAuthSimple().sign({
178 | action : method,
179 | path : url,
180 | parameters : params,
181 | signatures: {
182 | consumer_key : this.consumer_key,
183 | shared_secret : this.consumer_secret,
184 | oauth_secret : secret,
185 | oauth_token: token
186 | }
187 | });
188 |
189 | return result.signed_url;
190 | };
191 |
192 | /**
193 | * Generates the Authorization header based on the oauth parameters.
194 | * @param {String} url The base url of the resource you are querying.
195 | * @param {Object} opt_params Query parameters to include in the request.
196 | * @return {String} An Authorization header containing the oauth_* params.
197 | */
198 | ChromeExOAuth.prototype.getAuthorizationHeader = function(url, method,
199 | opt_params) {
200 | var token = this.getToken();
201 | var secret = this.getTokenSecret();
202 | if (!token || !secret) {
203 | throw new Error("No oauth token or token secret");
204 | }
205 |
206 | var params = opt_params || {};
207 |
208 | return OAuthSimple().getHeaderString({
209 | action: method,
210 | path : url,
211 | parameters : params,
212 | signatures: {
213 | consumer_key : this.consumer_key,
214 | shared_secret : this.consumer_secret,
215 | oauth_secret : secret,
216 | oauth_token: token
217 | }
218 | });
219 | };
220 |
221 | /*******************************************************************************
222 | * PRIVATE API METHODS
223 | * Used by the library. There should be no need to call these methods directly.
224 | ******************************************************************************/
225 |
226 | /**
227 | * Creates a new ChromeExOAuth object from the supplied configuration object.
228 | * @param {Object} oauth_config Configuration parameters in a JavaScript object.
229 | * The following parameters are recognized:
230 | * "request_url" {String} OAuth request token URL.
231 | * "authorize_url" {String} OAuth authorize token URL.
232 | * "access_url" {String} OAuth access token URL.
233 | * "consumer_key" {String} OAuth consumer key.
234 | * "consumer_secret" {String} OAuth consumer secret.
235 | * "scope" {String} OAuth access scope.
236 | * "app_name" {String} Application name.
237 | * "auth_params" {Object} Additional parameters to pass to the
238 | * Authorization token URL. For an example, 'hd', 'hl', 'btmpl':
239 | * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
240 | * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
241 | */
242 | ChromeExOAuth.fromConfig = function(oauth_config) {
243 | return new ChromeExOAuth(
244 | oauth_config['request_url'],
245 | oauth_config['authorize_url'],
246 | oauth_config['access_url'],
247 | oauth_config['consumer_key'],
248 | oauth_config['consumer_secret'],
249 | oauth_config['scope'],
250 | {
251 | 'app_name' : oauth_config['app_name'],
252 | 'auth_params' : oauth_config['auth_params']
253 | }
254 | );
255 | };
256 |
257 | /**
258 | * Initializes chrome_ex_oauth.html and redirects the page if needed to start
259 | * the OAuth flow. Once an access token is obtained, this function closes
260 | * chrome_ex_oauth.html.
261 | */
262 | ChromeExOAuth.initCallbackPage = function() {
263 | var background_page = chrome.extension.getBackgroundPage();
264 | var oauth_config = background_page.chromeExOAuthConfig;
265 | var oauth = ChromeExOAuth.fromConfig(oauth_config);
266 | background_page.chromeExOAuthRedirectStarted = true;
267 | oauth.initOAuthFlow(function (token, secret) {
268 | background_page.chromeExOAuthOnAuthorize(token, secret);
269 | background_page.chromeExOAuthRedirectStarted = false;
270 | chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
271 | chrome.tabs.remove(tabs[0].id);
272 | });
273 | });
274 | };
275 |
276 | /**
277 | * Sends an HTTP request. Convenience wrapper for XMLHttpRequest calls.
278 | * @param {String} method The HTTP method to use.
279 | * @param {String} url The URL to send the request to.
280 | * @param {Object} headers Optional request headers in key/value format.
281 | * @param {String} body Optional body content.
282 | * @param {Function} callback Function to call when the XMLHttpRequest's
283 | * ready state changes. See documentation for XMLHttpRequest's
284 | * onreadystatechange handler for more information.
285 | */
286 | ChromeExOAuth.sendRequest = function(method, url, headers, body, callback) {
287 | var xhr = new XMLHttpRequest();
288 | xhr.onreadystatechange = function(data) {
289 | callback(xhr, data);
290 | }
291 | xhr.open(method, url, true);
292 | if (headers) {
293 | for (var header in headers) {
294 | if (headers.hasOwnProperty(header)) {
295 | xhr.setRequestHeader(header, headers[header]);
296 | }
297 | }
298 | }
299 | xhr.send(body);
300 | };
301 |
302 | /**
303 | * Decodes a URL-encoded string into key/value pairs.
304 | * @param {String} encoded An URL-encoded string.
305 | * @return {Object} An object representing the decoded key/value pairs found
306 | * in the encoded string.
307 | */
308 | ChromeExOAuth.formDecode = function(encoded) {
309 | var params = encoded.split("&");
310 | var decoded = {};
311 | for (var i = 0, param; param = params[i]; i++) {
312 | var keyval = param.split("=");
313 | if (keyval.length == 2) {
314 | var key = ChromeExOAuth.fromRfc3986(keyval[0]);
315 | var val = ChromeExOAuth.fromRfc3986(keyval[1]);
316 | decoded[key] = val;
317 | }
318 | }
319 | return decoded;
320 | };
321 |
322 | /**
323 | * Returns the current window's querystring decoded into key/value pairs.
324 | * @return {Object} A object representing any key/value pairs found in the
325 | * current window's querystring.
326 | */
327 | ChromeExOAuth.getQueryStringParams = function() {
328 | var urlparts = window.location.href.split("?");
329 | if (urlparts.length >= 2) {
330 | var querystring = urlparts.slice(1).join("?");
331 | return ChromeExOAuth.formDecode(querystring);
332 | }
333 | return {};
334 | };
335 |
336 | /**
337 | * Binds a function call to a specific object. This function will also take
338 | * a variable number of additional arguments which will be prepended to the
339 | * arguments passed to the bound function when it is called.
340 | * @param {Function} func The function to bind.
341 | * @param {Object} obj The object to bind to the function's "this".
342 | * @return {Function} A closure that will call the bound function.
343 | */
344 | ChromeExOAuth.bind = function(func, obj) {
345 | var newargs = Array.prototype.slice.call(arguments).slice(2);
346 | return function() {
347 | var combinedargs = newargs.concat(Array.prototype.slice.call(arguments));
348 | func.apply(obj, combinedargs);
349 | };
350 | };
351 |
352 | /**
353 | * Encodes a value according to the RFC3986 specification.
354 | * @param {String} val The string to encode.
355 | */
356 | ChromeExOAuth.toRfc3986 = function(val){
357 | return encodeURIComponent(val)
358 | .replace(/\!/g, "%21")
359 | .replace(/\*/g, "%2A")
360 | .replace(/'/g, "%27")
361 | .replace(/\(/g, "%28")
362 | .replace(/\)/g, "%29");
363 | };
364 |
365 | /**
366 | * Decodes a string that has been encoded according to RFC3986.
367 | * @param {String} val The string to decode.
368 | */
369 | ChromeExOAuth.fromRfc3986 = function(val){
370 | var tmp = val
371 | .replace(/%21/g, "!")
372 | .replace(/%2A/g, "*")
373 | .replace(/%27/g, "'")
374 | .replace(/%28/g, "(")
375 | .replace(/%29/g, ")");
376 | return decodeURIComponent(tmp);
377 | };
378 |
379 | /**
380 | * Adds a key/value parameter to the supplied URL.
381 | * @param {String} url An URL which may or may not contain querystring values.
382 | * @param {String} key A key
383 | * @param {String} value A value
384 | * @return {String} The URL with URL-encoded versions of the key and value
385 | * appended, prefixing them with "&" or "?" as needed.
386 | */
387 | ChromeExOAuth.addURLParam = function(url, key, value) {
388 | var sep = (url.indexOf('?') >= 0) ? "&" : "?";
389 | return url + sep +
390 | ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value);
391 | };
392 |
393 | /**
394 | * Stores an OAuth token for the configured scope.
395 | * @param {String} token The token to store.
396 | */
397 | ChromeExOAuth.prototype.setToken = function(token) {
398 | localStorage[this.key_token + encodeURI(this.oauth_scope)] = token;
399 | };
400 |
401 | /**
402 | * Retrieves any stored token for the configured scope.
403 | * @return {String} The stored token.
404 | */
405 | ChromeExOAuth.prototype.getToken = function() {
406 | return localStorage[this.key_token + encodeURI(this.oauth_scope)];
407 | };
408 |
409 | /**
410 | * Stores an OAuth token secret for the configured scope.
411 | * @param {String} secret The secret to store.
412 | */
413 | ChromeExOAuth.prototype.setTokenSecret = function(secret) {
414 | localStorage[this.key_token_secret + encodeURI(this.oauth_scope)] = secret;
415 | };
416 |
417 | /**
418 | * Retrieves any stored secret for the configured scope.
419 | * @return {String} The stored secret.
420 | */
421 | ChromeExOAuth.prototype.getTokenSecret = function() {
422 | return localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
423 | };
424 |
425 | /**
426 | * Starts an OAuth authorization flow for the current page. If a token exists,
427 | * no redirect is needed and the supplied callback is called immediately.
428 | * If this method detects that a redirect has finished, it grabs the
429 | * appropriate OAuth parameters from the URL and attempts to retrieve an
430 | * access token. If no token exists and no redirect has happened, then
431 | * an access token is requested and the page is ultimately redirected.
432 | * @param {Function} callback The function to call once the flow has finished.
433 | * This callback will be passed the following arguments:
434 | * token {String} The OAuth access token.
435 | * secret {String} The OAuth access token secret.
436 | */
437 | ChromeExOAuth.prototype.initOAuthFlow = function(callback) {
438 | if (!this.hasToken()) {
439 | var params = ChromeExOAuth.getQueryStringParams();
440 | if (params['chromeexoauthcallback'] == 'true') {
441 | var oauth_token = params['oauth_token'];
442 | var oauth_verifier = params['oauth_verifier']
443 | this.getAccessToken(oauth_token, oauth_verifier, callback);
444 | } else {
445 | var request_params = {
446 | 'url_callback_param' : 'chromeexoauthcallback'
447 | }
448 | this.getRequestToken(function(url) {
449 | window.location.href = url;
450 | }, request_params);
451 | }
452 | } else {
453 | callback(this.getToken(), this.getTokenSecret());
454 | }
455 | };
456 |
457 | /**
458 | * Requests an OAuth request token.
459 | * @param {Function} callback Function to call once the authorize URL is
460 | * calculated. This callback will be passed the following arguments:
461 | * url {String} The URL the user must be redirected to in order to
462 | * approve the token.
463 | * @param {Object} opt_args Optional arguments. The following parameters
464 | * are accepted:
465 | * "url_callback" {String} The URL the OAuth provider will redirect to.
466 | * "url_callback_param" {String} A parameter to include in the callback
467 | * URL in order to indicate to this library that a redirect has
468 | * taken place.
469 | */
470 | ChromeExOAuth.prototype.getRequestToken = function(callback, opt_args) {
471 | if (typeof callback !== "function") {
472 | throw new Error("Specified callback must be a function.");
473 | }
474 | var url = opt_args && opt_args['url_callback'] ||
475 | window && window.top && window.top.location &&
476 | window.top.location.href;
477 |
478 | var url_param = opt_args && opt_args['url_callback_param'] ||
479 | "chromeexoauthcallback";
480 | var url_callback = ChromeExOAuth.addURLParam(url, url_param, "true");
481 |
482 | var result = OAuthSimple().sign({
483 | path : this.url_request_token,
484 | parameters: {
485 | "xoauth_displayname" : this.app_name,
486 | "scope" : this.oauth_scope,
487 | "oauth_callback" : url_callback
488 | },
489 | signatures: {
490 | consumer_key : this.consumer_key,
491 | shared_secret : this.consumer_secret
492 | }
493 | });
494 | var onToken = ChromeExOAuth.bind(this.onRequestToken, this, callback);
495 | ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
496 | };
497 |
498 | /**
499 | * Called when a request token has been returned. Stores the request token
500 | * secret for later use and sends the authorization url to the supplied
501 | * callback (for redirecting the user).
502 | * @param {Function} callback Function to call once the authorize URL is
503 | * calculated. This callback will be passed the following arguments:
504 | * url {String} The URL the user must be redirected to in order to
505 | * approve the token.
506 | * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
507 | * request token.
508 | */
509 | ChromeExOAuth.prototype.onRequestToken = function(callback, xhr) {
510 | if (xhr.readyState == 4) {
511 | if (xhr.status == 200) {
512 | var params = ChromeExOAuth.formDecode(xhr.responseText);
513 | var token = params['oauth_token'];
514 | this.setTokenSecret(params['oauth_token_secret']);
515 | var url = ChromeExOAuth.addURLParam(this.url_auth_token,
516 | "oauth_token", token);
517 | for (var key in this.auth_params) {
518 | if (this.auth_params.hasOwnProperty(key)) {
519 | url = ChromeExOAuth.addURLParam(url, key, this.auth_params[key]);
520 | }
521 | }
522 | callback(url);
523 | } else {
524 | throw new Error("Fetching request token failed. Status " + xhr.status);
525 | }
526 | }
527 | };
528 |
529 | /**
530 | * Requests an OAuth access token.
531 | * @param {String} oauth_token The OAuth request token.
532 | * @param {String} oauth_verifier The OAuth token verifier.
533 | * @param {Function} callback The function to call once the token is obtained.
534 | * This callback will be passed the following arguments:
535 | * token {String} The OAuth access token.
536 | * secret {String} The OAuth access token secret.
537 | */
538 | ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier,
539 | callback) {
540 | if (typeof callback !== "function") {
541 | throw new Error("Specified callback must be a function.");
542 | }
543 | var bg = chrome.extension.getBackgroundPage();
544 | if (bg.chromeExOAuthRequestingAccess == false) {
545 | bg.chromeExOAuthRequestingAccess = true;
546 |
547 | var result = OAuthSimple().sign({
548 | path : this.url_access_token,
549 | parameters: {
550 | "oauth_token" : oauth_token,
551 | "oauth_verifier" : oauth_verifier
552 | },
553 | signatures: {
554 | consumer_key : this.consumer_key,
555 | shared_secret : this.consumer_secret,
556 | oauth_secret : this.getTokenSecret(this.oauth_scope)
557 | }
558 | });
559 |
560 | var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback);
561 | ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
562 | }
563 | };
564 |
565 | /**
566 | * Called when an access token has been returned. Stores the access token and
567 | * access token secret for later use and sends them to the supplied callback.
568 | * @param {Function} callback The function to call once the token is obtained.
569 | * This callback will be passed the following arguments:
570 | * token {String} The OAuth access token.
571 | * secret {String} The OAuth access token secret.
572 | * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
573 | * access token.
574 | */
575 | ChromeExOAuth.prototype.onAccessToken = function(callback, xhr) {
576 | if (xhr.readyState == 4) {
577 | var bg = chrome.extension.getBackgroundPage();
578 | if (xhr.status == 200) {
579 | var params = ChromeExOAuth.formDecode(xhr.responseText);
580 | var token = params["oauth_token"];
581 | var secret = params["oauth_token_secret"];
582 | this.setToken(token);
583 | this.setTokenSecret(secret);
584 | bg.chromeExOAuthRequestingAccess = false;
585 | callback(token, secret);
586 | } else {
587 | bg.chromeExOAuthRequestingAccess = false;
588 | throw new Error("Fetching access token failed with status " + xhr.status);
589 | }
590 | }
591 | };
--------------------------------------------------------------------------------
/twitter-text.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * twitter-text 2.0.0
3 | *
4 | * Copyright 2012 Twitter, Inc.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this work except in compliance with the License.
8 | * You may obtain a copy of the License at:
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | */
12 | (function (global, factory) {
13 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
14 | typeof define === 'function' && define.amd ? define(factory) :
15 | (global.twttr = global.twttr || {}, global.twttr.txt = factory());
16 | }(this, (function () { 'use strict';
17 |
18 | var cashtag = /[a-z]{1,6}(?:[._][a-z]{1,2})?/i;
19 |
20 | var punct = /\!'#%&'\(\)*\+,\\\-\.\/:;<=>\?@\[\]\^_{|}~\$/;
21 |
22 | // Builds a RegExp
23 | var regexSupplant = function (regex, map, flags) {
24 | flags = flags || '';
25 | if (typeof regex !== 'string') {
26 | if (regex.global && flags.indexOf('g') < 0) {
27 | flags += 'g';
28 | }
29 | if (regex.ignoreCase && flags.indexOf('i') < 0) {
30 | flags += 'i';
31 | }
32 | if (regex.multiline && flags.indexOf('m') < 0) {
33 | flags += 'm';
34 | }
35 |
36 | regex = regex.source;
37 | }
38 |
39 | return new RegExp(regex.replace(/#\{(\w+)\}/g, function (match, name) {
40 | var newRegex = map[name] || '';
41 | if (typeof newRegex !== 'string') {
42 | newRegex = newRegex.source;
43 | }
44 | return newRegex;
45 | }), flags);
46 | };
47 |
48 | var spacesGroup = /\x09-\x0D\x20\x85\xA0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000/;
49 |
50 | var spaces = regexSupplant(/[#{spacesGroup}]/, { spacesGroup: spacesGroup });
51 |
52 | var validCashtag = regexSupplant('(^|#{spaces})(\\$)(#{cashtag})(?=$|\\s|[#{punct}])', { cashtag: cashtag, spaces: spaces, punct: punct }, 'gi');
53 |
54 | var extractCashtagsWithIndices = function (text) {
55 | if (!text || text.indexOf('$') === -1) {
56 | return [];
57 | }
58 |
59 | var tags = [];
60 |
61 | text.replace(validCashtag, function (match, before, dollar, cashtag, offset, chunk) {
62 | var startPosition = offset + before.length;
63 | var endPosition = startPosition + cashtag.length + 1;
64 | tags.push({
65 | cashtag: cashtag,
66 | indices: [startPosition, endPosition]
67 | });
68 | });
69 |
70 | return tags;
71 | };
72 |
73 | var hashSigns = /[##]/;
74 |
75 | var endHashtagMatch = regexSupplant(/^(?:#{hashSigns}|:\/\/)/, { hashSigns: hashSigns });
76 |
77 | var validCCTLD = regexSupplant(RegExp('(?:(?:' + '한국|香港|澳門|新加坡|台灣|台湾|中國|中国|გე|ไทย|ලංකා|ഭാരതം|ಭಾರತ|భారత్|சிங்கப்பூர்|இலங்கை|இந்தியா|ଭାରତ|ભારત|ਭਾਰਤ|' + 'ভাৰত|ভারত|বাংলা|भारोत|भारतम्|भारत|ڀارت|پاکستان|موريتانيا|مليسيا|مصر|قطر|فلسطين|عمان|عراق|سورية|' + 'سودان|تونس|بھارت|بارت|ایران|امارات|المغرب|السعودية|الجزائر|الاردن|հայ|қаз|укр|срб|рф|мон|мкд|ею|' + 'бел|бг|ελ|zw|zm|za|yt|ye|ws|wf|vu|vn|vi|vg|ve|vc|va|uz|uy|us|um|uk|ug|ua|tz|tw|tv|tt|tr|tp|to|' + 'tn|tm|tl|tk|tj|th|tg|tf|td|tc|sz|sy|sx|sv|su|st|ss|sr|so|sn|sm|sl|sk|sj|si|sh|sg|se|sd|sc|sb|sa|' + 'rw|ru|rs|ro|re|qa|py|pw|pt|ps|pr|pn|pm|pl|pk|ph|pg|pf|pe|pa|om|nz|nu|nr|np|no|nl|ni|ng|nf|ne|nc|' + 'na|mz|my|mx|mw|mv|mu|mt|ms|mr|mq|mp|mo|mn|mm|ml|mk|mh|mg|mf|me|md|mc|ma|ly|lv|lu|lt|ls|lr|lk|li|' + 'lc|lb|la|kz|ky|kw|kr|kp|kn|km|ki|kh|kg|ke|jp|jo|jm|je|it|is|ir|iq|io|in|im|il|ie|id|hu|ht|hr|hn|' + 'hm|hk|gy|gw|gu|gt|gs|gr|gq|gp|gn|gm|gl|gi|gh|gg|gf|ge|gd|gb|ga|fr|fo|fm|fk|fj|fi|eu|et|es|er|eh|' + 'eg|ee|ec|dz|do|dm|dk|dj|de|cz|cy|cx|cw|cv|cu|cr|co|cn|cm|cl|ck|ci|ch|cg|cf|cd|cc|ca|bz|by|bw|bv|' + 'bt|bs|br|bq|bo|bn|bm|bl|bj|bi|bh|bg|bf|be|bd|bb|ba|az|ax|aw|au|at|as|ar|aq|ao|an|am|al|ai|ag|af|' + 'ae|ad|ac' + ')(?=[^0-9a-zA-Z@]|$))'));
78 |
79 | var invalidCharsGroup = /\uFFFE\uFEFF\uFFFF\u202A-\u202E/;
80 |
81 | // simple string interpolation
82 | var stringSupplant = function (str, map) {
83 | return str.replace(/#\{(\w+)\}/g, function (match, name) {
84 | return map[name] || '';
85 | });
86 | };
87 |
88 | var invalidDomainChars = stringSupplant('#{punct}#{spacesGroup}#{invalidCharsGroup}', { punct: punct, spacesGroup: spacesGroup, invalidCharsGroup: invalidCharsGroup });
89 |
90 | var validDomainChars = regexSupplant(/[^#{invalidDomainChars}]/, { invalidDomainChars: invalidDomainChars });
91 |
92 | var validDomainName = regexSupplant(/(?:(?:#{validDomainChars}(?:-|#{validDomainChars})*)?#{validDomainChars}\.)/, { validDomainChars: validDomainChars });
93 |
94 | var validGTLD = regexSupplant(RegExp('(?:(?:' + '삼성|닷컴|닷넷|香格里拉|餐厅|食品|飞利浦|電訊盈科|集团|通販|购物|谷歌|诺基亚|联通|网络|网站|网店|网址|组织机构|移动|珠宝|点看|游戏|淡马锡|机构|書籍|时尚|新闻|政府|' + '政务|手表|手机|我爱你|慈善|微博|广东|工行|家電|娱乐|天主教|大拿|大众汽车|在线|嘉里大酒店|嘉里|商标|商店|商城|公益|公司|八卦|健康|信息|佛山|企业|中文网|中信|世界|' + 'ポイント|ファッション|セール|ストア|コム|グーグル|クラウド|みんな|คอม|संगठन|नेट|कॉम|همراه|موقع|موبايلي|كوم|كاثوليك|عرب|شبكة|' + 'بيتك|بازار|العليان|ارامكو|اتصالات|ابوظبي|קום|сайт|рус|орг|онлайн|москва|ком|католик|дети|' + 'zuerich|zone|zippo|zip|zero|zara|zappos|yun|youtube|you|yokohama|yoga|yodobashi|yandex|yamaxun|' + 'yahoo|yachts|xyz|xxx|xperia|xin|xihuan|xfinity|xerox|xbox|wtf|wtc|wow|world|works|work|woodside|' + 'wolterskluwer|wme|winners|wine|windows|win|williamhill|wiki|wien|whoswho|weir|weibo|wedding|wed|' + 'website|weber|webcam|weatherchannel|weather|watches|watch|warman|wanggou|wang|walter|walmart|' + 'wales|vuelos|voyage|voto|voting|vote|volvo|volkswagen|vodka|vlaanderen|vivo|viva|vistaprint|' + 'vista|vision|visa|virgin|vip|vin|villas|viking|vig|video|viajes|vet|versicherung|' + 'vermögensberatung|vermögensberater|verisign|ventures|vegas|vanguard|vana|vacations|ups|uol|uno|' + 'university|unicom|uconnect|ubs|ubank|tvs|tushu|tunes|tui|tube|trv|trust|travelersinsurance|' + 'travelers|travelchannel|travel|training|trading|trade|toys|toyota|town|tours|total|toshiba|' + 'toray|top|tools|tokyo|today|tmall|tkmaxx|tjx|tjmaxx|tirol|tires|tips|tiffany|tienda|tickets|' + 'tiaa|theatre|theater|thd|teva|tennis|temasek|telefonica|telecity|tel|technology|tech|team|tdk|' + 'tci|taxi|tax|tattoo|tatar|tatamotors|target|taobao|talk|taipei|tab|systems|symantec|sydney|' + 'swiss|swiftcover|swatch|suzuki|surgery|surf|support|supply|supplies|sucks|style|study|studio|' + 'stream|store|storage|stockholm|stcgroup|stc|statoil|statefarm|statebank|starhub|star|staples|' + 'stada|srt|srl|spreadbetting|spot|spiegel|space|soy|sony|song|solutions|solar|sohu|software|' + 'softbank|social|soccer|sncf|smile|smart|sling|skype|sky|skin|ski|site|singles|sina|silk|shriram|' + 'showtime|show|shouji|shopping|shop|shoes|shiksha|shia|shell|shaw|sharp|shangrila|sfr|sexy|sex|' + 'sew|seven|ses|services|sener|select|seek|security|secure|seat|search|scot|scor|scjohnson|' + 'science|schwarz|schule|school|scholarships|schmidt|schaeffler|scb|sca|sbs|sbi|saxo|save|sas|' + 'sarl|sapo|sap|sanofi|sandvikcoromant|sandvik|samsung|samsclub|salon|sale|sakura|safety|safe|' + 'saarland|ryukyu|rwe|run|ruhr|rugby|rsvp|room|rogers|rodeo|rocks|rocher|rmit|rip|rio|ril|' + 'rightathome|ricoh|richardli|rich|rexroth|reviews|review|restaurant|rest|republican|report|' + 'repair|rentals|rent|ren|reliance|reit|reisen|reise|rehab|redumbrella|redstone|red|recipes|' + 'realty|realtor|realestate|read|raid|radio|racing|qvc|quest|quebec|qpon|pwc|pub|prudential|pru|' + 'protection|property|properties|promo|progressive|prof|productions|prod|pro|prime|press|praxi|' + 'pramerica|post|porn|politie|poker|pohl|pnc|plus|plumbing|playstation|play|place|pizza|pioneer|' + 'pink|ping|pin|pid|pictures|pictet|pics|piaget|physio|photos|photography|photo|phone|philips|phd|' + 'pharmacy|pfizer|pet|pccw|pay|passagens|party|parts|partners|pars|paris|panerai|panasonic|' + 'pamperedchef|page|ovh|ott|otsuka|osaka|origins|orientexpress|organic|org|orange|oracle|open|ooo|' + 'onyourside|online|onl|ong|one|omega|ollo|oldnavy|olayangroup|olayan|okinawa|office|off|observer|' + 'obi|nyc|ntt|nrw|nra|nowtv|nowruz|now|norton|northwesternmutual|nokia|nissay|nissan|ninja|nikon|' + 'nike|nico|nhk|ngo|nfl|nexus|nextdirect|next|news|newholland|new|neustar|network|netflix|netbank|' + 'net|nec|nba|navy|natura|nationwide|name|nagoya|nadex|nab|mutuelle|mutual|museum|mtr|mtpc|mtn|' + 'msd|movistar|movie|mov|motorcycles|moto|moscow|mortgage|mormon|mopar|montblanc|monster|money|' + 'monash|mom|moi|moe|moda|mobily|mobile|mobi|mma|mls|mlb|mitsubishi|mit|mint|mini|mil|microsoft|' + 'miami|metlife|merckmsd|meo|menu|men|memorial|meme|melbourne|meet|media|med|mckinsey|mcdonalds|' + 'mcd|mba|mattel|maserati|marshalls|marriott|markets|marketing|market|map|mango|management|man|' + 'makeup|maison|maif|madrid|macys|luxury|luxe|lupin|lundbeck|ltda|ltd|lplfinancial|lpl|love|lotto|' + 'lotte|london|lol|loft|locus|locker|loans|loan|lixil|living|live|lipsy|link|linde|lincoln|limo|' + 'limited|lilly|like|lighting|lifestyle|lifeinsurance|life|lidl|liaison|lgbt|lexus|lego|legal|' + 'lefrak|leclerc|lease|lds|lawyer|law|latrobe|latino|lat|lasalle|lanxess|landrover|land|lancome|' + 'lancia|lancaster|lamer|lamborghini|ladbrokes|lacaixa|kyoto|kuokgroup|kred|krd|kpn|kpmg|kosher|' + 'komatsu|koeln|kiwi|kitchen|kindle|kinder|kim|kia|kfh|kerryproperties|kerrylogistics|kerryhotels|' + 'kddi|kaufen|juniper|juegos|jprs|jpmorgan|joy|jot|joburg|jobs|jnj|jmp|jll|jlc|jio|jewelry|jetzt|' + 'jeep|jcp|jcb|java|jaguar|iwc|iveco|itv|itau|istanbul|ist|ismaili|iselect|irish|ipiranga|' + 'investments|intuit|international|intel|int|insure|insurance|institute|ink|ing|info|infiniti|' + 'industries|immobilien|immo|imdb|imamat|ikano|iinet|ifm|ieee|icu|ice|icbc|ibm|hyundai|hyatt|' + 'hughes|htc|hsbc|how|house|hotmail|hotels|hoteles|hot|hosting|host|hospital|horse|honeywell|' + 'honda|homesense|homes|homegoods|homedepot|holiday|holdings|hockey|hkt|hiv|hitachi|hisamitsu|' + 'hiphop|hgtv|hermes|here|helsinki|help|healthcare|health|hdfcbank|hdfc|hbo|haus|hangout|hamburg|' + 'hair|guru|guitars|guide|guge|gucci|guardian|group|grocery|gripe|green|gratis|graphics|grainger|' + 'gov|got|gop|google|goog|goodyear|goodhands|goo|golf|goldpoint|gold|godaddy|gmx|gmo|gmbh|gmail|' + 'globo|global|gle|glass|glade|giving|gives|gifts|gift|ggee|george|genting|gent|gea|gdn|gbiz|' + 'garden|gap|games|game|gallup|gallo|gallery|gal|fyi|futbol|furniture|fund|fun|fujixerox|fujitsu|' + 'ftr|frontier|frontdoor|frogans|frl|fresenius|free|fox|foundation|forum|forsale|forex|ford|' + 'football|foodnetwork|food|foo|fly|flsmidth|flowers|florist|flir|flights|flickr|fitness|fit|' + 'fishing|fish|firmdale|firestone|fire|financial|finance|final|film|fido|fidelity|fiat|ferrero|' + 'ferrari|feedback|fedex|fast|fashion|farmers|farm|fans|fan|family|faith|fairwinds|fail|fage|' + 'extraspace|express|exposed|expert|exchange|everbank|events|eus|eurovision|etisalat|esurance|' + 'estate|esq|erni|ericsson|equipment|epson|epost|enterprises|engineering|engineer|energy|emerck|' + 'email|education|edu|edeka|eco|eat|earth|dvr|dvag|durban|dupont|duns|dunlop|duck|dubai|dtv|drive|' + 'download|dot|doosan|domains|doha|dog|dodge|doctor|docs|dnp|diy|dish|discover|discount|directory|' + 'direct|digital|diet|diamonds|dhl|dev|design|desi|dentist|dental|democrat|delta|deloitte|dell|' + 'delivery|degree|deals|dealer|deal|dds|dclk|day|datsun|dating|date|data|dance|dad|dabur|cyou|' + 'cymru|cuisinella|csc|cruises|cruise|crs|crown|cricket|creditunion|creditcard|credit|courses|' + 'coupons|coupon|country|corsica|coop|cool|cookingchannel|cooking|contractors|contact|consulting|' + 'construction|condos|comsec|computer|compare|company|community|commbank|comcast|com|cologne|' + 'college|coffee|codes|coach|clubmed|club|cloud|clothing|clinique|clinic|click|cleaning|claims|' + 'cityeats|city|citic|citi|citadel|cisco|circle|cipriani|church|chrysler|chrome|christmas|chloe|' + 'chintai|cheap|chat|chase|channel|chanel|cfd|cfa|cern|ceo|center|ceb|cbs|cbre|cbn|cba|catholic|' + 'catering|cat|casino|cash|caseih|case|casa|cartier|cars|careers|career|care|cards|caravan|car|' + 'capitalone|capital|capetown|canon|cancerresearch|camp|camera|cam|calvinklein|call|cal|cafe|cab|' + 'bzh|buzz|buy|business|builders|build|bugatti|budapest|brussels|brother|broker|broadway|' + 'bridgestone|bradesco|box|boutique|bot|boston|bostik|bosch|boots|booking|book|boo|bond|bom|bofa|' + 'boehringer|boats|bnpparibas|bnl|bmw|bms|blue|bloomberg|blog|blockbuster|blanco|blackfriday|' + 'black|biz|bio|bingo|bing|bike|bid|bible|bharti|bet|bestbuy|best|berlin|bentley|beer|beauty|' + 'beats|bcn|bcg|bbva|bbt|bbc|bayern|bauhaus|basketball|baseball|bargains|barefoot|barclays|' + 'barclaycard|barcelona|bar|bank|band|bananarepublic|banamex|baidu|baby|azure|axa|aws|avianca|' + 'autos|auto|author|auspost|audio|audible|audi|auction|attorney|athleta|associates|asia|asda|arte|' + 'art|arpa|army|archi|aramco|arab|aquarelle|apple|app|apartments|aol|anz|anquan|android|analytics|' + 'amsterdam|amica|amfam|amex|americanfamily|americanexpress|alstom|alsace|ally|allstate|allfinanz|' + 'alipay|alibaba|alfaromeo|akdn|airtel|airforce|airbus|aigo|aig|agency|agakhan|africa|afl|' + 'afamilycompany|aetna|aero|aeg|adult|ads|adac|actor|active|aco|accountants|accountant|accenture|' + 'academy|abudhabi|abogado|able|abc|abbvie|abbott|abb|abarth|aarp|aaa|onion' + ')(?=[^0-9a-zA-Z@]|$))'));
95 |
96 | var validPunycode = /(?:xn--[\-0-9a-z]+)/;
97 |
98 | var validSubdomain = regexSupplant(/(?:(?:#{validDomainChars}(?:[_-]|#{validDomainChars})*)?#{validDomainChars}\.)/, { validDomainChars: validDomainChars });
99 |
100 | var validDomain = regexSupplant(/(?:#{validSubdomain}*#{validDomainName}(?:#{validGTLD}|#{validCCTLD}|#{validPunycode}))/, { validDomainName: validDomainName, validSubdomain: validSubdomain, validGTLD: validGTLD, validCCTLD: validCCTLD, validPunycode: validPunycode });
101 |
102 | var validPortNumber = /[0-9]+/;
103 |
104 | var cyrillicLettersAndMarks = /\u0400-\u04FF/;
105 |
106 | var latinAccentChars = /\xC0-\xD6\xD8-\xF6\xF8-\xFF\u0100-\u024F\u0253\u0254\u0256\u0257\u0259\u025B\u0263\u0268\u026F\u0272\u0289\u028B\u02BB\u0300-\u036F\u1E00-\u1EFF/;
107 |
108 | var validGeneralUrlPathChars = regexSupplant(/[a-z#{cyrillicLettersAndMarks}0-9!\*';:=\+,\.\$\/%#\[\]\-\u2013_~@\|{latinAccentChars}]/i, { cyrillicLettersAndMarks: cyrillicLettersAndMarks, latinAccentChars: latinAccentChars });
109 |
110 | // Allow URL paths to contain up to two nested levels of balanced parens
111 | // 1. Used in Wikipedia URLs like /Primer_(film)
112 | // 2. Used in IIS sessions like /S(dfd346)/
113 | // 3. Used in Rdio URLs like /track/We_Up_(Album_Version_(Edited))/
114 | var validUrlBalancedParens = regexSupplant('\\(' + '(?:' + '#{validGeneralUrlPathChars}+' + '|' +
115 | // allow one nested level of balanced parentheses
116 | '(?:' + '#{validGeneralUrlPathChars}*' + '\\(' + '#{validGeneralUrlPathChars}+' + '\\)' + '#{validGeneralUrlPathChars}*' + ')' + ')' + '\\)', { validGeneralUrlPathChars: validGeneralUrlPathChars }, 'i');
117 |
118 | // Valid end-of-path chracters (so /foo. does not gobble the period).
119 | // 1. Allow = for empty URL parameters and other URL-join artifacts
120 | var validUrlPathEndingChars = regexSupplant(/[\+\-a-z#{cyrillicLettersAndMarks}0-9=_#\/#{latinAccentChars}]|(?:#{validUrlBalancedParens})/i, { cyrillicLettersAndMarks: cyrillicLettersAndMarks, latinAccentChars: latinAccentChars, validUrlBalancedParens: validUrlBalancedParens });
121 |
122 | // Allow @ in a url, but only in the middle. Catch things like http://example.com/@user/
123 | var validUrlPath = regexSupplant('(?:' + '(?:' + '#{validGeneralUrlPathChars}*' + '(?:#{validUrlBalancedParens}#{validGeneralUrlPathChars}*)*' + '#{validUrlPathEndingChars}' + ')|(?:@#{validGeneralUrlPathChars}+\/)' + ')', {
124 | validGeneralUrlPathChars: validGeneralUrlPathChars,
125 | validUrlBalancedParens: validUrlBalancedParens,
126 | validUrlPathEndingChars: validUrlPathEndingChars
127 | }, 'i');
128 |
129 | var validUrlPrecedingChars = regexSupplant(/(?:[^A-Za-z0-9@@$###{invalidCharsGroup}]|^)/, { invalidCharsGroup: invalidCharsGroup });
130 |
131 | var validUrlQueryChars = /[a-z0-9!?\*'@\(\);:&=\+\$\/%#\[\]\-_\.,~|]/i;
132 |
133 | var validUrlQueryEndingChars = /[a-z0-9\-_&=#\/]/i;
134 |
135 | var extractUrl = regexSupplant('(' + // $1 total match
136 | '(#{validUrlPrecedingChars})' + // $2 Preceeding chracter
137 | '(' + // $3 URL
138 | '(https?:\\/\\/)?' + // $4 Protocol (optional)
139 | '(#{validDomain})' + // $5 Domain(s)
140 | '(?::(#{validPortNumber}))?' + // $6 Port number (optional)
141 | '(\\/#{validUrlPath}*)?' + // $7 URL Path
142 | '(\\?#{validUrlQueryChars}*#{validUrlQueryEndingChars})?' + // $8 Query String
143 | ')' + ')', { validUrlPrecedingChars: validUrlPrecedingChars, validDomain: validDomain, validPortNumber: validPortNumber, validUrlPath: validUrlPath, validUrlQueryChars: validUrlQueryChars, validUrlQueryEndingChars: validUrlQueryEndingChars }, 'gi');
144 |
145 | var invalidShortDomain = regexSupplant(/^#{validDomainName}#{validCCTLD}$/i, { validDomainName: validDomainName, validCCTLD: validCCTLD });
146 |
147 | var invalidUrlWithoutProtocolPrecedingChars = /[-_.\/]$/;
148 |
149 | var asyncGenerator = function () {
150 | function AwaitValue(value) {
151 | this.value = value;
152 | }
153 |
154 | function AsyncGenerator(gen) {
155 | var front, back;
156 |
157 | function send(key, arg) {
158 | return new Promise(function (resolve, reject) {
159 | var request = {
160 | key: key,
161 | arg: arg,
162 | resolve: resolve,
163 | reject: reject,
164 | next: null
165 | };
166 |
167 | if (back) {
168 | back = back.next = request;
169 | } else {
170 | front = back = request;
171 | resume(key, arg);
172 | }
173 | });
174 | }
175 |
176 | function resume(key, arg) {
177 | try {
178 | var result = gen[key](arg);
179 | var value = result.value;
180 |
181 | if (value instanceof AwaitValue) {
182 | Promise.resolve(value.value).then(function (arg) {
183 | resume("next", arg);
184 | }, function (arg) {
185 | resume("throw", arg);
186 | });
187 | } else {
188 | settle(result.done ? "return" : "normal", result.value);
189 | }
190 | } catch (err) {
191 | settle("throw", err);
192 | }
193 | }
194 |
195 | function settle(type, value) {
196 | switch (type) {
197 | case "return":
198 | front.resolve({
199 | value: value,
200 | done: true
201 | });
202 | break;
203 |
204 | case "throw":
205 | front.reject(value);
206 | break;
207 |
208 | default:
209 | front.resolve({
210 | value: value,
211 | done: false
212 | });
213 | break;
214 | }
215 |
216 | front = front.next;
217 |
218 | if (front) {
219 | resume(front.key, front.arg);
220 | } else {
221 | back = null;
222 | }
223 | }
224 |
225 | this._invoke = send;
226 |
227 | if (typeof gen.return !== "function") {
228 | this.return = undefined;
229 | }
230 | }
231 |
232 | if (typeof Symbol === "function" && Symbol.asyncIterator) {
233 | AsyncGenerator.prototype[Symbol.asyncIterator] = function () {
234 | return this;
235 | };
236 | }
237 |
238 | AsyncGenerator.prototype.next = function (arg) {
239 | return this._invoke("next", arg);
240 | };
241 |
242 | AsyncGenerator.prototype.throw = function (arg) {
243 | return this._invoke("throw", arg);
244 | };
245 |
246 | AsyncGenerator.prototype.return = function (arg) {
247 | return this._invoke("return", arg);
248 | };
249 |
250 | return {
251 | wrap: function (fn) {
252 | return function () {
253 | return new AsyncGenerator(fn.apply(this, arguments));
254 | };
255 | },
256 | await: function (value) {
257 | return new AwaitValue(value);
258 | }
259 | };
260 | }();
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 | var _extends = Object.assign || function (target) {
277 | for (var i = 1; i < arguments.length; i++) {
278 | var source = arguments[i];
279 |
280 | for (var key in source) {
281 | if (Object.prototype.hasOwnProperty.call(source, key)) {
282 | target[key] = source[key];
283 | }
284 | }
285 | }
286 |
287 | return target;
288 | };
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 | var toConsumableArray = function (arr) {
325 | if (Array.isArray(arr)) {
326 | for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
327 |
328 | return arr2;
329 | } else {
330 | return Array.from(arr);
331 | }
332 | };
333 |
334 | 'use strict';
335 |
336 | /** Highest positive signed 32-bit float value */
337 |
338 | var maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
339 |
340 | /** Bootstring parameters */
341 | var base = 36;
342 | var tMin = 1;
343 | var tMax = 26;
344 | var skew = 38;
345 | var damp = 700;
346 | var initialBias = 72;
347 | var initialN = 128; // 0x80
348 | var delimiter = '-'; // '\x2D'
349 |
350 | /** Regular expressions */
351 | var regexPunycode = /^xn--/;
352 | var regexNonASCII = /[^\0-\x7E]/; // non-ASCII chars
353 | var regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
354 |
355 | /** Error messages */
356 | var errors = {
357 | 'overflow': 'Overflow: input needs wider integers to process',
358 | 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
359 | 'invalid-input': 'Invalid input'
360 | };
361 |
362 | /** Convenience shortcuts */
363 | var baseMinusTMin = base - tMin;
364 | var floor = Math.floor;
365 | var stringFromCharCode = String.fromCharCode;
366 |
367 | /*--------------------------------------------------------------------------*/
368 |
369 | /**
370 | * A generic error utility function.
371 | * @private
372 | * @param {String} type The error type.
373 | * @returns {Error} Throws a `RangeError` with the applicable error message.
374 | */
375 | function error(type) {
376 | throw new RangeError(errors[type]);
377 | }
378 |
379 | /**
380 | * A generic `Array#map` utility function.
381 | * @private
382 | * @param {Array} array The array to iterate over.
383 | * @param {Function} callback The function that gets called for every array
384 | * item.
385 | * @returns {Array} A new array of values returned by the callback function.
386 | */
387 | function map(array, fn) {
388 | var result = [];
389 | var length = array.length;
390 | while (length--) {
391 | result[length] = fn(array[length]);
392 | }
393 | return result;
394 | }
395 |
396 | /**
397 | * A simple `Array#map`-like wrapper to work with domain name strings or email
398 | * addresses.
399 | * @private
400 | * @param {String} domain The domain name or email address.
401 | * @param {Function} callback The function that gets called for every
402 | * character.
403 | * @returns {Array} A new string of characters returned by the callback
404 | * function.
405 | */
406 | function mapDomain(string, fn) {
407 | var parts = string.split('@');
408 | var result = '';
409 | if (parts.length > 1) {
410 | // In email addresses, only the domain name should be punycoded. Leave
411 | // the local part (i.e. everything up to `@`) intact.
412 | result = parts[0] + '@';
413 | string = parts[1];
414 | }
415 | // Avoid `split(regex)` for IE8 compatibility. See #17.
416 | string = string.replace(regexSeparators, '\x2E');
417 | var labels = string.split('.');
418 | var encoded = map(labels, fn).join('.');
419 | return result + encoded;
420 | }
421 |
422 | /**
423 | * Creates an array containing the numeric code points of each Unicode
424 | * character in the string. While JavaScript uses UCS-2 internally,
425 | * this function will convert a pair of surrogate halves (each of which
426 | * UCS-2 exposes as separate characters) into a single code point,
427 | * matching UTF-16.
428 | * @see `punycode.ucs2.encode`
429 | * @see
430 | * @memberOf punycode.ucs2
431 | * @name decode
432 | * @param {String} string The Unicode input string (UCS-2).
433 | * @returns {Array} The new array of code points.
434 | */
435 | function ucs2decode(string) {
436 | var output = [];
437 | var counter = 0;
438 | var length = string.length;
439 | while (counter < length) {
440 | var value = string.charCodeAt(counter++);
441 | if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
442 | // It's a high surrogate, and there is a next character.
443 | var extra = string.charCodeAt(counter++);
444 | if ((extra & 0xFC00) == 0xDC00) {
445 | // Low surrogate.
446 | output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
447 | } else {
448 | // It's an unmatched surrogate; only append this code unit, in case the
449 | // next code unit is the high surrogate of a surrogate pair.
450 | output.push(value);
451 | counter--;
452 | }
453 | } else {
454 | output.push(value);
455 | }
456 | }
457 | return output;
458 | }
459 |
460 | /**
461 | * Creates a string based on an array of numeric code points.
462 | * @see `punycode.ucs2.decode`
463 | * @memberOf punycode.ucs2
464 | * @name encode
465 | * @param {Array} codePoints The array of numeric code points.
466 | * @returns {String} The new Unicode string (UCS-2).
467 | */
468 | var ucs2encode = function ucs2encode(array) {
469 | return String.fromCodePoint.apply(String, toConsumableArray(array));
470 | };
471 |
472 | /**
473 | * Converts a basic code point into a digit/integer.
474 | * @see `digitToBasic()`
475 | * @private
476 | * @param {Number} codePoint The basic numeric code point value.
477 | * @returns {Number} The numeric value of a basic code point (for use in
478 | * representing integers) in the range `0` to `base - 1`, or `base` if
479 | * the code point does not represent a value.
480 | */
481 | var basicToDigit = function basicToDigit(codePoint) {
482 | if (codePoint - 0x30 < 0x0A) {
483 | return codePoint - 0x16;
484 | }
485 | if (codePoint - 0x41 < 0x1A) {
486 | return codePoint - 0x41;
487 | }
488 | if (codePoint - 0x61 < 0x1A) {
489 | return codePoint - 0x61;
490 | }
491 | return base;
492 | };
493 |
494 | /**
495 | * Converts a digit/integer into a basic code point.
496 | * @see `basicToDigit()`
497 | * @private
498 | * @param {Number} digit The numeric value of a basic code point.
499 | * @returns {Number} The basic code point whose value (when used for
500 | * representing integers) is `digit`, which needs to be in the range
501 | * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
502 | * used; else, the lowercase form is used. The behavior is undefined
503 | * if `flag` is non-zero and `digit` has no uppercase form.
504 | */
505 | var digitToBasic = function digitToBasic(digit, flag) {
506 | // 0..25 map to ASCII a..z or A..Z
507 | // 26..35 map to ASCII 0..9
508 | return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
509 | };
510 |
511 | /**
512 | * Bias adaptation function as per section 3.4 of RFC 3492.
513 | * https://tools.ietf.org/html/rfc3492#section-3.4
514 | * @private
515 | */
516 | var adapt = function adapt(delta, numPoints, firstTime) {
517 | var k = 0;
518 | delta = firstTime ? floor(delta / damp) : delta >> 1;
519 | delta += floor(delta / numPoints);
520 | for (; /* no initialization */delta > baseMinusTMin * tMax >> 1; k += base) {
521 | delta = floor(delta / baseMinusTMin);
522 | }
523 | return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
524 | };
525 |
526 | /**
527 | * Converts a Punycode string of ASCII-only symbols to a string of Unicode
528 | * symbols.
529 | * @memberOf punycode
530 | * @param {String} input The Punycode string of ASCII-only symbols.
531 | * @returns {String} The resulting string of Unicode symbols.
532 | */
533 | var decode = function decode(input) {
534 | // Don't use UCS-2.
535 | var output = [];
536 | var inputLength = input.length;
537 | var i = 0;
538 | var n = initialN;
539 | var bias = initialBias;
540 |
541 | // Handle the basic code points: let `basic` be the number of input code
542 | // points before the last delimiter, or `0` if there is none, then copy
543 | // the first basic code points to the output.
544 |
545 | var basic = input.lastIndexOf(delimiter);
546 | if (basic < 0) {
547 | basic = 0;
548 | }
549 |
550 | for (var j = 0; j < basic; ++j) {
551 | // if it's not a basic code point
552 | if (input.charCodeAt(j) >= 0x80) {
553 | error('not-basic');
554 | }
555 | output.push(input.charCodeAt(j));
556 | }
557 |
558 | // Main decoding loop: start just after the last delimiter if any basic code
559 | // points were copied; start at the beginning otherwise.
560 |
561 | for (var index = basic > 0 ? basic + 1 : 0; index < inputLength;) /* no final expression */{
562 |
563 | // `index` is the index of the next character to be consumed.
564 | // Decode a generalized variable-length integer into `delta`,
565 | // which gets added to `i`. The overflow checking is easier
566 | // if we increase `i` as we go, then subtract off its starting
567 | // value at the end to obtain `delta`.
568 | var oldi = i;
569 | for (var w = 1, k = base;; /* no condition */k += base) {
570 |
571 | if (index >= inputLength) {
572 | error('invalid-input');
573 | }
574 |
575 | var digit = basicToDigit(input.charCodeAt(index++));
576 |
577 | if (digit >= base || digit > floor((maxInt - i) / w)) {
578 | error('overflow');
579 | }
580 |
581 | i += digit * w;
582 | var t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
583 |
584 | if (digit < t) {
585 | break;
586 | }
587 |
588 | var baseMinusT = base - t;
589 | if (w > floor(maxInt / baseMinusT)) {
590 | error('overflow');
591 | }
592 |
593 | w *= baseMinusT;
594 | }
595 |
596 | var out = output.length + 1;
597 | bias = adapt(i - oldi, out, oldi == 0);
598 |
599 | // `i` was supposed to wrap around from `out` to `0`,
600 | // incrementing `n` each time, so we'll fix that now:
601 | if (floor(i / out) > maxInt - n) {
602 | error('overflow');
603 | }
604 |
605 | n += floor(i / out);
606 | i %= out;
607 |
608 | // Insert `n` at position `i` of the output.
609 | output.splice(i++, 0, n);
610 | }
611 |
612 | return String.fromCodePoint.apply(String, output);
613 | };
614 |
615 | /**
616 | * Converts a string of Unicode symbols (e.g. a domain name label) to a
617 | * Punycode string of ASCII-only symbols.
618 | * @memberOf punycode
619 | * @param {String} input The string of Unicode symbols.
620 | * @returns {String} The resulting Punycode string of ASCII-only symbols.
621 | */
622 | var encode = function encode(input) {
623 | var output = [];
624 |
625 | // Convert the input in UCS-2 to an array of Unicode code points.
626 | input = ucs2decode(input);
627 |
628 | // Cache the length.
629 | var inputLength = input.length;
630 |
631 | // Initialize the state.
632 | var n = initialN;
633 | var delta = 0;
634 | var bias = initialBias;
635 |
636 | // Handle the basic code points.
637 | var _iteratorNormalCompletion = true;
638 | var _didIteratorError = false;
639 | var _iteratorError = undefined;
640 |
641 | try {
642 | for (var _iterator = input[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
643 | var _currentValue2 = _step.value;
644 |
645 | if (_currentValue2 < 0x80) {
646 | output.push(stringFromCharCode(_currentValue2));
647 | }
648 | }
649 | } catch (err) {
650 | _didIteratorError = true;
651 | _iteratorError = err;
652 | } finally {
653 | try {
654 | if (!_iteratorNormalCompletion && _iterator.return) {
655 | _iterator.return();
656 | }
657 | } finally {
658 | if (_didIteratorError) {
659 | throw _iteratorError;
660 | }
661 | }
662 | }
663 |
664 | var basicLength = output.length;
665 | var handledCPCount = basicLength;
666 |
667 | // `handledCPCount` is the number of code points that have been handled;
668 | // `basicLength` is the number of basic code points.
669 |
670 | // Finish the basic string with a delimiter unless it's empty.
671 | if (basicLength) {
672 | output.push(delimiter);
673 | }
674 |
675 | // Main encoding loop:
676 | while (handledCPCount < inputLength) {
677 |
678 | // All non-basic code points < n have been handled already. Find the next
679 | // larger one:
680 | var m = maxInt;
681 | var _iteratorNormalCompletion2 = true;
682 | var _didIteratorError2 = false;
683 | var _iteratorError2 = undefined;
684 |
685 | try {
686 | for (var _iterator2 = input[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
687 | var currentValue = _step2.value;
688 |
689 | if (currentValue >= n && currentValue < m) {
690 | m = currentValue;
691 | }
692 | }
693 |
694 | // Increase `delta` enough to advance the decoder's state to ,
695 | // but guard against overflow.
696 | } catch (err) {
697 | _didIteratorError2 = true;
698 | _iteratorError2 = err;
699 | } finally {
700 | try {
701 | if (!_iteratorNormalCompletion2 && _iterator2.return) {
702 | _iterator2.return();
703 | }
704 | } finally {
705 | if (_didIteratorError2) {
706 | throw _iteratorError2;
707 | }
708 | }
709 | }
710 |
711 | var handledCPCountPlusOne = handledCPCount + 1;
712 | if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
713 | error('overflow');
714 | }
715 |
716 | delta += (m - n) * handledCPCountPlusOne;
717 | n = m;
718 |
719 | var _iteratorNormalCompletion3 = true;
720 | var _didIteratorError3 = false;
721 | var _iteratorError3 = undefined;
722 |
723 | try {
724 | for (var _iterator3 = input[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
725 | var _currentValue = _step3.value;
726 |
727 | if (_currentValue < n && ++delta > maxInt) {
728 | error('overflow');
729 | }
730 | if (_currentValue == n) {
731 | // Represent delta as a generalized variable-length integer.
732 | var q = delta;
733 | for (var k = base;; /* no condition */k += base) {
734 | var t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
735 | if (q < t) {
736 | break;
737 | }
738 | var qMinusT = q - t;
739 | var baseMinusT = base - t;
740 | output.push(stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)));
741 | q = floor(qMinusT / baseMinusT);
742 | }
743 |
744 | output.push(stringFromCharCode(digitToBasic(q, 0)));
745 | bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
746 | delta = 0;
747 | ++handledCPCount;
748 | }
749 | }
750 | } catch (err) {
751 | _didIteratorError3 = true;
752 | _iteratorError3 = err;
753 | } finally {
754 | try {
755 | if (!_iteratorNormalCompletion3 && _iterator3.return) {
756 | _iterator3.return();
757 | }
758 | } finally {
759 | if (_didIteratorError3) {
760 | throw _iteratorError3;
761 | }
762 | }
763 | }
764 |
765 | ++delta;
766 | ++n;
767 | }
768 | return output.join('');
769 | };
770 |
771 | /**
772 | * Converts a Punycode string representing a domain name or an email address
773 | * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
774 | * it doesn't matter if you call it on a string that has already been
775 | * converted to Unicode.
776 | * @memberOf punycode
777 | * @param {String} input The Punycoded domain name or email address to
778 | * convert to Unicode.
779 | * @returns {String} The Unicode representation of the given Punycode
780 | * string.
781 | */
782 | var toUnicode = function toUnicode(input) {
783 | return mapDomain(input, function (string) {
784 | return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string;
785 | });
786 | };
787 |
788 | /**
789 | * Converts a Unicode string representing a domain name or an email address to
790 | * Punycode. Only the non-ASCII parts of the domain name will be converted,
791 | * i.e. it doesn't matter if you call it with a domain that's already in
792 | * ASCII.
793 | * @memberOf punycode
794 | * @param {String} input The domain name or email address to convert, as a
795 | * Unicode string.
796 | * @returns {String} The Punycode representation of the given domain name or
797 | * email address.
798 | */
799 | var toASCII = function toASCII(input) {
800 | return mapDomain(input, function (string) {
801 | return regexNonASCII.test(string) ? 'xn--' + encode(string) : string;
802 | });
803 | };
804 |
805 | /*--------------------------------------------------------------------------*/
806 |
807 | /** Define the public API */
808 | var punycode = {
809 | /**
810 | * A string representing the current Punycode.js version number.
811 | * @memberOf punycode
812 | * @type String
813 | */
814 | 'version': '2.1.0',
815 | /**
816 | * An object of methods to convert from JavaScript's internal character
817 | * representation (UCS-2) to Unicode code points, and back.
818 | * @see
819 | * @memberOf punycode
820 | * @type Object
821 | */
822 | 'ucs2': {
823 | 'decode': ucs2decode,
824 | 'encode': ucs2encode
825 | },
826 | 'decode': decode,
827 | 'encode': encode,
828 | 'toASCII': toASCII,
829 | 'toUnicode': toUnicode
830 | };
831 |
832 | var punycode_1 = punycode;
833 |
834 | var validAsciiDomain = regexSupplant(/(?:(?:[\-a-z0-9#{latinAccentChars}]+)\.)+(?:#{validGTLD}|#{validCCTLD}|#{validPunycode})/gi, { latinAccentChars: latinAccentChars, validGTLD: validGTLD, validCCTLD: validCCTLD, validPunycode: validPunycode });
835 |
836 | var MAX_DOMAIN_LABEL_LENGTH = 63;
837 |
838 | // This is an extremely lightweight implementation of domain name validation according to RFC 3490
839 | // Our regexes handle most of the cases well enough
840 | // See https://tools.ietf.org/html/rfc3490#section-4.1 for details
841 | var idna = {
842 | toAscii: function toAscii(domain) {
843 | if (domain.startsWith('xn--') && !domain.match(validAsciiDomain)) {
844 | // Punycode encoded url cannot contain non ASCII characters
845 | return;
846 | }
847 |
848 | var labels = domain.split('.');
849 | var _iteratorNormalCompletion = true;
850 | var _didIteratorError = false;
851 | var _iteratorError = undefined;
852 |
853 | try {
854 | for (var _iterator = labels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
855 | var label = _step.value;
856 |
857 | var punycodeEncodedLabel = punycode_1.toASCII(label);
858 | if (punycodeEncodedLabel.length < 1 || punycodeEncodedLabel.length > MAX_DOMAIN_LABEL_LENGTH) {
859 | // DNS label has invalid length
860 | return;
861 | }
862 | }
863 | } catch (err) {
864 | _didIteratorError = true;
865 | _iteratorError = err;
866 | } finally {
867 | try {
868 | if (!_iteratorNormalCompletion && _iterator.return) {
869 | _iterator.return();
870 | }
871 | } finally {
872 | if (_didIteratorError) {
873 | throw _iteratorError;
874 | }
875 | }
876 | }
877 |
878 | return labels.join('.');
879 | }
880 | };
881 |
882 | var validSpecialCCTLD = /(?:(?:co|tv)(?=[^0-9a-zA-Z@]|$))/;
883 |
884 | var validSpecialShortDomain = regexSupplant(/^#{validDomainName}#{validSpecialCCTLD}$/i, { validDomainName: validDomainName, validSpecialCCTLD: validSpecialCCTLD });
885 |
886 | var validTcoUrl = /^https?:\/\/t\.co\/([a-z0-9]+)/i;
887 |
888 | var DEFAULT_PROTOCOL = 'https://';
889 | var DEFAULT_PROTOCOL_OPTIONS = { extractUrlsWithoutProtocol: true };
890 | var MAX_URL_LENGTH = 4096;
891 | var MAX_TCO_SLUG_LENGTH = 40;
892 |
893 | var extractUrlsWithIndices = function extractUrlsWithIndices(text) {
894 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_PROTOCOL_OPTIONS;
895 |
896 | if (!text || (options.extractUrlsWithoutProtocol ? !text.match(/\./) : !text.match(/:/))) {
897 | return [];
898 | }
899 |
900 | var urls = [];
901 |
902 | var _loop = function _loop() {
903 | var before = RegExp.$2;
904 | var url = RegExp.$3;
905 | var protocol = RegExp.$4;
906 | var domain = RegExp.$5;
907 | var path = RegExp.$7;
908 | var endPosition = extractUrl.lastIndex;
909 | var startPosition = endPosition - url.length;
910 |
911 | if (!isValidUrl(url, protocol || DEFAULT_PROTOCOL, domain)) {
912 | return 'continue';
913 | }
914 | // extract ASCII-only domains.
915 | if (!protocol) {
916 | if (!options.extractUrlsWithoutProtocol || before.match(invalidUrlWithoutProtocolPrecedingChars)) {
917 | return 'continue';
918 | }
919 |
920 | var lastUrl = null;
921 | var asciiEndPosition = 0;
922 | domain.replace(validAsciiDomain, function (asciiDomain) {
923 | var asciiStartPosition = domain.indexOf(asciiDomain, asciiEndPosition);
924 | asciiEndPosition = asciiStartPosition + asciiDomain.length;
925 | lastUrl = {
926 | url: asciiDomain,
927 | indices: [startPosition + asciiStartPosition, startPosition + asciiEndPosition]
928 | };
929 | if (path || asciiDomain.match(validSpecialShortDomain) || !asciiDomain.match(invalidShortDomain)) {
930 | urls.push(lastUrl);
931 | }
932 | });
933 |
934 | // no ASCII-only domain found. Skip the entire URL.
935 | if (lastUrl == null) {
936 | return 'continue';
937 | }
938 |
939 | // lastUrl only contains domain. Need to add path and query if they exist.
940 | if (path) {
941 | lastUrl.url = url.replace(domain, lastUrl.url);
942 | lastUrl.indices[1] = endPosition;
943 | }
944 | } else {
945 | // In the case of t.co URLs, don't allow additional path characters.
946 | if (url.match(validTcoUrl)) {
947 | var tcoUrlSlug = RegExp.$1;
948 | if (tcoUrlSlug && tcoUrlSlug.length > MAX_TCO_SLUG_LENGTH) {
949 | return 'continue';
950 | } else {
951 | url = RegExp.lastMatch;
952 | endPosition = startPosition + url.length;
953 | }
954 | }
955 | urls.push({
956 | url: url,
957 | indices: [startPosition, endPosition]
958 | });
959 | }
960 | };
961 |
962 | while (extractUrl.exec(text)) {
963 | var _ret = _loop();
964 |
965 | if (_ret === 'continue') continue;
966 | }
967 |
968 | return urls;
969 | };
970 |
971 | var isValidUrl = function isValidUrl(url, protocol, domain) {
972 | var urlLength = url.length;
973 | var punycodeEncodedDomain = idna.toAscii(domain);
974 | if (!punycodeEncodedDomain || !punycodeEncodedDomain.length) {
975 | return false;
976 | }
977 |
978 | urlLength = urlLength + punycodeEncodedDomain.length - domain.length;
979 | return protocol.length + urlLength <= MAX_URL_LENGTH;
980 | };
981 |
982 | var removeOverlappingEntities = function (entities) {
983 | entities.sort(function (a, b) {
984 | return a.indices[0] - b.indices[0];
985 | });
986 |
987 | var prev = entities[0];
988 | for (var i = 1; i < entities.length; i++) {
989 | if (prev.indices[1] > entities[i].indices[0]) {
990 | entities.splice(i, 1);
991 | i--;
992 | } else {
993 | prev = entities[i];
994 | }
995 | }
996 | };
997 |
998 | // Generated from unicode_regex/unicode_regex_groups.scala, same as objective c's \p{L}\p{M}
999 | var astralLetterAndMarks = /\ud800[\udc00-\udc0b\udc0d-\udc26\udc28-\udc3a\udc3c\udc3d\udc3f-\udc4d\udc50-\udc5d\udc80-\udcfa\uddfd\ude80-\ude9c\udea0-\uded0\udee0\udf00-\udf1f\udf30-\udf40\udf42-\udf49\udf50-\udf7a\udf80-\udf9d\udfa0-\udfc3\udfc8-\udfcf]|\ud801[\udc00-\udc9d\udd00-\udd27\udd30-\udd63\ude00-\udf36\udf40-\udf55\udf60-\udf67]|\ud802[\udc00-\udc05\udc08\udc0a-\udc35\udc37\udc38\udc3c\udc3f-\udc55\udc60-\udc76\udc80-\udc9e\udd00-\udd15\udd20-\udd39\udd80-\uddb7\uddbe\uddbf\ude00-\ude03\ude05\ude06\ude0c-\ude13\ude15-\ude17\ude19-\ude33\ude38-\ude3a\ude3f\ude60-\ude7c\ude80-\ude9c\udec0-\udec7\udec9-\udee6\udf00-\udf35\udf40-\udf55\udf60-\udf72\udf80-\udf91]|\ud803[\udc00-\udc48]|\ud804[\udc00-\udc46\udc7f-\udcba\udcd0-\udce8\udd00-\udd34\udd50-\udd73\udd76\udd80-\uddc4\uddda\ude00-\ude11\ude13-\ude37\udeb0-\udeea\udf01-\udf03\udf05-\udf0c\udf0f\udf10\udf13-\udf28\udf2a-\udf30\udf32\udf33\udf35-\udf39\udf3c-\udf44\udf47\udf48\udf4b-\udf4d\udf57\udf5d-\udf63\udf66-\udf6c\udf70-\udf74]|\ud805[\udc80-\udcc5\udcc7\udd80-\uddb5\uddb8-\uddc0\ude00-\ude40\ude44\ude80-\udeb7]|\ud806[\udca0-\udcdf\udcff\udec0-\udef8]|\ud808[\udc00-\udf98]|\ud80c[\udc00-\udfff]|\ud80d[\udc00-\udc2e]|\ud81a[\udc00-\ude38\ude40-\ude5e\uded0-\udeed\udef0-\udef4\udf00-\udf36\udf40-\udf43\udf63-\udf77\udf7d-\udf8f]|\ud81b[\udf00-\udf44\udf50-\udf7e\udf8f-\udf9f]|\ud82c[\udc00\udc01]|\ud82f[\udc00-\udc6a\udc70-\udc7c\udc80-\udc88\udc90-\udc99\udc9d\udc9e]|\ud834[\udd65-\udd69\udd6d-\udd72\udd7b-\udd82\udd85-\udd8b\uddaa-\uddad\ude42-\ude44]|\ud835[\udc00-\udc54\udc56-\udc9c\udc9e\udc9f\udca2\udca5\udca6\udca9-\udcac\udcae-\udcb9\udcbb\udcbd-\udcc3\udcc5-\udd05\udd07-\udd0a\udd0d-\udd14\udd16-\udd1c\udd1e-\udd39\udd3b-\udd3e\udd40-\udd44\udd46\udd4a-\udd50\udd52-\udea5\udea8-\udec0\udec2-\udeda\udedc-\udefa\udefc-\udf14\udf16-\udf34\udf36-\udf4e\udf50-\udf6e\udf70-\udf88\udf8a-\udfa8\udfaa-\udfc2\udfc4-\udfcb]|\ud83a[\udc00-\udcc4\udcd0-\udcd6]|\ud83b[\ude00-\ude03\ude05-\ude1f\ude21\ude22\ude24\ude27\ude29-\ude32\ude34-\ude37\ude39\ude3b\ude42\ude47\ude49\ude4b\ude4d-\ude4f\ude51\ude52\ude54\ude57\ude59\ude5b\ude5d\ude5f\ude61\ude62\ude64\ude67-\ude6a\ude6c-\ude72\ude74-\ude77\ude79-\ude7c\ude7e\ude80-\ude89\ude8b-\ude9b\udea1-\udea3\udea5-\udea9\udeab-\udebb]|\ud840[\udc00-\udfff]|\ud841[\udc00-\udfff]|\ud842[\udc00-\udfff]|\ud843[\udc00-\udfff]|\ud844[\udc00-\udfff]|\ud845[\udc00-\udfff]|\ud846[\udc00-\udfff]|\ud847[\udc00-\udfff]|\ud848[\udc00-\udfff]|\ud849[\udc00-\udfff]|\ud84a[\udc00-\udfff]|\ud84b[\udc00-\udfff]|\ud84c[\udc00-\udfff]|\ud84d[\udc00-\udfff]|\ud84e[\udc00-\udfff]|\ud84f[\udc00-\udfff]|\ud850[\udc00-\udfff]|\ud851[\udc00-\udfff]|\ud852[\udc00-\udfff]|\ud853[\udc00-\udfff]|\ud854[\udc00-\udfff]|\ud855[\udc00-\udfff]|\ud856[\udc00-\udfff]|\ud857[\udc00-\udfff]|\ud858[\udc00-\udfff]|\ud859[\udc00-\udfff]|\ud85a[\udc00-\udfff]|\ud85b[\udc00-\udfff]|\ud85c[\udc00-\udfff]|\ud85d[\udc00-\udfff]|\ud85e[\udc00-\udfff]|\ud85f[\udc00-\udfff]|\ud860[\udc00-\udfff]|\ud861[\udc00-\udfff]|\ud862[\udc00-\udfff]|\ud863[\udc00-\udfff]|\ud864[\udc00-\udfff]|\ud865[\udc00-\udfff]|\ud866[\udc00-\udfff]|\ud867[\udc00-\udfff]|\ud868[\udc00-\udfff]|\ud869[\udc00-\uded6\udf00-\udfff]|\ud86a[\udc00-\udfff]|\ud86b[\udc00-\udfff]|\ud86c[\udc00-\udfff]|\ud86d[\udc00-\udf34\udf40-\udfff]|\ud86e[\udc00-\udc1d]|\ud87e[\udc00-\ude1d]|\udb40[\udd00-\uddef]/;
1000 |
1001 | // Generated from unicode_regex/unicode_regex_groups.scala, same as objective c's \p{L}\p{M}
1002 | var bmpLetterAndMarks = /A-Za-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0300-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u0483-\u052f\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u05d0-\u05ea\u05f0-\u05f2\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06df-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u06ff\u0710-\u074a\u074d-\u07b1\u07ca-\u07f5\u07fa\u0800-\u082d\u0840-\u085b\u08a0-\u08b2\u08e4-\u0963\u0971-\u0983\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bc-\u09c4\u09c7\u09c8\u09cb-\u09ce\u09d7\u09dc\u09dd\u09df-\u09e3\u09f0\u09f1\u0a01-\u0a03\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a59-\u0a5c\u0a5e\u0a70-\u0a75\u0a81-\u0a83\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abc-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ad0\u0ae0-\u0ae3\u0b01-\u0b03\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3c-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5c\u0b5d\u0b5f-\u0b63\u0b71\u0b82\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd0\u0bd7\u0c00-\u0c03\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c58\u0c59\u0c60-\u0c63\u0c81-\u0c83\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbc-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0cde\u0ce0-\u0ce3\u0cf1\u0cf2\u0d01-\u0d03\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d-\u0d44\u0d46-\u0d48\u0d4a-\u0d4e\u0d57\u0d60-\u0d63\u0d7a-\u0d7f\u0d82\u0d83\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e01-\u0e3a\u0e40-\u0e4e\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb9\u0ebb-\u0ebd\u0ec0-\u0ec4\u0ec6\u0ec8-\u0ecd\u0edc-\u0edf\u0f00\u0f18\u0f19\u0f35\u0f37\u0f39\u0f3e-\u0f47\u0f49-\u0f6c\u0f71-\u0f84\u0f86-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u103f\u1050-\u108f\u109a-\u109d\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u135d-\u135f\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16f1-\u16f8\u1700-\u170c\u170e-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176c\u176e-\u1770\u1772\u1773\u1780-\u17d3\u17d7\u17dc\u17dd\u180b-\u180d\u1820-\u1877\u1880-\u18aa\u18b0-\u18f5\u1900-\u191e\u1920-\u192b\u1930-\u193b\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a1b\u1a20-\u1a5e\u1a60-\u1a7c\u1a7f\u1aa7\u1ab0-\u1abe\u1b00-\u1b4b\u1b6b-\u1b73\u1b80-\u1baf\u1bba-\u1bf3\u1c00-\u1c37\u1c4d-\u1c4f\u1c5a-\u1c7d\u1cd0-\u1cd2\u1cd4-\u1cf6\u1cf8\u1cf9\u1d00-\u1df5\u1dfc-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u20d0-\u20f0\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2183\u2184\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d7f-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2de0-\u2dff\u2e2f\u3005\u3006\u302a-\u302f\u3031-\u3035\u303b\u303c\u3041-\u3096\u3099\u309a\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua672\ua674-\ua67d\ua67f-\ua69d\ua69f-\ua6e5\ua6f0\ua6f1\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua7ad\ua7b0\ua7b1\ua7f7-\ua827\ua840-\ua873\ua880-\ua8c4\ua8e0-\ua8f7\ua8fb\ua90a-\ua92d\ua930-\ua953\ua960-\ua97c\ua980-\ua9c0\ua9cf\ua9e0-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa36\uaa40-\uaa4d\uaa60-\uaa76\uaa7a-\uaac2\uaadb-\uaadd\uaae0-\uaaef\uaaf2-\uaaf6\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab5f\uab64\uab65\uabc0-\uabea\uabec\uabed\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf870-\uf87f\uf882\uf884-\uf89f\uf8b8\uf8c1-\uf8d6\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe00-\ufe0f\ufe20-\ufe2d\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc/;
1003 |
1004 | var nonBmpCodePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/mg;
1005 |
1006 | // A hashtag must contain at least one unicode letter or mark, as well as numbers, underscores, and select special characters.
1007 | var hashtagAlpha = regexSupplant(/(?:[#{bmpLetterAndMarks}]|(?=#{nonBmpCodePairs})(?:#{astralLetterAndMarks}))/, { bmpLetterAndMarks: bmpLetterAndMarks, nonBmpCodePairs: nonBmpCodePairs, astralLetterAndMarks: astralLetterAndMarks });
1008 |
1009 | var astralNumerals = /\ud801[\udca0-\udca9]|\ud804[\udc66-\udc6f\udcf0-\udcf9\udd36-\udd3f\uddd0-\uddd9\udef0-\udef9]|\ud805[\udcd0-\udcd9\ude50-\ude59\udec0-\udec9]|\ud806[\udce0-\udce9]|\ud81a[\ude60-\ude69\udf50-\udf59]|\ud835[\udfce-\udfff]/;
1010 |
1011 | var bmpNumerals = /0-9\u0660-\u0669\u06f0-\u06f9\u07c0-\u07c9\u0966-\u096f\u09e6-\u09ef\u0a66-\u0a6f\u0ae6-\u0aef\u0b66-\u0b6f\u0be6-\u0bef\u0c66-\u0c6f\u0ce6-\u0cef\u0d66-\u0d6f\u0de6-\u0def\u0e50-\u0e59\u0ed0-\u0ed9\u0f20-\u0f29\u1040-\u1049\u1090-\u1099\u17e0-\u17e9\u1810-\u1819\u1946-\u194f\u19d0-\u19d9\u1a80-\u1a89\u1a90-\u1a99\u1b50-\u1b59\u1bb0-\u1bb9\u1c40-\u1c49\u1c50-\u1c59\ua620-\ua629\ua8d0-\ua8d9\ua900-\ua909\ua9d0-\ua9d9\ua9f0-\ua9f9\uaa50-\uaa59\uabf0-\uabf9\uff10-\uff19/;
1012 |
1013 | var hashtagSpecialChars = /_\u200c\u200d\ua67e\u05be\u05f3\u05f4\uff5e\u301c\u309b\u309c\u30a0\u30fb\u3003\u0f0b\u0f0c\xb7/;
1014 |
1015 | var hashtagAlphaNumeric = regexSupplant(/(?:[#{bmpLetterAndMarks}#{bmpNumerals}#{hashtagSpecialChars}]|(?=#{nonBmpCodePairs})(?:#{astralLetterAndMarks}|#{astralNumerals}))/, { bmpLetterAndMarks: bmpLetterAndMarks, bmpNumerals: bmpNumerals, hashtagSpecialChars: hashtagSpecialChars, nonBmpCodePairs: nonBmpCodePairs, astralLetterAndMarks: astralLetterAndMarks, astralNumerals: astralNumerals });
1016 |
1017 | var codePoint = /(?:[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])/;
1018 |
1019 | var hashtagBoundary = regexSupplant(/(?:^|\uFE0E|\uFE0F|$|(?!#{hashtagAlphaNumeric}|&)#{codePoint})/, { codePoint: codePoint, hashtagAlphaNumeric: hashtagAlphaNumeric });
1020 |
1021 | var validHashtag = regexSupplant(/(#{hashtagBoundary})(#{hashSigns})(?!\uFE0F|\u20E3)(#{hashtagAlphaNumeric}*#{hashtagAlpha}#{hashtagAlphaNumeric}*)/gi, { hashtagBoundary: hashtagBoundary, hashSigns: hashSigns, hashtagAlphaNumeric: hashtagAlphaNumeric, hashtagAlpha: hashtagAlpha });
1022 |
1023 | var extractHashtagsWithIndices = function extractHashtagsWithIndices(text, options) {
1024 | if (!options) {
1025 | options = { checkUrlOverlap: true };
1026 | }
1027 |
1028 | if (!text || !text.match(hashSigns)) {
1029 | return [];
1030 | }
1031 |
1032 | var tags = [];
1033 |
1034 | text.replace(validHashtag, function (match, before, hash, hashText, offset, chunk) {
1035 | var after = chunk.slice(offset + match.length);
1036 | if (after.match(endHashtagMatch)) {
1037 | return;
1038 | }
1039 | var startPosition = offset + before.length;
1040 | var endPosition = startPosition + hashText.length + 1;
1041 | tags.push({
1042 | hashtag: hashText,
1043 | indices: [startPosition, endPosition]
1044 | });
1045 | });
1046 |
1047 | if (options.checkUrlOverlap) {
1048 | // also extract URL entities
1049 | var urls = extractUrlsWithIndices(text);
1050 | if (urls.length > 0) {
1051 | var entities = tags.concat(urls);
1052 | // remove overlap
1053 | removeOverlappingEntities(entities);
1054 | // only push back hashtags
1055 | tags = [];
1056 | for (var i = 0; i < entities.length; i++) {
1057 | if (entities[i].hashtag) {
1058 | tags.push(entities[i]);
1059 | }
1060 | }
1061 | }
1062 | }
1063 |
1064 | return tags;
1065 | };
1066 |
1067 | var atSigns = /[@@]/;
1068 |
1069 | var endMentionMatch = regexSupplant(/^(?:#{atSigns}|[#{latinAccentChars}]|:\/\/)/, { atSigns: atSigns, latinAccentChars: latinAccentChars });
1070 |
1071 | var validMentionPrecedingChars = /(?:^|[^a-zA-Z0-9_!#$%&*@@]|(?:^|[^a-zA-Z0-9_+~.-])(?:rt|RT|rT|Rt):?)/;
1072 |
1073 | var validMentionOrList = regexSupplant('(#{validMentionPrecedingChars})' + // $1: Preceding character
1074 | '(#{atSigns})' + // $2: At mark
1075 | '([a-zA-Z0-9_]{1,20})' + // $3: Screen name
1076 | '(\/[a-zA-Z][a-zA-Z0-9_\-]{0,24})?', // $4: List (optional)
1077 | { validMentionPrecedingChars: validMentionPrecedingChars, atSigns: atSigns }, 'g');
1078 |
1079 | var extractMentionsOrListsWithIndices = function (text) {
1080 | if (!text || !text.match(atSigns)) {
1081 | return [];
1082 | }
1083 |
1084 | var possibleNames = [];
1085 |
1086 | text.replace(validMentionOrList, function (match, before, atSign, screenName, slashListname, offset, chunk) {
1087 | var after = chunk.slice(offset + match.length);
1088 | if (!after.match(endMentionMatch)) {
1089 | slashListname = slashListname || '';
1090 | var startPosition = offset + before.length;
1091 | var endPosition = startPosition + screenName.length + slashListname.length + 1;
1092 | possibleNames.push({
1093 | screenName: screenName,
1094 | listSlug: slashListname,
1095 | indices: [startPosition, endPosition]
1096 | });
1097 | }
1098 | });
1099 |
1100 | return possibleNames;
1101 | };
1102 |
1103 | var extractEntitiesWithIndices = function (text, options) {
1104 | var entities = extractUrlsWithIndices(text, options).concat(extractMentionsOrListsWithIndices(text)).concat(extractHashtagsWithIndices(text, { checkUrlOverlap: false })).concat(extractCashtagsWithIndices(text));
1105 |
1106 | if (entities.length == 0) {
1107 | return [];
1108 | }
1109 |
1110 | removeOverlappingEntities(entities);
1111 | return entities;
1112 | };
1113 |
1114 | var clone = function (o) {
1115 | var r = {};
1116 | for (var k in o) {
1117 | if (o.hasOwnProperty(k)) {
1118 | r[k] = o[k];
1119 | }
1120 | }
1121 |
1122 | return r;
1123 | };
1124 |
1125 | var BOOLEAN_ATTRIBUTES = {
1126 | 'disabled': true,
1127 | 'readonly': true,
1128 | 'multiple': true,
1129 | 'checked': true
1130 | };
1131 |
1132 | // Options which should not be passed as HTML attributes
1133 | var OPTIONS_NOT_ATTRIBUTES = {
1134 | 'urlClass': true,
1135 | 'listClass': true,
1136 | 'usernameClass': true,
1137 | 'hashtagClass': true,
1138 | 'cashtagClass': true,
1139 | 'usernameUrlBase': true,
1140 | 'listUrlBase': true,
1141 | 'hashtagUrlBase': true,
1142 | 'cashtagUrlBase': true,
1143 | 'usernameUrlBlock': true,
1144 | 'listUrlBlock': true,
1145 | 'hashtagUrlBlock': true,
1146 | 'linkUrlBlock': true,
1147 | 'usernameIncludeSymbol': true,
1148 | 'suppressLists': true,
1149 | 'suppressNoFollow': true,
1150 | 'targetBlank': true,
1151 | 'suppressDataScreenName': true,
1152 | 'urlEntities': true,
1153 | 'symbolTag': true,
1154 | 'textWithSymbolTag': true,
1155 | 'urlTarget': true,
1156 | 'invisibleTagAttrs': true,
1157 | 'linkAttributeBlock': true,
1158 | 'linkTextBlock': true,
1159 | 'htmlEscapeNonEntities': true
1160 | };
1161 |
1162 | var extractHtmlAttrsFromOptions = function (options) {
1163 | var htmlAttrs = {};
1164 | for (var k in options) {
1165 | var v = options[k];
1166 | if (OPTIONS_NOT_ATTRIBUTES[k]) {
1167 | continue;
1168 | }
1169 | if (BOOLEAN_ATTRIBUTES[k]) {
1170 | v = v ? k : null;
1171 | }
1172 | if (v == null) {
1173 | continue;
1174 | }
1175 | htmlAttrs[k] = v;
1176 | }
1177 | return htmlAttrs;
1178 | };
1179 |
1180 | var HTML_ENTITIES = {
1181 | '&': '&',
1182 | '>': '>',
1183 | '<': '<',
1184 | '"': '"',
1185 | "'": '''
1186 | };
1187 |
1188 | var htmlEscape = function (text) {
1189 | return text && text.replace(/[&"'><]/g, function (character) {
1190 | return HTML_ENTITIES[character];
1191 | });
1192 | };
1193 |
1194 | var BOOLEAN_ATTRIBUTES$1 = {
1195 | 'disabled': true,
1196 | 'readonly': true,
1197 | 'multiple': true,
1198 | 'checked': true
1199 | };
1200 |
1201 | var tagAttrs = function (attributes) {
1202 | var htmlAttrs = '';
1203 | for (var k in attributes) {
1204 | var v = attributes[k];
1205 | if (BOOLEAN_ATTRIBUTES$1[k]) {
1206 | v = v ? k : null;
1207 | }
1208 | if (v == null) {
1209 | continue;
1210 | }
1211 | htmlAttrs += ' ' + htmlEscape(k) + '="' + htmlEscape(v.toString()) + '"';
1212 | }
1213 | return htmlAttrs;
1214 | };
1215 |
1216 | var linkToText = function (entity, text, attributes, options) {
1217 | if (!options.suppressNoFollow) {
1218 | attributes.rel = 'nofollow';
1219 | }
1220 | // if linkAttributeBlock is specified, call it to modify the attributes
1221 | if (options.linkAttributeBlock) {
1222 | options.linkAttributeBlock(entity, attributes);
1223 | }
1224 | // if linkTextBlock is specified, call it to get a new/modified link text
1225 | if (options.linkTextBlock) {
1226 | text = options.linkTextBlock(entity, text);
1227 | }
1228 | var d = {
1229 | text: text,
1230 | attr: tagAttrs(attributes)
1231 | };
1232 | return stringSupplant('#{text}', d);
1233 | };
1234 |
1235 | var linkToTextWithSymbol = function (entity, symbol, text, attributes, options) {
1236 | var taggedSymbol = options.symbolTag ? '<' + options.symbolTag + '>' + symbol + '' + options.symbolTag + '>' : symbol;
1237 | text = htmlEscape(text);
1238 | var taggedText = options.textWithSymbolTag ? '<' + options.textWithSymbolTag + '>' + text + '' + options.textWithSymbolTag + '>' : text;
1239 |
1240 | if (options.usernameIncludeSymbol || !symbol.match(twttr.txt.regexen.atSigns)) {
1241 | return linkToText(entity, taggedSymbol + taggedText, attributes, options);
1242 | } else {
1243 | return taggedSymbol + linkToText(entity, taggedText, attributes, options);
1244 | }
1245 | };
1246 |
1247 | var linkToCashtag = function (entity, text, options) {
1248 | var cashtag = htmlEscape(entity.cashtag);
1249 | var attrs = clone(options.htmlAttrs || {});
1250 | attrs.href = options.cashtagUrlBase + cashtag;
1251 | attrs.title = '$' + cashtag;
1252 | attrs['class'] = options.cashtagClass;
1253 | if (options.targetBlank) {
1254 | attrs.target = '_blank';
1255 | }
1256 |
1257 | return linkToTextWithSymbol(entity, '$', cashtag, attrs, options);
1258 | };
1259 |
1260 | var rtlChars = /[\u0600-\u06FF]|[\u0750-\u077F]|[\u0590-\u05FF]|[\uFE70-\uFEFF]/mg;
1261 |
1262 | var linkToHashtag = function (entity, text, options) {
1263 | var hash = text.substring(entity.indices[0], entity.indices[0] + 1);
1264 | var hashtag = htmlEscape(entity.hashtag);
1265 | var attrs = clone(options.htmlAttrs || {});
1266 | attrs.href = options.hashtagUrlBase + hashtag;
1267 | attrs.title = '#' + hashtag;
1268 | attrs['class'] = options.hashtagClass;
1269 | if (hashtag.charAt(0).match(rtlChars)) {
1270 | attrs['class'] += ' rtl';
1271 | }
1272 | if (options.targetBlank) {
1273 | attrs.target = '_blank';
1274 | }
1275 |
1276 | return linkToTextWithSymbol(entity, hash, hashtag, attrs, options);
1277 | };
1278 |
1279 | var linkTextWithEntity = function (entity, options) {
1280 | var displayUrl = entity.display_url;
1281 | var expandedUrl = entity.expanded_url;
1282 |
1283 | // Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
1284 | // should contain the full original URL (expanded_url), not the display URL.
1285 | //
1286 | // Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
1287 | // font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
1288 | // Elements with font-size:0 get copied even though they are not visible.
1289 | // Note that display:none doesn't work here. Elements with display:none don't get copied.
1290 | //
1291 | // Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
1292 | // wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
1293 | // everything with the tco-ellipsis class.
1294 | //
1295 | // Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/#!/username/status/1234/photo/1
1296 | // For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
1297 | // For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine.
1298 |
1299 | var displayUrlSansEllipses = displayUrl.replace(/…/g, ''); // We have to disregard ellipses for matching
1300 | // Note: we currently only support eliding parts of the URL at the beginning or the end.
1301 | // Eventually we may want to elide parts of the URL in the *middle*. If so, this code will
1302 | // become more complicated. We will probably want to create a regexp out of display URL,
1303 | // replacing every ellipsis with a ".*".
1304 | if (expandedUrl.indexOf(displayUrlSansEllipses) != -1) {
1305 | var displayUrlIndex = expandedUrl.indexOf(displayUrlSansEllipses);
1306 | var v = {
1307 | displayUrlSansEllipses: displayUrlSansEllipses,
1308 | // Portion of expandedUrl that precedes the displayUrl substring
1309 | beforeDisplayUrl: expandedUrl.substr(0, displayUrlIndex),
1310 | // Portion of expandedUrl that comes after displayUrl
1311 | afterDisplayUrl: expandedUrl.substr(displayUrlIndex + displayUrlSansEllipses.length),
1312 | precedingEllipsis: displayUrl.match(/^…/) ? '…' : '',
1313 | followingEllipsis: displayUrl.match(/…$/) ? '…' : ''
1314 | };
1315 | for (var k in v) {
1316 | if (v.hasOwnProperty(k)) {
1317 | v[k] = htmlEscape(v[k]);
1318 | }
1319 | }
1320 | // As an example: The user tweets "hi http://longdomainname.com/foo"
1321 | // This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
1322 | // This will get rendered as:
1323 | //
1324 | // …
1325 | //
1333 | // http://longdomai
1334 | //
1335 | //
1336 | // nname.com/foo
1337 | //
1338 | //
1339 | //
1340 | // …
1341 | //
1342 | v['invisible'] = options.invisibleTagAttrs;
1343 | return stringSupplant("#{precedingEllipsis} #{beforeDisplayUrl}#{displayUrlSansEllipses}#{afterDisplayUrl} #{followingEllipsis}", v);
1344 | }
1345 | return displayUrl;
1346 | };
1347 |
1348 | var urlHasProtocol = /^https?:\/\//i;
1349 |
1350 | var linkToUrl = function (entity, text, options) {
1351 | var url = entity.url;
1352 | var displayUrl = url;
1353 | var linkText = htmlEscape(displayUrl);
1354 |
1355 | // If the caller passed a urlEntities object (provided by a Twitter API
1356 | // response with include_entities=true), we use that to render the display_url
1357 | // for each URL instead of it's underlying t.co URL.
1358 | var urlEntity = options.urlEntities && options.urlEntities[url] || entity;
1359 | if (urlEntity.display_url) {
1360 | linkText = linkTextWithEntity(urlEntity, options);
1361 | }
1362 |
1363 | var attrs = clone(options.htmlAttrs || {});
1364 |
1365 | if (!url.match(urlHasProtocol)) {
1366 | url = 'http://' + url;
1367 | }
1368 | attrs.href = url;
1369 |
1370 | if (options.targetBlank) {
1371 | attrs.target = '_blank';
1372 | }
1373 |
1374 | // set class only if urlClass is specified.
1375 | if (options.urlClass) {
1376 | attrs['class'] = options.urlClass;
1377 | }
1378 |
1379 | // set target only if urlTarget is specified.
1380 | if (options.urlTarget) {
1381 | attrs.target = options.urlTarget;
1382 | }
1383 |
1384 | if (!options.title && urlEntity.display_url) {
1385 | attrs.title = urlEntity.expanded_url;
1386 | }
1387 |
1388 | return linkToText(entity, linkText, attrs, options);
1389 | };
1390 |
1391 | var linkToMentionAndList = function (entity, text, options) {
1392 | var at = text.substring(entity.indices[0], entity.indices[0] + 1);
1393 | var user = htmlEscape(entity.screenName);
1394 | var slashListname = htmlEscape(entity.listSlug);
1395 | var isList = entity.listSlug && !options.suppressLists;
1396 | var attrs = clone(options.htmlAttrs || {});
1397 | attrs['class'] = isList ? options.listClass : options.usernameClass;
1398 | attrs.href = isList ? options.listUrlBase + user + slashListname : options.usernameUrlBase + user;
1399 | if (!isList && !options.suppressDataScreenName) {
1400 | attrs['data-screen-name'] = user;
1401 | }
1402 | if (options.targetBlank) {
1403 | attrs.target = '_blank';
1404 | }
1405 |
1406 | return linkToTextWithSymbol(entity, at, isList ? user + slashListname : user, attrs, options);
1407 | };
1408 |
1409 | // Default CSS class for auto-linked lists (along with the url class)
1410 | var DEFAULT_LIST_CLASS = 'tweet-url list-slug';
1411 | // Default CSS class for auto-linked usernames (along with the url class)
1412 | var DEFAULT_USERNAME_CLASS = 'tweet-url username';
1413 | // Default CSS class for auto-linked hashtags (along with the url class)
1414 | var DEFAULT_HASHTAG_CLASS = 'tweet-url hashtag';
1415 | // Default CSS class for auto-linked cashtags (along with the url class)
1416 | var DEFAULT_CASHTAG_CLASS = 'tweet-url cashtag';
1417 |
1418 | var autoLinkEntities = function (text, entities, options) {
1419 | var options = clone(options || {});
1420 | options.hashtagClass = options.hashtagClass || DEFAULT_HASHTAG_CLASS;
1421 | options.hashtagUrlBase = options.hashtagUrlBase || 'https://twitter.com/search?q=%23';
1422 | options.cashtagClass = options.cashtagClass || DEFAULT_CASHTAG_CLASS;
1423 | options.cashtagUrlBase = options.cashtagUrlBase || 'https://twitter.com/search?q=%24';
1424 | options.listClass = options.listClass || DEFAULT_LIST_CLASS;
1425 | options.usernameClass = options.usernameClass || DEFAULT_USERNAME_CLASS;
1426 | options.usernameUrlBase = options.usernameUrlBase || 'https://twitter.com/';
1427 | options.listUrlBase = options.listUrlBase || 'https://twitter.com/';
1428 | options.htmlAttrs = extractHtmlAttrsFromOptions(options);
1429 | options.invisibleTagAttrs = options.invisibleTagAttrs || "style='position:absolute;left:-9999px;'";
1430 |
1431 | // remap url entities to hash
1432 | var urlEntities, i, len;
1433 | if (options.urlEntities) {
1434 | urlEntities = {};
1435 | for (i = 0, len = options.urlEntities.length; i < len; i++) {
1436 | urlEntities[options.urlEntities[i].url] = options.urlEntities[i];
1437 | }
1438 | options.urlEntities = urlEntities;
1439 | }
1440 |
1441 | var result = '';
1442 | var beginIndex = 0;
1443 |
1444 | // sort entities by start index
1445 | entities.sort(function (a, b) {
1446 | return a.indices[0] - b.indices[0];
1447 | });
1448 |
1449 | var nonEntity = options.htmlEscapeNonEntities ? htmlEscape : function (text) {
1450 | return text;
1451 | };
1452 |
1453 | for (var i = 0; i < entities.length; i++) {
1454 | var entity = entities[i];
1455 | result += nonEntity(text.substring(beginIndex, entity.indices[0]));
1456 |
1457 | if (entity.url) {
1458 | result += linkToUrl(entity, text, options);
1459 | } else if (entity.hashtag) {
1460 | result += linkToHashtag(entity, text, options);
1461 | } else if (entity.screenName) {
1462 | result += linkToMentionAndList(entity, text, options);
1463 | } else if (entity.cashtag) {
1464 | result += linkToCashtag(entity, text, options);
1465 | }
1466 | beginIndex = entity.indices[1];
1467 | }
1468 | result += nonEntity(text.substring(beginIndex, text.length));
1469 | return result;
1470 | };
1471 |
1472 | var autoLink = function (text, options) {
1473 | var entities = extractEntitiesWithIndices(text, { extractUrlsWithoutProtocol: false });
1474 | return autoLinkEntities(text, entities, options);
1475 | };
1476 |
1477 | var autoLinkCashtags = function (text, options) {
1478 | var entities = extractCashtagsWithIndices(text);
1479 | return autoLinkEntities(text, entities, options);
1480 | };
1481 |
1482 | var autoLinkHashtags = function (text, options) {
1483 | var entities = extractHashtagsWithIndices(text);
1484 | return autoLinkEntities(text, entities, options);
1485 | };
1486 |
1487 | var autoLinkUrlsCustom = function (text, options) {
1488 | var entities = extractUrlsWithIndices(text, { extractUrlsWithoutProtocol: false });
1489 | return autoLinkEntities(text, entities, options);
1490 | };
1491 |
1492 | var autoLinkUsernamesOrLists = function (text, options) {
1493 | var entities = extractMentionsOrListsWithIndices(text);
1494 | return autoLinkEntities(text, entities, options);
1495 | };
1496 |
1497 | /**
1498 | * Copied from https://github.com/twitter/twitter-text/blob/master/js/twitter-text.js
1499 | */
1500 |
1501 | var convertUnicodeIndices = function convertUnicodeIndices(text, entities, indicesInUTF16) {
1502 | if (entities.length === 0) {
1503 | return;
1504 | }
1505 |
1506 | var charIndex = 0;
1507 | var codePointIndex = 0;
1508 |
1509 | // sort entities by start index
1510 | entities.sort(function (a, b) {
1511 | return a.indices[0] - b.indices[0];
1512 | });
1513 | var entityIndex = 0;
1514 | var entity = entities[0];
1515 |
1516 | while (charIndex < text.length) {
1517 | if (entity.indices[0] === (indicesInUTF16 ? charIndex : codePointIndex)) {
1518 | var len = entity.indices[1] - entity.indices[0];
1519 | entity.indices[0] = indicesInUTF16 ? codePointIndex : charIndex;
1520 | entity.indices[1] = entity.indices[0] + len;
1521 |
1522 | entityIndex++;
1523 | if (entityIndex === entities.length) {
1524 | // no more entity
1525 | break;
1526 | }
1527 | entity = entities[entityIndex];
1528 | }
1529 |
1530 | var c = text.charCodeAt(charIndex);
1531 | if (c >= 0xD800 && c <= 0xDBFF && charIndex < text.length - 1) {
1532 | // Found high surrogate char
1533 | c = text.charCodeAt(charIndex + 1);
1534 | if (c >= 0xDC00 && c <= 0xDFFF) {
1535 | // Found surrogate pair
1536 | charIndex++;
1537 | }
1538 | }
1539 | codePointIndex++;
1540 | charIndex++;
1541 | }
1542 | };
1543 |
1544 | var modifyIndicesFromUnicodeToUTF16 = function (text, entities) {
1545 | convertUnicodeIndices(text, entities, false);
1546 | };
1547 |
1548 | var autoLinkWithJSON = function (text, json, options) {
1549 | // map JSON entity to twitter-text entity
1550 | if (json.user_mentions) {
1551 | for (var i = 0; i < json.user_mentions.length; i++) {
1552 | // this is a @mention
1553 | json.user_mentions[i].screenName = json.user_mentions[i].screen_name;
1554 | }
1555 | }
1556 |
1557 | if (json.hashtags) {
1558 | for (var i = 0; i < json.hashtags.length; i++) {
1559 | // this is a #hashtag
1560 | json.hashtags[i].hashtag = json.hashtags[i].text;
1561 | }
1562 | }
1563 |
1564 | if (json.symbols) {
1565 | for (var i = 0; i < json.symbols.length; i++) {
1566 | // this is a $CASH tag
1567 | json.symbols[i].cashtag = json.symbols[i].text;
1568 | }
1569 | }
1570 |
1571 | // concatenate all entities
1572 | var entities = [];
1573 | for (var key in json) {
1574 | entities = entities.concat(json[key]);
1575 | }
1576 |
1577 | // modify indices to UTF-16
1578 | modifyIndicesFromUnicodeToUTF16(text, entities);
1579 |
1580 | return autoLinkEntities(text, entities, options);
1581 | };
1582 |
1583 | var version = 1;
1584 | var maxWeightedTweetLength = 140;
1585 | var scale = 1;
1586 | var defaultWeight = 1;
1587 | var transformedURLLength = 23;
1588 | var ranges = [];
1589 | var version1 = {
1590 | version: version,
1591 | maxWeightedTweetLength: maxWeightedTweetLength,
1592 | scale: scale,
1593 | defaultWeight: defaultWeight,
1594 | transformedURLLength: transformedURLLength,
1595 | ranges: ranges
1596 | };
1597 |
1598 | var version$1 = 2;
1599 | var maxWeightedTweetLength$1 = 280;
1600 | var scale$1 = 100;
1601 | var defaultWeight$1 = 200;
1602 | var transformedURLLength$1 = 23;
1603 | var ranges$1 = [{"start":0,"end":4351,"weight":100},{"start":8192,"end":8205,"weight":100},{"start":8208,"end":8223,"weight":100},{"start":8242,"end":8247,"weight":100}];
1604 | var version2 = {
1605 | version: version$1,
1606 | maxWeightedTweetLength: maxWeightedTweetLength$1,
1607 | scale: scale$1,
1608 | defaultWeight: defaultWeight$1,
1609 | transformedURLLength: transformedURLLength$1,
1610 | ranges: ranges$1
1611 | };
1612 |
1613 | // These json files are created by the build script
1614 | var defaults$1 = version2;
1615 |
1616 | var configs = {
1617 | defaults: defaults$1,
1618 | version1: version1,
1619 | version2: version2
1620 | };
1621 |
1622 | var convertUnicodeIndices$2 = function (text, entities, indicesInUTF16) {
1623 | if (entities.length == 0) {
1624 | return;
1625 | }
1626 |
1627 | var charIndex = 0;
1628 | var codePointIndex = 0;
1629 |
1630 | // sort entities by start index
1631 | entities.sort(function (a, b) {
1632 | return a.indices[0] - b.indices[0];
1633 | });
1634 | var entityIndex = 0;
1635 | var entity = entities[0];
1636 |
1637 | while (charIndex < text.length) {
1638 | if (entity.indices[0] == (indicesInUTF16 ? charIndex : codePointIndex)) {
1639 | var len = entity.indices[1] - entity.indices[0];
1640 | entity.indices[0] = indicesInUTF16 ? codePointIndex : charIndex;
1641 | entity.indices[1] = entity.indices[0] + len;
1642 |
1643 | entityIndex++;
1644 | if (entityIndex == entities.length) {
1645 | // no more entity
1646 | break;
1647 | }
1648 | entity = entities[entityIndex];
1649 | }
1650 |
1651 | var c = text.charCodeAt(charIndex);
1652 | if (c >= 0xD800 && c <= 0xDBFF && charIndex < text.length - 1) {
1653 | // Found high surrogate char
1654 | c = text.charCodeAt(charIndex + 1);
1655 | if (c >= 0xDC00 && c <= 0xDFFF) {
1656 | // Found surrogate pair
1657 | charIndex++;
1658 | }
1659 | }
1660 | codePointIndex++;
1661 | charIndex++;
1662 | }
1663 | };
1664 |
1665 | var extractCashtags = function (text) {
1666 | var cashtagsOnly = [],
1667 | cashtagsWithIndices = extractCashtagsWithIndices(text);
1668 |
1669 | for (var i = 0; i < cashtagsWithIndices.length; i++) {
1670 | cashtagsOnly.push(cashtagsWithIndices[i].cashtag);
1671 | }
1672 |
1673 | return cashtagsOnly;
1674 | };
1675 |
1676 | var extractHashtags = function (text) {
1677 | var hashtagsOnly = [];
1678 | var hashtagsWithIndices = extractHashtagsWithIndices(text);
1679 | for (var i = 0; i < hashtagsWithIndices.length; i++) {
1680 | hashtagsOnly.push(hashtagsWithIndices[i].hashtag);
1681 | }
1682 |
1683 | return hashtagsOnly;
1684 | };
1685 |
1686 | var extractMentionsWithIndices = function (text) {
1687 | var mentions = [];
1688 | var mentionOrList = void 0;
1689 | var mentionsOrLists = extractMentionsOrListsWithIndices(text);
1690 |
1691 | for (var i = 0; i < mentionsOrLists.length; i++) {
1692 | mentionOrList = mentionsOrLists[i];
1693 | if (mentionOrList.listSlug === '') {
1694 | mentions.push({
1695 | screenName: mentionOrList.screenName,
1696 | indices: mentionOrList.indices
1697 | });
1698 | }
1699 | }
1700 |
1701 | return mentions;
1702 | };
1703 |
1704 | var extractMentions = function (text) {
1705 | var screenNamesOnly = [],
1706 | screenNamesWithIndices = extractMentionsWithIndices(text);
1707 |
1708 | for (var i = 0; i < screenNamesWithIndices.length; i++) {
1709 | var screenName = screenNamesWithIndices[i].screenName;
1710 | screenNamesOnly.push(screenName);
1711 | }
1712 |
1713 | return screenNamesOnly;
1714 | };
1715 |
1716 | var validReply = regexSupplant(/^(?:#{spaces})*#{atSigns}([a-zA-Z0-9_]{1,20})/, { atSigns: atSigns, spaces: spaces });
1717 |
1718 | var extractReplies = function (text) {
1719 | if (!text) {
1720 | return null;
1721 | }
1722 |
1723 | var possibleScreenName = text.match(validReply);
1724 | if (!possibleScreenName || RegExp.rightContext.match(endMentionMatch)) {
1725 | return null;
1726 | }
1727 |
1728 | return possibleScreenName[1];
1729 | };
1730 |
1731 | var extractUrls = function (text, options) {
1732 | var urlsOnly = [];
1733 | var urlsWithIndices = extractUrlsWithIndices(text, options);
1734 |
1735 | for (var i = 0; i < urlsWithIndices.length; i++) {
1736 | urlsOnly.push(urlsWithIndices[i].url);
1737 | }
1738 |
1739 | return urlsOnly;
1740 | };
1741 |
1742 | var getCharacterWeight = function getCharacterWeight(ch, options) {
1743 | var defaultWeight = options.defaultWeight,
1744 | ranges = options.ranges;
1745 |
1746 | var weight = defaultWeight;
1747 | var chCodePoint = ch.charCodeAt(0);
1748 | if (Array.isArray(ranges)) {
1749 | for (var i = 0, length = ranges.length; i < length; i++) {
1750 | var currRange = ranges[i];
1751 | if (chCodePoint >= currRange.start && chCodePoint <= currRange.end) {
1752 | weight = currRange.weight;
1753 | break;
1754 | }
1755 | }
1756 | }
1757 |
1758 | return weight;
1759 | };
1760 |
1761 | var modifyIndicesFromUTF16ToUnicode = function (text, entities) {
1762 | convertUnicodeIndices(text, entities, true);
1763 | };
1764 |
1765 | var invalidChars = regexSupplant(/[#{invalidCharsGroup}]/, { invalidCharsGroup: invalidCharsGroup });
1766 |
1767 | var hasInvalidCharacters = function (text) {
1768 | return invalidChars.test(text);
1769 | };
1770 |
1771 | var urlHasHttps = /^https:\/\//i;
1772 |
1773 | /**
1774 | * [parseTweet description]
1775 | * @param {string} text tweet text to parse
1776 | * @param {Object} options config options to pass
1777 | * @return {Object} Fields in response described below:
1778 | *
1779 | * Response fields:
1780 | * weightedLength {int} the weighted length of tweet based on weights specified in the config
1781 | * valid {bool} If tweet is valid
1782 | * permillage {float} permillage of the tweet over the max length specified in config
1783 | * validRangeStart {int} beginning of valid text
1784 | * validRangeEnd {int} End index of valid part of the tweet text (inclusive) in utf16
1785 | * displayRangeStart {int} beginning index of display text
1786 | * displayRangeEnd {int} end index of display text (inclusive) in utf16
1787 | */
1788 | var parseTweet = function parseTweet() {
1789 | var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
1790 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : configs.defaults;
1791 |
1792 | var mergedOptions = _extends({}, configs.defaults, options);
1793 | var defaultWeight = mergedOptions.defaultWeight,
1794 | scale = mergedOptions.scale,
1795 | maxWeightedTweetLength = mergedOptions.maxWeightedTweetLength,
1796 | transformedURLLength = mergedOptions.transformedURLLength;
1797 |
1798 | var normalizedText = typeof String.prototype.normalize === 'function' ? text.normalize() : text;
1799 | var urlsWithIndices = extractUrlsWithIndices(normalizedText);
1800 | var tweetLength = normalizedText.length;
1801 |
1802 | var weightedLength = 0;
1803 | var validDisplayIndex = 0;
1804 | var valid = true;
1805 | // Go through every character and calculate weight
1806 |
1807 | var _loop = function _loop(_charIndex) {
1808 | // If a url begins at the specified index handle, add constant length
1809 | var urlEntity = urlsWithIndices.filter(function (_ref) {
1810 | var indices = _ref.indices;
1811 | return indices[0] === _charIndex;
1812 | })[0];
1813 | if (urlEntity) {
1814 | var url = urlEntity.url;
1815 |
1816 | weightedLength += transformedURLLength * scale;
1817 | _charIndex += url.length - 1;
1818 | } else {
1819 | if (isSurrogatePair(normalizedText, _charIndex)) {
1820 | _charIndex += 1;
1821 | }
1822 | weightedLength += getCharacterWeight(normalizedText.charAt(_charIndex), mergedOptions);
1823 | }
1824 |
1825 | // Only test for validity of character if it is still valid
1826 | if (valid) {
1827 | valid = !hasInvalidCharacters(normalizedText.substring(_charIndex, _charIndex + 1));
1828 | }
1829 | if (valid && weightedLength <= maxWeightedTweetLength * scale) {
1830 | validDisplayIndex = _charIndex;
1831 | }
1832 | charIndex = _charIndex;
1833 | };
1834 |
1835 | for (var charIndex = 0; charIndex < tweetLength; charIndex++) {
1836 | _loop(charIndex);
1837 | }
1838 |
1839 | weightedLength = weightedLength / scale;
1840 | valid = valid && weightedLength > 0 && weightedLength <= maxWeightedTweetLength;
1841 | var permillage = Math.floor(weightedLength / maxWeightedTweetLength * 1000);
1842 | var normalizationOffset = text.length - normalizedText.length;
1843 | validDisplayIndex += normalizationOffset;
1844 |
1845 | return {
1846 | weightedLength: weightedLength,
1847 | valid: valid,
1848 | permillage: permillage,
1849 | validRangeStart: 0,
1850 | validRangeEnd: validDisplayIndex,
1851 | displayRangeStart: 0,
1852 | displayRangeEnd: text.length > 0 ? text.length - 1 : 0
1853 | };
1854 | };
1855 |
1856 | var isSurrogatePair = function isSurrogatePair(text, cIndex) {
1857 | // Test if a character is the beginning of a surrogate pair
1858 | if (cIndex < text.length - 1) {
1859 | var c = text.charCodeAt(cIndex);
1860 | var cNext = text.charCodeAt(cIndex + 1);
1861 | return 0xD800 <= c && c <= 0xDBFF && 0xDC00 <= cNext && cNext <= 0xDFFF;
1862 | }
1863 | return false;
1864 | };
1865 |
1866 | var getTweetLength = function getTweetLength(text) {
1867 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : configs.defaults;
1868 |
1869 | return parseTweet(text, options).weightedLength;
1870 | };
1871 |
1872 | /**
1873 | * Copied from https://github.com/twitter/twitter-text/blob/master/js/twitter-text.js
1874 | */
1875 | var getUnicodeTextLength = function (text) {
1876 | return text.replace(nonBmpCodePairs, ' ').length;
1877 | };
1878 |
1879 | // this essentially does text.split(/<|>/)
1880 | // except that won't work in IE, where empty strings are ommitted
1881 | // so "<>".split(/<|>/) => [] in IE, but is ["", "", ""] in all others
1882 | // but "<<".split("<") => ["", "", ""]
1883 | var splitTags = function (text) {
1884 | var firstSplits = text.split('<'),
1885 | secondSplits = void 0,
1886 | allSplits = [],
1887 | split = void 0;
1888 |
1889 | for (var i = 0; i < firstSplits.length; i += 1) {
1890 | split = firstSplits[i];
1891 | if (!split) {
1892 | allSplits.push('');
1893 | } else {
1894 | secondSplits = split.split('>');
1895 | for (var j = 0; j < secondSplits.length; j += 1) {
1896 | allSplits.push(secondSplits[j]);
1897 | }
1898 | }
1899 | }
1900 |
1901 | return allSplits;
1902 | };
1903 |
1904 | var hitHighlight = function (text, hits, options) {
1905 | var defaultHighlightTag = 'em';
1906 |
1907 | hits = hits || [];
1908 | options = options || {};
1909 |
1910 | if (hits.length === 0) {
1911 | return text;
1912 | }
1913 |
1914 | var tagName = options.tag || defaultHighlightTag,
1915 | tags = ['<' + tagName + '>', '' + tagName + '>'],
1916 | chunks = splitTags(text),
1917 | i = void 0,
1918 | j = void 0,
1919 | result = '',
1920 | chunkIndex = 0,
1921 | chunk = chunks[0],
1922 | prevChunksLen = 0,
1923 | chunkCursor = 0,
1924 | startInChunk = false,
1925 | chunkChars = chunk,
1926 | flatHits = [],
1927 | index = void 0,
1928 | hit = void 0,
1929 | tag = void 0,
1930 | placed = void 0,
1931 | hitSpot = void 0;
1932 |
1933 | for (i = 0; i < hits.length; i += 1) {
1934 | for (j = 0; j < hits[i].length; j += 1) {
1935 | flatHits.push(hits[i][j]);
1936 | }
1937 | }
1938 |
1939 | for (index = 0; index < flatHits.length; index += 1) {
1940 | hit = flatHits[index];
1941 | tag = tags[index % 2];
1942 | placed = false;
1943 |
1944 | while (chunk != null && hit >= prevChunksLen + chunk.length) {
1945 | result += chunkChars.slice(chunkCursor);
1946 | if (startInChunk && hit === prevChunksLen + chunkChars.length) {
1947 | result += tag;
1948 | placed = true;
1949 | }
1950 |
1951 | if (chunks[chunkIndex + 1]) {
1952 | result += '<' + chunks[chunkIndex + 1] + '>';
1953 | }
1954 |
1955 | prevChunksLen += chunkChars.length;
1956 | chunkCursor = 0;
1957 | chunkIndex += 2;
1958 | chunk = chunks[chunkIndex];
1959 | chunkChars = chunk;
1960 | startInChunk = false;
1961 | }
1962 |
1963 | if (!placed && chunk != null) {
1964 | hitSpot = hit - prevChunksLen;
1965 | result += chunkChars.slice(chunkCursor, hitSpot) + tag;
1966 | chunkCursor = hitSpot;
1967 | if (index % 2 === 0) {
1968 | startInChunk = true;
1969 | } else {
1970 | startInChunk = false;
1971 | }
1972 | } else if (!placed) {
1973 | placed = true;
1974 | result += tag;
1975 | }
1976 | }
1977 |
1978 | if (chunk != null) {
1979 | if (chunkCursor < chunkChars.length) {
1980 | result += chunkChars.slice(chunkCursor);
1981 | }
1982 | for (index = chunkIndex + 1; index < chunks.length; index += 1) {
1983 | result += index % 2 === 0 ? chunks[index] : '<' + chunks[index] + '>';
1984 | }
1985 | }
1986 |
1987 | return result;
1988 | };
1989 |
1990 | var isInvalidTweet = function (text) {
1991 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : configs.defaults;
1992 |
1993 | if (!text) {
1994 | return 'empty';
1995 | }
1996 |
1997 | var mergedOptions = _extends({}, configs.defaults, options);
1998 | var maxLength = mergedOptions.maxWeightedTweetLength;
1999 |
2000 | // Determine max length independent of URL length
2001 | if (getTweetLength(text, mergedOptions) > maxLength) {
2002 | return 'too_long';
2003 | }
2004 |
2005 | if (hasInvalidCharacters(text)) {
2006 | return 'invalid_characters';
2007 | }
2008 |
2009 | return false;
2010 | };
2011 |
2012 | var isValidHashtag = function (hashtag) {
2013 | if (!hashtag) {
2014 | return false;
2015 | }
2016 |
2017 | var extracted = extractHashtags(hashtag);
2018 |
2019 | // Should extract the hashtag minus the # sign, hence the .slice(1)
2020 | return extracted.length === 1 && extracted[0] === hashtag.slice(1);
2021 | };
2022 |
2023 | var VALID_LIST_RE = regexSupplant(/^#{validMentionOrList}$/, { validMentionOrList: validMentionOrList });
2024 |
2025 | var isValidList = function (usernameList) {
2026 | var match = usernameList.match(VALID_LIST_RE);
2027 |
2028 | // Must have matched and had nothing before or after
2029 | return !!(match && match[1] == '' && match[4]);
2030 | };
2031 |
2032 | var isValidTweetText = function (text, options) {
2033 | return !isInvalidTweet(text, options);
2034 | };
2035 |
2036 | var validateUrlUnreserved = /[a-z\u0400-\u04FF0-9\-._~]/i;
2037 |
2038 | var validateUrlPctEncoded = /(?:%[0-9a-f]{2})/i;
2039 |
2040 | var validateUrlSubDelims = /[!$&'()*+,;=]/i;
2041 |
2042 | var validateUrlUserinfo = regexSupplant('(?:' + '#{validateUrlUnreserved}|' + '#{validateUrlPctEncoded}|' + '#{validateUrlSubDelims}|' + ':' + ')*', { validateUrlUnreserved: validateUrlUnreserved, validateUrlPctEncoded: validateUrlPctEncoded, validateUrlSubDelims: validateUrlSubDelims }, 'i');
2043 |
2044 | var validateUrlDomainSegment = /(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?)/i;
2045 |
2046 | var validateUrlDomainTld = /(?:[a-z](?:[a-z0-9\-]*[a-z0-9])?)/i;
2047 |
2048 | var validateUrlSubDomainSegment = /(?:[a-z0-9](?:[a-z0-9_\-]*[a-z0-9])?)/i;
2049 |
2050 | var validateUrlDomain = regexSupplant(/(?:(?:#{validateUrlSubDomainSegment}\.)*(?:#{validateUrlDomainSegment}\.)#{validateUrlDomainTld})/i, { validateUrlSubDomainSegment: validateUrlSubDomainSegment, validateUrlDomainSegment: validateUrlDomainSegment, validateUrlDomainTld: validateUrlDomainTld });
2051 |
2052 | var validateUrlDecOctet = /(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9]{2})|(?:2[0-4][0-9])|(?:25[0-5]))/i;
2053 |
2054 | var validateUrlIpv4 = regexSupplant(/(?:#{validateUrlDecOctet}(?:\.#{validateUrlDecOctet}){3})/i, { validateUrlDecOctet: validateUrlDecOctet });
2055 |
2056 | // Punting on real IPv6 validation for now
2057 | var validateUrlIpv6 = /(?:\[[a-f0-9:\.]+\])/i;
2058 |
2059 | // Punting on IPvFuture for now
2060 | var validateUrlIp = regexSupplant('(?:' + '#{validateUrlIpv4}|' + '#{validateUrlIpv6}' + ')', { validateUrlIpv4: validateUrlIpv4, validateUrlIpv6: validateUrlIpv6 }, 'i');
2061 |
2062 | var validateUrlHost = regexSupplant('(?:' + '#{validateUrlIp}|' + '#{validateUrlDomain}' + ')', { validateUrlIp: validateUrlIp, validateUrlDomain: validateUrlDomain }, 'i');
2063 |
2064 | var validateUrlPort = /[0-9]{1,5}/;
2065 |
2066 | var validateUrlAuthority = regexSupplant(
2067 | // $1 userinfo
2068 | '(?:(#{validateUrlUserinfo})@)?' +
2069 | // $2 host
2070 | '(#{validateUrlHost})' +
2071 | // $3 port
2072 | '(?::(#{validateUrlPort}))?', { validateUrlUserinfo: validateUrlUserinfo, validateUrlHost: validateUrlHost, validateUrlPort: validateUrlPort }, 'i');
2073 |
2074 | // These URL validation pattern strings are based on the ABNF from RFC 3986
2075 | var validateUrlPchar = regexSupplant('(?:' + '#{validateUrlUnreserved}|' + '#{validateUrlPctEncoded}|' + '#{validateUrlSubDelims}|' + '[:|@]' + ')', { validateUrlUnreserved: validateUrlUnreserved, validateUrlPctEncoded: validateUrlPctEncoded, validateUrlSubDelims: validateUrlSubDelims }, 'i');
2076 |
2077 | var validateUrlFragment = regexSupplant(/(#{validateUrlPchar}|\/|\?)*/i, { validateUrlPchar: validateUrlPchar });
2078 |
2079 | var validateUrlPath = regexSupplant(/(\/#{validateUrlPchar}*)*/i, { validateUrlPchar: validateUrlPchar });
2080 |
2081 | var validateUrlQuery = regexSupplant(/(#{validateUrlPchar}|\/|\?)*/i, { validateUrlPchar: validateUrlPchar });
2082 |
2083 | var validateUrlScheme = /(?:[a-z][a-z0-9+\-.]*)/i;
2084 |
2085 | // Modified version of RFC 3986 Appendix B
2086 | var validateUrlUnencoded = regexSupplant('^' + // Full URL
2087 | '(?:' + '([^:/?#]+):\\/\\/' + // $1 Scheme
2088 | ')?' + '([^/?#]*)' + // $2 Authority
2089 | '([^?#]*)' + // $3 Path
2090 | '(?:' + '\\?([^#]*)' + // $4 Query
2091 | ')?' + '(?:' + '#(.*)' + // $5 Fragment
2092 | ')?$', 'i');
2093 |
2094 | var validateUrlUnicodeSubDomainSegment = /(?:(?:[a-z0-9]|[^\u0000-\u007f])(?:(?:[a-z0-9_\-]|[^\u0000-\u007f])*(?:[a-z0-9]|[^\u0000-\u007f]))?)/i;
2095 |
2096 | var validateUrlUnicodeDomainSegment = /(?:(?:[a-z0-9]|[^\u0000-\u007f])(?:(?:[a-z0-9\-]|[^\u0000-\u007f])*(?:[a-z0-9]|[^\u0000-\u007f]))?)/i;
2097 |
2098 | // Unencoded internationalized domains - this doesn't check for invalid UTF-8 sequences
2099 | var validateUrlUnicodeDomainTld = /(?:(?:[a-z]|[^\u0000-\u007f])(?:(?:[a-z0-9\-]|[^\u0000-\u007f])*(?:[a-z0-9]|[^\u0000-\u007f]))?)/i;
2100 |
2101 | // Unencoded internationalized domains - this doesn't check for invalid UTF-8 sequences
2102 | var validateUrlUnicodeDomain = regexSupplant(/(?:(?:#{validateUrlUnicodeSubDomainSegment}\.)*(?:#{validateUrlUnicodeDomainSegment}\.)#{validateUrlUnicodeDomainTld})/i, { validateUrlUnicodeSubDomainSegment: validateUrlUnicodeSubDomainSegment, validateUrlUnicodeDomainSegment: validateUrlUnicodeDomainSegment, validateUrlUnicodeDomainTld: validateUrlUnicodeDomainTld });
2103 |
2104 | var validateUrlUnicodeHost = regexSupplant('(?:' + '#{validateUrlIp}|' + '#{validateUrlUnicodeDomain}' + ')', { validateUrlIp: validateUrlIp, validateUrlUnicodeDomain: validateUrlUnicodeDomain }, 'i');
2105 |
2106 | var validateUrlUnicodeAuthority = regexSupplant(
2107 | // $1 userinfo
2108 | '(?:(#{validateUrlUserinfo})@)?' +
2109 | // $2 host
2110 | '(#{validateUrlUnicodeHost})' +
2111 | // $3 port
2112 | '(?::(#{validateUrlPort}))?', { validateUrlUserinfo: validateUrlUserinfo, validateUrlUnicodeHost: validateUrlUnicodeHost, validateUrlPort: validateUrlPort }, 'i');
2113 |
2114 | function isValidMatch(string, regex, optional) {
2115 | if (!optional) {
2116 | // RegExp["$&"] is the text of the last match
2117 | // blank strings are ok, but are falsy, so we check stringiness instead of truthiness
2118 | return typeof string === 'string' && string.match(regex) && RegExp['$&'] === string;
2119 | }
2120 |
2121 | // RegExp["$&"] is the text of the last match
2122 | return !string || string.match(regex) && RegExp['$&'] === string;
2123 | }
2124 |
2125 | var isValidUrl$1 = function (url, unicodeDomains, requireProtocol) {
2126 | if (unicodeDomains == null) {
2127 | unicodeDomains = true;
2128 | }
2129 |
2130 | if (requireProtocol == null) {
2131 | requireProtocol = true;
2132 | }
2133 |
2134 | if (!url) {
2135 | return false;
2136 | }
2137 |
2138 | var urlParts = url.match(validateUrlUnencoded);
2139 |
2140 | if (!urlParts || urlParts[0] !== url) {
2141 | return false;
2142 | }
2143 |
2144 | var scheme = urlParts[1],
2145 | authority = urlParts[2],
2146 | path = urlParts[3],
2147 | query = urlParts[4],
2148 | fragment = urlParts[5];
2149 |
2150 | if (!((!requireProtocol || isValidMatch(scheme, validateUrlScheme) && scheme.match(/^https?$/i)) && isValidMatch(path, validateUrlPath) && isValidMatch(query, validateUrlQuery, true) && isValidMatch(fragment, validateUrlFragment, true))) {
2151 | return false;
2152 | }
2153 |
2154 | return unicodeDomains && isValidMatch(authority, validateUrlUnicodeAuthority) || !unicodeDomains && isValidMatch(authority, validateUrlAuthority);
2155 | };
2156 |
2157 | var isValidUsername = function (username) {
2158 | if (!username) {
2159 | return false;
2160 | }
2161 |
2162 | var extracted = extractMentions(username);
2163 |
2164 | // Should extract the username minus the @ sign, hence the .slice(1)
2165 | return extracted.length === 1 && extracted[0] === username.slice(1);
2166 | };
2167 |
2168 | (function () {
2169 | if (typeof Object.assign != 'function') {
2170 | // Must be writable: true, enumerable: false, configurable: true
2171 | Object.defineProperty(Object, "assign", {
2172 | value: function assign(target, varArgs) {
2173 | // .length of function is 2
2174 | 'use strict';
2175 |
2176 | if (target == null) {
2177 | // TypeError if undefined or null
2178 | throw new TypeError('Cannot convert undefined or null to object');
2179 | }
2180 |
2181 | var to = Object(target);
2182 |
2183 | for (var index = 1; index < arguments.length; index++) {
2184 | var nextSource = arguments[index];
2185 |
2186 | if (nextSource != null) {
2187 | // Skip over if undefined or null
2188 | for (var nextKey in nextSource) {
2189 | // Avoid bugs when hasOwnProperty is shadowed
2190 | if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
2191 | to[nextKey] = nextSource[nextKey];
2192 | }
2193 | }
2194 | }
2195 | }
2196 | return to;
2197 | },
2198 | writable: true,
2199 | configurable: true
2200 | });
2201 | }
2202 | })();
2203 |
2204 | var regexen = {
2205 | astralLetterAndMarks: astralLetterAndMarks,
2206 | astralNumerals: astralNumerals,
2207 | atSigns: atSigns,
2208 | bmpLetterAndMarks: bmpLetterAndMarks,
2209 | bmpNumerals: bmpNumerals,
2210 | cashtag: cashtag,
2211 | codePoint: codePoint,
2212 | cyrillicLettersAndMarks: cyrillicLettersAndMarks,
2213 | endHashtagMatch: endHashtagMatch,
2214 | endMentionMatch: endMentionMatch,
2215 | // extractUrl,
2216 | hashSigns: hashSigns,
2217 | hashtagAlpha: hashtagAlpha,
2218 | hashtagAlphaNumeric: hashtagAlphaNumeric,
2219 | hashtagBoundary: hashtagBoundary,
2220 | hashtagSpecialChars: hashtagSpecialChars,
2221 | invalidChars: invalidChars,
2222 | invalidCharsGroup: invalidCharsGroup,
2223 | invalidDomainChars: invalidDomainChars,
2224 | invalidShortDomain: invalidShortDomain,
2225 | invalidUrlWithoutProtocolPrecedingChars: invalidUrlWithoutProtocolPrecedingChars,
2226 | latinAccentChars: latinAccentChars,
2227 | nonBmpCodePairs: nonBmpCodePairs,
2228 | punct: punct,
2229 | rtlChars: rtlChars,
2230 | spaces: spaces,
2231 | spacesGroup: spacesGroup,
2232 | urlHasHttps: urlHasHttps,
2233 | urlHasProtocol: urlHasProtocol,
2234 | validAsciiDomain: validAsciiDomain,
2235 | validateUrlAuthority: validateUrlAuthority,
2236 | validateUrlDecOctet: validateUrlDecOctet,
2237 | validateUrlDomain: validateUrlDomain,
2238 | validateUrlDomainSegment: validateUrlDomainSegment,
2239 | validateUrlDomainTld: validateUrlDomainTld,
2240 | validateUrlFragment: validateUrlFragment,
2241 | validateUrlHost: validateUrlHost,
2242 | validateUrlIp: validateUrlIp,
2243 | validateUrlIpv4: validateUrlIpv4,
2244 | validateUrlIpv6: validateUrlIpv6,
2245 | validateUrlPath: validateUrlPath,
2246 | validateUrlPchar: validateUrlPchar,
2247 | validateUrlPctEncoded: validateUrlPctEncoded,
2248 | validateUrlPort: validateUrlPort,
2249 | validateUrlQuery: validateUrlQuery,
2250 | validateUrlScheme: validateUrlScheme,
2251 | validateUrlSubDelims: validateUrlSubDelims,
2252 | validateUrlSubDomainSegment: validateUrlSubDomainSegment,
2253 | validateUrlUnencoded: validateUrlUnencoded,
2254 | validateUrlUnicodeAuthority: validateUrlUnicodeAuthority,
2255 | validateUrlUnicodeDomain: validateUrlUnicodeDomain,
2256 | validateUrlUnicodeDomainSegment: validateUrlUnicodeDomainSegment,
2257 | validateUrlUnicodeDomainTld: validateUrlUnicodeDomainTld,
2258 | validateUrlUnicodeHost: validateUrlUnicodeHost,
2259 | validateUrlUnicodeSubDomainSegment: validateUrlUnicodeSubDomainSegment,
2260 | validateUrlUnreserved: validateUrlUnreserved,
2261 | validateUrlUserinfo: validateUrlUserinfo,
2262 | validCashtag: validCashtag,
2263 | validCCTLD: validCCTLD,
2264 | validDomain: validDomain,
2265 | validDomainChars: validDomainChars,
2266 | validDomainName: validDomainName,
2267 | validGeneralUrlPathChars: validGeneralUrlPathChars,
2268 | validGTLD: validGTLD,
2269 | validHashtag: validHashtag,
2270 | validMentionOrList: validMentionOrList,
2271 | validMentionPrecedingChars: validMentionPrecedingChars,
2272 | validPortNumber: validPortNumber,
2273 | validPunycode: validPunycode,
2274 | validReply: validReply,
2275 | validSpecialCCTLD: validSpecialCCTLD,
2276 | validSpecialShortDomain: validSpecialShortDomain,
2277 | validSubdomain: validSubdomain,
2278 | validTcoUrl: validTcoUrl,
2279 | validUrlBalancedParens: validUrlBalancedParens,
2280 | validUrlPath: validUrlPath,
2281 | validUrlPathEndingChars: validUrlPathEndingChars,
2282 | validUrlPrecedingChars: validUrlPrecedingChars,
2283 | validUrlQueryChars: validUrlQueryChars,
2284 | validUrlQueryEndingChars: validUrlQueryEndingChars
2285 | };
2286 |
2287 | var index = {
2288 | autoLink: autoLink,
2289 | autoLinkCashtags: autoLinkCashtags,
2290 | autoLinkEntities: autoLinkEntities,
2291 | autoLinkHashtags: autoLinkHashtags,
2292 | autoLinkUrlsCustom: autoLinkUrlsCustom,
2293 | autoLinkUsernamesOrLists: autoLinkUsernamesOrLists,
2294 | autoLinkWithJSON: autoLinkWithJSON,
2295 | configs: configs,
2296 | convertUnicodeIndices: convertUnicodeIndices$2,
2297 | extractCashtags: extractCashtags,
2298 | extractCashtagsWithIndices: extractCashtagsWithIndices,
2299 | extractEntitiesWithIndices: extractEntitiesWithIndices,
2300 | extractHashtags: extractHashtags,
2301 | extractHashtagsWithIndices: extractHashtagsWithIndices,
2302 | extractHtmlAttrsFromOptions: extractHtmlAttrsFromOptions,
2303 | extractMentions: extractMentions,
2304 | extractMentionsOrListsWithIndices: extractMentionsOrListsWithIndices,
2305 | extractMentionsWithIndices: extractMentionsWithIndices,
2306 | extractReplies: extractReplies,
2307 | extractUrls: extractUrls,
2308 | extractUrlsWithIndices: extractUrlsWithIndices,
2309 | getTweetLength: getTweetLength,
2310 | getUnicodeTextLength: getUnicodeTextLength,
2311 | hasInvalidCharacters: hasInvalidCharacters,
2312 | hitHighlight: hitHighlight,
2313 | htmlEscape: htmlEscape,
2314 | isInvalidTweet: isInvalidTweet,
2315 | isValidHashtag: isValidHashtag,
2316 | isValidList: isValidList,
2317 | isValidTweetText: isValidTweetText,
2318 | isValidUrl: isValidUrl$1,
2319 | isValidUsername: isValidUsername,
2320 | linkTextWithEntity: linkTextWithEntity,
2321 | linkToCashtag: linkToCashtag,
2322 | linkToHashtag: linkToHashtag,
2323 | linkToMentionAndList: linkToMentionAndList,
2324 | linkToText: linkToText,
2325 | linkToTextWithSymbol: linkToTextWithSymbol,
2326 | linkToUrl: linkToUrl,
2327 | modifyIndicesFromUTF16ToUnicode: modifyIndicesFromUTF16ToUnicode,
2328 | modifyIndicesFromUnicodeToUTF16: modifyIndicesFromUnicodeToUTF16,
2329 | regexen: regexen,
2330 | removeOverlappingEntities: removeOverlappingEntities,
2331 | parseTweet: parseTweet,
2332 | splitTags: splitTags,
2333 | tagAttrs: tagAttrs
2334 | };
2335 |
2336 | return index;
2337 |
2338 | })));
2339 |
--------------------------------------------------------------------------------