mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-10 04:52:38 +01:00
Enhancement/fb requests (#575)
* Update lambdas to references * Simplify regex and parsers * Fix some parsing and add more tests * Improve message parser and tests * Simplify parser * Shorten interfaces * Push rem * Create notification parser * Clean up notification service * Clean up notification service * Add safe cookie fallback * Fix cookie reference * Make parsers only hold cookie string * Clean up cookie references * Fix up login and event theme * Update changelog Remove workspace backup
This commit is contained in:
parent
8080d43dbd
commit
1769dbcef9
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,4 +10,4 @@
|
||||
.externalNativeBuild
|
||||
*.min.css
|
||||
.sass-cache/
|
||||
/projectFilesBackup
|
||||
/projectFilesBackup
|
@ -142,6 +142,7 @@ dependencies {
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}"
|
||||
androidTestImplementation "com.android.support.test:rules:${TEST_RULE}"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-reflect:${KOTLIN}"
|
||||
testImplementation "junit:junit:${JUNIT}"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN}"
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4,
|
||||
._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq,
|
||||
._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector,
|
||||
._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._529j,
|
||||
._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper,
|
||||
._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz,
|
||||
.tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0,
|
||||
@ -76,4 +76,9 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS
|
||||
._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before,
|
||||
._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before {
|
||||
background: $divider !important;
|
||||
}
|
||||
|
||||
//fab
|
||||
button ._v89 ._54k8._1fl1 {
|
||||
background: $accent !important;
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
._1lf4, ._1hiz, ._xod, ._5ag5,
|
||||
._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35,
|
||||
._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu,
|
||||
._18qg, ._1_ac,
|
||||
._18qg, ._1_ac, ._529p,
|
||||
._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08,
|
||||
._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_,
|
||||
textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782,
|
||||
|
@ -1,4 +1,4 @@
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #d7b0d7 !important; }
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._529p, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #d7b0d7 !important; }
|
||||
|
||||
strong > a, ._15ks ._2q8z._2q8z, ._1e3e { color: #3b5998 !important; }
|
||||
|
||||
@ -8,7 +8,7 @@ a, ._5fpq { color: #d59ed5 !important; }
|
||||
|
||||
#viewport { background: #451515 !important; }
|
||||
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: rgba(255, 0, 255, 0.02) !important; }
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._529j, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: rgba(255, 0, 255, 0.02) !important; }
|
||||
|
||||
._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._55wo { background: #496296 !important; }
|
||||
|
||||
@ -28,6 +28,8 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS
|
||||
|
||||
._220g, ._1_y8:after, ._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before, ._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before { background: rgba(215, 176, 215, 0.3) !important; }
|
||||
|
||||
button ._v89 ._54k8._1fl1 { background: #3b5998 !important; }
|
||||
|
||||
._15kl::before, ._37fd .inlineComposerButton, ._1hb:before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(215, 176, 215, 0.3) !important; }
|
||||
|
||||
._4_d1 { border-right: 1px solid rgba(215, 176, 215, 0.3) !important; }
|
||||
|
@ -1,4 +1,4 @@
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: $T$ !important; }
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._529p, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: $T$ !important; }
|
||||
|
||||
strong > a, ._15ks ._2q8z._2q8z, ._1e3e { color: $A$ !important; }
|
||||
|
||||
@ -8,7 +8,7 @@ a, ._5fpq { color: $TT$ !important; }
|
||||
|
||||
#viewport { background: $B$ !important; }
|
||||
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: $BT$ !important; }
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._529j, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: $BT$ !important; }
|
||||
|
||||
._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._55wo { background: $C$ !important; }
|
||||
|
||||
@ -28,6 +28,8 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS
|
||||
|
||||
._220g, ._1_y8:after, ._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before, ._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before { background: $D$ !important; }
|
||||
|
||||
button ._v89 ._54k8._1fl1 { background: $A$ !important; }
|
||||
|
||||
._15kl::before, ._37fd .inlineComposerButton, ._1hb:before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid $D$ !important; }
|
||||
|
||||
._4_d1 { border-right: 1px solid $D$ !important; }
|
||||
|
@ -1,4 +1,4 @@
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; }
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._529p, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; }
|
||||
|
||||
strong > a, ._15ks ._2q8z._2q8z, ._1e3e { color: #5d86dd !important; }
|
||||
|
||||
@ -8,7 +8,7 @@ a, ._5fpq { color: #eee !important; }
|
||||
|
||||
#viewport { background: #000 !important; }
|
||||
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: #000 !important; }
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._529j, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: #000 !important; }
|
||||
|
||||
._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._55wo { background: rgba(0, 0, 0, 0.35) !important; }
|
||||
|
||||
@ -28,6 +28,8 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS
|
||||
|
||||
._220g, ._1_y8:after, ._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before, ._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before { background: rgba(255, 255, 255, 0.3) !important; }
|
||||
|
||||
button ._v89 ._54k8._1fl1 { background: #5d86dd !important; }
|
||||
|
||||
._15kl::before, ._37fd .inlineComposerButton, ._1hb:before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(255, 255, 255, 0.3) !important; }
|
||||
|
||||
._4_d1 { border-right: 1px solid rgba(255, 255, 255, 0.3) !important; }
|
||||
|
@ -1,4 +1,4 @@
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; }
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._529p, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; }
|
||||
|
||||
strong > a, ._15ks ._2q8z._2q8z, ._1e3e { color: #5d86dd !important; }
|
||||
|
||||
@ -8,7 +8,7 @@ a, ._5fpq { color: #eee !important; }
|
||||
|
||||
#viewport { background: #303030 !important; }
|
||||
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: #303030 !important; }
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._529j, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: #303030 !important; }
|
||||
|
||||
._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._55wo { background: #353535 !important; }
|
||||
|
||||
@ -28,6 +28,8 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS
|
||||
|
||||
._220g, ._1_y8:after, ._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before, ._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before { background: rgba(255, 255, 255, 0.3) !important; }
|
||||
|
||||
button ._v89 ._54k8._1fl1 { background: #5d86dd !important; }
|
||||
|
||||
._15kl::before, ._37fd .inlineComposerButton, ._1hb:before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(255, 255, 255, 0.3) !important; }
|
||||
|
||||
._4_d1 { border-right: 1px solid rgba(255, 255, 255, 0.3) !important; }
|
||||
|
@ -1,4 +1,4 @@
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; }
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._529p, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; }
|
||||
|
||||
strong > a, ._15ks ._2q8z._2q8z, ._1e3e { color: #5d86dd !important; }
|
||||
|
||||
@ -8,7 +8,7 @@ a, ._5fpq { color: #eee !important; }
|
||||
|
||||
#viewport { background: rgba(0, 0, 0, 0.1) !important; }
|
||||
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: transparent !important; }
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._529j, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: transparent !important; }
|
||||
|
||||
._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._55wo { background: rgba(0, 0, 0, 0.25) !important; }
|
||||
|
||||
@ -28,6 +28,8 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS
|
||||
|
||||
._220g, ._1_y8:after, ._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before, ._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before { background: rgba(255, 255, 255, 0.3) !important; }
|
||||
|
||||
button ._v89 ._54k8._1fl1 { background: #5d86dd !important; }
|
||||
|
||||
._15kl::before, ._37fd .inlineComposerButton, ._1hb:before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(255, 255, 255, 0.3) !important; }
|
||||
|
||||
._4_d1 { border-right: 1px solid rgba(255, 255, 255, 0.3) !important; }
|
||||
|
@ -1,4 +1,4 @@
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #000 !important; }
|
||||
[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._eu5, ._1tcc, ._3g9-, ._29z_, ._3xz7, ._ib-, ._3bg5 ._56dq, ._477i, ._2vxk, .touched *, ._1_yj, ._1_yl, ._4pj9, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._50vk, ._1m2u, ._31y7, ._4kcb, ._1lf6, ._1lf5, ._1lf4, ._1hiz, ._xod, ._5ag5, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, ._529p, ._5aga, ._5ag9, ._537a, .acy, ._5ro_, ._6-l ._2us7, ._4mp, ._2b08, ._14v5 ._14v8, ._1440, ._1442, ._1448, ._4ks_, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, div.sharerSelector, .footer, .mentions-input, .mentions-placeholder, .largeStatusBox .placeHolder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #000 !important; }
|
||||
|
||||
strong > a, ._15ks ._2q8z._2q8z, ._1e3e { color: #3b5998 !important; }
|
||||
|
||||
@ -8,7 +8,7 @@ a, ._5fpq { color: #111 !important; }
|
||||
|
||||
#viewport { background: #fafafa !important; }
|
||||
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: #fafafa !important; }
|
||||
body, #root, #header, [style*="background-color"], ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, ._1t4h, ._uoq, ._6-l ._2us7, ._6-l ._6-p, ._333v, div.sharerSelector, ._529j, ._1g05, .acy, ._51-g, ._533c, ._ib-, .sharerAttachmentEmpty, .sharerBottomWrapper, ._3bg5 ._56do, ._5hfh, ._52e-, .mQuestionsPollResultsBar, ._5hoc, ._5oxw, ._32_4, ._1hiz, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._2q7u, ._2q7v, ._577z, ._2u4w, ._3u9p, ._3u9t, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, ._5vq5, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._i3g, ._3jcf, .error, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1, ._35au, ._cwy, ._1rfn ._1rfk ._4vc-, ._1rfk, ._1rfk ._2v9s, ._301x { background: #fafafa !important; }
|
||||
|
||||
._31nf, ._2v9s, ._d4i, article._55wo, ._10c_, ._2jl2, ._55wo { background: #fff !important; }
|
||||
|
||||
@ -28,6 +28,8 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS
|
||||
|
||||
._220g, ._1_y8:after, ._2zh4::before, ._2ip_ ._2zh4::before, ._2ip_ ._15kk::before, ._2ip_ ._15kk + ._4u3j::before, ._58a0:before, ._43mh::before, ._43mh::after, ._1_-1::before, ._1kmv:after, ._1_ac:before { background: rgba(0, 0, 0, 0.3) !important; }
|
||||
|
||||
button ._v89 ._54k8._1fl1 { background: #3b5998 !important; }
|
||||
|
||||
._15kl::before, ._37fd .inlineComposerButton, ._1hb:before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(0, 0, 0, 0.3) !important; }
|
||||
|
||||
._4_d1 { border-right: 1px solid rgba(0, 0, 0, 0.3) !important; }
|
||||
|
@ -58,6 +58,7 @@ import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
|
||||
import com.pitchedapps.frost.fragments.BaseFragment
|
||||
import com.pitchedapps.frost.parsers.FrostSearch
|
||||
import com.pitchedapps.frost.parsers.SearchParser
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.pitchedapps.frost.utils.iab.FrostBilling
|
||||
@ -127,6 +128,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
|
||||
onCreateBilling()
|
||||
}
|
||||
|
||||
|
||||
fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) {
|
||||
(0 until tabs.tabCount).asSequence().forEach { i ->
|
||||
action(i, tabs.getTabAt(i)!!.customView as BadgedIcon)
|
||||
@ -193,7 +195,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
|
||||
-3L -> launchNewTask(LoginActivity::class.java, clearStack = false)
|
||||
-4L -> launchNewTask(SelectorActivity::class.java, cookies(), false)
|
||||
else -> {
|
||||
FbCookie.switchUser(profile.identifier, { refreshAll() })
|
||||
FbCookie.switchUser(profile.identifier, this@BaseMainActivity::refreshAll)
|
||||
tabsForEachView { _, view -> view.badgeText = null }
|
||||
}
|
||||
}
|
||||
@ -248,7 +250,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
|
||||
onClick { _ -> onClick(); false }
|
||||
}
|
||||
|
||||
fun refreshAll() {
|
||||
private fun refreshAll() {
|
||||
fragmentSubject.onNext(REQUEST_REFRESH)
|
||||
}
|
||||
|
||||
@ -266,8 +268,8 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
|
||||
runOnUiThread { searchView?.results = results }
|
||||
else
|
||||
doAsync {
|
||||
val data = SearchParser.query(query) ?: return@doAsync
|
||||
val items = data.map { SearchItem(it.href, it.title, it.description) }.toMutableList()
|
||||
val data = SearchParser.query(FbCookie.webCookie, query)?.data?.results ?: return@doAsync
|
||||
val items = data.map(FrostSearch::toSearchItem).toMutableList()
|
||||
if (items.isNotEmpty())
|
||||
items.add(SearchItem("${FbItem._SEARCH.url}?q=$query", string(R.string.show_all_results), iicon = null))
|
||||
searchViewCache.put(query, items)
|
||||
|
@ -25,10 +25,9 @@ import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.pitchedapps.frost.web.LoginWebView
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.internal.operators.single.SingleToObservable
|
||||
import io.reactivex.subjects.SingleSubject
|
||||
|
||||
|
||||
@ -37,18 +36,18 @@ import io.reactivex.subjects.SingleSubject
|
||||
*/
|
||||
class LoginActivity : BaseActivity() {
|
||||
|
||||
val toolbar: Toolbar by bindView(R.id.toolbar)
|
||||
val web: LoginWebView by bindView(R.id.login_webview)
|
||||
val swipeRefresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh)
|
||||
val textview: AppCompatTextView by bindView(R.id.textview)
|
||||
val profile: ImageView by bindView(R.id.profile)
|
||||
private val toolbar: Toolbar by bindView(R.id.toolbar)
|
||||
private val web: LoginWebView by bindView(R.id.login_webview)
|
||||
private val swipeRefresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh)
|
||||
private val textview: AppCompatTextView by bindView(R.id.textview)
|
||||
private val profile: ImageView by bindView(R.id.profile)
|
||||
|
||||
val profileObservable = SingleSubject.create<Boolean>()
|
||||
val usernameObservable = SingleSubject.create<String>()
|
||||
lateinit var profileLoader: RequestManager
|
||||
private val profileSubject = SingleSubject.create<Boolean>()
|
||||
private val usernameSubject = SingleSubject.create<String>()
|
||||
private lateinit var profileLoader: RequestManager
|
||||
|
||||
// Helper to set and enable swipeRefresh
|
||||
var refresh: Boolean
|
||||
private var refresh: Boolean
|
||||
get() = swipeRefresh.isRefreshing
|
||||
set(value) {
|
||||
if (value) swipeRefresh.isEnabled = true
|
||||
@ -73,10 +72,12 @@ class LoginActivity : BaseActivity() {
|
||||
profileLoader = Glide.with(profile)
|
||||
}
|
||||
|
||||
fun loadInfo(cookie: CookieModel) {
|
||||
private fun loadInfo(cookie: CookieModel) {
|
||||
refresh = true
|
||||
Observable.zip(SingleToObservable(profileObservable), SingleToObservable(usernameObservable),
|
||||
BiFunction<Boolean, String, Pair<Boolean, String>> { foundImage, name -> Pair(foundImage, name) })
|
||||
Single.zip<Boolean, String, Pair<Boolean, String>>(
|
||||
profileSubject,
|
||||
usernameSubject,
|
||||
BiFunction(::Pair))
|
||||
.observeOn(AndroidSchedulers.mainThread()).subscribe { (foundImage, name) ->
|
||||
refresh = false
|
||||
if (!foundImage) {
|
||||
@ -85,7 +86,11 @@ class LoginActivity : BaseActivity() {
|
||||
}
|
||||
textview.text = String.format(getString(R.string.welcome), name)
|
||||
textview.fadeIn()
|
||||
frostAnswers { logLogin(LoginEvent().putMethod("frost_browser").putSuccess(true)) }
|
||||
frostAnswers {
|
||||
logLogin(LoginEvent()
|
||||
.putMethod("frost_browser")
|
||||
.putSuccess(true))
|
||||
}
|
||||
/*
|
||||
* The user may have logged into an account that is already in the database
|
||||
* We will let the db handle duplicates and load it now after the new account has been saved
|
||||
@ -102,23 +107,23 @@ class LoginActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
|
||||
fun loadProfile(id: Long) {
|
||||
private fun loadProfile(id: Long) {
|
||||
profileLoader.load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener<Drawable> {
|
||||
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
|
||||
profileObservable.onSuccess(true)
|
||||
profileSubject.onSuccess(true)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
|
||||
e.logFrostAnswers("Profile loading exception")
|
||||
profileObservable.onSuccess(false)
|
||||
profileSubject.onSuccess(false)
|
||||
return false
|
||||
}
|
||||
}).into(profile)
|
||||
}
|
||||
|
||||
fun loadUsername(cookie: CookieModel) {
|
||||
cookie.fetchUsername { usernameObservable.onSuccess(it) }
|
||||
private fun loadUsername(cookie: CookieModel) {
|
||||
cookie.fetchUsername(usernameSubject::onSuccess)
|
||||
}
|
||||
|
||||
override fun backConsumer(): Boolean {
|
||||
@ -129,4 +134,14 @@ class LoginActivity : BaseActivity() {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
web.resumeTimers()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
web.pauseTimers()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.pitchedapps.frost.contracts
|
||||
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
|
||||
/**
|
||||
|
@ -13,8 +13,17 @@ package com.pitchedapps.frost.facebook
|
||||
* Matches the fb_dtsg component of a page containing it as a hidden value
|
||||
*/
|
||||
val FB_DTSG_MATCHER: Regex by lazy { Regex("name=\"fb_dtsg\" value=\"(.*?)\"") }
|
||||
val FB_REV_MATCHER: Regex by lazy{Regex("\"app_version\":\"(.*?)\"")}
|
||||
|
||||
/**
|
||||
* Matches user id from cookie
|
||||
*/
|
||||
val FB_USER_MATCHER: Regex by lazy { Regex("c_user=([0-9]*);") }
|
||||
val FB_USER_MATCHER: Regex by lazy { Regex("c_user=([0-9]*);") }
|
||||
|
||||
val FB_EPOCH_MATCHER: Regex by lazy { Regex(":([0-9]+)") }
|
||||
val FB_NOTIF_ID_MATCHER: Regex by lazy { Regex("notif_id\":([0-9]+)") }
|
||||
val FB_MESSAGE_NOTIF_ID_MATCHER: Regex by lazy { Regex("[thread|user]_fbid_([0-9]+)") }
|
||||
val FB_CSS_URL_MATCHER: Regex by lazy { Regex("url\\([\"|'](.*?)[\"|']\\)") }
|
||||
|
||||
operator fun MatchResult?.get(groupIndex: Int) = this?.groupValues?.get(groupIndex)
|
||||
|
||||
|
@ -11,7 +11,13 @@ import org.apache.commons.text.StringEscapeUtils
|
||||
/**
|
||||
* Created by Allan Wang on 21/12/17.
|
||||
*/
|
||||
data class RequestAuth(val userId: Long = -1, val cookie: String = "", val fb_dtsg: String = "")
|
||||
data class RequestAuth(val userId: Long = -1,
|
||||
val cookie: String = "",
|
||||
val fb_dtsg: String = "",
|
||||
val rev: String = "") {
|
||||
val isValid
|
||||
get() = userId > 0 && cookie.isNotEmpty() && fb_dtsg.isNotEmpty() && rev.isNotEmpty()
|
||||
}
|
||||
|
||||
private val client: OkHttpClient by lazy {
|
||||
val builder = OkHttpClient.Builder()
|
||||
@ -21,7 +27,7 @@ private val client: OkHttpClient by lazy {
|
||||
builder.build()
|
||||
}
|
||||
|
||||
private fun List<Pair<String, Any?>>.toForm(): RequestBody {
|
||||
private fun List<Pair<String, Any?>>.toForm(): FormBody {
|
||||
val builder = FormBody.Builder()
|
||||
forEach { (key, value) ->
|
||||
val v = value?.toString() ?: ""
|
||||
@ -30,6 +36,12 @@ private fun List<Pair<String, Any?>>.toForm(): RequestBody {
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun List<Pair<String, Any?>>.withEmptyData(vararg key: String): List<Pair<String, Any?>> {
|
||||
val newList = toMutableList()
|
||||
newList.addAll(key.map { it to null })
|
||||
return newList
|
||||
}
|
||||
|
||||
private fun String.requestBuilder() = Request.Builder()
|
||||
.header("Cookie", this)
|
||||
.header("User-Agent", USER_AGENT_BASIC)
|
||||
@ -38,25 +50,33 @@ private fun String.requestBuilder() = Request.Builder()
|
||||
private fun Request.Builder.call() = client.newCall(build())
|
||||
|
||||
|
||||
fun Pair<Long, String>.getAuth(): RequestAuth? {
|
||||
fun Pair<Long, String>.getAuth(): RequestAuth {
|
||||
val (userId, cookie) = this
|
||||
var auth = RequestAuth(userId, cookie)
|
||||
val call = cookie.requestBuilder()
|
||||
.url(FB_URL_BASE)
|
||||
.url("https://touch.facebook.com")
|
||||
.get()
|
||||
.call()
|
||||
call.execute().body()?.charStream()?.useLines {
|
||||
it.forEach {
|
||||
val text = StringEscapeUtils.unescapeEcmaScript(it)
|
||||
val result = FB_DTSG_MATCHER.find(text)
|
||||
val fb_dtsg = result?.groupValues?.get(1)
|
||||
val fb_dtsg = FB_DTSG_MATCHER.find(text)[1]
|
||||
if (fb_dtsg != null) {
|
||||
L.d(null, "fb_dtsg for $userId: $fb_dtsg")
|
||||
return RequestAuth(userId, cookie, fb_dtsg)
|
||||
auth = auth.copy(fb_dtsg = fb_dtsg)
|
||||
if (auth.isValid) return auth
|
||||
}
|
||||
|
||||
val rev = FB_REV_MATCHER.find(text)[1]
|
||||
if (rev != null) {
|
||||
L.d(null, "rev for $userId: $rev")
|
||||
auth = auth.copy(rev = rev)
|
||||
if (auth.isValid) return auth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
return auth
|
||||
}
|
||||
|
||||
fun RequestAuth.markNotificationRead(notifId: Long): Call {
|
||||
@ -65,13 +85,9 @@ fun RequestAuth.markNotificationRead(notifId: Long): Call {
|
||||
"click_type" to "notification_click",
|
||||
"id" to notifId,
|
||||
"target_id" to "null",
|
||||
"m_sess" to null,
|
||||
"fb_dtsg" to fb_dtsg,
|
||||
"__dyn" to null,
|
||||
"__req" to null,
|
||||
"__ajax__" to null,
|
||||
"__user" to userId
|
||||
)
|
||||
).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
|
||||
|
||||
return cookie.requestBuilder()
|
||||
.url("${FB_URL_BASE}a/jewel_notifications_log.php")
|
||||
@ -96,3 +112,18 @@ fun RequestAuth.markNotificationsRead(vararg notifId: Long) = zip<Long, Boolean,
|
||||
response.body()?.charStream()?.read(buffer) ?: return@zip false
|
||||
!buffer.toString().contains("error")
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the call and attempt to check validity
|
||||
*/
|
||||
fun Call.executeAndCheck(): Boolean {
|
||||
val body = execute().body() ?: return false
|
||||
var empty = true
|
||||
body.charStream().useLines {
|
||||
it.forEach {
|
||||
if (empty && it.isNotEmpty()) empty = false
|
||||
if (it.contains("error")) return true
|
||||
}
|
||||
}
|
||||
return !empty
|
||||
}
|
||||
|
@ -13,7 +13,9 @@ import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.contracts.DynamicUiContract
|
||||
import com.pitchedapps.frost.contracts.FrostContentParent
|
||||
import com.pitchedapps.frost.contracts.MainActivityContract
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.enums.FeedSort
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.parsers.FrostParser
|
||||
import com.pitchedapps.frost.utils.*
|
||||
@ -28,6 +30,9 @@ import org.jetbrains.anko.toast
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-11-07.
|
||||
*
|
||||
* All fragments pertaining to the main view
|
||||
* Must be attached to activities implementing [MainActivityContract]
|
||||
*/
|
||||
abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
|
||||
|
||||
@ -59,6 +64,12 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
|
||||
|
||||
protected abstract val layoutRes: Int
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (context !is MainActivityContract)
|
||||
throw IllegalArgumentException("${this::class.java.simpleName} is not attached to a context implementing MainActivityContract")
|
||||
}
|
||||
|
||||
override final fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(layoutRes, container, false)
|
||||
val content = view as? FrostContentParent
|
||||
@ -162,7 +173,7 @@ abstract class BaseFragment : Fragment(), FragmentContract, DynamicUiContract {
|
||||
override fun onTabClick(): Unit = content?.core?.onTabClicked() ?: Unit
|
||||
}
|
||||
|
||||
abstract class RecyclerFragment<T, Item : IItem<*, *>> : BaseFragment(), RecyclerContentContract {
|
||||
abstract class RecyclerFragment<T : Any, Item : IItem<*, *>> : BaseFragment(), RecyclerContentContract {
|
||||
|
||||
override val layoutRes: Int = R.layout.view_content_recycler
|
||||
|
||||
@ -199,7 +210,7 @@ abstract class RecyclerFragment<T, Item : IItem<*, *>> : BaseFragment(), Recycle
|
||||
progress(10)
|
||||
val doc = frostJsoup(baseUrl)
|
||||
progress(60)
|
||||
val data = parser.parse(doc)
|
||||
val data = parser.parse(FbCookie.webCookie, doc)
|
||||
if (data == null) {
|
||||
context?.toast(R.string.error_generic)
|
||||
L.eThrow("RecyclerFragment failed for ${baseEnum.name}")
|
||||
@ -207,7 +218,7 @@ abstract class RecyclerFragment<T, Item : IItem<*, *>> : BaseFragment(), Recycle
|
||||
return@doAsync callback(false)
|
||||
}
|
||||
progress(80)
|
||||
val items = toItems(data)
|
||||
val items = toItems(data.data)
|
||||
progress(97)
|
||||
adapter.setNewList(items)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import com.pitchedapps.frost.contracts.FrostContentContainer
|
||||
import com.pitchedapps.frost.contracts.FrostContentCore
|
||||
import com.pitchedapps.frost.contracts.FrostContentParent
|
||||
import com.pitchedapps.frost.contracts.MainActivityContract
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.views.FrostRecyclerView
|
||||
import io.reactivex.disposables.Disposable
|
||||
|
||||
|
@ -1,6 +1,14 @@
|
||||
package com.pitchedapps.frost.parsers
|
||||
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.facebook.FB_CSS_URL_MATCHER
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.facebook.get
|
||||
import com.pitchedapps.frost.services.NotificationContent
|
||||
import com.pitchedapps.frost.utils.frostJsoup
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-10-06.
|
||||
@ -13,80 +21,88 @@ import org.jsoup.nodes.Document
|
||||
* The return type must be nonnull if no parsing errors occurred, as null signifies a parse error
|
||||
* If null really must be allowed, use Optionals
|
||||
*/
|
||||
interface FrostParser<T> {
|
||||
/**
|
||||
* Extracts data from the JSoup document
|
||||
* In some cases, the document can be created directly from a connection
|
||||
* In other times, it needs to be created from scripts, which otherwise
|
||||
* won't be parsed
|
||||
*/
|
||||
fun parse(doc: Document): T?
|
||||
interface FrostParser<out T : Any> {
|
||||
|
||||
/**
|
||||
* Parse a String input
|
||||
* Url to request from
|
||||
*/
|
||||
fun parse(text: String?): T?
|
||||
val url: String
|
||||
|
||||
/**
|
||||
* Take in doc and emit debug output
|
||||
* Call parsing with default implementation using cookie
|
||||
*/
|
||||
fun debug(doc: Document): String
|
||||
fun parse(cookie: String?): ParseResponse<T>?
|
||||
|
||||
/**
|
||||
* Attempts to parse input and emit a debugger
|
||||
* Call parsing with given document
|
||||
*/
|
||||
fun debug(text: String?): String
|
||||
fun parse(cookie: String?, document: Document): ParseResponse<T>?
|
||||
|
||||
/**
|
||||
* Call parsing with given data
|
||||
*/
|
||||
fun parseFromData(cookie: String?, text: String): ParseResponse<T>?
|
||||
|
||||
}
|
||||
|
||||
internal abstract class FrostParserBase<T> : FrostParser<T> {
|
||||
data class FrostLink(val text: String, val href: String)
|
||||
|
||||
override final fun parse(text: String?): T? {
|
||||
text ?: return null
|
||||
data class ParseResponse<out T>(val cookie: String, val data: T) {
|
||||
override fun toString() = "ParseResponse\ncookie: $cookie\ndata:\n$data"
|
||||
}
|
||||
|
||||
interface ParseNotification {
|
||||
fun getUnreadNotifications(data: CookieModel): List<NotificationContent>
|
||||
}
|
||||
|
||||
internal fun <T> List<T>.toJsonString(tag: String, indent: Int) = StringBuilder().apply {
|
||||
val tabs = "\t".repeat(indent)
|
||||
append("$tabs$tag: [\n\t$tabs")
|
||||
append(this@toJsonString.joinToString("\n\t$tabs"))
|
||||
append("\n$tabs]\n")
|
||||
}.toString()
|
||||
|
||||
/**
|
||||
* T should have a readable toString() function
|
||||
* [redirectToText] dictates whether all data should be converted to text then back to document before parsing
|
||||
*/
|
||||
internal abstract class FrostParserBase<out T : Any>(private val redirectToText: Boolean) : FrostParser<T> {
|
||||
|
||||
override final fun parse(cookie: String?) = parse(cookie, frostJsoup(cookie, url))
|
||||
|
||||
override final fun parseFromData(cookie: String?, text: String): ParseResponse<T>? {
|
||||
cookie ?: return null
|
||||
val doc = textToDoc(text) ?: return null
|
||||
return parse(doc)
|
||||
val data = parseImpl(doc) ?: return null
|
||||
return ParseResponse(cookie, data)
|
||||
}
|
||||
|
||||
protected abstract fun textToDoc(text: String): Document?
|
||||
|
||||
override fun debug(text: String?): String {
|
||||
val result = mutableListOf<String>()
|
||||
result.add("Testing parser for ${this::class.java.simpleName}")
|
||||
if (text == null) {
|
||||
result.add("Null text input")
|
||||
return result.joinToString("\n")
|
||||
}
|
||||
val doc = textToDoc(text)
|
||||
if (doc == null) {
|
||||
result.add("Null document from text")
|
||||
return result.joinToString("\n")
|
||||
}
|
||||
return debug(doc, result)
|
||||
override fun parse(cookie: String?, document: Document): ParseResponse<T>? {
|
||||
cookie ?: return null
|
||||
if (redirectToText)
|
||||
return parseFromData(cookie, document.toString())
|
||||
val data = parseImpl(document) ?: return null
|
||||
return ParseResponse(cookie, data)
|
||||
}
|
||||
|
||||
override final fun debug(doc: Document): String {
|
||||
val result = mutableListOf<String>()
|
||||
result.add("Testing parser for ${this::class.java.simpleName}")
|
||||
return debug(doc, result)
|
||||
protected abstract fun parseImpl(doc: Document): T?
|
||||
|
||||
// protected abstract fun parse(doc: Document): T?
|
||||
|
||||
/**
|
||||
* Attempts to find inner <i> element with some style containing a url
|
||||
* Returns the formatted url, or an empty string if nothing was found
|
||||
*/
|
||||
protected fun Element.getInnerImgStyle() =
|
||||
FB_CSS_URL_MATCHER.find(select("i.img[style*=url]").attr("style"))[1]?.formattedFbUrl ?: ""
|
||||
|
||||
protected open fun textToDoc(text: String) = if (!redirectToText)
|
||||
Jsoup.parse(text)
|
||||
else
|
||||
throw RuntimeException("${this::class.java.simpleName} requires text redirect but did not implement textToDoc")
|
||||
|
||||
protected fun parseLink(element: Element?): FrostLink? {
|
||||
val a = element?.getElementsByTag("a")?.first() ?: return null
|
||||
return FrostLink(a.text(), a.attr("href"))
|
||||
}
|
||||
|
||||
private fun debug(doc: Document, result: MutableList<String>): String {
|
||||
val output = parse(doc)
|
||||
if (output == null) {
|
||||
result.add("Output is null")
|
||||
return result.joinToString("\n")
|
||||
} else {
|
||||
result.add("Output is not null")
|
||||
}
|
||||
debugImpl(output, result)
|
||||
return result.joinToString("\n")
|
||||
}
|
||||
|
||||
protected abstract fun debugImpl(data: T, result: MutableList<String>)
|
||||
}
|
||||
|
||||
object FrostRegex {
|
||||
val epoch = Regex(":([0-9]+)")
|
||||
val notifId = Regex("notif_id\":([0-9]+)")
|
||||
val messageNotifId = Regex("thread_fbid_([0-9]+)")
|
||||
val profilePicture = Regex("url\\(\"(.*?)\"\\)")
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package com.pitchedapps.frost.parsers
|
||||
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.facebook.*
|
||||
import com.pitchedapps.frost.services.NotificationContent
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
import org.jsoup.Jsoup
|
||||
@ -14,13 +16,55 @@ import org.jsoup.nodes.Element
|
||||
* We can parse out the content we want directly and load it ourselves
|
||||
*
|
||||
*/
|
||||
object MessageParser : FrostParser<Triple<List<FrostThread>, FrostLink?, List<FrostLink>>> by MessageParserImpl()
|
||||
object MessageParser : FrostParser<FrostMessages> by MessageParserImpl()
|
||||
|
||||
data class FrostThread(val id: Int, val img: String, val title: String, val time: Long, val url: String, val unread: Boolean, val content: String?)
|
||||
data class FrostMessages(val threads: List<FrostThread>,
|
||||
val seeMore: FrostLink?,
|
||||
val extraLinks: List<FrostLink>
|
||||
) : ParseNotification {
|
||||
override fun toString() = StringBuilder().apply {
|
||||
append("FrostMessages {\n")
|
||||
append(threads.toJsonString("threads", 1))
|
||||
append("\tsee more: $seeMore\n")
|
||||
append(extraLinks.toJsonString("extra links", 1))
|
||||
append("}")
|
||||
}.toString()
|
||||
|
||||
data class FrostLink(val text: String, val href: String)
|
||||
override fun getUnreadNotifications(data: CookieModel) =
|
||||
threads.filter(FrostThread::unread).map {
|
||||
with(it) {
|
||||
NotificationContent(
|
||||
data = data,
|
||||
notifId = Math.abs(id.toInt()),
|
||||
href = url,
|
||||
title = title,
|
||||
text = content ?: "",
|
||||
timestamp = time,
|
||||
profileUrl = img
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageParserImpl : FrostParserBase<Triple<List<FrostThread>, FrostLink?, List<FrostLink>>>() {
|
||||
/**
|
||||
* [id] user/thread id, or current time fallback
|
||||
* [img] parsed url for profile img
|
||||
* [time] time of message
|
||||
* [url] link to thread
|
||||
* [unread] true if image is unread, false otherwise
|
||||
* [content] optional string for thread
|
||||
*/
|
||||
data class FrostThread(val id: Long,
|
||||
val img: String,
|
||||
val title: String,
|
||||
val time: Long,
|
||||
val url: String,
|
||||
val unread: Boolean,
|
||||
val content: String?)
|
||||
|
||||
private class MessageParserImpl : FrostParserBase<FrostMessages>(true) {
|
||||
|
||||
override val url = FbItem.MESSAGES.url
|
||||
|
||||
override fun textToDoc(text: String): Document? {
|
||||
var content = StringEscapeUtils.unescapeEcmaScript(text)
|
||||
@ -39,32 +83,29 @@ private class MessageParserImpl : FrostParserBase<Triple<List<FrostThread>, Fros
|
||||
return Jsoup.parseBodyFragment("<div $content")
|
||||
}
|
||||
|
||||
override fun parse(doc: Document): Triple<List<FrostThread>, FrostLink?, List<FrostLink>>? {
|
||||
val threadList = doc.getElementById("threadlist_rows")
|
||||
override fun parseImpl(doc: Document): FrostMessages? {
|
||||
val threadList = doc.getElementById("threadlist_rows") ?: return null
|
||||
val threads: List<FrostThread> = threadList.getElementsByAttributeValueContaining("id", "thread_fbid_")
|
||||
.mapNotNull { parseMessage(it) }
|
||||
.mapNotNull(this::parseMessage)
|
||||
val seeMore = parseLink(doc.getElementById("see_older_threads"))
|
||||
val extraLinks = threadList.nextElementSibling().select("a")
|
||||
.mapNotNull { parseLink(it) }
|
||||
return Triple(threads, seeMore, extraLinks)
|
||||
.mapNotNull(this::parseLink)
|
||||
return FrostMessages(threads, seeMore, extraLinks)
|
||||
}
|
||||
|
||||
private fun parseMessage(element: Element): FrostThread? {
|
||||
val a = element.getElementsByTag("a").first() ?: return null
|
||||
val abbr = element.getElementsByTag("abbr")
|
||||
val epoch = FrostRegex.epoch.find(abbr.attr("data-store"))
|
||||
?.groupValues?.getOrNull(1)?.toLongOrNull() ?: -1L
|
||||
val epoch = FB_EPOCH_MATCHER.find(abbr.attr("data-store"))[1]?.toLongOrNull() ?: -1L
|
||||
//fetch id
|
||||
val id = FrostRegex.messageNotifId.find(element.id())
|
||||
?.groupValues?.getOrNull(1)?.toLongOrNull() ?: System.currentTimeMillis()
|
||||
val id = FB_MESSAGE_NOTIF_ID_MATCHER.find(element.id())[1]?.toLongOrNull()
|
||||
?: System.currentTimeMillis()
|
||||
val content = element.select("span.snippet").firstOrNull()?.text()?.trim()
|
||||
//fetch convo pic
|
||||
val p = element.select("i.img[style*=url]")
|
||||
val pUrl = FrostRegex.profilePicture.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: ""
|
||||
val img = element.getInnerImgStyle()
|
||||
L.v("url", a.attr("href"))
|
||||
return FrostThread(
|
||||
id = id.toInt(),
|
||||
img = pUrl.formattedFbUrl,
|
||||
id = id,
|
||||
img = img,
|
||||
title = a.text(),
|
||||
time = epoch,
|
||||
url = a.attr("href").formattedFbUrl,
|
||||
@ -73,15 +114,4 @@ private class MessageParserImpl : FrostParserBase<Triple<List<FrostThread>, Fros
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseLink(element: Element?): FrostLink? {
|
||||
val a = element?.getElementsByTag("a")?.first() ?: return null
|
||||
return FrostLink(a.text(), a.attr("href"))
|
||||
}
|
||||
|
||||
override fun debugImpl(data: Triple<List<FrostThread>, FrostLink?, List<FrostLink>>, result: MutableList<String>) {
|
||||
result.addAll(data.first.map(FrostThread::toString))
|
||||
result.add("See more link:")
|
||||
result.add("\t${data.second}")
|
||||
result.addAll(data.third.map(FrostLink::toString))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
package com.pitchedapps.frost.parsers
|
||||
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.facebook.*
|
||||
import com.pitchedapps.frost.services.NotificationContent
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-12-25.
|
||||
*
|
||||
*/
|
||||
object NotifParser : FrostParser<FrostNotifs> by NotifParserImpl()
|
||||
|
||||
data class FrostNotifs(
|
||||
val notifs: List<FrostNotif>,
|
||||
val seeMore: FrostLink?
|
||||
) : ParseNotification {
|
||||
override fun toString() = StringBuilder().apply {
|
||||
append("FrostNotifs {\n")
|
||||
append(notifs.toJsonString("notifs", 1))
|
||||
append("\tsee more: $seeMore\n")
|
||||
append("}")
|
||||
}.toString()
|
||||
|
||||
override fun getUnreadNotifications(data: CookieModel) =
|
||||
notifs.filter(FrostNotif::unread).map {
|
||||
with(it) {
|
||||
NotificationContent(
|
||||
data = data,
|
||||
notifId = Math.abs(id.toInt()),
|
||||
href = url,
|
||||
title = null,
|
||||
text = content ?: "",
|
||||
timestamp = time,
|
||||
profileUrl = img
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [id] notif id, or current time fallback
|
||||
* [img] parsed url for profile img
|
||||
* [time] time of message
|
||||
* [url] link to thread
|
||||
* [unread] true if image is unread, false otherwise
|
||||
* [content] optional string for thread
|
||||
*/
|
||||
data class FrostNotif(val id: Long,
|
||||
val img: String,
|
||||
val time: Long,
|
||||
val url: String,
|
||||
val unread: Boolean,
|
||||
val content: String?)
|
||||
|
||||
private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) {
|
||||
|
||||
override val url = FbItem.NOTIFICATIONS.url
|
||||
|
||||
override fun parseImpl(doc: Document): FrostNotifs? {
|
||||
val notificationList = doc.getElementById("notifications_list") ?: return null
|
||||
val notifications = notificationList.getElementsByAttributeValueContaining("id", "list_notif_")
|
||||
.mapNotNull { parseNotif(it) }
|
||||
val seeMore = parseLink(doc.getElementsByAttributeValue("href", "/notifications.php?more").first())
|
||||
return FrostNotifs(notifications, seeMore)
|
||||
}
|
||||
|
||||
private fun parseNotif(element: Element): FrostNotif? {
|
||||
val a = element.getElementsByTag("a").first() ?: return null
|
||||
val abbr = element.getElementsByTag("abbr")
|
||||
val epoch = FB_EPOCH_MATCHER.find(abbr.attr("data-store"))[1]?.toLongOrNull() ?: -1L
|
||||
//fetch id
|
||||
val id = FB_NOTIF_ID_MATCHER.find(element.id())[1]?.toLongOrNull()
|
||||
?: System.currentTimeMillis()
|
||||
val img = element.getInnerImgStyle()
|
||||
val timeString = abbr.text()
|
||||
val content = a.text().replace("\u00a0", " ").removeSuffix(timeString).trim() //remove
|
||||
L.v("url", a.attr("href"))
|
||||
return FrostNotif(
|
||||
id = id,
|
||||
img = img,
|
||||
time = epoch,
|
||||
url = a.attr("href").formattedFbUrl,
|
||||
unread = !element.hasClass("acw"),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package com.pitchedapps.frost.parsers
|
||||
|
||||
import ca.allanwang.kau.utils.withMaxLength
|
||||
import ca.allanwang.kau.searchview.SearchItem
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.parsers.FrostSearch.Companion.create
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.frostJsoup
|
||||
import org.jsoup.Jsoup
|
||||
@ -12,11 +14,11 @@ import org.jsoup.nodes.Element
|
||||
/**
|
||||
* Created by Allan Wang on 2017-10-09.
|
||||
*/
|
||||
object SearchParser : FrostParser<List<FrostSearch>> by SearchParserImpl() {
|
||||
fun query(input: String): List<FrostSearch>? {
|
||||
object SearchParser : FrostParser<FrostSearches> by SearchParserImpl() {
|
||||
fun query(cookie: String?, input: String): ParseResponse<FrostSearches>? {
|
||||
val url = "${FbItem._SEARCH.url}?q=${if (input.isNotBlank()) input else "a"}"
|
||||
L.i(null, "Search Query $url")
|
||||
return parse(frostJsoup(url))
|
||||
return parse(cookie, frostJsoup(url))
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,25 +27,40 @@ enum class SearchKeys(val key: String) {
|
||||
EVENTS("keywords_events")
|
||||
}
|
||||
|
||||
data class FrostSearches(val results: List<FrostSearch>) {
|
||||
|
||||
override fun toString() = StringBuilder().apply {
|
||||
append("FrostSearches {\n")
|
||||
append(results.toJsonString("results", 1))
|
||||
append("}")
|
||||
}.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* As far as I'm aware, all links are independent, so the queries don't matter
|
||||
* A lot of it is tracking information, which I'll strip away
|
||||
* Other text items are formatted for safety
|
||||
*
|
||||
* Note that it's best to create search results from [create]
|
||||
*/
|
||||
class FrostSearch(href: String, title: String, description: String?) {
|
||||
val href = with(href.indexOf("?")) { if (this == -1) href else href.substring(0, this) }
|
||||
val title = title.format()
|
||||
val description = description?.format()
|
||||
data class FrostSearch(val href: String, val title: String, val description: String?) {
|
||||
|
||||
private fun String.format() = replace("\n", " ").withMaxLength(50)
|
||||
|
||||
override fun toString(): String
|
||||
= "FrostSearch(href=$href, title=$title, description=$description)"
|
||||
fun toSearchItem() = SearchItem(href, title, description)
|
||||
|
||||
companion object {
|
||||
fun create(href: String, title: String, description: String?) = FrostSearch(
|
||||
with(href.indexOf("?")) { if (this == -1) href else href.substring(0, this) },
|
||||
title.format(),
|
||||
description?.format()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchParserImpl : FrostParserBase<List<FrostSearch>>() {
|
||||
override fun parse(doc: Document): List<FrostSearch>? {
|
||||
private class SearchParserImpl : FrostParserBase<FrostSearches>(false) {
|
||||
|
||||
override val url = "${FbItem._SEARCH.url}?q=a"
|
||||
|
||||
override fun parseImpl(doc: Document): FrostSearches? {
|
||||
val container: Element = doc.getElementById("BrowseResultsContainer")
|
||||
?: doc.getElementById("root")
|
||||
?: return null
|
||||
@ -51,19 +68,11 @@ private class SearchParserImpl : FrostParserBase<List<FrostSearch>>() {
|
||||
*
|
||||
* Removed [data-store*=result_id]
|
||||
*/
|
||||
return container.select("a.touchable[href]").filter(Element::hasText).map {
|
||||
FrostSearch(it.attr("href").formattedFbUrl,
|
||||
return FrostSearches(container.select("a.touchable[href]").filter(Element::hasText).map {
|
||||
FrostSearch.create(it.attr("href").formattedFbUrl,
|
||||
it.select("._uoi").first()?.text() ?: "",
|
||||
it.select("._1tcc").first()?.text())
|
||||
}.filter { it.title.isNotBlank() }
|
||||
}
|
||||
|
||||
|
||||
override fun textToDoc(text: String): Document? = Jsoup.parse(text)
|
||||
|
||||
override fun debugImpl(data: List<FrostSearch>, result: MutableList<String>) {
|
||||
result.add("Has size ${data.size}")
|
||||
result.addAll(data.map(FrostSearch::toString))
|
||||
}.filter { it.title.isNotBlank() })
|
||||
}
|
||||
|
||||
}
|
@ -24,12 +24,17 @@ import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.FrostWebActivity
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.dbflow.NotificationModel
|
||||
import com.pitchedapps.frost.dbflow.lastNotificationTime
|
||||
import com.pitchedapps.frost.enums.OverlayContext
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.parsers.FrostThread
|
||||
import com.pitchedapps.frost.parsers.FrostParser
|
||||
import com.pitchedapps.frost.parsers.MessageParser
|
||||
import com.pitchedapps.frost.parsers.NotifParser
|
||||
import com.pitchedapps.frost.parsers.ParseNotification
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import org.jetbrains.anko.runOnUiThread
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-07-08.
|
||||
@ -88,23 +93,66 @@ class FrostNotificationTarget(val context: Context,
|
||||
* Enum to handle notification creations
|
||||
*/
|
||||
enum class NotificationType(
|
||||
private val groupPrefix: String,
|
||||
private val overlayContext: OverlayContext,
|
||||
private val contentRes: Int,
|
||||
private val pendingUrl: String,
|
||||
private val fbItem: FbItem,
|
||||
private val parser: FrostParser<ParseNotification>,
|
||||
private val getTime: (notif: NotificationModel) -> Long,
|
||||
private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
|
||||
private val ringtone: () -> String) {
|
||||
GENERAL("frost", OverlayContext.NOTIFICATION, R.string.notifications, FbItem.NOTIFICATIONS.url, { Prefs.notificationRingtone }),
|
||||
MESSAGE("frost_im", OverlayContext.MESSAGE, R.string.messages, FbItem.MESSAGES.url, { Prefs.messageRingtone });
|
||||
GENERAL(OverlayContext.NOTIFICATION,
|
||||
FbItem.NOTIFICATIONS,
|
||||
NotifParser,
|
||||
NotificationModel::epoch,
|
||||
{ notif, time -> notif.copy(epoch = time) },
|
||||
Prefs::notificationRingtone),
|
||||
MESSAGE(OverlayContext.MESSAGE,
|
||||
FbItem.MESSAGES,
|
||||
MessageParser,
|
||||
NotificationModel::epochIm,
|
||||
{ notif, time -> notif.copy(epochIm = time) },
|
||||
Prefs::messageRingtone);
|
||||
|
||||
private val groupPrefix = "frost_${name.toLowerCase(Locale.CANADA)}"
|
||||
|
||||
/**
|
||||
* Get unread data from designated parser
|
||||
* Display notifications for those after old epoch
|
||||
* Save new epoch
|
||||
*/
|
||||
fun fetch(context: Context, data: CookieModel) {
|
||||
val response = parser.parse(data.cookie)
|
||||
?: return L.eThrow("$name notification data not found")
|
||||
val notifs = response.data.getUnreadNotifications(data)
|
||||
if (notifs.isEmpty()) return
|
||||
var notifCount = 0
|
||||
val userId = data.id
|
||||
val prevNotifTime = lastNotificationTime(userId)
|
||||
val prevLatestEpoch = getTime(prevNotifTime)
|
||||
L.v("Notif $name prev epoch $prevLatestEpoch")
|
||||
var newLatestEpoch = prevLatestEpoch
|
||||
notifs.forEach { notif ->
|
||||
L.v("Notif timestamp ${notif.timestamp}")
|
||||
if (notif.timestamp <= prevLatestEpoch) return@forEach
|
||||
createNotification(context, notif, notifCount == 0)
|
||||
if (notif.timestamp > newLatestEpoch)
|
||||
newLatestEpoch = notif.timestamp
|
||||
notifCount++
|
||||
}
|
||||
if (newLatestEpoch != prevLatestEpoch)
|
||||
putTime(prevNotifTime, newLatestEpoch).save()
|
||||
L.d("Notif $name new epoch ${getTime(lastNotificationTime(userId))}")
|
||||
summaryNotification(context, userId, notifCount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and submit a new notification with the given [content]
|
||||
* If [withDefaults] is set, it will also add the appropriate sound, vibration, and light
|
||||
* Note that when we have multiple notifications coming in at once, we don't want to have defaults for all of them
|
||||
*/
|
||||
fun createNotification(context: Context, content: NotificationContent, withDefaults: Boolean) {
|
||||
private fun createNotification(context: Context, content: NotificationContent, withDefaults: Boolean) {
|
||||
with(content) {
|
||||
val intent = Intent(context, FrostWebActivity::class.java)
|
||||
intent.data = Uri.parse(href.formattedFbUrl)
|
||||
intent.data = Uri.parse(href)
|
||||
intent.putExtra(ARG_USER_ID, data.id)
|
||||
intent.putExtra(ARG_OVERLAY_CONTEXT, overlayContext)
|
||||
val group = "${groupPrefix}_${data.id}"
|
||||
@ -142,16 +190,16 @@ enum class NotificationType(
|
||||
* This will always produce sound, vibration, and lights based on preferences
|
||||
* and will only show if we have at least 2 notifications
|
||||
*/
|
||||
fun summaryNotification(context: Context, userId: Long, count: Int) {
|
||||
private fun summaryNotification(context: Context, userId: Long, count: Int) {
|
||||
frostAnswersCustom("Notifications", "Type" to name, "Count" to count)
|
||||
if (count <= 1) return
|
||||
val intent = Intent(context, FrostWebActivity::class.java)
|
||||
intent.data = Uri.parse(pendingUrl)
|
||||
intent.data = Uri.parse(fbItem.url)
|
||||
intent.putExtra(ARG_USER_ID, userId)
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
|
||||
val notifBuilder = context.frostNotification.withDefaults(ringtone())
|
||||
.setContentTitle(context.string(R.string.frost_name))
|
||||
.setContentText("$count ${context.string(contentRes)}")
|
||||
.setContentText("$count ${context.string(fbItem.titleId)}")
|
||||
.setGroup("${groupPrefix}_$userId")
|
||||
.setGroupSummary(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
@ -167,13 +215,10 @@ enum class NotificationType(
|
||||
data class NotificationContent(val data: CookieModel,
|
||||
val notifId: Int,
|
||||
val href: String,
|
||||
val title: String? = null,
|
||||
val title: String? = null, // defaults to frost title
|
||||
val text: String,
|
||||
val timestamp: Long,
|
||||
val profileUrl: String) {
|
||||
constructor(data: CookieModel, thread: FrostThread)
|
||||
: this(data, thread.id, thread.url, thread.title, thread.content ?: "", thread.time, thread.img)
|
||||
}
|
||||
val profileUrl: String)
|
||||
|
||||
const val NOTIFICATION_PERIODIC_JOB = 7
|
||||
|
||||
|
@ -7,18 +7,11 @@ import android.support.v4.app.NotificationManagerCompat
|
||||
import ca.allanwang.kau.utils.string
|
||||
import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.dbflow.lastNotificationTime
|
||||
import com.pitchedapps.frost.dbflow.loadFbCookiesSync
|
||||
import com.pitchedapps.frost.facebook.FbItem
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.parsers.MessageParser
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.frostAnswersCustom
|
||||
import com.pitchedapps.frost.utils.frostJsoup
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.concurrent.Future
|
||||
|
||||
/**
|
||||
@ -27,8 +20,7 @@ import java.util.concurrent.Future
|
||||
* Service to manage notifications
|
||||
* Will periodically check through all accounts in the db and send notifications when appropriate
|
||||
*
|
||||
* Note that general notifications are parsed directly with Jsoup,
|
||||
* but instant messages are done so with a headless webview as it is generated from JS
|
||||
* All fetching is done through parsers
|
||||
*/
|
||||
class NotificationService : JobService() {
|
||||
|
||||
@ -36,13 +28,6 @@ class NotificationService : JobService() {
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
companion object {
|
||||
val epochMatcher: Regex by lazy { Regex(":([0-9]*?),") }
|
||||
val notifIdMatcher: Regex by lazy { Regex("notif_id\":([0-9]*?),") }
|
||||
val messageNotifIdMatcher: Regex by lazy { Regex("thread_fbid_([0-9]+)") }
|
||||
val profMatcher: Regex by lazy { Regex("url\\(\"(.*?)\"\\)") }
|
||||
}
|
||||
|
||||
override fun onStopJob(params: JobParameters?): Boolean {
|
||||
val time = System.currentTimeMillis() - startTime
|
||||
L.d("Notification service has finished abruptly in $time ms")
|
||||
@ -70,104 +55,28 @@ class NotificationService : JobService() {
|
||||
override fun onStartJob(params: JobParameters?): Boolean {
|
||||
L.i("Fetching notifications")
|
||||
future = doAsync {
|
||||
val context = weakRef.get()
|
||||
?: return@doAsync L.eThrow("NotificationService had null weakRef to self")
|
||||
val currentId = Prefs.userId
|
||||
val cookies = loadFbCookiesSync()
|
||||
cookies.forEach {
|
||||
val current = it.id == currentId
|
||||
if (current || Prefs.notificationAllAccounts)
|
||||
fetchGeneralNotifications(it)
|
||||
if (Prefs.notificationsInstantMessages && (current || Prefs.notificationsImAllAccounts))
|
||||
fetchMessageNotifications(it)
|
||||
NotificationType.GENERAL.fetch(context, it)
|
||||
if (Prefs.notificationsInstantMessages
|
||||
&& (current || Prefs.notificationsImAllAccounts))
|
||||
NotificationType.MESSAGE.fetch(context, it)
|
||||
}
|
||||
finish(params)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun logNotif(text: String): NotificationContent? {
|
||||
private fun logNotif(text: String): NotificationContent? {
|
||||
L.eThrow("NotificationService: $text")
|
||||
return null
|
||||
}
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------
|
||||
* General notification logic.
|
||||
* Fetch notifications -> Filter new ones -> Parse notifications ->
|
||||
* Show notifications -> Show group notification
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
fun fetchGeneralNotifications(data: CookieModel) {
|
||||
L.d("Notif fetch", data.toString())
|
||||
val doc = frostJsoup(data.cookie, FbItem.NOTIFICATIONS.url)
|
||||
//aclb for unread, acw for read
|
||||
val unreadNotifications = (doc.getElementById("notifications_list") ?: return L.eThrow("Notification list not found")).getElementsByClass("aclb")
|
||||
var notifCount = 0
|
||||
//val prevLatestEpoch = 1498931565L // for testing
|
||||
val prevNotifTime = lastNotificationTime(data.id)
|
||||
val prevLatestEpoch = prevNotifTime.epoch
|
||||
L.v("Notif Prev Latest Epoch $prevLatestEpoch")
|
||||
var newLatestEpoch = prevLatestEpoch
|
||||
unreadNotifications.forEach unread@ { elem ->
|
||||
val notif = parseNotification(data, elem) ?: return@unread
|
||||
L.v("Notif timestamp ${notif.timestamp}")
|
||||
if (notif.timestamp <= prevLatestEpoch) return@unread
|
||||
NotificationType.GENERAL.createNotification(this, notif, notifCount == 0)
|
||||
if (notif.timestamp > newLatestEpoch)
|
||||
newLatestEpoch = notif.timestamp
|
||||
notifCount++
|
||||
}
|
||||
if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).save()
|
||||
L.d("Notif new latest epoch ${lastNotificationTime(data.id).epoch}")
|
||||
NotificationType.GENERAL.summaryNotification(this, data.id, notifCount)
|
||||
}
|
||||
|
||||
fun parseNotification(data: CookieModel, element: Element): NotificationContent? {
|
||||
val a = element.getElementsByTag("a").first() ?: return logNotif("IM No a tag")
|
||||
val abbr = element.getElementsByTag("abbr")
|
||||
val epoch = epochMatcher.find(abbr.attr("data-store"))?.groups?.get(1)?.value?.toLong() ?: return logNotif("IM No epoch")
|
||||
//fetch id
|
||||
val notifId = notifIdMatcher.find(a.attr("data-store"))?.groups?.get(1)?.value?.toLong() ?: System.currentTimeMillis()
|
||||
val timeString = abbr.text()
|
||||
val text = a.text().replace("\u00a0", " ").removeSuffix(timeString).trim() //remove
|
||||
if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out
|
||||
//fetch profpic
|
||||
val p = element.select("i.img[style*=url]")
|
||||
val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: ""
|
||||
return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl)
|
||||
}
|
||||
|
||||
/*
|
||||
* ----------------------------------------------------------------
|
||||
* Instant message notification logic.
|
||||
* Fetch notifications -> Filter new ones -> Parse notifications ->
|
||||
* Show notifications -> Show group notification
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
fun fetchMessageNotifications(data: CookieModel) {
|
||||
L.d("Notif IM fetch", data.toString())
|
||||
val doc = frostJsoup(data.cookie, FbItem.MESSAGES.url)
|
||||
val (threads, _, _) = MessageParser.parse(doc.toString()) ?: return L.e("Could not parse IM")
|
||||
|
||||
var notifCount = 0
|
||||
val prevNotifTime = lastNotificationTime(data.id)
|
||||
val prevLatestEpoch = prevNotifTime.epochIm
|
||||
L.v("Notif Prev Latest Im Epoch $prevLatestEpoch")
|
||||
var newLatestEpoch = prevLatestEpoch
|
||||
threads.filter { it.unread }.forEach { notif ->
|
||||
L.v("Notif Im timestamp ${notif.time}")
|
||||
if (notif.time <= prevLatestEpoch) return@forEach
|
||||
NotificationType.MESSAGE.createNotification(this, NotificationContent(data, notif), notifCount == 0)
|
||||
if (notif.time > newLatestEpoch)
|
||||
newLatestEpoch = notif.time
|
||||
notifCount++
|
||||
}
|
||||
if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save()
|
||||
L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}")
|
||||
NotificationType.MESSAGE.summaryNotification(this, data.id, notifCount)
|
||||
}
|
||||
|
||||
private fun Context.debugNotification(text: String) {
|
||||
if (!BuildConfig.DEBUG) return
|
||||
val notifBuilder = frostNotification.withDefaults()
|
||||
|
@ -22,7 +22,7 @@ import com.pitchedapps.frost.views.Keywords
|
||||
*/
|
||||
fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
|
||||
text(R.string.notification_frequency, { Prefs.notificationFreq }, { Prefs.notificationFreq = it }) {
|
||||
text(R.string.notification_frequency, Prefs::notificationFreq, { Prefs.notificationFreq = it }) {
|
||||
val options = longArrayOf(-1, 15, 30, 60, 120, 180, 300, 1440, 2880)
|
||||
val texts = options.map { if (it <= 0) string(R.string.no_notifications) else minuteToText(it) }
|
||||
onClick = {
|
||||
@ -52,23 +52,27 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
}
|
||||
}
|
||||
|
||||
checkbox(R.string.notification_all_accounts, { Prefs.notificationAllAccounts }, { Prefs.notificationAllAccounts = it }) {
|
||||
checkbox(R.string.notification_all_accounts, Prefs::notificationAllAccounts, { Prefs.notificationAllAccounts = it }) {
|
||||
descRes = R.string.notification_all_accounts_desc
|
||||
}
|
||||
|
||||
checkbox(R.string.notification_messages, { Prefs.notificationsInstantMessages }, { Prefs.notificationsInstantMessages = it; reloadByTitle(R.string.notification_messages_all_accounts) }) {
|
||||
checkbox(R.string.notification_messages, Prefs::notificationsInstantMessages, { Prefs.notificationsInstantMessages = it; reloadByTitle(R.string.notification_messages_all_accounts) }) {
|
||||
descRes = R.string.notification_messages_desc
|
||||
}
|
||||
|
||||
checkbox(R.string.notification_messages_all_accounts, { Prefs.notificationsImAllAccounts }, { Prefs.notificationsImAllAccounts = it }) {
|
||||
checkbox(R.string.notification_messages_all_accounts, Prefs::notificationsImAllAccounts, { Prefs.notificationsImAllAccounts = it }) {
|
||||
descRes = R.string.notification_messages_all_accounts_desc
|
||||
enabler = { Prefs.notificationsInstantMessages }
|
||||
enabler = Prefs::notificationsInstantMessages
|
||||
}
|
||||
|
||||
checkbox(R.string.notification_sound, { Prefs.notificationSound }, { Prefs.notificationSound = it; reloadByTitle(R.string.notification_ringtone, R.string.message_ringtone) })
|
||||
checkbox(R.string.notification_sound, Prefs::notificationSound, {
|
||||
Prefs.notificationSound = it
|
||||
reloadByTitle(R.string.notification_ringtone,
|
||||
R.string.message_ringtone)
|
||||
})
|
||||
|
||||
fun KPrefText.KPrefTextContract<String>.ringtone(code: Int) {
|
||||
enabler = { Prefs.notificationSound }
|
||||
enabler = Prefs::notificationSound
|
||||
textGetter = {
|
||||
if (it.isBlank()) string(R.string.kau_default)
|
||||
else RingtoneManager.getRingtone(this@getNotificationPrefs, Uri.parse(it))
|
||||
@ -87,17 +91,17 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
}
|
||||
}
|
||||
|
||||
text(R.string.notification_ringtone, { Prefs.notificationRingtone }, { Prefs.notificationRingtone = it }) {
|
||||
text(R.string.notification_ringtone, Prefs::notificationRingtone, { Prefs.notificationRingtone = it }) {
|
||||
ringtone(SettingsActivity.REQUEST_NOTIFICATION_RINGTONE)
|
||||
}
|
||||
|
||||
text(R.string.message_ringtone, { Prefs.messageRingtone }, { Prefs.messageRingtone = it }) {
|
||||
text(R.string.message_ringtone, Prefs::messageRingtone, { Prefs.messageRingtone = it }) {
|
||||
ringtone(SettingsActivity.REQUEST_MESSAGE_RINGTONE)
|
||||
}
|
||||
|
||||
checkbox(R.string.notification_vibrate, { Prefs.notificationVibrate }, { Prefs.notificationVibrate = it })
|
||||
checkbox(R.string.notification_vibrate, Prefs::notificationVibrate, { Prefs.notificationVibrate = it })
|
||||
|
||||
checkbox(R.string.notification_lights, { Prefs.notificationLights }, { Prefs.notificationLights = it })
|
||||
checkbox(R.string.notification_lights, Prefs::notificationLights, { Prefs.notificationLights = it })
|
||||
|
||||
plainText(R.string.notification_fetch_now) {
|
||||
descRes = R.string.notification_fetch_now_desc
|
||||
|
@ -2,6 +2,7 @@ package com.pitchedapps.frost.web
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
@ -12,6 +13,7 @@ import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.facebook.FB_LOGIN_URL
|
||||
import com.pitchedapps.frost.facebook.FB_USER_MATCHER
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.facebook.get
|
||||
import com.pitchedapps.frost.injectors.CssHider
|
||||
import com.pitchedapps.frost.injectors.jsInject
|
||||
import com.pitchedapps.frost.utils.L
|
||||
@ -31,7 +33,7 @@ class LoginWebView @JvmOverloads constructor(
|
||||
private lateinit var progressCallback: (Int) -> Unit
|
||||
|
||||
init {
|
||||
FbCookie.reset { setupWebview() }
|
||||
FbCookie.reset(this::setupWebview)
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@ -62,13 +64,14 @@ class LoginWebView @JvmOverloads constructor(
|
||||
if (!url.isFacebookUrl) return@doAsync
|
||||
val cookie = CookieManager.getInstance().getCookie(url) ?: return@doAsync
|
||||
L.d("Checking cookie for login", cookie)
|
||||
val id = FB_USER_MATCHER.find(cookie)?.groupValues?.get(1)?.toLong() ?: return@doAsync
|
||||
val id = FB_USER_MATCHER.find(cookie)[1]?.toLong() ?: return@doAsync
|
||||
uiThread { onFound(id, cookie) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageCommitVisible(view: WebView, url: String?) {
|
||||
super.onPageCommitVisible(view, url)
|
||||
L.d("Login page commit visible")
|
||||
view.setBackgroundColor(Color.TRANSPARENT)
|
||||
if (url.isFacebookUrl)
|
||||
view.jsInject(CssHider.HEADER,
|
||||
|
@ -6,6 +6,13 @@
|
||||
<item text="" />
|
||||
-->
|
||||
|
||||
<version title="v1.7.2" />
|
||||
<item text="Optimize login view" />
|
||||
<item text="Rewrite parsers" />
|
||||
<item text="Fix message notification icons" />
|
||||
<item text="Small theme updates" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
|
||||
<version title="v1.7.1" />
|
||||
<item text="Fix launching messages in new overlay" />
|
||||
@ -14,8 +21,6 @@
|
||||
<item text="Automatically bring toolbar up when keyboard is shown" />
|
||||
<item text="Rewrite theme components to fully support AMOLED and improve light" />
|
||||
<item text="Properly pause webviews when not in use" />
|
||||
<item text="" />
|
||||
<item text="" />
|
||||
|
||||
<version title="v1.7.0" />
|
||||
<item text="Fully customize your tabs! Check out settings > appearance > main activity tabs" />
|
||||
|
@ -0,0 +1,46 @@
|
||||
package com.pitchedapps.frost.facebook
|
||||
|
||||
import com.pitchedapps.frost.internal.COOKIE
|
||||
import com.pitchedapps.frost.internal.assertComponentsNotEmpty
|
||||
import com.pitchedapps.frost.internal.assertDescending
|
||||
import com.pitchedapps.frost.internal.authDependent
|
||||
import com.pitchedapps.frost.parsers.*
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.test.fail
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 24/12/17.
|
||||
*/
|
||||
class FbParseTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
authDependent()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T : Any> FrostParser<T>.test(action: T.() -> Unit = {}) {
|
||||
val response = parse(COOKIE)
|
||||
?: fail("${this::class.java.simpleName} returned null for $url")
|
||||
println(response)
|
||||
response.data.action()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun message() = MessageParser.test {
|
||||
threads.forEach(FrostThread::assertComponentsNotEmpty)
|
||||
threads.map(FrostThread::time).assertDescending("thread time values")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun search() = SearchParser.test()
|
||||
|
||||
@Test
|
||||
fun notif() = NotifParser.test {
|
||||
notifs.forEach(FrostNotif::assertComponentsNotEmpty)
|
||||
notifs.map(FrostNotif::time).assertDescending("notif time values")
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.pitchedapps.frost.facebook
|
||||
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 24/12/17.
|
||||
*/
|
||||
class FbRegexTest {
|
||||
@Test
|
||||
fun userIdRegex() {
|
||||
val id = 12349876L
|
||||
val cookie = "wd=1366x615; c_user=$id; act=1234%2F12; m_pixel_ratio=1; presence=hello; x-referer=asdfasdf"
|
||||
assertEquals(id, FB_USER_MATCHER.find(cookie)[1]?.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fbDtsgRegex() {
|
||||
val fb_dtsg = "readme"
|
||||
val input = "data-sigil=\"mbasic_inline_feed_composer\">\u003Cinput type=\"hidden\" name=\"fb_dtsg\" value=\"$fb_dtsg\" autocomplete=\"off\" \\/>\u003Cinput type=\"hidden\" name=\"privacyx\" value=\"12345\""
|
||||
assertEquals(fb_dtsg, FB_DTSG_MATCHER.find(input)[1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ppRegex() {
|
||||
val img = "https\\3a //scontent-yyz1-1.xx.fbcdn.net/v/asdf1234.jpg?efg\\3d 333\\26 oh\\3d 77\\26 oe\\3d 444"
|
||||
val ppStyle = "background:#d8dce6 url('$img') no-repeat center;background-size:100% 100%;-webkit-background-size:100% 100%;width:58px;height:58px;"
|
||||
assertEquals(StringEscapeUtils.unescapeCsv(img), StringEscapeUtils.unescapeCsv(FB_CSS_URL_MATCHER.find(ppStyle)[1]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun msgNotifIdRegex() {
|
||||
val id = 1273491646093428L
|
||||
val data = "threadlist_row_other_user_fbid_thread_fbid_$id"
|
||||
assertEquals(id, FB_MESSAGE_NOTIF_ID_MATCHER.find(data)[1]?.toLong(), "thread_fbid mismatch")
|
||||
val userData = "threadlist_row_other_user_fbid_${id}thread_fbid_"
|
||||
assertEquals(id, FB_MESSAGE_NOTIF_ID_MATCHER.find(userData)[1]?.toLong(), "user_fbid mismatch")
|
||||
|
||||
}
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
package com.pitchedapps.frost.facebook
|
||||
|
||||
import com.pitchedapps.frost.internal.AUTH
|
||||
import com.pitchedapps.frost.internal.COOKIE
|
||||
import com.pitchedapps.frost.internal.FB_DTSG
|
||||
import com.pitchedapps.frost.internal.USER_ID
|
||||
import org.junit.Assume
|
||||
import com.pitchedapps.frost.internal.authDependent
|
||||
import okhttp3.Call
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.*
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 21/12/17.
|
||||
@ -19,44 +18,34 @@ class FbRequestTest {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
Assume.assumeTrue(COOKIE.isNotEmpty())
|
||||
authDependent()
|
||||
}
|
||||
|
||||
val AUTH: RequestAuth by lazy { RequestAuth(USER_ID, COOKIE, FB_DTSG) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun userIdRegex() {
|
||||
val id = 12349876L
|
||||
val cookie = "wd=1366x615; c_user=$id; act=1234%2F12; m_pixel_ratio=1; presence=hello; x-referer=asdfasdf"
|
||||
assertEquals(id, FB_USER_MATCHER.find(cookie)?.groupValues?.get(1)?.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fbDtsgRegex() {
|
||||
val fb_dtsg = "readme"
|
||||
val input = "data-sigil=\"mbasic_inline_feed_composer\">\u003Cinput type=\"hidden\" name=\"fb_dtsg\" value=\"$fb_dtsg\" autocomplete=\"off\" \\/>\u003Cinput type=\"hidden\" name=\"privacyx\" value=\"12345\""
|
||||
assertEquals(fb_dtsg, FB_DTSG_MATCHER.find(input)?.groupValues?.get(1))
|
||||
/**
|
||||
* Used to emulate [executeAndCheck]
|
||||
* Must be consistent with that method
|
||||
*/
|
||||
private fun Call.assertNoError() {
|
||||
val data = execute().body()?.string() ?: fail("Content was null")
|
||||
println("Call response: $data")
|
||||
assertTrue(data.isNotEmpty(), "Content was empty")
|
||||
assertFalse(data.contains("error"), "Content had error")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun auth() {
|
||||
val auth = (USER_ID to COOKIE).getAuth()
|
||||
assertNotNull(auth)
|
||||
assertEquals(USER_ID, auth!!.userId)
|
||||
assertEquals(USER_ID, auth.userId)
|
||||
assertEquals(COOKIE, auth.cookie)
|
||||
println("Test auth: priv $FB_DTSG, test ${auth.fb_dtsg}")
|
||||
println("Test auth: ${auth.fb_dtsg}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markNotification() {
|
||||
val notifId = 1513544657695779
|
||||
|
||||
val out = AUTH.markNotificationRead(notifId)
|
||||
.execute().body()?.string() ?: ""
|
||||
println(out)
|
||||
|
||||
assertFalse(out.contains("error"))
|
||||
AUTH.markNotificationRead(notifId).assertNoError()
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,15 @@
|
||||
package com.pitchedapps.frost.internal
|
||||
|
||||
import com.pitchedapps.frost.facebook.FB_USER_MATCHER
|
||||
import com.pitchedapps.frost.facebook.*
|
||||
import com.pitchedapps.frost.utils.frostJsoup
|
||||
import org.junit.Assume
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.*
|
||||
import kotlin.reflect.full.starProjectedType
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 21/12/17.
|
||||
@ -24,5 +30,46 @@ val PROPS: Properties by lazy {
|
||||
}
|
||||
|
||||
val COOKIE: String by lazy { PROPS.getProperty("COOKIE") ?: "" }
|
||||
val FB_DTSG: String by lazy { PROPS.getProperty("FB_DTSG") ?: "" }
|
||||
val USER_ID: Long by lazy { FB_USER_MATCHER.find(COOKIE)?.groupValues?.get(1)?.toLong() ?: -1 }
|
||||
val USER_ID: Long by lazy { FB_USER_MATCHER.find(COOKIE)[1]?.toLong() ?: -1 }
|
||||
val AUTH: RequestAuth by lazy {
|
||||
(USER_ID to COOKIE).getAuth().apply {
|
||||
println("Auth:\nuser:$userId\nfb_dtsg: $fb_dtsg\nrev: $rev\nvalid: $isValid")
|
||||
}
|
||||
}
|
||||
|
||||
val VALID_COOKIE: Boolean by lazy {
|
||||
val data = testJsoup(FbItem.SETTINGS.url)
|
||||
data.title() == "Settings"
|
||||
}
|
||||
|
||||
fun testJsoup(url: String) = frostJsoup(COOKIE, url)
|
||||
|
||||
fun authDependent() {
|
||||
println("Auth Dependent")
|
||||
Assume.assumeTrue(COOKIE.isNotEmpty() && VALID_COOKIE)
|
||||
Assume.assumeTrue(AUTH.isValid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that component strings are nonempty and are properly parsed
|
||||
* To be used for data classes
|
||||
*/
|
||||
fun Any.assertComponentsNotEmpty() {
|
||||
val components = this::class.members.filter { it.name.startsWith("component") }
|
||||
if (components.isEmpty())
|
||||
fail("${this::class.simpleName} has no components")
|
||||
components.forEach {
|
||||
when (it.returnType) {
|
||||
String::class.starProjectedType -> {
|
||||
val result = it.call(this) as String
|
||||
assertTrue(result.isNotEmpty(), "${it.name} returned empty string")
|
||||
if (result.startsWith("https"))
|
||||
assertTrue(result.startsWith("https://"), "${it.name} has poorly formatted output $result")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Comparable<T>> List<T>.assertDescending(tag: String) {
|
||||
assertEquals(sortedDescending(), this, "$tag not sorted in descending order")
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package com.pitchedapps.frost.parsers
|
||||
|
||||
import com.pitchedapps.frost.facebook.FB_EPOCH_MATCHER
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
import com.pitchedapps.frost.facebook.get
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ -15,7 +17,7 @@ class MessageParserTest {
|
||||
@Test
|
||||
fun parseEpoch() {
|
||||
val input = "{\"time\":1507301642,\"short\":true,\"forceseconds\":false}"
|
||||
assertEquals(1507301642, FrostRegex.epoch.find(input)!!.groupValues[1].toLong())
|
||||
assertEquals(1507301642, FB_EPOCH_MATCHER.find(input)[1]!!.toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -18,5 +18,5 @@ fun <T : Any> T.getResource(path: String): String? {
|
||||
|
||||
fun <T : Any, P : Any> T.debug(path: String, parser: FrostParser<P>) {
|
||||
val content = getResource("priv/$path.html") ?: return
|
||||
println(parser.debug(content))
|
||||
// println(parser.debug(content))
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## v1.7.0
|
||||
## v1.7.1
|
||||
* Fix launching messages in new overlay
|
||||
* Fix some errors in launching pages
|
||||
* Redid base design to prepare for native views
|
||||
* Automatically bring toolbar up when keyboard is shown
|
||||
* Rewrite theme components to fully support AMOLED and improve light
|
||||
* Properly pause webviews when not in use
|
||||
|
||||
## v1.6.8
|
||||
## v1.7.0
|
||||
* Fully customize your tabs! Check out settings > appearance > main activity tabs
|
||||
* Optimize scripts
|
||||
* Add more theme components
|
||||
|
@ -18,7 +18,7 @@ TARGET_SDK=27
|
||||
BUILD_TOOLS=27.0.2
|
||||
|
||||
KAU=399a0bf
|
||||
KOTLIN=1.2.0
|
||||
KOTLIN=1.2.10
|
||||
|
||||
ANDROID_SUPPORT_LIBS=27.0.2
|
||||
COMMONS_TEXT=1.2
|
||||
|
Loading…
Reference in New Issue
Block a user